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.
can I pass a hash to an instance method to create instance variables?
Sure you can do something like this in your initialize method:
hash.each do |k, v|
instance_variable_set("@#{k}", v)
self.class.send(:attr_reader, k)
end
Here's an example using your input hash:
class Reminder
def initialize(hash)
hash.each do |k, v|
instance_variable_set("@#{k}", v)
self.class.send(:attr_reader, k)
end
end
end
reminder_hash = {"bot_client_id"=>"test-client-id", "recurring"=>true, "recurring_natural_language"=>"everyday", "time_string"=>"10AM", "time_zone"=>"America/Los_Angeles", "via"=>"slack", "keyword"=>"test-keyword", "status"=>"active", "created_time"=>1444366166000}
reminder = Reminder.new(reminder_hash)
puts reminder
puts reminder.bot_client_id
Output:
#<Reminder:0x007f8a48831498>
test-client-id
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}
Convert hash params into instance variables on Ruby initializer
I want to do this in a clean way.
You won't get attr_accessor
s and instance variables without defining them. The below is using some simple metaprogramming (does it qualify for "clean"?)
class PriceChange
def initialize(data = {})
data.each_pair do |key, value|
instance_variable_set("@#{key}", value)
self.class.instance_eval { attr_accessor key.to_sym }
end
end
end
Usage:
price_change = PriceChange.new(foo: :foo, bar: :bar)
#=> #<PriceChange:0x007fb3a1755178 @bar=:bar, @foo=:foo>
price_change.foo
#=> :foo
price_change.foo = :baz
#=> :baz
price_change.foo
#=> :baz
Create hash from variables in loop
In your code example, the variables are all declared as being instance variables, because they're prefixed with @
.
However, the variables within the loop are simply working/temporary variables, not instance variables. Additionally, x.get_some_data
is probably not working, since x
is just the loop variable and contains 456
, abc
, etc., and not an object with the desired method. Thus, the following code should produce your desired result:
def get_data(codes)
result = []
codes.each do |code|
y = get_some_data(code)
result << {
code: code,
brand: y['brand'],
size: y['size']
}
end
result
end
This is a very verbose example; you can put the whole logic in map
, if the return value of get_some_data
permits it.
A more elegant version would utilize Enumerable#each_with_object (the Array
class includes Enumereable
):
def get_data(codes)
codes.each_with_object([]) do |code, result|
y = get_some_data(code)
result << {
code: code,
brand: y['brand'],
size: y['size']
}
end
end
Thanks, Cary Swoveland, for pointing this out!
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
Better way to convert several instance variables into hash with ruby?
Rails has a method on Object
called instance_values
for just this. Here's the code on GitHub.
class C
def initialize(x, y)
@x, @y = x, y
end
end
C.new(0, 1).instance_values # => {"x" => 0, "y" => 1}
C.new(0, 1).instance_values.symbolize_keys # => {:x => 0, :y => 1}
How to iterate over dict keys when assigning values to multiple variables
tree[hash][i] for i in "xyz"
is already a generator comprehension, but it needs to be parenthesized unless it's passed as sole argument of a function like this:
my_function(tree[hash][i] for i in "xyz") # this is valid
my_function(tree[hash][i] for i in "xyz", 12) # this is invalid, parsing conflict
my_function((tree[hash][i] for i in "xyz"), 12) # this is valid
This is the same with your assignment expression. The parentheses are needed to avoid ambiguity when parsing.
this answer solves the issue, but is creating an unnecessary list when a generator comprehension is enough:
A, B, C = (tree[hash][i] for i in "xyz")
Unpacking assigns left hand variables by iterating on the generator comprehension without creating an unnecessary list.
Also maybe avoid hashing 3 times, use a variable
h = tree[hash]
A, B, C = (h[i] for i in "xyz")
variant with map
and operator.itemgetter
to avoid loops:
import operator
A,B,C = map(operator.itemgetter(tree[hash]),"xyz")
Aside: avoid hash
as a variable name, since it's the built-in function to hash objects.
Related Topics
How to Put Assertions in Ruby Code
Iterate Every Month with Date Objects
How to Get Request.Uri in Model in Rails
Creating Categories on Jekyll Driven Site
How to Read the Body Text of an Email Using Ruby's Net/Imap Library
Ruby Classes: Initialize Self VS. @Variable
How to Use Nokogiri to Parse an Xml File
Starting or Restarting Unicorn with Capistrano 3.X
How to Get Searchlogic to Work with Rails 3
Catching Line Numbers in Ruby Exceptions
How to Require a Specific Version of a Ruby Gem
Ruby - Bundle Install/Update Too Slow
Namespaced Models in Rails: What's the State of the Union
Ruby Replace String with Captured Regex Pattern