Getting #with_index when inheriting from Array - ruby

I'd like my SomeArray#map to return an "array" of the SomeArray class.
class SomeArray < Array
def map
SomeArray.new(super)
end
end
some_array = SomeArray.new(["foo", "bar", "baz"])
p some_array.class #=> SomeArray
p some_array.map { |e| e }.class #=> SomeArray
Except now I also want to be able to use the Enumerator#with_index instance method. So ideally something like this would work:
some_array.map.with_index { |e, i| e }.class #=> SomeArray
How would that work?
I've tried:
class SomeArray < Array
def map
SomeArray.new(super)
end
def with_index(offset = 0)
super
end
end
some_array = SomeArray.new(["foo", "bar", "baz"])
p some_array.class #=> SomeArray
p some_array.map.with_index { |e, i| e }.class #=> no implicit conversion of Enumerator into Integer (TypeError)
But it's not working.

I think the problem here is that you're treating enumerable and array as if they're the same, which they're not.
Specifically, this in the map call: SomeArray.new(super).
I can reproduce your error:
[6] pry(main)> Array.new [1].map
TypeError: no implicit conversion of Enumerator into Integer
Now, when you pass a block to map, it works:
Array.new([1].map { |x| x })
=> [1]
But in your map.with_index you're not doing this.
You can do something like this:
module Foo
include Enumerable
def map
puts "calling my map"
super
end
def with_index
puts "calling my with_index"
super
end
end
class MyArr < Array
include Foo
end
puts MyArr.new([1]).map.with_index { |x, y| [x,y] }
# calling my map
# calling my with_index
# 1
# 0
Which kind of begs the question of why you're writing this class that just calls super. But in the case that you want to modify the default functionality of enumerable, this is one way.

The main problem is that map is an Array method while with_index is an Enumerator method.
Array methods are defined to just call super, and convert the output array to SomeArray.
Enumerator methods are defined with a to_enum first, convert the output to an Array and then to SomeArray.
It probably isn't the best structure for what you want to do, and isn't very efficient either. It's just a proof a concept!
class SomeArray < Array
# Array methods are overwritten here :
[:map, :select, :reject].each do |array_method_name|
define_method array_method_name do |*p, &block|
SomeArray.new(super(*p, &block).to_a)
end
end
# Enumerator methods are defined for SomeArray here :
[:with_index, :with_object].each do |enum_method_name|
define_method enum_method_name do |*p, &block|
SomeArray.new(to_enum.public_send(enum_method_name, *p, &block).to_a)
end
end
end
some_array = SomeArray.new(%w(foo bar baz biz))
p some_array.map { |s| s * 2 }.with_index.select { |_, i| i.even? }
#=> [["foofoo", 0], ["bazbaz", 2]]
p some_array.map { |s| s * 2 }.with_index.select { |_, i| i.even? }.class
#=> SomeArray

Related

Automatically generate 'attr_reader' for symbols

I have the following code where I set attr_reader and attr_writer manually.
class Pairs
attr_reader :pair, :asks, :bids, :isFrozen, :seq, :main_currency, :sub_currency
attr_writer :pair, :asks, :bids, :isFrozen, :seq
def initialize (key, args)
#pair = key
#main_currency, #sub_currency = key.split('_')
args.each {|k,v|
if numeric?(v) then v=v.to_f end
self.instance_variable_set("##{k}".to_sym, v)
}
end
private
def numeric?(string)
Float(string) != nil rescue false
end
end
Is there a way to automatically set them based on the keys of the arguments, like I'm automatically filling #k with v? Can I set attr_reader for each #k?
I suppose something like:
self.attr_reader("##{k}")
or even better for all objects of the class, something like:
Pairs << attr_reader("##{k}")
I am going to assume that you may be creating this with many keys specific to different Hash if this is the case then rather than clutter the individual instances with unneeded readers for non existent keys let's use the singleton_class for this.
So your final Pairs class could look something like
class Pairs
attr_reader :main_currency, :sub_currency
attr_accessor :pair, :asks, :bids, :isFrozen, :seq
def initialize (key, args)
#pair = key
#main_currency, #sub_currency = key.split('_')
args.each do |k,v|
singleton_class.send(:attr_reader,k)
instance_variable_set("##{k}", convert_numeric(v))
end
# Alternatively:
# args.each do |k,v|
# val = convert_numeric(v)
# define_singleton_method(k) {val}
# end
end
private
def convert_numeric(val)
Float(Rational(val)) rescue val
end
end
TL;DR
For Example: (using #mudasobwa's approach)
class C
def extend_self_with_reader name
self.class.send :attr_reader, name
end
def initialize *keys
keys.each(&method(:extend_self_with_reader))
end
end
This causes subsequent readers to clutter the instance and bleed across instances:
a = C.new(:a,:b)
a.a #=> nil
b = C.new
b.a #=> nil
c = C.new(:r)
c.a #=> nil
c.r #=> nil
a.methods.sort - Object.methods
#=> [:a, :b, :extend_self_with_reader, :r]
a.r #=> nil (hmmmmm)
Instead localize these readers buy using the singleton_class of the instance like:
class C
def initialize *keys
singleton_class.send(:attr_reader, *keys)
end
end
Then
a = C.new(:a,:b)
a.a #=> nil
b = C.new
b.a #=> NoMethodError: undefined method `a'
c = C.new(:r)
c.a #=> NoMethodError: undefined method `a'
c.r #=> nil
a.r #=> NoMethodError: undefined method `r'
a.methods.sort - Object.methods
#=> [:a,:b]
b.methods.sort - Object.methods
#=> []
Using the singleton_class localizes these readers to the instance of the object rather than bleeding them into the Class definition. If attr_reader is not a requirement then this would also be sufficient:
keys.each {|k| define_singleton_method(k) {}}
I doubt I understood the question, but from what I get you want to dynamically extend your class with attribute readers at runtime.
This method would do:
def extend_self_with_reader name
self.class.send :attr_reader, name
end
Test:
class C
def extend_self_with_reader name
self.class.send :attr_reader, name
end
def initialize *keys
puts keys.inspect
keys.each(&method(:extend_self_with_reader))
end
end
cc = C.new(*%i|a b c|)
cc.a #⇒ nil
Perhaps look into define_method.
I'm not 100% sure that I understand the problem, but check this out:
hash = { a: 1, b: 2, c: 3 }
hash.keys.each do |key|
define_method(key) do
hash[key]
end
end
There are now methods for a, b, and c:
a => 1
b => 2
c => 3
That essentially makes an attr_reader for all the keys in the hash. You could do something similar for an attr_writer.

Check how many parameters block being passed has, and then invoke yield with different parameters

I want to know if something like this is possible, as hash.each does two different things based on arguments passed to block
{ "a"=>3, "b"=>2}.each {|k| puts k}
{ "a"=>3, "b"=>2}.each {|k,v| puts k}
Both output different things, and not just what's below..
a
b
..
a
b
I get this
a
3
b
2
..
a
b
So I want to know if there is a way to get my function to do something custom depending on the number of arguments of block being passed to it.
like this
def my_method
if yield.args==2
yield("hi","bro")
else
yield("what's up")
end
end
my_method {|a,b| puts a; puts b;} #"hi" "bro"
my_method {|a| puts a; } #"whats up"
def my_method(&block)
if block.arity == 2
yield("hi", "bro")
else
yield("what's up")
end
end
Also look at the documentation since Proc#arity has special behavior for default/named arguments, etc.
You can capture the block as a Proc and then inspect its parameters:
def my_method(&blk)
blk.parameters
end
my_method {|a|}
# => [[:opt, :a]]
my_method {|a, b|}
# => [[:opt, :a], [:opt, :b]]
Note, however, that this is not what happens with Hash#each. Hash#each just follows the Enumerable#each protocol and always yields one single item to the block, which is a two-element Array of key and value. Normal block parameter binding then takes care of the rest:
def my_method
yield ['Hello', 'World']
end
my_method {|a| p a }
# ["Hello", "World"]
my_method {|a, b| p a, b }
# "Hello"
# "World"
my_method {|a, b, c| p a, b, c }
# "Hello"
# "World"
# nil

Ruby: Convert nested hash to object?

I am trying to convert a hash that includes nested hashes to object, such that attributes (including nested attributes) can be accessed using dot syntax.
So far first hash object is converted successfully by this code:
class Hashit
def initialize(hash)
hash.each do |k,v|
self.instance_variable_set("##{k}", v)
self.class.send(:define_method, k, proc{self.instance_variable_get("##{k}")})
self.class.send(:define_method, "#{k}=", proc{|v| self.instance_variable_set("##{k}", v)})
end
end
end
The problem is, this approach doesn't work for nested hashes:
h = Hashit.new({a: '123r', b: {c: 'sdvs'}})
=> #<Hashit:0x00000006516c78 #a="123r", #b={:c=>"sdvs"}>
Note that in the output, #b={:c=>"sdvs"} wasn't converted; it's still a hash.
How can I convert a nested hash to an object?
You can use OpenStruct
http://ruby-doc.org/stdlib-2.0.0/libdoc/ostruct/rdoc/OpenStruct.html
user = OpenStruct.new({name: "Jimmy Cool", age: "25"})
user.name #Jimmy Cool
user.age #25
Another way is to use JSON and OpenStruct, which are standard ruby libs:
irb:
> require 'JSON'
=> true
> r = JSON.parse({a: { b: { c: 1 }}}.to_json, object_class: OpenStruct)
=> #<OpenStruct a=#<OpenStruct b=#<OpenStruct c=1>>>
> r.a.b.c
=> 1
You need to add recursivity:
class Hashit
def initialize(hash)
hash.each do |k,v|
self.instance_variable_set("##{k}", v.is_a?(Hash) ? Hashit.new(v) : v)
self.class.send(:define_method, k, proc{self.instance_variable_get("##{k}")})
self.class.send(:define_method, "#{k}=", proc{|v| self.instance_variable_set("##{k}", v)})
end
end
end
h = Hashit.new({a: '123r', b: {c: 'sdvs'}})
# => #<Hashit:0x007fa6029f4f70 #a="123r", #b=#<Hashit:0x007fa6029f4d18 #c="sdvs">>
Ruby has an inbuilt data structure OpenStruct to solve something like this. Still, there is a problem. It is not recursive. So, you can extend OpenStruct class like this:
# Keep this in lib/open_struct.rb
class OpenStruct
def initialize(hash = nil)
#table = {}
if hash
hash.each_pair do |k, v|
k = k.to_sym
#table[k] = v.is_a?(Hash) ? OpenStruct.new(v) : v
end
end
end
def method_missing(mid, *args) # :nodoc:
len = args.length
if mname = mid[/.*(?==\z)/m]
if len != 1
raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
end
modifiable?[new_ostruct_member!(mname)] = args[0].is_a?(Hash) ? OpenStruct.new(args[0]) : args[0]
elsif len == 0 # and /\A[a-z_]\w*\z/ =~ mid #
if #table.key?(mid)
new_ostruct_member!(mid) unless frozen?
#table[mid]
end
else
begin
super
rescue NoMethodError => err
err.backtrace.shift
raise
end
end
end
end
and remember to require 'open_struct.rb' next time you want to use OpenStruct.
Now you can do something like this:
person = OpenStruct.new
person.name = "John Smith"
person.age = 70
person.more_info = {interests: ['singing', 'dancing'], tech_skills: ['Ruby', 'C++']}
puts person.more_info.interests
puts person.more_info.tech_skills
You could check the type on v when you initialize the object and call new to get a new Hashit when it is a another hash.
class Hashit
def initialize(hash)
hash.each do |k,v|
self.instance_variable_set("##{k}", v.is_a?(Hash) ? Hashit.new(v) : v)
self.class.send(:define_method, k, proc{self.instance_variable_get("##{k}")})
self.class.send(:define_method, "#{k}=", proc{|v| self.instance_variable_set("##{k}", v)})
end
end
end
and the resulting snippet from before would be:
h = Hashit.new({a: '123r', b: {c: 'sdvs'}})
=> #<Hashit:0x007fa71421a850 #a="123r", #b=#<Hashit:0x007fa71421a5a8 #c="sdvs">>
If I understand the question correctly, this should do it:
class Hashit
def initialize(hash)
convert_to_obj(hash)
end
private
def convert_to_obj(h)
h.each do |k,v|
self.class.send(:attr_accessor, k)
instance_variable_set("##{k}", v)
convert_to_obj(v) if v.is_a? Hash
end
end
end
h = Hashit.new( { a: '123r',
b: { c: 'sdvs', d: { e: { f: 'cat' }, g: {h: 'dog'} } } })
#=> #<Hashit:0x000001018eee58 #a="123r",
# #b={:c=>"sdvs", :d=>{:e=>{:f=>"cat"}, :g=>{:h=>"dog"}}},
# #c="sdvs", #d={:e=>{:f=>"cat"}, :g=>{:h=>"dog"}},
# #e={:f=>"cat"}, #f="cat", #g={:h=>"dog"}, #h="dog">
h.instance_variables
#=> [:#a, :#b, :#c, :#d, :#e, :#f, :#g, :#h]
Hashit.instance_methods(false)
#=> [:a, :a=, :b, :b=, :c, :c=, :d, :d=, :e, :e=, :f, :f=, :g, :g=, :h, :h=]
h.d
#=> {:e=>{:f=>"cat"}}
h.d = "cat"
h.d
#=> "cat"

Calls block once for each element of an array, passing that element as a parameter

I have some difficulties for using Ruby block, passing in a method.
As in the following case, I would like to display each element of #array, from Box instance (using .each method):
class Box
def initialize
#array = [:foo, :bar]
end
def each(&block)
# well, hm..
end
end
a = Box.new
a.each { |element| puts element }
You really just need to delegate to the each method on #array and pass it the block. Additionally, you can include the Enumerable mix-in to gain access to the methods it provides (e.g. map, inject, etc...):
class Box
include Enumerable
def initialize
#array = [:foo, :bar]
end
def each(&block)
#array.each(&block)
end
end
More information on the Enumerable module is available in the documentation.
For this simple example, you actually aren't required to pass the block explicitly:
def each
#array.each{|e| yield e}
end
Passing the block (which is a Proc object) explicitly allows you to test it for things, like the number of arguments that it expects:
class Box
...
def each(&block)
#array.each do |e|
case block.arity
when 0
yield
when 1
yield e
when 2
yield e, :baz
else
yield
end
end
end
end
a = Box.new
a.each { puts "nothing" } # displays "nothing, nothing"
a.each { |e| puts e } # displays "foo, bar"
a.each { |e1, e2| puts "#{e1} #{e2}" } # displays "foo baz, bar baz"

Ruby: catching access to variable inside class

I have a class, which de-constructs incoming string into a nested array cascade.
For example for an input abcde it will produce a [[[[a,b],c],d],e] array.
Just now, if I access to set any top level value of cascade, the []=(index, value) method of my class will be invoked. But I also need to catch the access to the nested array within cascade of arbitrary level.
See example below, where accessing x[0][0] obviously doesn't invoke a class method []=. So, is it possible to catch that access within a class method (or at least in a different way)?
class MyClass
attr_accessor :cascade
def initialize string
build_cascade string.split(//)
end
def build_cascade array
if array.length > 2
array[0] = array[0..1]
array.delete_at(1)
build_cascade array
else
#cascade = array
end
end
def []=(index, value)
puts 'You\'ve just tried to set \''+value.to_s+'\' for \''+index.to_s+'\' of #cascade!'
end
def [](index)
#cascade[index]
end
end
x = MyClass.new('abcdefghigk')
puts x.inspect
x[0] = 5 # => You've just tried to set '5' for '0' of #cascade!
x[0][0] = 10 #= > ~ no output ~
The problem is that you are calling []= on the sub-array contained within your main array.
in other words, you are calling [] on your class, which you implement to return that array element, and then []= on a generic Array, which you have not blocked write access to.
you could implement the structure to have your class create its subarrays by using other instances of MyClass, or you could overwrite the []= method of Array to restrict access.
Its also worth noting that depending on how this would be used, overwriting methods on a class like Array is not usually a great idea so you might want go for something like my first suggestion.
In Ruby you can patch objects, add new methods, redefine old ones freely. So you can just patch all the arrays you create so they tell you when they are being accessed.
class A
def patch_array(arr)
class << arr
alias old_access_method []=
def []= (i, v)
#cascade_object.detect_access(self)
old_access_method(i,v)
end
end
s = self
arr.instance_eval {
#cascade_object = s
}
end
def detect_access(arr)
p 'access detected!'
end
end
a = A.new
arr = [1, 2]
a.patch_array(arr)
arr[1] = 3 # prints 'access detected!'
p arr
new_arr = [1,4]
new_arr[1] = 5 #prints nothing
p new_arr
#!/usr/bin/ruby1.8
require 'forwardable'
class MyClass
extend Forwardable
attr_accessor :cascade
def initialize string
#cascade = decorate(build_cascade string.split(//))
end
private
def build_cascade array
if array.length <= 2
array
else
build_cascade([array[0..1]] + array[2..-1])
end
end
def decorate(array)
return array unless array.is_a?(Array)
class << array
alias old_array_assign []=
def []=(index, value)
puts "#{self}[#{index}] = #{value}"
old_array_assign(index, value)
end
end
array.each do |e|
decorate(e)
end
array
end
def_delegators :#cascade, :[], :[]=
end
x = MyClass.new('abcdefghigk')
p x.cascade
# => [[[[[[[[[["a", "b"], "c"], "d"], "e"], "f"], "g"], "h"], "i"], "g"], "k"]
x[0][1] = 5 # => abcdefghig[1] = 5
x[0][0][1] = 10 # => abcdefghi[1] = 10
x[0][0][0][1] = 100 # => abcdefgh[1] = 100
p x.cascade
# => [[[[[[[[[["a", "b"], "c"], "d"], "e"], "f"], "g"], 100], 10], 5], "k"]

Resources