58

I have a class called Note, which includes an instance variable called time_spent. I want to be able to do something like this:

current_user.notes.inject{|total_time_spent,note| total_time_spent + note.time_spent}

Is this possible by mixing in the Enumerable module? I know you are supposed to do add include Enumerable to the class and then define an each method, but should the each method be a class or instance method? What goes in the each method?

I'm using Ruby 1.9.2

Strigoides
  • 4,329
  • 5
  • 23
  • 25
ben
  • 29,229
  • 42
  • 124
  • 179
  • 1
    Excuse me if my question is clueless, but since `current_user.notes` is already an array, meaning it already includes Enumerable, why do you need to do anything more? Your example can already run with a minor change: `current_user.notes.inject(0) {|total_time_spent,note| total_time_spent + note.time_spent}` – Bad Request Jan 12 '14 at 03:58
  • Or if you're using rails: `current_user.notes.sum(&:time_spent)` – aidan Oct 06 '16 at 06:30

2 Answers2

100

It's easy, just include the Enumerable module and define an each instance method, which more often than not will just use some other class's each method. Here's a really simplified example:

class ATeam
  include Enumerable

  def initialize(*members)
    @members = members
  end

  def each(&block)
    @members.each do |member|
      block.call(member)
    end
    # or
    # @members.each(&block)
  end
end

ateam = ATeam.new("Face", "B.A. Barracus", "Murdoch", "Hannibal")
#use any Enumerable method from here on
p ateam.map(&:downcase)

For further info, I recommend the following article: Ruby Enumerable Magic: The Basics.

In the context of your question, if what you expose through an accessor already is a collection, you probably don't need to bother with including Enumerable.

Michael Kohl
  • 66,324
  • 14
  • 138
  • 158
  • you could also say `yield member` instead of `block.call(member)` – Milovan Zogovic Mar 14 '12 at 10:43
  • 2
    When I use explicit block arguments I prefer to work with the proc objects. Personally I'd use the `[]` alias for `call` normally (`block[member]`), but thought that might be confusing for OP. – Michael Kohl Mar 14 '12 at 13:47
  • 13
    why not simply `@members.each(&block)`? – tokland Jul 05 '12 at 21:23
  • 10
    @tokland For the sake of everyone understanding what this does. – Michael Kohl Jul 05 '12 at 21:39
  • 4
    @MichaelKohl: granted, a novice may have some problems understanding it at first, but it's a pretty common idiom, it won't hurt to mention it. Note also that doing it this way you get the default behavious of `each` for free: when no block is sent, you get an enumerator. If you explicitly yield or call the block the other way, you'll get an error. – tokland Jul 05 '12 at 21:49
  • @tokland Fair enough, added it. Since you have the necessary rep, feel free to add stuff like this yourself in the future. – Michael Kohl Jul 05 '12 at 21:55
  • 5
    @MichaelKohl: Great, thanks. Yeah, well, I can edit, but I think it's more polite to comment first, and only if the user is unreachable, edit code. – tokland Jul 05 '12 at 22:08
  • Been a while, but any idea how I would do this for a class that inherits from ActiveRecord::Base? I can't create the initialize method because it's already in AR, so I don't know what the @members variable is to iterate over in each(@block) – Trevor McKendrick Jul 02 '14 at 00:00
  • If you needed to fetch the count/size of the collection, do you need to define these methods yourself? – Andrew Aug 08 '15 at 18:29
  • Yes. There are many ways you could implement your custom class, so you'll have to provide them yourself. Or just delegate methods to `@members` instead of implementing new ones. – Michael Kohl Aug 09 '15 at 08:09
  • Just a quick addition, the *Enumerable* documentation also states to implement the *<=>* operator if you're planning to use *max*, *min* or *sort*. – 3limin4t0r Oct 27 '17 at 09:02
5

The Enumerable documentations says the following:

The Enumerable mixin provides collection classes with several traversal and searching methods, and with the ability to sort. The class must provide a method each, which yields successive members of the collection. If Enumerable#max, #min, or #sort is used, the objects in the collection must also implement a meaningful <=> operator, as these methods rely on an ordering between members of the collection.

This means implementing each on the collection. If you're interested in using #max, #min or #sort you should implement <=> on its members.

See: Enumerable

3limin4t0r
  • 19,353
  • 2
  • 31
  • 52
  • To be clear, you only need to implement `each` in the collection class. It's the *members* of the collection that need to have a meaningful `<=>` method defined (but only if you want them to be comparable). – wyattisimo Feb 21 '19 at 18:13
  • I agree that it's currently not entirely clear that the collection items need to implement *<=>*. I'll update the anwer to better reflect this. – 3limin4t0r Feb 22 '19 at 10:59