Ruby Metaprogramming: Dynamic Instance Variable Names

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



Leave a reply



Submit