Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby add method to a class

Assume I have a class :

class Foo
end

To add a method to this class I know 2 options:

  1. Reopening the class and implement the method :

    class Foo
      def bar
      end
    end
    
  2. using class_eval to implement method :

    Foo.class_eval { def bar; end}
    

What is the difference? Which one is better?

like image 212
Saman Mohamadi Avatar asked Dec 30 '15 11:12

Saman Mohamadi


People also ask

How to use add method in Ruby?

Ruby | Set add() method The add is an inbuilt method in Ruby which adds an element to the set. It returns itself after addition. Parameters: The function takes the object to be added to the set. Return Value: It adds the object to the set and returns self.

How to define class method in Ruby?

Class Methods are the methods that are defined inside the class, public class methods can be accessed with the help of objects. The method is marked as private by default, when a method is defined outside of the class definition. By default, methods are marked as public which is defined in the class definition.

What is Define_method in Ruby?

define_method is a method defined in Module class which you can use to create methods dynamically. To use define_method , you call it with the name of the new method and a block where the parameters of the block become the parameters of the new method.


2 Answers

Actually, there are a few other ways to add new methods to a class. For example, you can also define the methods in a module, and mix the module into the original class.

module ExtraMethods
  def bar
  end
end

Foo.class_eval { include ExtraMethods }
class Foo
  include ExtraMethods
end

There is no real better or worse. The two (or three) ways you mentioned have different behavior and you may want to use one or another depending on your need (or preference). In most cases, this is subjective. In other cases, it really depends on how your code is structured.

The main objective difference between reopening the class vs using class_eval is that the first one is also a class definition, whereas the second one requires the original class to already be defined.

In practice, reopening the class in some cases may cause some unexpected side effects. Let's suppose you defined Foo in the file lib/foo.rb, with a bunch of methods. Then you reopen Foo in config/initializers/extra.rb and you add the bar method.

In myclass.rb you use Foo, but instead of requiring lib/foo.rb manually, you rely on an autoload feature.

If extra.rb is loaded before lib/foo.rb, what could happen is that the Foo class is already defined in your environment, and your code will not load lib/foo.rb. What you will have is a Foo class containing only the bar extension you defined, and not the original Foo one.

In other words, if for whatever reason you reopen the class to add some methods without making sure the full origina class definition is loaded first (or after), your code may break if it relies on autoload.

Conversely, Foo.class_eval calls a method on Foo, therefore it expects the original Foo definition to be already present at the time you try to add new methods. This ensures that, when you add new methods, the Foo class will already be defined.

In conclusion, the main difference is that reopening the class allows you (for better or for worse) to add methods to a class that may not have been loaded yet, whereas the class_eval requires the class to already be defined.

In general, unless I'm defining namespaced subclasses or reopening classes I have full control of, I prefer the second approach as in large codebases it keeps the code more maintainable. In fact, I generally use mixins if I extend third-party classes so that I can retain the full method ancestor chain if I need to override existing methods.

like image 143
Simone Carletti Avatar answered Oct 20 '22 23:10

Simone Carletti


The second approach is very handy when you need some dynamic thing. Ruby actually has several scopes:

# scope one, opened with `class` keyword
class ...
  # scope two, opened with `def` keyword
  def ...
  end
end

With class_eval, you can share scopes.

>> foo = 1
=> 1
>> class Foo
>>   puts foo
>>   def bar
>>     puts foo
>>   end
>> end
NameError: undefined local variable or method 'foo' for Foo:Class
        from (irb):3:in <class:Foo>
        from (irb):2
>> Foo
=> Foo
>> Foo.class_eval {
?>   puts foo
>>   define_method :bar do
>>     puts foo
>>   end
>> }
1
=> :bar
>> Foo.new.bar
1
like image 30
Philidor Avatar answered Oct 20 '22 23:10

Philidor