I would like to understand how define_method works and how to properly use the variables outside of the definition block. Here is my code:
class Test
def self.plugin
for i in 1..2
define_method("test#{i}".to_sym) do
p i
end
end
end
plugin
end
ob = Test.new
ob.test1 #=> 2 (I would expect 1)
ob.test2 #=> 2 (I would expect 2)
It seems that in the methods test1 and test2, the value of i is not substituted during the definition, but is computed directly on the spot when the method is called. So we see only the latest value of i, which is 2. But where does Ruby take this value from? And is there a way to let test#{i} print i?
In this particular case, I could do a workaround using __method__, but probably there is a better solution.
As mentionned in comments this is down to closures - the block passed to define_method captures the local variables from its scope (and not just their value as you found out).
for doesn't create a new scope, so your method 'sees' the change to i. If you use a block (for example with each) then a new scope is created and this won't happen. You just need to change your code to
class Test
def self.plugin
(1..2).each do |i|
define_method("test#{i}".to_sym) do
p i
end
end
end
plugin
end
which is basically what the question linked by Arup was about
Related
In the example below, I would have thought that close_over_me would be available to the returned object through a closure.
But clearly that is not the case.
Why not?
Also, what options are available for making the code work as expected (that is, returning the value 1 rather than an error)?
def test_closure(close_over_me: 1)
Object.new.tap do |x|
def x.captured_var
close_over_me
end
end
end
o = test_closure
o.captured_var # NameError: undefined local variable or method `close_over_me' for #<Object:0x007fb46b41a718>
The reason is that the def keyword defines a new scope.
To work around this, you can use Object#define_singleton_method instead:
def test_closure(close_over_me: 1)
Object.new.tap do |x|
x.define_singleton_method(:captured_var) do
close_over_me
end
end
end
o = test_closure
o.captured_var # => 1
It's so-called "scope gate". Keywords def/class/module push all local variables out of scope.
You'll have to come up with a way to do your patching without those.
I would like to understand how define_method works and how to properly use the variables outside of the definition block. Here is my code:
class Test
def self.plugin
for i in 1..2
define_method("test#{i}".to_sym) do
p i
end
end
end
plugin
end
ob = Test.new
ob.test1 #=> 2 (I would expect 1)
ob.test2 #=> 2 (I would expect 2)
It seems that in the methods test1 and test2, the value of i is not substituted during the definition, but is computed directly on the spot when the method is called. So we see only the latest value of i, which is 2. But where does Ruby take this value from? And is there a way to let test#{i} print i?
In this particular case, I could do a workaround using __method__, but probably there is a better solution.
As mentionned in comments this is down to closures - the block passed to define_method captures the local variables from its scope (and not just their value as you found out).
for doesn't create a new scope, so your method 'sees' the change to i. If you use a block (for example with each) then a new scope is created and this won't happen. You just need to change your code to
class Test
def self.plugin
(1..2).each do |i|
define_method("test#{i}".to_sym) do
p i
end
end
end
plugin
end
which is basically what the question linked by Arup was about
I'm trying to keep a hash local to one function that remembers its state between calls to the function. But I don't know how to declare it without a closure (as some users suggested in a similar thread).
I know C++ more thoroughly than ruby, and in C++, I would have ordinarily used a static local variable, like in the first example here: http://msdn.microsoft.com/en-us/library/s1sb61xd.aspx
I managed to hack something together in ruby using the defined? function:
def func x
if not defined? #hash
#hash = Hash.new
end
if #hash[x]
puts 'spaghetti'
else
#hash[x] = true
puts x.to_s
end
end
func 1
func 1
This prints, the following, which is kind of what I want. The only problem is that #hash can be accessed outside of that function.
1
spaghetti
Is there any "cleaner", more preferred way to declare a variable with this behavior (without a factory)? I was going to create two or three variables like #hash, so I was looking for a better way to express this concisely.
What you're doing is pretty common in Ruby, but also so common you don't need to make a big fuss about it. All #-type instance variables are local to that instance only. Keep in mind "instance" generally refers to an instance of a class, but it can refer to the instance of the class as well.
You can use ## to refer to a class instance variable from the context of an instance, but this tends to get messy in practice.
What you want to do is one of the following.
A variable that persists between method calls, but only within the context of a single object instance:
def func(x)
# Instance variables are always "defined" in the sense that
# they evaluate as nil by default. You won't get an error
# for referencing one without declaring it first like you do
# with regular variables.
#hash ||= { }
if #hash[x]
puts 'spaghetti'
else
#hash[x] = true
puts x.to_s
end
end
A variable that persists between method calls, but only within the context of all object instances:
def func(x)
# Instance variables are always "defined" in the sense that
# they evaluate as nil by default. You won't get an error
# for referencing one without declaring it first like you do
# with regular variables.
##hash ||= { }
if ##hash[x]
puts 'spaghetti'
else
##hash[x] = true
puts x.to_s
end
end
This is usually made cleaner by wrapping the ##hash into a class method. This has the secondary effect of making testing easier:
def self.func_hash
#func_hash ||= { }
end
def func(x)
if self.class.func_hash[x]
puts 'spaghetti'
else
self.class.func_hash[x] = true
puts x.to_s
end
end
When I invoke a method that doesn't exist, method_missing will tell me the name of the method. When I attempt to access a variable that hasn't been set, the value is simply nil.
I'm attempting to dynamically intercept access to nil instance variables and return a value based on the name of the variable being accessed. The closest equivalent would be PHP's __get. Is there any equivalent functionality in Ruby?
I do not believe this is possible in Ruby. The recommended way would be to use a ''user'' method rather than a ''#user'' instance var in your templates.
This is consistent with the way you deal with Ruby objects externally (''obj.user'' is a method which refers to ''#user'', but is actually not ''#user'' itself). If you need any kind of special logic with an attribute, your best bet is to use a method (or method_missing), regardless if you're accessing it from inside or outside the object.
See my answer to another similar question. But just because you can do it doesn't mean that it's a good idea. Sensible design can generally overcome the need for this kind of thing and allow you to produce more readable and hence maintainable code.
instance_variable_get seems to be the closest equivalent of PHP's __get from what I can see (although I'm not a PHP user).
Looking at the relevant Ruby source code, the only 'missing' method for variables is const_missing for constants, nothing for instance variables.
there isn't an instance_variable_missing (at least that I know of)
But why are you accessing randomly named instance variables anyway?
If your thread all the access to the object state through method calls (as you should anyway) then you wouldn't need this.
If you are looking for a way to define magic stuff without messing up with the method lookup, you may want to use const_missing.
A bit late but, instance_variable_missing is the same as method_missing to a point... Take the following class:
class Test
def method_missing(*args)
puts args.inspect
end
end
t = Test.new
Now let's get some instance variables:
t.pineapples #=> [:pineapples]
t.pineapples = 5 #=> [:pineapples=,5]
Not sure why the method is nil for you...
EDIT:
By the sounds of it you want to accomplish:
t = SomeClass.new
t.property.child = 1
So let's try returning a Test object from our previous example:
class Test
def method_missing(*args)
puts args.inspect
return Test.new
end
end
So what happens when we call:
t = Test.new
t.property.child = 1
#=>[:property]
#=>[:child=,1]
So this goes to show that this is indeed possible to do. OpenStruct uses this same technique to set instance variables dynamically. In the below example, I create EternalStruct which does exactly what you wanted:
require 'ostruct'
class EternalStruct < OpenStruct
def method_missing(*args)
ret = super(*args)
if !ret
newES = EternalStruct.new
self.__send__((args[0].to_s + "=").to_sym, newES)
return newES
end
end
end
Usage of EternalStruct:
t = EternalStruct.new
t.foo.bar.baz = "Store me!"
t.foo.bar.baz #=> "Store me!"
t.foo #=> #<EternalStruct bar=#<EternalStruct baz="Store me!">>
t.a = 1
t.a #=> 1
t.b #=> #<EternalStruct:...>
t.b = {}
t.b #=> {}
def t.c(arg)
puts arg
end
t.c("hi there") #=> "hi there"
To be honest, I still confused about the instance variable and local variable, not sure which should be used.
only one condition I know about local variable that can't be used is:
class MyClass
def initialize
local_var = 1
#instance_var = 1
end
def show_local_var
local_var
end
def show_instance_var
#instance_var
end
end
apparently, MyClass.new.show_instance_var works while MyClass.new_show_local_var not
the other thing about the two kind of variables is that the block seems share the same local scope, so the local variable can be referenced:
local_var = 1
3.times do
puts local_var
end
There are all I know about the distinctions, is there any other available? please let me know
if there is any articles about this, that would be so helpful for me,
A local variable is used "right here right now" and can't be accessed from anywhere else.
class MyClass
def foo
local_var = 2
#I use this to do some sort of calculation
#instance_var = local_var + 34
local_var = 5 * #instance_var
puts local_var
end
# From here, local_var is unaccessible.
end
Once you're out of scope (foo's end is passed) local_var is no more and can't be referred to.
The instance variable is available to the whole class at all times.
class MyClass
def initialize
#instance_var = 0
end
def foo
local_var = 2
#I use this to do some sort of calculation
#instance_var = local_var + 34
end
def some_operation
if #instance_var == 36
#instance_var = 3
else
#instance_var = 1
end
end
end
So when you call m = MyClass.new and later on m.some_operation, it's working with the same #instance_varĀ .
And while we're at it, there are also Class variables (defined ##class_var) that are accessible from any instance of the class.
I don't have an article in particular to provide you, but some googling about ruby variable scope and about each type of variable independently should provide you with all the information you need!
The second example you describe is called a Closure. Paul puts it quite nicely there:
A closure is a block of code which meets three criteria:
It can be passed around as a value and
executed on demand by anyone who has that value, at which time
it can refer to variables from the context in which it was created (i.e. it is closed with respect to variable access, in the mathematical sense of the word "closed").
For a nice, quick introduction about the available scopes in Ruby you may refer to the Ruby Programming wikibook.
There is
Local Scope
Global Scope
Instance Scope
Class Scope
The "Default Scope", as it is sometimes referred to when executing code with no scope modifiers surrounding it, as in
#iv = "Who do I belong to?"
is the scope of the 'main' object.
Local scope is limited to the location in which the variable is declared, i.e. a function, or a for loop, but cannot be accessed from outside that location. However, if you nest other constructs within the function, for loop, etc. then the inner constructs can access the local variable. Instance variables are scoped to the instance of the class.
Article on ruby variable scope
Article on scope in general