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:
Object.assign
only copies enumerable properties of an object. However, the methods and properties of a class are non-enumerable.- 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
Mongodb with Mongoid in Rails - Geospatial Indexing
Ruby Working on Array Elements in Groups of Four
Find Out If an Ip Is Within a Range of Ips
Ruby: Most Concise Way to Use an Env Variable If It Exists, Otherwise Use Default Value
Is Require File.Expand_Path(..., _File_) the Best Practice
Measure and Benchmark Time for Ruby Methods
How to Get Activerecord Associations via Reflection
How to Generate a Random Number Between a and B in Ruby
How to Run Shell Commands on Server in Capistrano V3
Rake VS. Thor for Automation Scripts
Rails: Good Rspec2 Example Usage? (Also: Cucumber, Pickle, Capybara)
Ruby Method Calls Declared in Class Body
Ruby Variable Assignment in a Conditional "If" Modifier
Ruby on Rails: Permission Denied When Using "Rails Generate Controller Welcome"