2012-10-06 13 views
27
class Foo 
    attr_accessor :name, :age, :email, :gender, :height 

    def initalize params 
    @name = params[:name] 
    @age = params[:age] 
    @email = params[:email] 
    . 
    . 
    . 
    end 

これは愚かなやり方です。 Rubyのオブジェクトを初期化するためのより良い/もっと慣用的な方法は何ですか?Rubyで新規に属性をきれいに初期化する方法は?

ルビー1.9.3

+0

私は実際にはこの質問に対する答えは「いいえ」だと思う - 良い方法があるかもしれないが、より良い、よりイディオム的な方法があれば、以下に明確にアップ・投票された答えがあるだろう。 –

答えて

15
def initialize(params) 
    params.each do |key, value| 
    instance_variable_set("@#{key}", value) 
    end 
end 
+4

私はこれを嫌うのは一般的ですが、もしそれを使うつもりなら、少なくともインスタンス変数を設定するのではなくセッターを呼び出してください。あなたがivarsを使って変数の名前を混乱させるなら、あなたはそれを知らないでしょうが、メソッドを使って、NoMethodErrorを得るでしょう。 –

+1

@ジョシュア:それは完全に有効です。私は、あなたのメソッドが一般的には好ましいことに同意しますが、すべてのインスタンス変数に対してsetterを提供するとは限りません(オブジェクト初期化後に変更しないなど)。 –

+1

笑、私はこのスレッドで答えていたことに気付かなかった。とにかく、私はセッター/ゲッターを介してほとんど常にアクセスします。それらを変更したくない場合は、プライベートとして宣言してください(例:https://github.com/JoshCheek/seeing_is_believing/blob/3161fb906d38ebb4300333f20d84bd58fa3e7652/lib/seeing_is_believing/binary/commentable_lines.rb#L32-37) )しかし、実際には、このようなイニシャライザコードはデータ構造体でのみ使用するべきなので、この問題は実際には起こりません。 –

36

あなただけのキーを反復処理するとセッターを呼び出すことができます。あなたが無効なキーを渡すとそれが奪取されるので、私はこれを好む。

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 
+1

私はこれも好きですが、すべてのインスタンス変数がセッターを持つクラスでのみ役に立ちそうです。 –

1

ハッシュを唯一の引数として受け取っている場合は、それをインスタンス変数として保持しないでください。値が必要なときはいつでも、ハッシュから呼び出すことができます。インスタンス変数名は、簡単に呼び出せるように短くしておくことができます。

class Foo 
    attr_reader :p 
    def initalize p 
    @p = p 
    end 
    def foo 
    do_something_with(@p[:name]) 
    ... 
    end 
end 

@p[:name]があなたのためにまだ長すぎる場合は、インスタンス変数としてPROCを保存し、@p.(:name)などの関連する値を呼び出すことができます。

class Foo 
    attr_reader :p 
    def initialize p 
    @p = ->x{p[x]} 
    end 
    def foo 
    do_something_with(@p.(:name)) 
    ... 
    end 
end 

また、代わりに、ハッシュを呼び出してキーを適用するメソッドを定義する方法もあります。

class Foo 
    def initalize p 
    @p = p 
    end 
    def get key 
    @p[key] 
    end 
    def foo 
    do_something_with(get(:name)) 
    ... 
    end 
end 

値を設定する場合は、設定メソッドを定義し、必要に応じて無効なキーがないかどうかをさらに確認できます。

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 
+0

これは、可能な漂白参照を残します: 'h = {:a =>:b}; Foo.new(h); h [:a] =:ナンセンス。 –

3

paramsのすべてのキーを使用していない場合は、不本意な名前を定義できます。私はそれがちょっと白い名前のリストでなければならないと思う。

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' 
+0

不要な属性を定義しない - それが良い点です。 –

+0

'@@ attributes = [:name、:age、:email、:gender、:height]'の代わりに 'get_attributes'を使う方法は?それらは 'attr_accessor:name、:age、:email、:gender、:height'で既に定義されています。 –

+0

また、 'attr_accessor * @@ attributes'とすることもできます。ここでのホワイトリストには、あらゆる意味があります。 –

5

本当に引数の実際のリストを指定するだけではないのはなぜですか?

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 

initializeが間違ったアリティで呼び出された場合、引数や調達のエラーのため、例えばデフォルト値)このバージョンでは、他よりもより多くのコード行かもしれないが、それは組み込みの言語機能を活用することが容易になります。

+3

位置パラメータは、リファクタリングが困難な多くの理由から迷惑になることがあります。 –

+0

うん。私は可能な限り明確にすることを好むので、私はこのアプローチが好きです。これは、最も簡単な理由を説明するコードです。位置のパラメータは後で署名を変更するのをより困難にしますが、それは私が作ろうとするトレードオフです。 –

4
Foo = Struct.new(:name, :age, :email, :gender, :height) 

十分に機能するクラスであれば十分です。デモ:

p Foo.class # Class 

employee = Foo.new("smith", 29, "[email protected]", "m", 1.75) #create an instance 
p employee.class # Foo 
p employee.methods.sort # huge list which includes name, name=, age, age= etc 
+1

これは、 'include ActiveModel :: Model'と組み合わせてうまく動作しないことに注意してください。 –

9

は、一般化のビットとジョシュアチークの答えを生かすために、

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初期ミックスインは、我々は、我々がしたいカスタムの初期化を必要とするを使用している場合ちょうどスーパーを呼び出す:

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"> 
関連する問題