Ruby.Metaprogramming. Class_Eval

Ruby.Metaprogramming. class_eval

You will find a solution for your problem in Sergios answer. Here an explanation, what's going wrong in your code.

With

class_eval %Q{
@#{attr_name}_history=[1,2,3]
}

you execute

 @bar_history = [1,2,3]

You execute this on class level, not in object level.
The variable @bar_history is not available in a Foo-object, but in the Foo-class.

With

puts f.bar_history.to_s

you access the -never on object level defined- attribute @bar_history.

When you define a reader on class level, you have access to your variable:

class << Foo 
attr_reader :bar_history
end
p Foo.bar_history #-> [1, 2, 3]

Ruby - Using class_eval to define methods

This was fun!!!

class Class
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s # make sure it's a string
attr_reader attr_name
attr_reader attr_name+"_history"
class_eval %Q"
def #{attr_name}=(value)
if !defined? @#{attr_name}_history
@#{attr_name}_history = [@#{attr_name}]
end
@#{attr_name} = value
@#{attr_name}_history << value
end
"
end
end

class Foo
attr_accessor_with_history :bar
end

class Foo2
attr_accessor_with_history :bar
def initialize()
@bar = 'init'
end
end

f = Foo.new
f.bar = 1
f.bar = nil
f.bar = '2'
f.bar = [1,nil,'2',:three]
f.bar = :three
puts "First bar:", f.bar.inspect, f.bar_history.inspect
puts "Correct?", f.bar_history == [f.class.new.bar, 1, nil, '2', [1,nil,'2',:three], :three] ? "yes" : "no"
old_bar_history = f.bar_history.inspect

f2 = Foo2.new
f2.bar = 'baz'
f2.bar = f2
puts "\nSecond bar:", f2.bar.inspect, f2.bar_history.inspect
puts "Correct?", f2.bar_history == [f2.class.new.bar, 'baz', f2] ? "yes" : "no"

puts "\nIs the old f.bar intact?", f.bar_history.inspect == old_bar_history ? "yes" : "no"

Note that the only reason you need to use strings with class_eval is so that you can refer to the value of attr_name when defining the custom setter. Otherwise one would normally pass a block to class_eval.

Metaprogramming Ruby: defining inheritance in class_eval

If overriding existing functionality is a hard requirement here, you need to have those existing methods defined in a module that's also included.

class SomeClass
include DefaultBehaviour
end

module DefaultBehaviour
def run
puts "ran default"
end
end

module AlternateBehaviour
def run
puts "ran alternate"
end
end

SomeClass.class_eval {
include AlternateBehaviour
}

SomeClass.new.run #=> "ran alternate"

The reason for this is because of ruby's method lookup path.

It starts off as SomeClass -> Object.

When you include AlternateBehaviour, it becomes SomeClass -> AlternateBehaviour -> Object. So methods defined directly on SomeClass still take precedence.

However, if those methods are defined on DefaultBehaviour, the lookup path becomes SomeClass -> AlternateBehaviour -> DefaultBehaviour -> Object, so your alternate method takes priority. Whichever module was included most recently is the highest priority.

In the case where you do not have control of the original class, you can do instead:

module AlternateBehaviour
def self.included(base)
base.send(:remove_method, :run)
end

def run
puts "ran alternate"
end
end

Though at this point, one starts to wonder whether you might be better off by just doing

SomeClass.class_eval {
def run
"ran alternate"
end
end

Ruby metaprogramming: adding @variables to existing 'initialize' method (using class_eval)

In your setter method just check if the the history instance variable is already initialized:

def #{attr_name}=(new_value)
@#{attr_name}=new_value
@#{attr_name}_history ||= [nil]
@#{attr_name}_history.push(new_value)
end

You'll need another getter for your history variable that sets your default value if it was not set before:

def #{attr_name}_history
@#{attr_name}_history ||= [nil]
end

Then you could remove your initialize method, that was btw vulnerable to be overwritten.

Avoiding class_eval in Ruby metaprogramming

Use define_method:

class Module
def return_empty_set *names
names.each do |name|
define_method(name){Set.new}
end
end
end

ruby self.class.class_eval or singleton_class.class_eval

The difference between instances of these T classes is in the method lookup algorithm: method is always searched in the singleton class (and its modules) and only if it is not found here, it is searched in the class.

This mean if we add method test to the first implementation of class T after initialization we will get different result than when we do the same for second implementation of class T:

# First example
class T
def initialize
self.class.class_eval do
def test
return self.class.object_id
end
end
end
end

t = T.new

class T
def test
'overriden'
end
end

puts t.test # => 'overriden'

class T
def initialize
singleton_class.class_eval do
def test
return self.class.object_id
end
end
end
end

t = T.new

class T
def test
'overriden'
end
end

puts t.test # => 77697390


Related Topics



Leave a reply



Submit