How to cleanly initialize attributes in Ruby with new?

蓝咒 提交于 2019-11-27 23:26:57
Zach Kemp
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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!