In Ruby How to Automatically Populate Instance Variables Somehow in the Initialize Method

in Ruby can I automatically populate instance variables somehow in the initialize method?

You can use instance_variable_set like this:

params.each do |key, value|
self.instance_variable_set("@#{key}".to_sym, value)
end

Quickly setting instance variables with options hash in Ruby?

def initialize(opts={})
opts.each { |k,v| instance_variable_set("@#{k}", v) }
end

Does Ruby's attr_accessor automatically create instance variables for attributes?

No. Instance variables are not defined until you assign to them, and attr_accessor doesn't do so automatically.

Attempting to access an undefined instance variable returns nil, but doesn't define that variable. They don't actually get defined until you write to them. attr_accessor relies on this behaviour and doesn't do anything except define a getter/setter.

You can verify this by checking out .instance_variables:

class Test
attr_accessor :something
end

A new instance of x has no instance variables:

x = Test.new # => #<Test:0xb775314c>
x.instance_variables # => []

Invoking the getter does not cause @something to become defined:

x.something # => nil
x.instance_variables # => []

Invoking the setter does cause @something to become defined:

x.something = 3 # => 3
x.instance_variables # => ["@something"]

Setting something back to nil doesn't cause instance_variables to revert, so we can be sure that the first empty array returned isn't simply a case of instance_variables omitting nil values:

x.something = nil # => nil
x.instance_variables # => ["@something"]

You can also verify that this isn't simply behaviour specific to attr_accessor:

class Test
def my_method

@x # nil

instance_variables # []

@x = 3

instance_variables # ["@x"]
end
end

Test.new.my_method

Ruby: Automatically set instance variable as method argument?

After some pondering, I wondered if it's possible to actually get the argument names from a ruby method. If so, I could use a special argument prefix like "iv_" to indicate which args should be set as instance variables.

And it is possible: How to get argument names using reflection.

Yes! So I can maybe write a module to handle this for me. Then I got stuck because if I call the module's helper method, it doesn't know the values of the arguments because they're local to the caller. Ah, but ruby has Binding objects.

Here's the module (ruby 1.9 only):

module InstanceVarsFromArgsSlurper
# arg_prefix must be a valid local variable name, and I strongly suggest
# ending it with an underscore for readability of the slurped args.
def self.enable_for(mod, arg_prefix)
raise ArgumentError, "invalid prefix name" if arg_prefix =~ /[^a-z0-9_]/i
mod.send(:include, self)
mod.instance_variable_set(:@instance_vars_from_args_slurper_prefix, arg_prefix.to_s)
end

def slurp_args(binding)
defined_prefix = self.class.instance_variable_get(:@instance_vars_from_args_slurper_prefix)
method_name = caller[0][/`.*?'/][1..-2]
param_names = method(method_name).parameters.map{|p| p.last.to_s }
param_names.each do |pname|
# starts with and longer than prefix
if pname.start_with?(defined_prefix) and (pname <=> defined_prefix) == 1
ivar_name = pname[defined_prefix.size .. -1]
eval "@#{ivar_name} = #{pname}", binding
end
end
nil
end
end

And here's the usage:

class User
InstanceVarsFromArgsSlurper.enable_for(self, 'iv_')

def initialize(iv_name, age)
slurp_args(binding) # this line does all the heavy lifting
p [:iv_name, iv_name]
p [:age, age]
p [:@name, @name]
p [:@age, @age]
end
end

user = User.new("Methuselah", 969)
p user

Output:

[:iv_name, "Methuselah"]
[:age, 969]
[:@name, "Methuselah"]
[:@age, nil]
#<User:0x00000101089448 @name="Methuselah">

It doesn't let you have an empty method body, but it is DRY. I'm sure it can be enhanced further by merely specifying which methods should have this behavior (implemented via alias_method), rather than calling slurp_args in each method - the specification would have to be after all the methods are defined though.

Note that the module and helper method name could probably be improved. I just used the first thing that came to mind.

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.

Create attr_reader methods while setting the values for the according instance variables in the initialize method?

You can open the eigenclass from within the method and set the attribute there:

class SomeClass
def initialize(arg)
(class << self; self; end).send(:attr_reader, :hello)
@hello = arg
end
end

That way each instance's eigenclass will have that attribute reader. But really it only makes sense to do things that way if the attribute name is dynamic, and can vary from instance to instance. If it's always hello, I don't see any drawback to just defining it in the class like your original code block.

For example, if you are dynamically passing in the attribute name, you could do it like this:

class SomeClass
def initialize(attr, arg)
(class << self; self; end).send(:attr_reader, attr.to_sym)
instance_variable_set("@#{attr}", arg)
end
end

This is compatible with Ruby 1.8. Taking a tip from @HenrikN in the comment to your question, you can use define_singleton_method in Ruby 1.9:

class SomeClass
def initialize(attr, arg)
define_singleton_method(attr) { instance_variable_get("@#{attr}") }
instance_variable_set("@#{attr}", arg)
end
end

Ruby—subclasses using instance variables of the parent

One quick way to achieve your goal would be to use the little known SimpleDelegator class:

class A
attr_reader :var

def initialize(var)
@var = var
end
end

require 'delegate'

class B < SimpleDelegator
def test
puts var
end
end

a1 = A.new('one')
a2 = A.new('two')
b1 = B.new(a1)
b1 = B.new(a1)
b2 = B.new(a2)
b3 = B.new(a2)

b3.test
#=> 'two'
b2.test
#=> 'two'
b1.test
#=> 'one'

This blog article ("5 ways of forwarding your work to some other object in Ruby") might be of interest to you.



Related Topics



Leave a reply



Submit