const_get but for variables
So I know you can say Kernel.const_get("ClassName") and you'll get back the class to which the string corresponds in name. But what about for variables? Is there a way to do:
test = "heyas"
some_method_here("test") #=> "heyas"
Thanks so much
The fact is that I need it in more complex code, real example:
class User
class Validations
class << self
def username_len
return u.len > 3 and u开发者_如何学运维.len < 22
end
# ...
end
end
def validate(u,e,p)
[:u, :e, :p].each do |v|
Validations.send(:define_singleton_method, v, lambda { eval(v.to_s) }) # see this line
end
#other code to run validations
end
end
So you see, there's no better way, is there?
No, there is only class_variable_get
, instance_variable_get
and const_get
. There is no local_variable_get
. But it wouldn't make sense anyway: local variables are local to the current method body, module body, class body or script body, that's why they are called "local" variables, after all! You simply cannot access them from another method.
Is there a way to do:
test = "heyas" some_method_here("test") #=> "heyas"
No, there is no way to do this. And there cannot possibly be a way to do it. test
is a local variable, which means it only exists in the current script body. It does not exist inside the body of some_method_here
. That's the whole point of local variables: you cannot ever, under any circumstances, access them from somewhere else.
Regarding your comment on another answer:
def my_func(str) var_a = 'hey' var_b = 'ah' return some_method_here(str) end #=> should return the corresponding variable's value, e.g. my_func('var_a') #=> 'hey'
Again, this cannot possibly work, since the whole point of local variables is that they cannot be accessed from anywhere else.
But there is a pretty simple variation which does exactly what you want:
def my_func(str)
{
'var_a' => 'hey',
'var_b' => 'ah'
}[str]
end
#=> should return the corresponding variable's value, e.g. my_func('var_a')
#=> 'hey'
This can of course be further simplified to:
my_func = {'var_a' => 'hey', 'var_b' => 'ah'}
#=> should return the corresponding variable's value, e.g. my_func['var_a']
#=> 'hey'
Given that you can only pass in a limited number of different options, it's probably better to use symbols instead:
my_func = {var_a: 'hey', var_b: 'ah'}
#=> should return the corresponding variable's value, e.g. my_func[:var_a]
#=> 'hey'
What you are asking is basically: pass in a key, get a value out. That's exactly what a Hash
is.
EDIT: After the revised question, this is the best that I could come up with:
def validate(u, e, p)
local_variables.zip(local_variables.map {|var|
eval(var.to_s)
}).each {|var, val|
Validations.send(:define_singleton_method, var) { val }
}
end
However, I think there is something seriously wrong with the design. You overwrite singleton methods of User::Validations
based on different instances of User
. By the very definition of singleton method, there can only ever be one copy of those in the system. But you have many different instances of User
, and every time you call User#validate
it will overwrite the only copies of User::Validations.u
, User::Validations.e
and User::Validations.p
at which point they will start to behave completely differently for the entire system.
In other words, you are changing the behavior of the entire system based on a single instance. And there can be many instances and every time, the behavior of the system changes.
That just can't be right.
u1 = User.new
u1.validate('u1', :e1, 1)
p User::Validations.u, User::Validations.e, User::Validations.p
# => 'u1'
# => :e1
# => 1
u2 = User.new
u2.validate('U2', :E2, 2.0)
# => warning: method redefined; discarding old u
# => warning: method redefined; discarding old e
# => warning: method redefined; discarding old p
# ^^^^^^^^^^^^^^^^
# Oops!
p User::Validations.u, User::Validations.e, User::Validations.p
# => 'U2'
# => :E2
# => 2.0
# ^^^
# Completely different results for the exact same arguments
Maybe I'm missing something, but why can't you just do test
to get its value? If you are defining instance variables, there is instance_variable_get.
Edit: as mentioned in my comment below, you could do
def my_func(str); var_a="hey"; var_b="ah"; eval str; end
my_func('var_a') #=> "hey"
But this seems a little insane to me, if not a lot insane.
try this:
def validate(u,e,p)
[[:u, u], [:e, e], [:p, p]].each do |v|
Validations.send(:define_singleton_method, v.first, lambda { v.last })
end
end
OR
def validate(u,e,p)
{:u => u, :e => e, :p => p}.each do |k, v|
Validations.send(:define_singleton_method, k, lambda { v })
end
end
Binding has access to your local variables, so you can do it with binding#eval
eval("#{var_name}")
will do it. Don't run with untrusted input obviously. If you want to avoid errors for undefined variables, you can use:
eval("#{var_name}") if eval("defined?(#{var_name})") == 'local-variable'
E.g.
test = 'heyas'
var_name = :test
eval("#{var_name}") if eval("defined?(#{var_name})") == 'local-variable' #=> "heyas"
var_name = :not_set
eval("#{var_name}") if eval("defined?(#{var_name})") == 'local-variable' #=> "nil"
On edge ruby, these are defined in:
Binding#local_variable_defined?
Binding#local_variable_get
Binding#local_variable_set
so in a future version you'll likely be able to use those calls directly.
精彩评论