In Ruby, you can easily access local variables programmatically by using local_variables and eval. I would really like to have meta-programming access to these variables using a single method call such as
# define a variable in this scope, such as
x = 5
Foo.explore_locals # inside the Foo#explore_locals method, access x
where Foo is some external module. The idea is to display and export local variables in a nice way.
What should be inside the explore_locals method? Is there any way to make this possible? If absolutely necessary, I guess it could be
Foo.explore_locals binding
but this is much less elegant for the application I have in mind.
It's a shame there isn't a built-in way to get the caller's binding. The block trick seems to be the usual answer to this question.
However there is another 'trick' which existed for older 1.8 Ruby versions called binding_of_caller. Looks like quix ported it to 1.9. You might want to check that out:
https://github.com/quix/binding_of_caller
Here is an example (but it requires extra braces {} which I would rather avoid if possible):
module Foo
def self.explore_locals &block
p block.binding.eval 'local_variables'
end
end
local_1 = 3
Foo.explore_locals{} # shows [:local_1, :_]
Related
I have a string, which has been created at runtime. I want to use this string as a variable to store some data into it. How can I convert the string into a variable name?
If you can forgive an # sign in front of the variable name, the following will work:
variable_name = ... # determine user-given variable name
instance_variable_set("##{variable_name}", :something)
This will create a variable named #whatever, with its value set to :something. The :something, clearly, could be anything you want. This appears to work in global scope, by declaring a spontaneous Object instance which binds everything (I cannot find a reference for this).
The instance_variable_get method will let you retrieve a value by name in the same manner.
instance_variable_get("##{variable_name}")
You can use eval() for this provided that you've declared your variable first:
>> foo = []
>> eval("foo")[1] = "bar"
>> foo[1]
=> "bar"
Here are the docs.
Rather than do that directly, why not consider using a hash instead. The string would be the key, and the value you want to store would be the value. Something like this:
string_values = { }
some_string = params[:some_string] # parameter was, say "Hello"
string_values[some_string] = 42
string_values # { 'Hello' => 42 }
some_number = string_values[some_string]
some_number # 42
This has a couple of benefits. First, it means you're not doing anything magic that might be hard to figure out later. Second, you're using a very common Ruby idiom that's used for similar functionality throughout Rails.
Now simply using instance_variable_set method, you can create a instance variable at runtime.
instance_variable_set('#' + 'users', User.all)
I don't mean to be negative, but tread carefully. Ruby gives you a lot of features for highly dynamic programming such as define_method, storing blocks as Proc objects to be called later, etc. Generally these are cleaner code and far safer. 99% of the time using eval() is a mistake.
And absolutely never use eval() on a string that contains user submitted input.
As Jason Watkins says, a lot of people might be quick to use eval() first thing and this would be a serious mistake. Most of the time you can use the technique described by Tack if you've taken care to use a class instance variable instead of a local one.
Local variables are generally not retrievable. Anything with the # or ## prefix is easily retrieved.
I have already wrote a class in ruby, call it Foo. Now I want to create some instance of it. However, I want to use the value stored in a variable as the name of the instance.
For example, the value of a variable bar is "ABC". Now I want to make the name of the new instance "ABC" as the statement ABC = Foo.new
Can I realize this? If it is possible, please tell me how. Thanks!!!
In Ruby, you should also not be afraid to use eval(), as long as you feed it only data constructed by you, the programmer, without unsafe external influence. In this case,
cocos = "ABC"
class_sym = :Foo
eval "#{cocos} = #{class_sym}.new"
Eval has bad reputation in many languages, but in Ruby, its quite comfortable and basically as fast as static code.
as a local variable, no, but you can use instance_variable_set and friends to name the created objects as instance variables.
You can also use const_set if you are really working with constants:
constant = 'ABC'
Object.const_set constant.to_sym, Foo.new
I'm wanting to use the &method(:method_name) idiom when there's more than one object required by method_name. Can I do this under Ruby 1.9?
For example, if I've got
def move_file(old_filename, new_filename)
STDERR.puts "Moving #{old_filename.inspect} to #{new_filename.inspect}"
# Implementation for careful moving goes here
end
old_filenames = ["foo.txt", "bar.txt", "hoge.ja.txt"]
new_filenames = ["foo_20110915.txt", "bar_20110915.txt", "hoge_20110915.ja.txt"]
the code
old_filenames.zip(new_filenames).each(&method(:move_file))
works under Ruby 1.8, but not under Ruby 1.9. Under Ruby 1.9, it's trying to do move_file(["foo.txt", "foo_20110915.txt"]) instead of move_file("foo.txt", "foo_20110915.txt").
How do I splattify it so it has the correct arity?
Workarounds I'm aware of:
Replace def move_file(old_filename, new_filename) with def move_file(*arguments)
Replace each(&method(:move_file)) with
each{|old_filename, new_filename| move_file(old_filename, new_filename)}
Instead
each{|old_filename, new_filename| move_file(old_filename, new_filename)}
you should be able to do
each{|pair| move_file(*pair)}
But I don't know how you'd pull off blockless variant (I needed it couple of times as well). I guess &-shorthand was made to make the syntax simpler, and is not meant to be clogged much (whether it will be passed an array as an array, or splatted, for example). :)
How do I splattify it so it has the correct arity?
I don't think there is a way to do this while being compatible to both Ruby versions. What you could do is wrap it into a lambda
move_from_to = Proc.new {|*both| move_files(*both) }
The thing is - block and proc arity is something that got addressed in Ruby 1.9 so there might be a difference in behavior there. Also see prc.lambda? here http://www.ruby-doc.org/core/classes/Proc.html for info on what it does to the arity.
This question is also related to what you want to do (the solution there is to resplat and unsplat manually): Inconsistency of arity between Hash.each and lambdas
I am trying to dynamically set (not create, it already has to exist) a global ruby variable in a method. The variable name is determined from the passed symbol. What I am currently doing is the following:
def baz(symbol)
eval("$#{symbol}_bar = 42")
end
$foo_bar = 0
baz(:foo)
puts $foo_bar # => 42
But to me, this kind of feels very wrong. Is this the way to do this? Or can it be done differently? Also, I don't know how evals perform in ruby. Does it run much slower than
$foo_bar = 42
The method looks fine to me. This guy says that eval efficiency is much worse, though the post is 3 years old.
I will point out that this method suggests you have a lot of global variables, which is generally a code smell if the code base is significant.
If you can use an instance variable instead, there is Object#instance_variable_set.
def baz(symbol)
instance_variable_set("##{symbol}_bar", 42)
end
Note that it only accepts variable names that can be accepted as an instance variable (starting with #). If you put anything else in the first argument, it will return an error. For the global variable counterpart to it, there is a discussion here: Forum: Ruby
Either way, you also have the problem of accessing the variable. How are you going to do that?
I have a string, which has been created at runtime. I want to use this string as a variable to store some data into it. How can I convert the string into a variable name?
If you can forgive an # sign in front of the variable name, the following will work:
variable_name = ... # determine user-given variable name
instance_variable_set("##{variable_name}", :something)
This will create a variable named #whatever, with its value set to :something. The :something, clearly, could be anything you want. This appears to work in global scope, by declaring a spontaneous Object instance which binds everything (I cannot find a reference for this).
The instance_variable_get method will let you retrieve a value by name in the same manner.
instance_variable_get("##{variable_name}")
You can use eval() for this provided that you've declared your variable first:
>> foo = []
>> eval("foo")[1] = "bar"
>> foo[1]
=> "bar"
Here are the docs.
Rather than do that directly, why not consider using a hash instead. The string would be the key, and the value you want to store would be the value. Something like this:
string_values = { }
some_string = params[:some_string] # parameter was, say "Hello"
string_values[some_string] = 42
string_values # { 'Hello' => 42 }
some_number = string_values[some_string]
some_number # 42
This has a couple of benefits. First, it means you're not doing anything magic that might be hard to figure out later. Second, you're using a very common Ruby idiom that's used for similar functionality throughout Rails.
Now simply using instance_variable_set method, you can create a instance variable at runtime.
instance_variable_set('#' + 'users', User.all)
I don't mean to be negative, but tread carefully. Ruby gives you a lot of features for highly dynamic programming such as define_method, storing blocks as Proc objects to be called later, etc. Generally these are cleaner code and far safer. 99% of the time using eval() is a mistake.
And absolutely never use eval() on a string that contains user submitted input.
As Jason Watkins says, a lot of people might be quick to use eval() first thing and this would be a serious mistake. Most of the time you can use the technique described by Tack if you've taken care to use a class instance variable instead of a local one.
Local variables are generally not retrievable. Anything with the # or ## prefix is easily retrieved.