Maintaining same class using delegation in Ruby - ruby

I'm trying to wrap my head around delegation vs. inheritance so I'm manually delegating a version of Array. One of the specific reasons I read to do this is because when you use things like enumerables, your returned value on the inherited methods reverts back to the parent class (i.e. Array). So I did this:
module PeepData
# A list of Peeps
class Peeps
include Enumerable
def initialize(list = [])
#list = list
end
def [](index)
#list[index]
end
def each(...)
#list.each(...)
end
def reverse
Peeps.new(#list.reverse)
end
def last
#list.last
end
def join(...)
#list.join(...)
end
def from_csv(csv_table)
#list = []
csv_table.each { |row| #list << Peep.new(row.to_h) }
end
def include(field, value)
Peeps.new(select { |row| row[field] == value })
end
def exclude(field, value)
Peeps.new(select { |row| row[field] != value })
end
def count_by_field(field)
result = {}
#list.each do |row|
result[row[field]] = result[row[field]].to_i + 1
end
result
end
protected
attr_reader :list
end
end
When I instantiate this, my include and exclude function great and return a Peeps class but when using an enumerable like select, it returns Array, which prevents me from chaining further Peeps specific methods after the select. This is exactly what I'm trying to avoid with learning about delegation.
p = Peeps.new
p.from_csv(csv_generated_array_of_hashes)
p.select(&:certified?).class
returns Array
If I override select, wrapping it in Peeps.new(), I get a "SystemStackError: stack level too deep". It seems to be recursively burying the list deeper into the list during the select enumeration.
def select(...)
Peeps.new(#list.select(...))
end
Any help and THANKS!

I would recommend using both Forwardable and Enumerable. Use Forwardable to delegate the each method to your list (to satisfy the Enumerable interface requirement), and also forward any Array methods you might want to include that are not part of the Enumerable module, such as size. I would also suggest not overriding the behavior of select as it is supposed to return an array and would at the very least lead to confusion. I would suggest something like the subset provided below to implement the behavior you are looking for.
require 'forwardable'
class Peeps
include Enumerable
extend Forwardable
def_delegators :#list, :each, :size
def initialize(list = [])
#list = list
end
def subset(&block)
selected = #list.select(&block)
Peeps.new(selected)
end
protected
attr_reader :list
end
Example usage:
peeps = Peeps.new([:a,:b,:c])
subset = peeps.subset {|s| s != :b}
puts subset.class
peeps.each do |peep|
puts peep
end
puts peeps.size
puts subset.size
produces:
Peeps
a
b
c
3
2

I think that if Peeps#select will return an Array, then it is OK to include Enumerable. But, you want Peeps#select to return a Peeps. I don't think you should include Enumerable. It's misleading to claim to be an Enumerable if you don't conform to its interface. This is just my opinion. There is no clear consensus on this in the ecosystem. See "Examples from the ecosystem" below.
If we accept that we cannot include Enumerable, here's the first implementation that comes to my mind.
require 'minitest/autorun'
class Peeps
ARRAY_METHODS = %i[flat_map map reject select]
ELEMENT_METHODS = %i[first include? last]
def initialize(list)
#list = list
end
def inspect
#list.join(', ')
end
def method_missing(mth, *args, &block)
if ARRAY_METHODS.include?(mth)
self.class.new(#list.send(mth, *args, &block))
elsif ELEMENT_METHODS.include?(mth)
#list.send(mth, *args, &block)
else
super
end
end
end
class PeepsTest < Minitest::Test
def test_first
assert_equal('alice', Peeps.new(%w[alice bob charlie]).first)
end
def test_include?
assert Peeps.new(%w[alice bob charlie]).include?('bob')
end
def test_select
peeps = Peeps.new(%w[alice bob charlie]).select { |i| i < 'c' }
assert_instance_of(Peeps, peeps)
assert_equal('alice, bob', peeps.inspect)
end
end
I don't normally use method_missing, but it seemed convenient.
Examples from the ecosystem
There doesn't seem to be a consensus on how strictly to follow interfaces.
ActionController::Parameters used to inherit Hash. Inheritance ceased in Rails 5.1.
ActiveSupport::HashWithIndifferentAccess still inherits Hash.

As mentioned in the other answer, this isn't really proper usage of Enumerable. That said, you could still include Enumerable and use some meta-programming to override the methods that you want to be peep-chainable:
module PeepData
class Peeps
include Enumerable
PEEP_CHAINABLES = [:map, :select]
PEEP_CHAINABLES.each do |method_name|
define_method(method_name) do |&block|
self.class.new(super(&block))
end
end
# solution for select without meta-programming looks like this:
# def select
# Peeps.new(super)
# end
end
end
Just so you know, this really has nothing to do with inheritance vs delegation. If Peeps extended Array, you would have the exact same issue, and the exact solution above would still work.

Related

Create hash of instance methods references

I have a class that can parse different types of messages and what I want to do is to create a hash that will use the msg type id as the keys and different instance methods as the values.
Something like this:
class Parser
def initialize(msg_id)
#my_methods = {1 => method_1, 2 => method_2, 3 => method_3}
#my_methods[msg_id]()
end
def method_1
end
def method_2
end
def method_3
end end
I know it's possible, but I am not sure how to do it. I tried using the self.method(:method_1) as a value but I got an error saying that method_1 is not defined.
Thank you
The simplest possible changes to fix your code are like this:
class Parser
def initialize(msg_id)
#my_methods = { 1 => method(:method_1), 2 => method(:method_2), 3 => method(:method_3) }
#my_methods[msg_id].()
end
def method_1; end
def method_2; end
def method_3; end
end
I.e. use the Object#method method to get a Method object, and use the Method#call method to execute it.
However, there are a few improvements we could make. For one, your Hash associates Integers with values. But there is a better data structure which already does that: an Array. (Note: if your message IDs are not assigned sequentially, then a Hash is probably the right choice, but from the looks of your example, they are just Integers counting up from 1.)
And secondly, hardcoding the methods inside the Parser#initialize method is probably not a good idea. There should be a declarative description of the protocol, i.e. the message IDs and their corresponding method names somewhere.
class Parser
# this will make your message IDs start at 0, though
PROTOCOL_MAPPING = [:method_1, :method_2, :method_3].freeze
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map(&method(:method))
#my_methods[msg_id].()
end
def method_1; end
def method_2; end
def method_3; end
end
Another possibility would be something like this:
class Parser
PROTOCOL_MAPPING = []
private_class_method def self.parser(name)
PROTOCOL_MAPPING << name
end
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map(&method(:method))
#my_methods[msg_id].()
end
parser def method_1; end
parser def method_2; end
parser def method_3; end
end
Or maybe this:
class Parser
PROTOCOL_MAPPING = {}
private_class_method def self.parser(msg_id, name)
PROTOCOL_MAPPING[msg_id] = name
end
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map {|msg_id, name| [msg_id, method(name)] }.to_h.freeze
#my_methods[msg_id].()
end
parser 1, def method_1; end
parser 2, def method_2; end
parser 3, def method_3; end
end
While provided answer would work fine, there are few "minor" issues with it:
If there'd be tons of methods, hardcoding such hash would take time, and since it is not dynamic (because you have to update the hash manually each time new method is added to the class body) it is very error prone.
Even though you are within the class, and technically have access to all methods defined with any visibility scope with implicit receiver (including private and protected), it is still a good practice to only rely on public interface, thus, I'd recommend to use Object#public_send.
So here is what I would suggest (despite the fact I do not see how the idea of having such map would work in real life):
class Parser
def initialize(msg_id)
# generate a dynamic hash with keys starting with 1
# and ending with the size of the methods count
methods_map = Hash[(1..instance_methods.size).zip(instance_methods)]
# Use public_send to ensure, only public methods are accessed
public_send(methods_map[msg_id])
end
# create a method, which holds a list of all instance methods defined in the class
def instance_methods
self.class.instance_methods(false)
end
end
After a quick thought I refactored it a bit, so that we hide the implementation of the mapping to private methods:
class Parser
def initialize(msg_id)
public_send(methods_map[msg_id])
end
# methods omitted
private
def methods_map # not methods_hash, because what we do is mapping
Hash[(1..instance_methods.size).zip(instance_methods)]
# or
# Hash[instance_methods.each.with_index(1).map(&:reverse)]
end
def instance_methods
self.class.instance_methods(false)
end
end
The method you're looking for is send.
Note that the values in your hash need to be symbols to be passed to send.
class Parser
def initialize(msg_id)
#my_methods = {1 => :method_1, 2 => :method_2, 3 => :method_3}
send(#my_methods[msg_id])
end
def method_1
end
def method_2
end
def method_3
end
end
Documentation here

How to test a class method that modifies an attribute of another class in a containerised way rspec

I have an issue I have been whacking my head against for hours now, and neither I nor anyone I have asked has been able to come up with a suitable answer.
Essentially, I am writing a method that allows me to edit an instance variable of another method. I have multiple ways of doing this, however my issue is with writing the test for this method. I have tried many different double types, however as they are immutable and do not store states, I did not manage to find a way to make it work.
Here is the class whose working variable is changed:
class MyClass
attr_writer :working
def working?
#working
end
end
Here is the class and method that change it:
class OtherClass
def makes_work
#ary_of_instances_of_MyClass_to_fix.map do |x|
x.working = true
#ary_of_fixed_objects << x
end
end
end
(The actual class is much larger, but I have only included a generalised version of the method in question. I can put all of the specific code up in a gist if it would help)
So I need a way to test that makes_work does in fact accept the array of objects to be changed, changes them and appends them to array_of_fixed_objects. What would be the best way of testing this in a containerised way, without requiring MyClass?
My last attempt was using spies to see what methods were called on my dummy instance, however a range of failures, depending on what I did. Here is the most recent test I wrote:
describe '#make_work' do
it 'returns array of working instances' do
test_obj = spy('test_obj')
subject.ary_of_instances_of_MyClass_to_fix = [test_obj]
subject.makes_work
expect(test_obj).to have_received(working = true)
end
end
This currently throws the error:
undefined method to_sym for true:TrueClass
Many thanks for any help! I apologise if some formatting/ info is a little bit messed up, I am still pretty new to this whole stackoverflow thing!
I think the problem is have_received(working = true), it should be have_received(:working=).with(true)
Edit:
Examples of using have_received
https://github.com/rspec/rspec-mocks#test-spies
https://relishapp.com/rspec/rspec-mocks/v/3-5/docs/setting-constraints/matching-arguments
This works for me
class MyClass
attr_writer :working
def working?
#working
end
end
class OtherClass
attr_writer :ary_of_instances_of_MyClass_to_fix
def initialize
#ary_of_fixed_objects = []
end
def makes_work
#ary_of_instances_of_MyClass_to_fix.map do |x|
x.working = true
#ary_of_fixed_objects << x
end
end
end
describe '#make_work' do
subject { OtherClass.new }
it 'returns array of working instances' do
test_obj = spy('test_obj')
subject.ary_of_instances_of_MyClass_to_fix = [test_obj]
subject.makes_work
expect(test_obj).to have_received(:working=).with(true)
end
end
If you'd rather just avoid stubbing, you could use an instance of OpenStruct instead of a double:
class OtherClass
attr_writer :ary_of_instances_of_MyClass_to_fix
def initialize
#ary_of_instances_of_MyClass_to_fix, #ary_of_fixed_objects = [], []
end
def makes_work
#ary_of_instances_of_MyClass_to_fix.map do |x|
x.working = true
#ary_of_fixed_objects << x
end
#ary_of_fixed_objects
end
end
require 'ostruct'
RSpec.describe "#makes_work" do
describe "given an array" do
let(:array) { [OpenStruct.new(working: nil)] }
subject { OtherClass.new }
before do
subject.ary_of_instances_of_MyClass_to_fix = array
end
it "sets the 'working' attribute for each element" do
expect(array.map(&:working)).to eq [nil]
subject.makes_work
expect(array.map(&:working)).to eq [true]
end
end
end

Ruby wrapper functions

I have a class that wraps other instances to provide additional functionality (a presenter) and want to now have subclasses that provide the same functionality. So it would be something like this:
class BasePresenter
attr_accessor :base_object
def initialize(base_object)
self.base_object = base_object
end
end
class WalrusPresenter < BasePresenter
end
And I want to be able to do this:
BasePresenter(:bubbles)
#=> <# BasePresenter #base_object=:bubbles >
WalrusPresenter(:frank)
#=> <# WalrusPresenter #base_object=:frank >
Update
I think the functional differences are outside of the scope of the question, but they seem to be a sticking point so I'll add them.
I am not trying to delegate .new.
BasePresenter.new takes a single argument and wraps it in a presenter. BasePresenter() takes an object and:
If it is already a presenter, return it
If it is an array of objects, map over them and create a new presenter
If it is a single object, wrap it in a presenter and return it.
This is closer to ActiveSupport's Array#wrap, but I think the parenthetical syntax is rather communicative of its functionality so I'd like to use that if possible. The inheritance part is what is tripping me up; defining it once one the base class so it is useable on all children.
BasePresenter(:bubbles)
#=> <# BasePresenter #base_object=:bubbles >
BasePresenter([:waldorf, :statler])
#=> [ <# BasePresenter #base_object=:waldorf >, <# BasePresenter #base_object=:staler> ]
WalrusPresenter.new(:frank)
#=> <# WalrusPresenter #base_object=:frank >
WalrusPresenter([:harold, :richard])
#=> [ <# WalrusPresenter #base_object=:harold >, <# WalrusPresenter #base_object=:richard > ]
WalrusPresenter(WalrusPresenter(WalrusPresenter(:frank)))
#=> <# WalrusPresenter #base_object=:frank >
I might be missing the point of your question, but to me it looks like you just forgot to use new to create instances of your classes:
BasePresenter.new(:bubbles)
# => #<BasePresenter:0x00000001ae33b8 #base_object=:bubbles>
WalrusPresenter.new(:frank)
# => #<WalrusPresenter:0x00000001ae7878 #base_object=:frank>
Update: Mischa has already responded to your comment in about the same way I would. Kernel#Array tries calling to_ary on its argument, then tries calling to_a on it if that fails, then creates an Array with the argument as its sole element if that fails too.
It's not clear what behavior you want; it seems like you just want to get around using new, which of course you can do, but it would be silly:
def BasePresenter(base)
BasePresenter.new(base)
end
def WalrusPresenter(base)
WalrusPresenter.new(base)
end
You could do some metaprogramming to avoid repeating yourself when creating the wrappers. But I fail to see the reason you want to avoid new, and without a very good reason to do so, I'd have to recommend against it. Ruby uses new to instantiate objects.
Update 2: Thanks for clarifying what you're looking for. Here's the first thing that came to mind for me. You can definitely clean it up a bit, but something like this should get you started. (wrap doesn't need to be in BasePresenter at all.) Anyway, here you go:
class BasePresenter
attr_accessor :base_object
def initialize(base_object)
self.base_object = base_object
end
def self.wrap klass
Object.class_eval do
define_method klass.to_s do |object|
case object
when BasePresenter
object
when Array
object.map{|base| klass.new(base)}
else
klass.new(object)
end
end
end
end
BasePresenter.wrap BasePresenter
def self.inherited child_klass
BasePresenter.wrap child_klass
end
end
class WalrusPresenter < BasePresenter
end
This seems to do what you want:
BasePresenter(:bubbles)
# => #<BasePresenter:0x00000001db05a0 #base_object=:bubbles>
BasePresenter([:waldorf, :statler])
# => [#<BasePresenter:0x00000001da7c98 #base_object=:waldorf>,
#<BasePresenter:0x00000001da7c70 #base_object=:statler>]
WalrusPresenter.new(:frank)
# => #<WalrusPresenter:0x00000001da4070 #base_object=:frank>
WalrusPresenter([:harold, :richard])
# => [#<WalrusPresenter:0x00000001d773e0 #base_object=:harold>,
#<WalrusPresenter:0x00000001d773b8 #base_object=:richard>]
WalrusPresenter(WalrusPresenter(WalrusPresenter(:frank)))
# => #<WalrusPresenter:0x00000001d6c760 #base_object=:frank>
First, I don't think what you're trying to do is super wise - there are probably better ways to accomplish what you want. But if you really want to create a new operator against a class by adding parenthesis handling, the code below does the job I think..
I'm copying the gnarly class handling code from this great blog post by CodeErr.
Here is my solution for your problem using his code:
class Object
alias_method :orig_method_missing, :method_missing
def method_missing(m, *a, &b)
klass = begin
(self.is_a?(Module) ? self : self.class).const_get(m)
rescue NameError
end
return klass.send(:parens, *a, &b) if klass.respond_to? :parens
orig_method_missing m, *a, &b
end
end
class X
##class_arg = {}
def self.class_arg
##class_arg[self.name]
end
def self.parens(*args)
##class_arg[self.name] = args[0]
Class.new(self).class_eval do
define_method :initialize do
super(*args)
end
self
end
end
end
class Y < X
end
class Z < X
end
Z(:bubbles)
Y(:frank)
puts Z.class_arg # "bubbles"
puts Y.class_arg # "frank"
My solution:
class BasePresenter
attr_accessor :base_object
def initialize(base_object)
self.base_object = base_object
end
Object.class_eval do
def BasePresenter base_object
BasePresenter.new base_object
end
end
def self.inherited child_klass
Object.class_eval do
define_method child_klass.to_s.to_sym do |base_object|
child_klass.new base_object
end
end
end
end
class WalrusPresenter < BasePresenter
end
class Test < WalrusPresenter; end
puts BasePresenter(:bubbles)
puts WalrusPresenter(:frank)
puts Test(:test)

How to do attr_accessor_with_default in ruby?

Some code that I had that used attr_accessor_with_default in a rails model is now giving me a deprecation warning, telling me to "Use Ruby instead!"
So, thinking that maybe there was a new bit in ruby 1.9.2 that made attr_accessor handle defaults, I googled it, but I don't see that. I did see a bunch of methods to override attr_accessor to handle defaults though.
Is that what they mean when they tell me to "Use Ruby?" Or am I supposed to write full getters/setters now? Or is there some new way I can't find?
This apidock page suggests to just do it in the initialize method.
class Something
attr_accessor :pancakes
def initialize
#pancakes = true
super
end
end
Don't forget to call super especially when using ActiveRecord or similar.
attr_accessor :pancakes
def after_initialize
return unless new_record?
self.pancakes = 11
end
This ensures that the value is initialized to some default for new record only.
Since you probably know your data quite well, it can be quite acceptable to assume nil is not a valid value.
This means you can do away with an after_initialize, as this will be executed for every object you create. As several people have pointed out, this is (potentially) disastrous for performance. Also, inlining the method as in the example is deprecated in Rails 3.1 anyway.
To 'use Ruby instead' I would take this approach:
attr_writer :pancakes
def pancakes
return 12 if #pancakes.nil?
#pancakes
end
So trim down the Ruby magic just a little bit and write your own getter. After all this does exactly what you are trying to accomplish, and it's nice and simple enough for anyone to wrap his/her head around.
This is an ooooold question, but the general problem still crops up - and I found myself here.
The other answers are varied and interesting, but I found problems with all of them when initializing arrays (especially as I wanted to be able to use them at a class level before initialize was called on the instance). I had success with:
attr_writer :pancakes
def pancakes
#pancakes ||= []
end
If you use = instead of ||= you will find that the << operator fails for adding the first element to the array. (An anonymous array is created, a value is assigned to it, but it's never assigned back to #pancakes.)
For example:
obj.pancakes
#=> []
obj.pancakes << 'foo'
#=> ['foo']
obj.pancakes
#=> []
#???#!%$##%FRAK!!!
As this is quite a subtle problem and could cause a few head scratches, I thought it was worth mentioning here.
This pattern will need to be altered for a bool, for example if you want to default to false:
attr_writer :pancakes
def pancakes
#pancakes.nil? ? #pancakes = false : #pancakes
end
Although you could argue that the assignment isn't strictly necessary when dealing with a bool.
There's nothing magical in 1.9.2 for initializing instance variables that you set up with attr_accessor. But there is the after_initialize callback:
The after_initialize callback will be called whenever an Active Record object is instantiated, either by directly using new or when a record is loaded from the database. It can be useful to avoid the need to directly override your Active Record initialize method.
So:
attr_accessor :pancakes
after_initialize :init
protected
def init
#pancakes = 11
end
This is safer than something like this:
def pancakes
#pancakes ||= 11
end
because nil or false might be perfectly valid values after initialization and assuming that they're not can cause some interesting bugs.
I'm wondering if just using Rails implementation would work for you:
http://apidock.com/rails/Module/attr_accessor_with_default
def attr_accessor_with_default(sym, default = nil, &block)
raise 'Default value or block required' unless !default.nil? || block
define_method(sym, block_given? ? block : Proc.new { default })
module_eval( def #{sym}=(value) # def age=(value) class << self; attr_reader :#{sym} end # class << self; attr_reader :age end ##{sym} = value # #age = value end # end, __FILE__, __LINE__ + 1)
end
You can specify default values for instances of any class (not only ActiveRecords) after applying patch to Module:
class Zaloop
attr_accessor var1: :default_value, var2: 2
def initialize
self.initialize_default_values
end
end
puts Zaloop.new.var1 # :default_value
Patch for module:
Module.module_eval do
alias _original_attr_accessor attr_accessor
def attr_accessor(*args)
attr_names = extract_default_values args
_original_attr_accessor *attr_names
end
alias _original_attr_reader attr_reader
def attr_reader(*args)
attr_names = extract_default_values args
_original_attr_reader *attr_names
end
def extract_default_values(args)
#default_values ||= {}
attr_names = []
args.map do |arg|
if arg.is_a? Hash
arg.each do |key, value|
define_default_initializer if #default_values.empty?
#default_values[key] = value
attr_names << key
end
else
attr_names << arg
end
end
attr_names
end
def define_default_initializer
default_values = #default_values
self.send :define_method, :initialize_default_values do
default_values.each do |key, value|
instance_variable_set("##{key}".to_sym, value)
end
end
end
def initialize_default_values
# Helper for autocomplete and syntax highlighters
end
end

How do I add 'each' method to Ruby object (or should I extend Array)?

I have an object Results that contains an array of result objects along with some cached statistics about the objects in the array. I'd like the Results object to be able to behave like an array. My first cut at this was to add methods like this
def <<(val)
#result_array << val
end
This feels very c-like and I know Ruby has better way.
I'd also like to be able to do this
Results.each do |result|
result.do_stuff
end
but am not sure what the each method is really doing under the hood.
Currently I simply return the underlying array via a method and call each on it which doesn't seem like the most-elegant solution.
Any help would be appreciated.
For the general case of implementing array-like methods, yes, you have to implement them yourself. Vava's answer shows one example of this. In the case you gave, though, what you really want to do is delegate the task of handling each (and maybe some other methods) to the contained array, and that can be automated.
require 'forwardable'
class Results
include Enumerable
extend Forwardable
def_delegators :#result_array, :each, :<<
end
This class will get all of Array's Enumerable behavior as well as the Array << operator and it will all go through the inner array.
Note, that when you switch your code from Array inheritance to this trick, your << methods would start to return not the object intself, like real Array's << did -- this can cost you declaring another variable everytime you use <<.
each just goes through array and call given block with each element, that is simple. Since inside the class you are using array as well, you can just redirect your each method to one from array, that is fast and easy to read/maintain.
class Result
include Enumerable
def initialize
#results_array = []
end
def <<(val)
#results_array << val
end
def each(&block)
#results_array.each(&block)
end
end
r = Result.new
r << 1
r << 2
r.each { |v|
p v
}
#print:
# 1
# 2
Note that I have mixed in Enumerable. That will give you a bunch of array methods like all?, map, etc. for free.
BTW with Ruby you can forget about inheritance. You don't need interface inheritance because duck-typing doesn't really care about actual type, and you don't need code inheritance because mixins are just better for that sort of things.
Your << method is perfectly fine and very Ruby like.
To make a class act like an array, without actually inheriting directly from Array, you can mix-in the Enumerable module and add a few methods.
Here's an example (including Chuck's excellent suggestion to use Forwardable):
# You have to require forwardable to use it
require "forwardable"
class MyArray
include Enumerable
extend Forwardable
def initialize
#values = []
end
# Map some of the common array methods to our internal array
def_delegators :#values, :<<, :[], :[]=, :last
# I want a custom method "add" available for adding values to our internal array
def_delegator :#values, :<<, :add
# You don't need to specify the block variable, yield knows to use a block if passed one
def each
# "each" is the base method called by all the iterators so you only have to define it
#values.each do |value|
# change or manipulate the values in your value array inside this block
yield value
end
end
end
m = MyArray.new
m << "fudge"
m << "icecream"
m.add("cake")
# Notice I didn't create an each_with_index method but since
# I included Enumerable it knows how and uses the proper data.
m.each_with_index{|value, index| puts "m[#{index}] = #{value}"}
puts "What about some nice cabbage?"
m[0] = "cabbage"
puts "m[0] = #{m[0]}"
puts "No! I meant in addition to fudge"
m[0] = "fudge"
m << "cabbage"
puts "m.first = #{m.first}"
puts "m.last = #{m.last}"
Which outputs:
m[0] = fudge
m[1] = icecream
m[2] = cake
What about some nice cabbage?
m[0] = cabbage
No! I meant in addition to fudge
m.first = fudge
m.last = cabbage
This feels very c-like and I know Ruby
has better way.
If you want an object to 'feel' like an array, than overriding << is a good idea and very 'Ruby'-ish.
but am not sure what the each method
is really doing under the hood.
The each method for Array just loops through all the elements (using a for loop, I think). If you want to add your own each method (which is also very 'Ruby'-ish), you could do something like this:
def each
0.upto(#result_array.length - 1) do |x|
yield #result_array[x]
end
end
If you create a class Results that inherit from Array, you will inherit all the functionality.
You can then supplement the methods that need change by redefining them, and you can call super for the old functionality.
For example:
class Results < Array
# Additional functionality
def best
find {|result| result.is_really_good? }
end
# Array functionality that needs change
def compact
delete(ininteresting_result)
super
end
end
Alternatively, you can use the builtin library forwardable. This is particularly useful if you can't inherit from Array because you need to inherit from another class:
require 'forwardable'
class Results
extend Forwardable
def_delegator :#result_array, :<<, :each, :concat # etc...
def best
#result_array.find {|result| result.is_really_good? }
end
# Array functionality that needs change
def compact
#result_array.delete(ininteresting_result)
#result_array.compact
self
end
end
In both of these forms, you can use it as you want:
r = Results.new
r << some_result
r.each do |result|
# ...
end
r.compact
puts "Best result: #{r.best}"
Not sure I'm adding anything new, but decided to show a very short code that I wish I could have found in the answers to quickly show available options. Here it is without the enumerator that #shelvacu talks about.
class Test
def initialize
#data = [1,2,3,4,5,6,7,8,9,0,11,12,12,13,14,15,16,172,28,38]
end
# approach 1
def each_y
#data.each{ |x| yield(x) }
end
#approach 2
def each_b(&block)
#data.each(&block)
end
end
Lets check performance:
require 'benchmark'
test = Test.new
n=1000*1000*100
Benchmark.bm do |b|
b.report { 1000000.times{ test.each_y{|x| #foo=x} } }
b.report { 1000000.times{ test.each_b{|x| #foo=x} } }
end
Here's the result:
user system total real
1.660000 0.000000 1.660000 ( 1.669462)
1.830000 0.000000 1.830000 ( 1.831754)
This means yield is marginally faster than &block what we already know btw.
UPDATE: This is IMO the best way to create an each method which also takes care of returning an enumerator
class Test
def each
if block_given?
#data.each{|x| yield(x)}
else
return #data.each
end
end
end
If you really do want to make your own #each method, and assuming you don't want to forward, you should return an Enumerator if no block is given
class MyArrayLikeClass
include Enumerable
def each(&block)
return enum_for(__method__) if block.nil?
#arr.each do |ob|
block.call(ob)
end
end
end
This will return an Enumerable object if no block is given, allowing Enumerable method chaining

Resources