Ruby: catching access to variable inside class - ruby

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"]

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.

Ruby define a numeric index in an enumerable class without inheriting from Array or Hash and print it out

I have the following class:
class AEnumerableObject
include Enumerable
def initialize
#original_list = []
end
def [] index
#original_list[index]
end
def []= index, value
#original_list[index] = value
end
def each &block
#original_list.each_with_index do |item, i|
yield item, i
end if block_given?
end
end
If I run inline commands
$ object = AEnumerableObject.new
$ object[0] = 1
$ object[1] = 2
$ object[2] = 3
$ p object
should show
[1, 2, 3]
but it actually shows
#<AEnumerableObject:... #original_list=[1, 2, 3]>
while if I run
$ p object.class
it shows
=> AEnumerableObject
How do I implement an Array like print method?
UPDATED
I implemented the methods #[], #[]= and #each (so now it’s iterable). But how to show it like an Array in line command?
Your problem is that you have not implemented the inspect method. At the moment, the default inspect definition is used in your class. You may want to define it like:
class AEnumerableObject
def inspect
#original_list.inspect
end
end
As you can see on the left column (with all the defined methods in the module) of the documentation, the Enumerate module does not define the methods #[] and #[]=, which must be provided by the class.
Also, the class that includes Enumerate must provide an #each method.
class Test
include Enumerate
def [](i)
# do your stuff
end
def []=(i, v)
# do your stuff
end
def each
# required!
end
end
Implementing the required methods does not create any (let's say) "relation" with the Array class. The only way to create such a "relation" is through inheritance.
Let's make a silly example with some sort of linked list just to see how it works the idea...It is not robust nor well implemented... It is only an idea:
class Node
include Enumerable
attr_reader :data
def initialize(payload, n = nil)
#node = n if n.is_a? Node
#data = payload
end
def [](n)
if n == 0
self
else
#node[n - 1]
end
end
def []=(n, v)
if n == 0
#data = v
else
#node[n - 1] = v
end
end
def each(&block)
block.call(self)
#node.each(&block) if #node
end
def <=>(o)
#data <=> o.data
end
def to_s
"[" + self.map { |e| "#{e.data}" }.join(", ") + "]"
end
end
a = Node.new("Node 0")
b = Node.new("Node 1", a)
c = Node.new("Node 2", b)
d = Node.new("Node 3", c)
d[2] = "Edited"
d.each do |n|
puts n.data
end
# Let's use an enumerable method
s = d.select { |n| n.data == "Node 0" }[0]
puts "#{a.inspect} = #{s.inspect}"
puts a, b, c, d
# => Output:
#
# Node 3
# Node 2
# Edited
# Node 0
# #<Node:0x0000562e121e2440 #data="Node 0"> = #<Node:0x0000562e121e2440 #data="Node 0">
# [Node 0]
# [Edited, Node 0]
# [Node 2, Edited, Node 0]
# [Node 3, Node 2, Edited, Node 0]
As alternatives, you may consider:
# 1. Inheritance
def Test < Array
# do your stuff
end
# 2. Reopen the Array class
def Array
# do your stuff
end
The methods #[] and #[]= for the Array class are defined in the C source of the interpreter.

Getting #with_index when inheriting from Array

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

Appending dynamic value to ruby instance variable

I know this code doesn't look good at all , but i just want to explain my requirement. I want to know is there any good or alternative approach to it.
Actually, i want to create a new stack and whenever one stack has reached its capacity. I want to keep track of number of stacks created like #stack_1, #stack_2 ...by incrementing #number += 1 like #stack_#number. And for every stack, i want to maintain a #current_position pointer which is specific to every stack like #stack_2 has #current_position_2. So i want to create dynamic instance variables.
Example:
def initialize
#number = 1
#stack+"#{#number}" = Array.new(10)
#current_position_"#{#number}" = 0
end
Output should be something like #stack1 = Array.new(10).
Lets say if i increment value of #number += 1, it should look like #stack2 = Array.new(10)
Instead of array I suggest you to use Hash Map
#stack = Hash.new
#stack[#number] = <Your Array>
Be Careful if the #number is same your array will be replaced..
For more information about hash maps http://www.ruby-doc.org/core-1.9.3/Hash.html
You can do it like this:
instance_variable_set("#stack#{#number}", Array.new(10, :a))
#stack1
#=> [:a, :a, :a, :a, :a, :a, :a, :a, :a, :a]
instance_variable_set("#stack#{#number+1}", Array.new(10, :b))
#stack2
#=> [:b, :b, :b, :b, :b, :b, :b, :b, :b, :b]
instance_variable_set("#current_position_#{#number}", 0)
#current_position_1
#=> 0
Instead of creating instance variables to track a stack's state from the outside, you could create a Stack class that tracks its state internally. Here's a very simple one:
class StackOverflow < StandardError; end
class Stack
def initialize
#stack = []
end
def position
#stack.size
end
def full?
position == 2 # small size for demonstration purposes
end
def push(obj)
raise StackOverflow if full?
#stack << obj
end
end
stack = Stack.new
stack.push "foo"
stack.full? #=> false
stack.push "bar"
stack.full? #=> true
stack.push "baz" #=> StackOverflow
Having a working stack, you can build something like a StackGroup to handle multiple stacks:
class StackGroup
attr_reader :stacks
def initialize
#stacks = [Stack.new]
end
def push(obj)
#stacks << Stack.new if #stacks.last.full?
stacks.last.push(obj)
end
end
stack_group = StackGroup.new
stack_group.push "foo"
stack_group.push "bar"
stack_group.stacks.size #=> 1
stack_group.push "baz" # no error here
stack_group.stacks.size #=> 2
stack_group.stacks
#=> [#<Stack:0x007f9d8b886b18 #stack=["foo", "bar"]>,
# #<Stack:0x007f9d8b886a50 #stack=["baz"]>]

Delegate attribute methods to the parent object

I have the following class:
class Alphabet
attr_reader :letter_freqs, :statistic_letter
def initialize(lang)
#lang = lang
case lang
when :en
#alphabet = ('A'..'Z').to_a
#letter_freqs = { ... }
when :ru
#alphabet = ('А'..'Я').to_a.insert(6, 'Ё')
#letter_freqs = { ... }
...
end
#statistic_letter = #letter_freqs.max_by { |k, v| v }[0]
end
end
foo = Alphabet.new(:en)
The central member here is #alphabet.
I'd like to make it some sort of a container class to invoke Array methods directly like
foo[i]
foo.include?
instead of explicitly accessing #alphabet:
foo.alphabet[i]
foo.alphabet.include?
I know I could define a lot of methods like
def [](i)
#alphabet[i]
end
but I'm looking for a proper way of "inheriting" them.
You can use Forwardable (it is included in the Ruby standard library):
require 'forwardable'
class Alphabet
extend Forwardable
def_delegators :#alphabet, :[], :include?
def initialize
#alphabet = ('A'..'Z').to_a
end
end
foo = Alphabet.new
p foo[0] #=> "A"
p foo.include? 'ç' #=> false
If you wish to delegate all the methods not defined by your class you can use SimpleDelegator (also in the standard library); it lets you delegate all the methods that are not responded by the instance to an object specified by __setobj__:
require 'delegate'
class Alphabet < SimpleDelegator
def initialize
#alphabet = ('A'..'Z').to_a
__setobj__(#alphabet)
end
def index
'This is not #alphabet.index'
end
end
foo = Alphabet.new
p foo[0] #=> "A"
p foo.include? 'ç' #=> false
p foo.index #=> "This is not #alphabet.index"
When the delegate doesn't need to be dynamic you can arrange the master class to be a subclass of DelegateClass, passing the name of the class to be delegated as argument and calling super passing the object to be delegated in the #initialize method of the master class:
class Alphabet < DelegateClass(Array)
def initialize
#alphabet = ('A'..'Z').to_a
super(#alphabet)
end
More info about the delegation design pattern in Ruby here
You could extend the Forwardable module:
class Alphabet
require 'forwardable'
extend Forwardable
attr_accessor :alphabet
def initialize
#alphabet = [1,2,3]
end
def_delegator :#alphabet, :[], :include?
end
Then you can do:
alpha = Alphabet.new
alpha[1]==hey.alphabet[1]
=> true
Warning:
Don't try to delegate all methods (don't know if that's even possible) since they probably share some of the same method names such as class, which would probably make chaos.
In Ruby you can extend Objects like this.
class Array
def second
self[1]
end
end
[1, 2, 3, 4, 5].second
# => 2
Or if you want to inherit an Array.
class Foo < Array
def second
self[1]
end
end
[1, 2, 3, 4, 5].include? 2
# => true
[1, 2, 3, 4, 5].second
# => 2
If you have any further questions, comment and I will update the answer.

Resources