Tomislav Mikulin
Tomislav Mikulin

Reputation: 5936

How to add methods to the Enumerable module?

How do I add my custom methods onto the existing Enumerable module in Ruby? I'm running Ruby 2.0.

Upvotes: 2

Views: 1518

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110675

All methods defined in the Enumerable module require receivers to be either enumerators or have a method each. Let's suppose we wish to create an enumerable method my_to_a similar to Enumerable#to_a except it returns just the first n of the receiver's elements (or, if it's an enumerator, the first n elements it generates), n being my_to_a's argument.

module Enumerable
  def my_to_a(n)
    enum = is_a?(Enumerator) ? self : each
    n.times.with_object([]) { |o,a| a << enum.next }
   end
end

[0,1,2,3,4].my_to_a(3)
  #=> [0, 1, 2] 
(0..4).my_to_a(3)
  #=> [0, 1, 2] 
{ a:1, b:2, c:3, d:4, e:5 }.my_to_a(3)
  #=> [[:a, 1], [:b, 2], [:c, 3]] 
'0ab_1ab_2ab_3ab_4ab'.gsub(/.(?=ab)/).my_to_a(3)
  #=> ["0", "1", "2"] 
require 'prime'; Prime.my_to_a(4)
  #=> [2, 3, 5, 7] 
[0,1,2,3,4].my_to_a(6)
  #=> StopIteration (iteration reached an end)
'0ab_1ab_2ab_3ab_4ab'.gsub(/.(?=ab)/).my_to_a(6)
  #=> StopIteration (iteration reached an end)

The line

enum = is_a?(Enumerator) ? self : each

sets enum to self if self is an enumerator and to the enumerator self.each otherwise. The latter case assumes self has a method each, meaning that the class self.class has an instance method each (or a method each has been defined on self's singleton class). each must return an enumerator. In the example, is_a?(Enumerator) (equivalent to self.is_a?(Enumerator)) is true only for the two examples that employ String#gsub with one argument and no block.1 In the other examples self.class.included_modules.includes?(Enumerable) #=> true and self.class.instance_methods.include?(:each) #=> true. Those classes are Array, Range, Hash and Prime. each is an instance method for the first three, a class method for Prime.

Once we have the enumerator enum the line

n.times.with_object([]) { |o,a| a << enum.next }

is straightforward. See Integer#times, Enumerator#with_object and Enumerator#next.

1 Note that Enumerator.included_modules #=> [Enumerable, Kernel], so any method that returns an enumerator (instance of Enumerator) responds to Enumerable methods. That is, there is no need for the method's class (such as String) to include Enumerable or have an method each.

Upvotes: 1

user4413010
user4413010

Reputation:

In Ruby, you can use monkey-patching, which incorporates the concept of open classes. This means that classes in Ruby can be modified at any time. For example, you could create a double method in the number class.

class Integer < Numeric
  def self.double
    self * 2
  end
end

Now you could call a double method on a number

4.double
=> 8

Hope this helps

Upvotes: 2

J&#246;rg W Mittag
J&#246;rg W Mittag

Reputation: 369458

The same way you add methods to every other module.

If you want to add method bar with parameters baz and quux to module Foo you write

module Foo
  def bar(baz, quux)
    # code code code
  end
end

So, if you want to add method histogram to module Enumerable you write

module Enumerable
  # Counts the number of occurrences of each unique object in `self`.
  #
  # @return [Hash<E, Integer>] a `Hash` of objects mapped to number of occurrences in `self`.
  def histogram
    group_by(&:itself).map {|k, v| [k, v.size] }.to_hash
  end
end

Upvotes: 3

Related Questions