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.
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
Shortcut to assigning instance variables
Person = Struct.new(:name, :artist, :duration) do
# more code to the Person class
end
Your other option is to pass a Hash/keyword of variables instead and use something like ActiveModel::Model
https://github.com/rails/rails/blob/master/activemodel/lib/active_model/model.rb#L78-L81
def initialize(params={})
params.each do |attr, value|
self.instance_variable_set("@#{attr}", value)
end if params
end
ruby initialize method - purpose of initialize arguments
You can set an instance variable in any method in your class.
initialize
is a method that is executed immediately after calling Person.new
.
All external data for new object is passed through the arguments of .new(args)
.
Your line @age = age
- it's the same that @age = nil
.
This is due to the fact that age
is absent in the arguments of initialize
.
Also you have attr_accessor :age
.
It's equal, that you have methods:
def age
@age
end
def age=(age)
@age = age
end
So you can set instance variable like this:
john = Person.new('John')
p john.age #=> nil
john.age = 5
p john.age #=> 5
Is there a way to store an instance method inside a variable without running that function first? In Ruby
PLEASE DO NOT TELL ME I CAN DO
method(:something)
this does not help as it is an instance method being called by another instance.
I would really like to not tell you that, but unfortunately, that is the correct answer:
rerun = start_game_ui.method(:prompt)
# Then, later when you need to call it:
result = rerun.()
Not using Object#method
, as you require in your question, leads to significant added complexity, e.g. by passing around the receiver and the name of the method separately:
rerun_receiver_and_message = [start_game_ui, :prompt]
# Then, later when you need to call it:
result = rerun_receiver_and_message.first.public_send(rerun_receiver_and_message.last)
Confused about send method to set instance variables
First off: you cannot set instance variables with Object#send
. Object#send
sends messages, it doesn't set instance variables. Of course, you can send a message which may or may not then in turn invoke a method which may or may not in turn then set an instance variable, but that's not the doing of Object#send
, it's the doing of whatever method was invoked in response to the message you sent.
If you want to dynamically set instance variables, use Object#instance_variable_set
:
class Dog
def initialize(**attrs)
attrs.each do |attr, value|
instance_variable_set(:"@#{attr}", value)
end
end
end
dog = Dog.new(talk: 'bruuuf')
In this case, however, it looks like you don't actually want to set an instance variable but rather call a setter method. Setter methods have names that end with an =
sign, e.g. Dog#talk=
:
class Dog
def initialize(**attrs)
attrs.each do |attr, value|
send(:"#{attr}=", value)
end
end
end
dog = Dog.new(talk: 'bruuuf')
Note, of course, that this assumes that the method Dog#talk=
actually exists. If it doesn't, you will get a NoMethodError
.
Ruby Method Arguments - Can they change truth value automatically?
To achieve what you are looking for, you should wrap the forest
method in a class and define each argument as an instance variable.
class Forest
attr_accessor :sword, :armour, :ring
def initialize(sword = false, armour = false, ring = false)
@sword = sword
@armour = armour
@ring = ring
end
end
So now, you can declare an instance of Forest
,
forest = Forest.new
All the variables default to false
, unless you explicitly write true
.
With the attr_accessor
, you can access and set all the variables.
forest.sword #=> false
forest.sword = true
forest.sword #=> true
Related Topics
Nanoc Changing the Base Path When Deploying Page in Github
Ruby/Rails Array of Strings to Postgresql Insert
The Program 'Rails' Is Currently Not Installed
Ruby Is Already Using the Class Name of My Model
Rails Cancan and State MAChine - Authorizing States
Insert Rows on Specific Line in a File
Project Euler #3 in Ruby Solution Times Out
Compass Error for Command 'Grunt Server'
Scraping an Angularjs Application
Linking Two Models in a Multi-Model Form
How to Refactor Openssl Pkcs5_Keyivgen in Ruby
Ruby on Rails How to Deal with Nan
How to Test Whether a String Would Match a Glob in Ruby
How to Convert 1 to "First", 2 to "Second", and So On, in Ruby