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
Ruby 1.9.2 - Read and Parse a Remote CSV
Sidekiq Worker Not Getting Triggered
How Can One Set Property Values When Initializing an Object in Ruby
Write Array of Radix-2 Numeric Strings to Binary File in Ruby
Ruby: How to Check If a String Contains Multiple Items
Ftps (Tls/Ssl) from Ruby on Rails App
Ruby Scope of Data After _End_
How to Convert Activerecord Table Name to Model Class Name
How to Get a List of Gems That Are Installed That Have Native Extensions
Could Not Find Rake-10.0.4 in Any of the Sources (Bundler::Gemnotfound)
Clicking a Button with Ruby Mechanize
Ruby Selenium Webdriver Unable to Find Mozilla Geckodriver
How to Add "Somewhere" a 'Before(:Each)' Hook So That All Spec File Can Run It
Specifying Content Type in Rspec
Case Expression Different in Ruby 1.9
How to Run Rake with --Trace Within Capistrano
Regex to Validate String Having Only Characters (Not Special Characters), Blank Spaces and Numbers
Get All Variables Defined Through 'Attr_Accessor' Without Overriding 'Attr_Accessor'