I was working on a homework assignment when I ran into a frustrating issue. The assignment is an exercise in Ruby metaprogramming and the goal is to define an 'attr_accessor_with_history' that does all the same things as 'attr_accessor', but also provides a history of all values that an attribute has ever been. Here is the provided code from the assignment along with some code I added in an attempt to complete the assignment:
class Class
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s
attr_hist_name = attr_name+'_history'
history_hash = {attr_name => []}
#getter
self.class_eval("def #{attr_name} ; ##{attr_name} ; end")
#setter
self.class_eval %Q{
def #{attr_name}=(val)
# add to history
##{attr_hist_name} = [nil] if ##{attr_hist_name}.nil?
##{attr_hist_name} << val
history_hash[##{attr_name}] = ##{attr_hist_name}
# set the value itself
##{attr_name} = val
end
def history(attr) ; #history_hash[attr.to_s] ; end
}
end
end
class Foo
attr_accessor_with_history :bar
attr_accessor_with_history :crud
end
f = Foo.new # => #<Foo:0x127e678>
f.bar = 3 # => 3
f.bar = :wowzo # => :wowzo
f.bar = 'boo!' # => 'boo!'
puts f.history(:bar) # => [3, :wowzo, 'boo!']
f.crud = 42
f.crud = "Hello World!"
puts f.history(:crud)
I wanted to use a hash to store different histories for different attributes but I cannot access that hash in the class_eval statement for the setter. No matter how I try to set it up I always either seem to get a NoMethodError for the []= method because 'history_hash' somehow becomes type NilClass, or a NameError occurs because it sees 'history_hash' as an undefined local variable or method. How do I use the hash in the class_eval statements?
or a NameError occurs because it sees 'history_hash' as an undefined local variable or method
I'd say you can't, because it is a local variable, one that is inaccessible in the context you want it. However, why do you even need it? I'm reasonably sure it's in the "some code I added in an attempt to complete the assignment", and not the original assignment code (which, I assume, expects you to store the history of #bar in #bar_history - or else what is attr_hist_name all about?)
I'm also uncomfortable about string evals; it's generally not necessary, and Ruby can do better, with its powerful metaprogramming facilities. Here's how I'd do it:
class Class
def attr_accessor_with_history(attr_name)
attr_setter_name = :"#{attr_name}="
attr_getter_name = :"#{attr_name}"
attr_hist_name = :"##{attr_name}_history"
attr_name = :"##{attr_name}"
self.class_eval do
define_method(attr_getter_name) do
instance_variable_get(attr_name)
end
define_method(attr_setter_name) do |val|
instance_variable_set(attr_name, val)
history = instance_variable_get(attr_hist_name)
instance_variable_set(attr_hist_name, history = []) unless history
history << val
end
end
end
end
class Object
def history(attr_name)
attr_hist_name = :"##{attr_name}_history"
instance_variable_get(attr_hist_name)
end
end
Finally, as it's monkey-patching base classes, I'd rather use refinements to add it where needed, but that's probably an overkill for an assignment.
Related
I would like to deny creating instance variables in Ruby,to prevent unattended variables being created 'by mistake'.
My class:
class Test
def initialize
#a = 'Var A'
end
def make_new
#b = 'Var B' <-- I would like to deny creation of any variables that were not defined during the init
end
end
I don't claim this is a good idea, but just b/c it's kind of interesting, here is a solution that will throw an exception when a new ivar is created, but will also let you modify defined instance variables (unlike freezing the class). Just threw this together, there are undoubtably some issues w/ it, including the fact that it duplicates every method :)
module IvarBlocker
def method_added(method)
alias_name = "__#{method}_orig"
return if method == :initialize || method_defined?(alias_name) || method.match(/__.*_orig/)
alias_method alias_name, method
define_method(method) do |*args|
ivars_before = instance_variables.dup
send(alias_name, *args).tap { raise "New Ivar assigned" if !(instance_variables - ivars_before).empty? }
end
end
end
Usage
class Test
extend IvarBlocker
def initialize
#a = 1
end
def set_b
#b = 2
end
def set_a
#a = 6
end
end
t = Test.new #=> #<Test:0x007f87f13c41e8 #a=1>
t.set_b #=> RuntimeError: New Ivar assigned
t.set_a #=> 6
You can freeze object instances at the end of initialize method:
class Test
def initialize
#a = 'Var A'
freeze
end
def make_new
#b = 'Var B' # I would like to deny creation of any variables that were not defined during the init
end
end
t=Test.new
p t.instance_variable_get :#a
# "Var A"
t.make_new
#a.rb:24:in `make_new': can't modify frozen Test (RuntimeError)
# from a.rb:30:in `<main>'
t.instance_variable_set :#c, 'Var C'
# a.rb:31:in `instance_variable_set': can't modify frozen Test (RuntimeError)
# from a.rb:31:in `<main>'
class << t
#d = 'Var D'
end
#a.rb:33:in `singletonclass': can't modify frozen Class (RuntimeError)
# from a.rb:32:in `<main>'
p t.instance_variable_get :#d
There is a way - a hacky (but fun) way which is not meant for production (and is relatively slow). My sample implementation works for a single object only, but can be extended to support many objects.
Let's assume the following setup:
class Foo
def initialize
#a = :foo
end
def set_b; #b = 3; end
def set_c; #c = 7; end
end
def freeze_variables_of(obj)
frozen_variables = obj.instance_variables
set_trace_func lambda {|event, file, line, id, binding, classname|
if classname == obj.class
this = binding.eval 'self'
if this == obj
(this.instance_variables - frozen_variables).each {|var| this.remove_instance_variable var}
end
end
}
end
With the use of set_trace_func we can set a Proc which is called very often (usually more than once per statement). In that Proc we can check instance variables and remove unwanted variables.
Let's look at an example:
a = Foo.new
# => #<Foo:0x007f6f9db75cc8 #a=:foo>
a.set_b; a
# => #<Foo:0x007f6f9db75cc8 #a=:foo, #b=3>
freeze_variables_of a
a.set_c; a
# => #<Foo:0x007f6f9db75cc8 #a=:foo, #b=3>
We see that after doing the "freeze", set_c cannot set the instance variable (in fact the variable is removed at the very moment the set_c method returns).
In contrast to freezing the object (with a.freeze) (which I'd recommend for any real world application), this way allows you to modify all allowed instance variables while forbidding new ones.
This even works, if you directly assign instance variables (while Alex' method is probably faster, but relies on accessor methods).
There is no way to prevent creation of accidental instance variables defined that way. Why do you want to do this? What would you want such code to achieve?
I have an instance variable #foo and I want to write some code so that I get string 'foo'
any hint?
If all you have is a reference to the object, you can't really do it cleanly.
def foo
bar #something
end
def bar(value)
value # no clean way to know this is #something
end
The only hack I can think of is to loop through ALL instance variables on self, looking for matches. But its a very messy approach that's likely to be slow.
def bar(value)
instance_variables.each do |ivar_name|
if instance_variable_get(ivar_name) == value
return ivar_name.to_s.sub(/^#/, '') # change '#something' to 'something'
end
end
# return nil if no match was found
nil
end
#something = 'abc123'
bar #something # returns 'something'
# But passing the same value, will return a value it's equal to as well
bar 'abc123' # returns 'something'
This works because instance_variables returns an array of symbols that are the names of instance variables.
instance_variables
#=> [:#something, :#whatever]
And instance_variable_get allows you to fetch the value by it's name.
instance_variable_get :#something # note the #
#=> 'abc123'
Combine the two methods and you can get close to what you want.
Just use it wisely. Before using a solution based on this, see if you can refactor things a way so that it's not necessary. Meta-programming is like a martial art. You should know how it works, but have the discipline to avoid using it whenever possible.
In Ruby, you can only manipulate objects. Variables (including instance variables) aren't objects.
Besides, what do you want your magic method to return in this case:
foo = Object.new
bar = foo
#baz = bar
#qux = bar
magic_method(foo) # what should the return value be and how would it know?
You can call the method instance_variables to get the name of all instance variables of an object. Caution though that instance variables are only included in that list after they have been initialized.
>> class A; attr_accessor :foo; end
=> nil
>> a = A.new
=> #<A:0x103b310b0>
>> a.instance_variables
=> []
>> a.foo = 42
=> 42
>> a.instance_variables
=> ["#foo"]
class Object
def get_name
line_number = caller[0].split(':')[1].to_i
line_exectued = File.readlines( __FILE__)[line_number-1]
line_exectued.match(/(\S+).get_name/)[1]
end
end
inconceivable = true
p inconceivable.get_name
I have this code:
l = lambda { a }
def some_function
a = 1
end
I just want to access a by the lambda and a special scope which has defined a already somewhere like inside some_function in the example, or just soon later in the same scope as:
l = lambda { a }
a = 1
l.call
Then I found when calling l, it is still using its own binding but not the new one where it was called.
And then I tried to use it as:
l.instance_eval do
a = 1
call
end
But this also failed, it is strange that I can't explain why.
I know the one of the solution is using eval, in which I could special a binding and executing some code in text, but I really do not want to use as so.
And, I know it is able to use a global variable or instance variable. However, actually my code is in a deeper embedded environment, so I don't want to break the completed parts if not quite necessary.
I have referred the Proc class in the documentation, and I found a function names binding that referred to the Proc's context. While the function only provided a way to access its binding but cannot change it, except using Binding#eval. It evaluate text also, which is exactly what I don't like to do.
Now the question is, do I have a better (or more elegant) way to implement this? Or using eval is already the regular manner?
Edit to reply to #Andrew:
Okay, this is a problem which I met when I'm writing a lexical parser, in which I defined a array with fixed-number of items, there including at least a Proc and a regular expression. My purpose is to matching the regular expressions and execute the Procs under my special scope, where the Proce will involved some local variables that should be defined later. And then I met the problem above.
Actually I suppose it is not same completely to that question, as mine is how to pass in binding to a Proc rather than how to pass it out.
#Niklas:
Got your answer, I think that is what exactly I want. It has solved my problem perfectly.
You can try the following hack:
class Proc
def call_with_vars(vars, *args)
Struct.new(*vars.keys).new(*vars.values).instance_exec(*args, &self)
end
end
To be used like this:
irb(main):001:0* lambda { foo }.call_with_vars(:foo => 3)
=> 3
irb(main):002:0> lambda { |a| foo + a }.call_with_vars({:foo => 3}, 1)
=> 4
This is not a very general solution, though. It would be better if we could give it Binding instance instead of a Hash and do the following:
l = lambda { |a| foo + a }
foo = 3
l.call_with_binding(binding, 1) # => 4
Using the following, more complex hack, this exact behaviour can be achieved:
class LookupStack
def initialize(bindings = [])
#bindings = bindings
end
def method_missing(m, *args)
#bindings.reverse_each do |bind|
begin
method = eval("method(%s)" % m.inspect, bind)
rescue NameError
else
return method.call(*args)
end
begin
value = eval(m.to_s, bind)
return value
rescue NameError
end
end
raise NoMethodError
end
def push_binding(bind)
#bindings.push bind
end
def push_instance(obj)
#bindings.push obj.instance_eval { binding }
end
def push_hash(vars)
push_instance Struct.new(*vars.keys).new(*vars.values)
end
def run_proc(p, *args)
instance_exec(*args, &p)
end
end
class Proc
def call_with_binding(bind, *args)
LookupStack.new([bind]).run_proc(self, *args)
end
end
Basically we define ourselves a manual name lookup stack and instance_exec our proc against it. This is a very flexible mechanism. It not only enables the implementation of call_with_binding, it can also be used to build up much more complex lookup chains:
l = lambda { |a| local + func(2) + some_method(1) + var + a }
local = 1
def func(x) x end
class Foo < Struct.new(:add)
def some_method(x) x + add end
end
stack = LookupStack.new
stack.push_binding(binding)
stack.push_instance(Foo.new(2))
stack.push_hash(:var => 4)
p stack.run_proc(l, 5)
This prints 15, as expected :)
UPDATE: Code is now also available at Github. I use this for one my projects too now.
class Proc
def call_with_obj(obj, *args)
m = nil
p = self
Object.class_eval do
define_method :a_temp_method_name, &p
m = instance_method :a_temp_method_name; remove_method :a_temp_method_name
end
m.bind(obj).call(*args)
end
end
And then use it as:
class Foo
def bar
"bar"
end
end
p = Proc.new { bar }
bar = "baz"
p.call_with_obj(self) # => baz
p.call_with_obj(Foo.new) # => bar
Perhaps you don't actually need to define a later, but instead only need to set it later.
Or (as below), perhaps you don't actually need a to be a local variable (which itself references an array). Instead, perhaps you can usefully employ a class variable, such as ##a. This works for me, by printing "1":
class SomeClass
def l
#l ||= lambda { puts ##a }
end
def some_function
##a = 1
l.call
end
end
SomeClass.new.some_function
a similar way:
class Context
attr_reader :_previous, :_arguments
def initialize(_previous, _arguments)
#_previous = _previous
#_arguments = _arguments
end
end
def _code_def(_previous, _arguments = [], &_block)
define_method("_code_#{_previous}") do |_method_previous, _method_arguments = []|
Context.new(_method_previous, _method_arguments).instance_eval(&_block)
end
end
_code_def('something') do
puts _previous
puts _arguments
end
I'm trying to DRY up some code, and I feel like Ruby's variable assignment must provide a way to simplify this. I have a class with a number of different instance variables defined. Some of these are intended to be hidden (or read-only), but many are public, with read/write access.
For all of the variables with public write-access, I want to perform a certain method after each assignment. I know that, in general, I can do this:
def foo=(new_foo)
#foo = new_foo
post_process(#foo)
end
def bar=(new_bar)
#bar = new_bar
post_process(#foo)
end
However, it seems that there should be a nice way to DRY this up, since I'm doing essentially the same thing after each assignment (ie, running the same method, and passing the newly-assigned variable as a parameter to that method). Since I have a number of such variables, it would be great to have a general-purpose solution.
Simpler solution
If you assign those variables in batch, you can do something like this:
kv_pairs = {:foo => new_foo_value,
:bar => new_bar_value}
kv_pairs.each do |k, v|
self.send(k.to_s + '=', v)
post_process(v)
end
Metaprogramming
Here's some ruby magic :-)
module PostProcessAssignments
def hooked_accessor( *symbols )
symbols.each { | symbol |
class_eval( "def #{symbol}() ##{symbol}; end" )
class_eval( "def #{symbol}=(val) ##{symbol} = val; post_process('#{symbol}', val); end" )
}
end
end
class MyClass
extend PostProcessAssignments
hooked_accessor :foo
def post_process prop, val
puts "#{prop} was set to #{val}"
end
end
mc = MyClass.new
mc.foo = 4
puts mc.foo
Outputs:
foo was set to 4
4
Let's say I have a class Foo and the constructor takes 2 parameters.
Based on these parameters the initialize method does some heavy calculations and stores them as variables in the instance of the class. Object created.
Now I want to optimize this and create a cache of these objects. When creating a new Foo object, I want to return a existing one from the cache if the parameters match. How can I do this?
I currently have a self.new_using_cache(param1, param2), but I would love to have this integrated in the normal Foo.new().
Is this possible in any way?
I can also deduct that using .new() combined with a cache is not really syntactical correct.
That would mean that the method should be called new_or_from_cache().
clarification
It's not just about the heavy calculation, it's also preferred because of limiting the amount of duplicate objects. I don't want 5000 objects in memory, when I can have 50 unique ones from a cache. So I really need to customize the .new method, not just the cached values.
class Foo
##cache = {}
def self.new(value)
if ##cache[value]
##cache[value]
else
##cache[value] = super(value)
end
end
def initialize(value)
#value = value
end
end
puts Foo.new(1).object_id #2148123860
puts Foo.new(2).object_id #2148123820 (different from first instance)
puts Foo.new(1).object_id #2148123860 (same as first instance)
You can actually define self.new, then call super if you actually want to use Class#new.
Also, this totally approach prevents any instantiation from ever occurring if a new instance isn't actually needed. This is die to the fact the initialize method doesn't actually make the decision.
Here's a solution I came up with by defining a generic caching module. The module expects your class to implement the "retrieve_from_cache" and "store_in_cache" methods. If those methods don't exist, it doesn't attempt to do any fancy caching.
module CacheInitializer
def new(*args)
if respond_to?(:retrieve_from_cache) &&
cache_hit = retrieve_from_cache(*args)
cache_hit
else
object = super
store_in_cache(object, *args) if respond_to?(:store_in_cache)
object
end
end
end
class MyObject
attr_accessor :foo, :bar
extend CacheInitializer
#cache = {}
def initialize(foo, bar)
#foo = foo
#bar = bar
end
def self.retrieve_from_cache(foo, bar)
# grab the object from the cache
#cache[cache_key(foo, bar)]
end
def self.store_in_cache(object, foo, bar)
# write back to cache
#cache[cache_key(foo, bar)] = object
end
private
def self.cache_key(foo, bar)
foo + bar
end
end
Something like this?
class Foo
##cache = {}
def initialize prm1, prm2
if ##cache.key?([prm1, prm2]) then #prm1, #prm2 = ##cache[[prm1, prm2]] else
#prm1 = ...
#prm2 = ...
##cache[[prm1, prm2]] = [#prm1, #prm2]
end
end
end
Edited
To not create an instance when the parameters are the same as before,
class Foo
##cache = {}
def self.new prm1, prm2
return if ##cache.key?([prm1, prm2])
#prm1 = ...
#prm2 = ...
##cache[[prm1, prm2]] = [#prm1, #prm2]
super
end
end
p Foo.new(1, 2)
p Foo.new(3, 4)
p Foo.new(1, 2)
# => #<Foo:0x897c4f0>
# => #<Foo:0x897c478>
# => nil
You could use a class-level instance variable to store results from previous object instantiations:
class Foo
#object_cache = {}
def initialize(param1, param2)
#foo1 = #object_cache[param1] || #object_cache[param1] = expensive_calculation
#foo2 = #object_cache[param2] || #object_cache[param2] = expensive_calculation
end
private
def expensive_calculation
...
enf
end
As you probably know you have reinvented the factory method design pattern and it's a perfectly valid solution using your name for the factory method. In fact, it's probably better to do it without redefining new if anyone else is going to have to understand it.
But, it can be done. Here is my take:
class Test
##cache = {}
class << self
alias_method :real_new, :new
end
def self.new p1
o = ##cache[p1]
if o
s = "returning cached object"
else
##cache[p1] = o = real_new(p1)
s = "created new object"
end
puts "%s (%d: %x)" % [s, p1, o.object_id]
o
end
def initialize p
puts "(initialize #{p})"
end
end
Test.new 1
Test.new 2
Test.new 1
Test.new 2
Test.new 3
And this results in:
(initialize 1)
created new object (1: 81176de0)
(initialize 2)
created new object (2: 81176d54)
returning cached object (1: 81176de0)
returning cached object (2: 81176d54)
(initialize 3)