How to Use the Enumerable Mixin in My Class

How do I use the Enumerable mixin in my class?

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.

Adding Enumerable mixin at the class-level in Ruby

You can use include from within class << self:

class Foo

class << self
include Enumerable
end

def self.each
yield 1
yield 2
yield 3
end
end

Foo.map { |x| x * 2 } # => [2, 4, 6]

This pattern is used in Ruby's Prime class. Writing include to include a module looks cleaner to me, but you can probably use extend as well (see Uri Agassi's answer).

There would be a difference if the included module relied on the included callback (Enumerable doesn't):

module M
def self.included(other)
puts "included in #{other}"
end
end

class Foo
class << self
include M
end
end
#=> "included in #<Class:Foo>"

class Bar
extend M
end
#=> nothing

As noted by Зелёный you could define each right within the block: (and without using self)

class Foo
class << self
include Enumerable

def each
# ...
end
end
end

implement `each` for Enumerable mixin in Ruby

The return value of each is supposed to be the receiver, i.e. self. But you are returning the result of calling @num.each. Now, as I just said, each returns self, ergo @num.each returns @num.

The fix is simple: just return self:

def each
return enum_for(:each) unless block_given?
@num.each { |i| yield i + 1 }
self
end

Or, possibly a bit more Rubyish:

def each
return enum_for(:each) unless block_given?
tap { @num.each { |i| yield i + 1 }}
end

[Actually, as of Ruby 1.8.7+, each is also supposed to return an Enumerator when called without a block, but you are already correctly handling that. Tip: if you want to implement optimized versions of some of the other Enumerable methods by overriding them, or want to add your own Enumerable-like methods with similar behavior, as the original ones, you are going to cut&paste that exact same line of code over and over, and at one point, you will accidentally forget to change the name of the method. If you replace the line with return enum_for(__callee__) unless block_given?, you don't have to remember changing the name.]

include a new mixin in all enumerable classes

As far as I know you have to write the method code inside the Enumerable module

module Enumerable
def so
"StackOverflow!"
end
end

a = [1, 3, 7]
a.so
#=> "StackOverflow!"

Using methods of Enumerable in custom Class

I override to_s, I expect each index of array to contain a string like "apple: red". Instead I get this: ...

Two things are wrong here.

1) you have to implement Apple#to_s, not Fruit#to_s:

class Apple
attr_accessor :color

def to_s
"apple: #{color}"
end
end

2) you have to implement inspect or define it as an alias:

class Apple
attr_accessor :color

def to_s
"apple: #{color}"
end
alias inspect to_s
end

This will give you:

fruit = Fruit.new
a1 = Apple.new
a1.color = :red
a2 = Apple.new
a2.color = :green
a3 = Apple.new
a3.color = :yellow
fruit.apples.push a1
fruit.apples.push a2
fruit.apples.push a3

fruit
#=> #<Fruit:0x00007faa3686b7c0 @apples=[apple: red, apple: green, apple: yellow]>

Second issue is when I include Enumerable, the instance methods of Enumerable should have been added to the ancestor chain ...

When you write:

fruit.each.with_index(1)

you are calling with_index on the return value of each. That's where the error occurs:

fruit.each
#=> LocalJumpError: no block given (yield)

You have to return an instance of Enumerator when no block is given. This can be achieved using a conditional (see mudasobwa's answer) or by passing the block along:

def each(&block)
@apples.each(&block)
end

There's another issue with your code: not Fruit but Apple is the class that should implement <=> and include Comparable. Because when sorting @apples, the items are being compared to each other:

class Apple
include Comparable

attr_accessor :color

def <=> o
color <=> o.color
end

# ...
end

Note that there's a catch when including Enumerable. Although you are able to use all those methods, you can easily lose your wrapping class and end up with a plain array:

fruit
#=> #<Fruit:0x00007faa3686b7c0 @apples=[apple: red, apple: green, apple: yellow]>

fruit.sort
#=> [apple: green, apple: red, apple: yellow]

Why is `each` in ruby not defined in the enumerable module?

From the documentation for Enumerable:

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.

So the Enumerable module requires that classes which include it implement each on their own. All the other methods in Enumerable depend on each being implemented by the class which includes Enumerable.

For example:

class OneTwoThree
include Enumerable

# OneTwoThree has no `each` method!
end

# This throws an error:
OneTwoThree.new.map{|x| x * 2 }
# NoMethodError: undefined method `each' for #<OneTwoThree:0x83237d4>

class OneTwoThree
# But if we define an `each` method...
def each
yield 1
yield 2
yield 3
end
end

# Then it works!
OneTwoThree.new.map{|x| x * 2 }
#=> [2, 4, 6]

Mixins for ES6 classes, transpiled with babel

There are two problems with your mixins:

  1. Object.assign only copies enumerable properties of an object. However, the methods and properties of a class are non-enumerable.
  2. The methods and properties of a class are not defined on the constructor. They are defined on the prototype of the constructor.

This is how you would extend a class using mixins:

class CartoonCharacter {
constructor(author) {
this.author = author;
}

drawnBy() {
console.log("drawn by", this.author);
}
}

class Human {
haveFun() {
console.log("drinking beer");
}
}

mixin(CartoonCharacter, Human);

class Simpson extends CartoonCharacter {
constructor(author) {
super(author);
}
}

let homer = new Simpson("Matt Groening");
homer.drawnBy(); // expected: drawn by Matt Groening
homer.haveFun(); // expected: drinking beer

function mixin(target, source) {
target = target.prototype; source = source.prototype;

Object.getOwnPropertyNames(source).forEach(function (name) {
if (name !== "constructor") Object.defineProperty(target, name,
Object.getOwnPropertyDescriptor(source, name));
});
}

It works as expected in Babel: demo.

Delegate properties of Enumerable to class

I assume you're trying to define delegator on the class level

You may try to define it like this:

class << self
extend Forwardable

attr_accessor :objects
def_delegator :objects, :count
end

I mean, the only difference between your code and this code is extending by Forwardable inside class << self

Using Enumerable to make standard iterators available to modify iterators

You could also just subclass Array and override methods you want:

class NumberArray < Array
def inject
odds = select(&:odd?)
odds.inject(:+).to_f / odds.size
end
end

a = NumberArray.new([1,2,3,4,5])
a.inject # => 3.0

If it makes any sense to do it this way is another thing, but that's what you asked :)



Related Topics



Leave a reply



Submit