Ruby: Mass Initializing Instance Variables

How can I initialize instance variable?

If you want to persist anything between requests you need to store it somewhere:

  • Database
  • Memory based storage (Redis, Memcached)
  • File system

You can also pass state back and forth between the client and server without actually storing it with:

  • HTTP cookies
  • Query string parameters

Using a class variable is not really going to solve anything. It will only hold the variable as long as the class is held in memory. Every time the class is reloaded it will be reset.

Multi-threading is another huge issue here as Rails servers are commonly multi-threaded and class variables are not thread safe.

Initializing multiple objects at once?

I want to start by saying this is very strange Ruby code and not something you'd typically do. That's not meant as an insult, just to say that Ruby devs tend to follow the same or similar guidelines on structuring objects and this chunk of code feels like it's ported from another language.

The issue you're seeing is due to the fact that within the initialize method you're not creating any new objects but instead updating the instance variables and pushing self into a class variable. self is referencing this instance directly which means the class variable array is filling up with references to the same object. If you're adamant on keeping the code the same then you should instead push a duplicate of your object after you've updated the instance variables.

@@market << self.dup

This creates a duplicate object that has a different memory address and reference.

If you're looking to write more idiomatic code you'd want to use multiple objects and not rely on class variables at all. If you're not interpolating a variable in a string use single quotes instead of double quotes. Keep object methods simple and focused on specific tasks. These are just a few things Ruby developers consider when writing code, but find what works best for you.

Take something like this for instance:

class Market
attr_accessor :id, :name, :symbol, :price, :price_movement_24h, :market_cap

def initialize(data = {})
@id = data['id'].to_s
@name = data['name'].to_s
@symbol = data['symbol'].to_s
@price = data['current_price'].to_s
@price_movement_24h = data['price_change_percentage_24h'].to_s
@market_cap = data['market_cap'].to_s
end
end

class ImportService
def self.from_api(url)
response = JSON.parse(open(url).read) || []
response.map { |data| Market.new(data) }
end
end

You could then call this as such:

@market_data = ImportService.from_api(BASE_URL + 'markets?vs_currency=usd')

Initializing instance variables in Mixins

module Example
def self.included(base)
base.instance_variable_set :@example_ivar, :foo
end
end

Edit: Note that this is setting a class instance variable. Instance variables on the instance can't be created when the module is mixed into the class, since those instances haven't been created yet. You can, though, create an initialize method in the mixin, e.g.:

module Example
def self.included(base)
base.class_exec do
def initialize
@example_ivar = :foo
end
end
end
end

There may be a way to do this while calling the including class's initialize method (anybody?). Not sure. But here's an alternative:

class Foo
include Example

def initialize
@foo = :bar
after_initialize
end
end

module Example
def after_initialize
@example_ivar = :foo
end
end

How to initialise an instance variable dynamically in ruby?

Mocking @sites data:

@sites = [OpenStruct.new(
site_1_location: 'Japan',
site_2_location: 'India',
site_3_location: 'Chile',
site_4_location: 'Singapore'
)]

You can use instance_variable_set to set the instance variable

@sites.each do |site|
1.upto(4) do |i|
instance_variable_set("@var#{i}", site.send("site_#{i}_location"))
end
end

Now you can access the variables:

@var1 # returns "Japan"
@var2 # returns "India"
@var3 # returns "Chile"
@var4 # returns "Singapore"

ruby Initializing instance variable outside method

It does have a use: Class variables. The normal Ruby class variable implementation, @@, shares the same variable between a superclass and its subclasses:

class A
@@v = 0
def self.v; @@v; end
def self.v=(val); @@v=val; end
end
class B < A; end
A.v #-> 0
A.v= 3 #-> 3
B.v #->3
B.v= 42 #-> 42
A.v #-> 42

Obviously, this is pretty useless (except for the fact that, unlike class instance variables, class variables are also accessible directly in instance methods, as opposed to through self.class). But the same example with class instance variables:

class A
@v = 0
def self.v; @v; end
def self.v=(val); @v=val; end
end
class B < A; end
A.v #-> 0
A.v= 3 #-> 3
B.v= 42 #-> 42
A.v #-> 3

Also, class instance variables can harness all of the metaprogramming already written for instance variables, like so:

class Foo
class << self
attr_accessor :v #Uses a class instance variable
end
end

Ruby: Variable initialization within classes

attr_accessor :symbol do the same as attr_writer :symbol and attr_reader :symbol, i.e. it creates both reader (def symbol; @symbol; end) and writer (def symbol=(value); @symbol = value; end).

Initialize is a method called every time new instance of the class is being created. It is not the same as new method as some classes may have its own custom factory methods. You don't need to define your initialize method, only problem is that then symbol reader would return nil, as the local variable would not been set.

In ruby everything is a method. In case of objects, object.attr = value is just a short for object.attr=(value) where attr= is just another method. (Similarly << operator is defined as a method on Array class, attr_accessor is a method defined on class "Class").

Class Level Instance Variables in Ruby

In your answer you don't output the class level instance variable. Besides the usual syntax (@foo), an instance variable can be accessed via a method (instance_variable_get(:@foo)). You can use this method to read instance variables of other objects, not only self.

Here's a modified version of your code

require 'active_support/core_ext'

class MyClass
cattr_reader :class_variable

def self.new_instance(cv, cliv, iv)
@@class_variable = cv
@class_level_instance_variable = cliv
self.new(iv)
end

def initialize(iv)
@instance_variable = iv
end

def use
puts "class_var=#{self.class.class_variable.inspect}"
puts "class inst var: #{self.class.instance_variable_get(:@class_level_instance_variable)}"
puts "inst_var=#{@instance_variable.inspect}"
end
end

c = []
c << MyClass.new_instance(1,2,3)
c << MyClass.new_instance(4,5,6)
c << MyClass.new_instance(7,8,9)

c[0].use
c[1].use
c[2].use
# >> class_var=7
# >> class inst var: 8
# >> inst_var=3
# >> class_var=7
# >> class inst var: 8
# >> inst_var=6
# >> class_var=7
# >> class inst var: 8
# >> inst_var=9

See, class inst var is always 8 (just as class var is always 7). This is because you output values after all your modifications are made. And since class level variables are shared, the last modification wins.

c << MyClass.new_instance(7,8,9)

If you were to output from initializer (as was in your first version of code), you'd see different results.

# >> class_var=1
# >> class inst var: 2
# >> inst_var=3
# >> class_var=4
# >> class inst var: 5
# >> inst_var=6
# >> class_var=7
# >> class inst var: 8
# >> inst_var=9

Can you dynamically initialize multiple variables on one line in ruby?

There is a way, using eval, but you would rather not want to use it (and I would even go that far to say that it might be better not to learn it until well later).

There is simply no case when you would use that instead of plain arrays.

For your example, one should use class Range and method map:

(0..3).map{|i| i * 2}
#=> [0, 2, 4, 6]

You can see that this has been done without declaring any variable - even i is alive just within the block passed to map. It doesn't exist afterwards.

initializing variables in ActiveRecord's classes

attr_accessor_with_default :used, false

Or if you want to use initialize approach you can define callback after_initialize

def after_initialize
@used = false
end

Using attr_accessor_with_default with an object literal (e.g. attr_accessor_with_default :used, false) is unsafe to use with any mutable objects. Specifically, it means that different instances of your class will have the same object as their default value. This is sort of like trying to use a class variable (@@my_var) where you want an instance variable (@my_var). If you want to use a mutable object (e.g. a String, Array, or Hash), you must use the block syntax:

attr_accessor_with_default(:used) { Array.new }


Related Topics



Leave a reply



Submit