Skip to content

Instantly share code, notes, and snippets.

@janko
Created November 13, 2012 17:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save janko/4067009 to your computer and use it in GitHub Desktop.
Save janko/4067009 to your computer and use it in GitHub Desktop.
Eliminate need for class eval
# lib/carrierwave/orm/activerecord.rb from jnicklas/carrierwave
# before
class_eval <<-RUBY, __FILE__, __LINE__+1
def #{column}=(new_file)
column = _mounter(:#{column}).serialization_column
send(:"\#{column}_will_change!")
super
end
def remote_#{column}_url=(url)
column = _mounter(:#{column}).serialization_column
send(:"\#{column}_will_change!")
super
end
def remove_#{column}!
super
_mounter(:#{column}).remove = true
_mounter(:#{column}).write_identifier
end
def serializable_hash(options=nil)
hash = {}
except = options && options[:except] && Array.wrap(options[:except]).map(&:to_s)
only = options && options[:only] && Array.wrap(options[:only]).map(&:to_s)
self.class.uploaders.each do |column, uploader|
if (!only && !except) || (only && only.include?(column.to_s)) || (except && !except.include?(column.to_s))
hash[column.to_s] = _mounter(column).uploader.serializable_hash
end
end
super(options).merge(hash)
end
RUBY
# after
define_method("#{column}=") do |new_file|
serialization_column = _mounter(column.to_sym).serialization_column
send(:"#{serialization_column}_will_change!")
super
end
define_method("remote_#{column}_url=") do |url|
serialization_column = _mounter(column.to_sym).serialization_column
send(:"#{serialization_column}_will_change!")
super
end
define_method("remove_#{column}!") do
super
_mounter(column.to_sym).remove = true
_mounter(column.to_sym).write_identifier
end
def serializable_hash(options = nil)
hash = {}
except = options && options[:except] && Array.wrap(options[:except]).map(&:to_s)
only = options && options[:only] && Array.wrap(options[:only]).map(&:to_s)
self.class.uploaders.each do |column, uploader|
if (!only && !except) || (only && only.include?(column.to_s)) || (except && !except.include?(column.to_s))
hash[column.to_s] = _mounter(column).uploader.serializable_hash
end
end
super(options).merge(hash)
end
# lib/faraday/connection.rb from technoweenie/faraday
# before
%w[get head delete].each do |method|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{method}(url = nil, params = nil, headers = nil)
run_request(:#{method}, url, nil, headers) { |request|
request.params.update(params) if params
yield request if block_given?
}
end
RUBY
end
# after
[:get, :head, :delete].each do |method|
define_method(method) do |url = nil, params = nil, headers = nil|
run_request(method, url, nil, headers) { |request|
request.params.update(params) if params
yield request if block_given?
}
end
end
@mislav
Copy link

mislav commented Nov 14, 2012

Some things that are wrong with your refactoring:

  1. define_method and methods defined with Ruby's def syntax are not interchangeable. The former creates a closure, which is something that you might want in some cases, but want to avoid in other cases. With def, you're sure that the method has its own scope.

  2. You can't call super without brackets inside a method created with define_method, otherwise you will get:

    RuntimeError: implicit argument passing of super from method defined by define_method() is not supported.
    Specify all arguments explicitly
    
  3. You can't do define_method(method) do |url = nil| (have a default value) in Ruby 1.8

  4. Methods created with define_method are slower to call. Observe:

    require 'benchmark'
    
    dummy = Class.new {
      def norm() end
      define_method(:meta) {}
    }.new
    
    n = 5000000
    Benchmark.bm do |x|
      x.report('norm') { n.times { dummy.norm } }
      x.report('meta') { n.times { dummy.meta } }
    end

@janko
Copy link
Author

janko commented Nov 15, 2012

Thank you, I really didn't know about most of those things. Now it's much clearer why do many people use it.

But I don't think this benchmark is significant, because then there would be a downsize in refactoring a big method into many smaller ones.

@janko
Copy link
Author

janko commented Nov 15, 2012

Also, by "ugly" I obviously meant class_eval, not define_method :P

@janko
Copy link
Author

janko commented Mar 17, 2013

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment