Iterate and Set Ruby Object Instance Variables

How to dynmically set instance variable through a loop?

There are several ways, how to do this:

The first one is using ruby methods: instance_variable_set and instance_variable_get:

# set new instance variable
instance_variable_set("@client_token_#{plan.id}", gateway.client_token.generate)

# read the instance variable
instance_variable_get("@client_token_#{plan.id}")

You could also use hash, instead of instance variables and it would probably be better

client_tokens = {}
@plans.map do |plan|
client_tokens[plan.id] = gateway.client_token.generate
end

# and than read it with
client_tokens[plan.id]

If you want it accessible from more places than directly in the view, you can define it as the instance variable @client_tokens = {}


If you would like to change it to completely JS thing. you could do something like this:

<% @plans.map do |plan| %>
<button onclick='braintreeClick("<%= gateway.client_token.generate %>")'... > Click </button>
...
<% end %>
<script>
function braintreeClick(client_token) {
...
}
</script>

You would generate token and put it directly to braintreeClick function call, now you don't have to bother with instance variables or more ruby code at all. This is better solution than the previous two.


The best solution would probably be to use unobtrusive javascript call. I don't know what JS framework you use, so I will demonstrate it with the jQuery:

<% @plans.map do |plan| %>
<button class="js-brain-tree-button" data-client-token="<%= gateway.client_token.generate %>" ... > Click </button>
...
<% end %>
<script>
function braintreeClick(e) {
var $button = $(e.currentTarget);
var client_token = $button.data("client-token");
...
};

$(document).ready(function() {
$(".js-brain-tree-button").on('click', braintreeClick);
});
</script>

Note: sorry if there are some typos or errors, I didn't test the code, but it should show the concept

Declaring instance variables iterating over a hash!

class MyClass
def initialize()
hash = {"key1" => "value1","key2" => "value2","key3" => "value3"}
hash.each do |k,v|
instance_variable_set("@#{k}",v)
# if you want accessors:
eigenclass = class<<self; self; end
eigenclass.class_eval do
attr_accessor k
end
end
end
end

The eigenclass is a special class belonging just to a single object, so methods defined there will be instance methods of that object but not belong to other instances of the object's normal class.

Ruby - Iterating over Instances

You can use ObjectSpace to retrieve all instantiated objects of a given class:
let say you have a class named MyClass and you want all the instances of that class then

ObjectSpace.each_object(MyClass) do |obj|
#do what ever you want to do with that object
end

how ever this is a bad idea to do it. it will also load MyClass instances that are still in memory from earlier requests that haven't been garbage-collected.

attr_reader should be set to access the obj directly (class variables). It's also possible to define methods to access them.

When do Ruby instance variables get set?

Instance variables in ruby may be a bit confusing when first learning Ruby, especially if you are accustomed to another OO language like Java.

You cannot simply declare an instance variable.

One of the most important things to know about instance variables in ruby, apart from the notation with an @ sign prefix, is that they spring into life the first time they are assigned to.

class Hello
def create_some_state
@hello = "hello"
end
end

h = Hello.new
p h.instance_variables

h.create_some_state
p h.instance_variables

# Output
[]
["@hello"]

You can use the method Object#instance_variables to list all instance variables of an object.

You normally “declare” and initialize all the instance variables in the initialize method. Another way to clearly document which instance variables that should be publicly available is to use the Module methods attr_accessor (read/write), attr_writer (write) and attr_reader (read). These methods will synthesize different accessor methods for the listed instance variable.

class Hello
attr_accessor :hello
end

h = Hello.new
p h.instance_variables

h.hello = "hello"
p h.instance_variables

# Output
[]
["@hello"]

The instance variable still isn’t created until it’s assigned to using the synthesized Hello#hello= method.

Another important issue, like kch described, is that you need to be aware of the different contexts active when declaring a class. When declaring a class the default receiver (self) in the outermost scope will be the object that represents the class itself. Hence your code will first create a class instance variable when assigning to @hello on the class level.

Inside methods self will be the object on which the method is invoked, hence you are trying to print the value of an instance variable with the name @hello in the object, which doesn’t exists (note that it’s perfectly legal to read a non existing instance variable).

Convert instance variables and their values into a hash

class Klass
def initialize
@a = 2
@b = 2
end

# define your own methods
def attributes
instance_variables.map do |var|
[var[1..-1].to_sym, instance_variable_get(var)]
end.to_h
end
end

Klass.new.attributes # => {:a=>2, :b=>2}

Looping through a controller instance variable in a view in Rails

@submissions always displays the submission of the first day because you've never changed it after you initialized it in the controller. Incrementing @num_days_ago in the view does nothing because the view does not use it.

If you want to display submissions since the first day, you need to set an array of submission from the first day to @submissions.

@submissions = num_days_since_first_day.times.map do |num_days_ago| 
Submission.daily(num_days_ago).paginate(page: params[:page], per_page: 10).order('rank DESC')
end

And render each of them as you iterate in the view.

<%= render @submissions[num] %>

I think you can remove some redundant code and further refactor the code above, but that's the basic idea.



Related Topics



Leave a reply



Submit