class Foo
attr_accessor :name, :age, :email, :gender, :height
def initalize params
@name = params[:name]
@age = params[:age]
@email = params[:email]
.
.
.
end
This seems like a silly way of doing it. What is a better/more idiomatic way of initalizing objects in Ruby?
Ruby 1.9.3
def initialize(params)
params.each do |key, value|
instance_variable_set("@#{key}", value)
end
end
You can just iterate over the keys and invoke the setters. I prefer this, because it will catch if you pass an invalid key.
class Foo
attr_accessor :name, :age, :email, :gender, :height
def initialize params = {}
params.each { |key, value| send "#{key}=", value }
end
end
foo = Foo.new name: 'Josh', age: 456
foo.name # => "Josh"
foo.age # => 456
foo.email # => nil
To capitalize on Joshua Cheek's answer with a bit of generalization
module Initializable
def initialize(params = {})
params.each do |key, value|
setter = "#{key}="
send(setter, value) if respond_to?(setter.to_sym, false)
end
end
end
class Foo
include Initializable
attr_accessor :name, :age, :email, :gender, :height
end
Foo.new name: 'Josh', age: 456
=> #<Foo:0x007fdeac02ecb0 @name="Josh", @age=456>
NB If the initialization mix-in has been used and we need custom initialization, we'd just call super:
class Foo
include Initializable
attr_accessor :name, :age, :email, :gender, :height, :handler
def initialize(*)
super
self.handler = "#{self.name} #{self.age}"
end
end
Foo.new name: 'Josh', age: 45
=> #<Foo:0x007fe94c0446f0 @name="Josh", @age=45, @handler="Josh 45">
Foo = Struct.new(:name, :age, :email, :gender, :height)
This is enough for a fully functioning class. Demo:
p Foo.class # Class
employee = Foo.new("smith", 29, "smith@foo.com", "m", 1.75) #create an instance
p employee.class # Foo
p employee.methods.sort # huge list which includes name, name=, age, age= etc
Why not just explicitly specify an actual list of arguments?
class Foo
attr_accessor :name, :age, :email, :gender, :height
def initialize(name, age, email, gender, height)
@name = name
@age = age
@email = email
@gender = gender
@height = height
end
end
This version may be more lines of code than others, but it makes it easier to leverage built-in language features (e.g. default values for arguments or raising errors if initialize
is called with incorrect arity).
Using all keys from params is not correct, you can define unwilling names. I think it should be kinda white list of names
class Foo
@@attributes = [:name, :age, :email, :gender, :height]
@@attributes.each do |attr|
class_eval { attr_accessor "#{attr}" }
end
def initialize params
@@attributes.each do |attr|
instance_variable_set("@#{attr}", params[attr]) if params[attr]
end
end
end
Foo.new({:name => 'test'}).name #=> 'test'
If you are receiving a hash as the sole argument, why not just keep that as an instance variable? Whenever you need a value, call it from the hash. You can keep the instance variable name short so that it can be easily called.
class Foo
attr_reader :p
def initalize p
@p = p
end
def foo
do_something_with(@p[:name])
...
end
end
If @p[:name]
is still too lengthy for you, then you can save a proc as an instance variable, and call the relevant value like @p.(:name)
.
class Foo
attr_reader :p
def initialize p
@p = ->x{p[x]}
end
def foo
do_something_with(@p.(:name))
...
end
end
Or, still an alternative way is to define a method that calls the hash and applies the key.
class Foo
def initalize p
@p = p
end
def get key
@p[key]
end
def foo
do_something_with(get(:name))
...
end
end
If want to set the values, you can define a setter method, and further check for invalid keys if you want.
class Foo
Keys = [:name, :age, :email, :gender, :height]
def initalize p
raise "Invalid key in argument" unless (p.keys - Keys).empty?
@p = p
end
def set key, value
raise "Invalid key" unless Keys.key?(key)
@p[key] = value
end
def get key
@p[key]
end
def foo
do_something_with(get(:name))
...
end
end
来源:https://stackoverflow.com/questions/12763016/how-to-cleanly-initialize-attributes-in-ruby-with-new