开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜