Ruby Metaprogramming: dynamic instance variable names
The method you are looking for is instance_variable_set
. So:
hash.each { |name, value| instance_variable_set(name, value) }
Or, more briefly,
hash.each &method(:instance_variable_set)
If your instance variable names are missing the "@" (as they are in the OP's example), you'll need to add them, so it would be more like:
hash.each { |name, value| instance_variable_set("@#{name}", value) }
How to dynamically alter variable names?
You can't, but you can use a Hash
to get a similar result:
(1..1000).each_with_object({}) do |i, users|
users["user#{i}"] = User.create(:name => "Bob#{i}")
end
If you need to access the hash outside the block, just assign it to a variable:
users = (1..1000).each_with_object({}) { |i, users| users["user#{i}"] = User.create(:name => "Bob#{i}") }
And access a specific user (e.g. user1
) like this:
users["user1"]
Or you could use only i
as a key:
users = (1..1000).each_with_object({}) { |i, users| users[i] = User.create(:name => "Bob#{i}") }
And access a specific user (e.g. user1
) like this:
users[1]
dynamic variable names using Ruby's instance_variable_set
Instance variables might be the wrong tool for the job. If all you want to do is create three users, then:
3.times do |i|
User.create(:username => "user_#{i}")
end
If you need to retain the User objects for later use, then you can use an array:
@users = 3.times.map do |i|
User.create(:username => "user_#{i}")
end
after which @users[0]
will retrieve the first instance of User, @users[1]
the second, &c.
Dynamically set local variables in Ruby
The problem here is that the block inside each_pair has a different scope. Any local variables assigned therein will only be accessible therein. For instance, this:
args = {}
args[:a] = 1
args[:b] = 2
args.each_pair do |k,v|
key = k.to_s
eval('key = v')
eval('puts key')
end
puts a
Produces this:
1
2
undefined local variable or method `a' for main:Object (NameError)
In order to get around this, you could create a local hash, assign keys to this hash, and access them there, like so:
args = {}
args[:a] = 1
args[:b] = 2
localHash = {}
args.each_pair do |k,v|
key = k.to_s
localHash[key] = v
end
puts localHash['a']
puts localHash['b']
Of course, in this example, it's merely copying the original hash with strings for keys. I'm assuming that the actual use-case, though, is more complex.
How can I make dynamic variable names that are set to the strings within an array
It's a better idea to use a hash or open struct:
require 'ostruct'
queries = stages.each_with_object(OpenStruct.new) do |stage, struct|
struct[stage] = ElasticSearch::Job.query(
stage: stage,
titan_user_id: titan_user['id'],
gte: start_date,
lte: end_date
)
end
You'll need to namespace references to queries
:
snapshot.prospecting_bids = queries.prospecting['aggregations']['total_count']['value']
But it's very similar to your pseudocode, and avoiding metaprogramming makes the method more easily comprehendable.
If you wanted to avoid namespacing, you can make a mini-dsl:
queries.instance_eval do
snapshot.prospecting_bids = prospecting['aggregations']['total_count']['value']
end
Class instance with names defined by string
Its pretty unclear what you actually want here since an identifier in ruby starting with an uppercase letter in Ruby is a constant.
John = Person.new
Jane = Person.new
Barbara = Person.new
Bob = Person.new
You can dynamically assign constants with Module#const_set
.
module MyModule
['John', 'Jane', 'Barbara', 'Bob'].each do |name|
const_set(name, Person.new)
end
end
# this imports the constants into Main which is the global scope
include MyModule
John
=> #<Person:0x007f973586a618>
Instance variables on the other hand use the @ sigil. You can dynamically assign instance variables with instance_variable_set:
['John', 'Jane', 'Barbara', 'Bob'].map(&:downcase).each do |name|
instance_variable_set("@#{name}", Person.new)
end
@john
# => #<Person:0x007f9734089530>
While you can declare an instance variable named @John it violates the conventions of the language.
Local variables cannot actually be defined dynamically. You can only change existing variables via eval
and binding.local_variable_set
.
def foo
a = 1
bind = binding
bind.local_variable_set(:a, 2) # set existing local variable `a'
bind.local_variable_set(:b, 3) # create new local variable `b'
# `b' exists only in binding
p bind.local_variable_get(:a) #=> 2
p bind.local_variable_get(:b) #=> 3
p a #=> 2
p b #=> NameError
end
Related Topics
How Does This Ruby Injection Magic Work
Converting a Hash into a Nested Hash
Rails Select Drop Down for States
Add Timestamps to an Existing Table
Descending Sort by Value of a Hash in Ruby
How to Make Rails 3.1 Use SASS (Over SCSS) as the Default
Removing a Model in Rails (Reverse of "Rails G Model Title...")
Ruby: What Is the Easiest Way to Remove the First Element from an Array
Ruby: Inject Issue When Turning Array into Hash
Use Rvm to Force Specific Ruby in Xcode Run Script Build Phase
Given a Class, See If Instance Has Method (Ruby)
What Does "+=" (Plus Equals) Mean