2009-08-15 10 views
11

これはまったく問題ではありませんが、write_attributeの問題がRails 'Active Recordのオブジェクトである場合の解決方法に関するレポートです。これが同じ問題に直面している他の人にも役立つことを願っています。ActiveRecordでのセッターオーバーライドの問題

例を説明しましょう。非常にシンプルな

class Book < ActiveRecord::Base 
    belongs_to :author 
end 

class Author < ActiveRecord::Base 
    has_many :books 
end 

:あなたは二つのクラス、BookAuthorがあるとします。しかし、何らかの理由でBookauthor =メソッドを無効にする必要があります。私がRailsに慣れていない私は、Railsを使ったAgile Web Developmentに関するSam Rubyの提案に従いました。attribute_writer privateメソッドを使用してください。だから私の最初の試行は:

class Book < ActiveRecord::Base 
    belongs_to :author 

    def author=(author) 
    author = Author.find_or_initialize_by_name(author) if author.is_a? String 
    self.write_attribute(:author, author) 
    end 
end 

残念ながら、これは動作しません。それは私が、コンソールから得るものです:

>> book = Book.new(:name => "Alice's Adventures in Wonderland", :pub_year => 1865) 
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil> 
>> book.author = "Lewis Carroll" 
=> "Lewis Carroll" 
>> book 
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil> 
>> book.author 
=> nil 

それはRailsのは、それがオブジェクトであり、何も行いません認識していないようです:attribuition後、著者はまだゼロであります!もちろん、私はwrite_attribute(:author_id, author.id)を試すことができますが、作者がまだ保存されていない(まだIDがありません!)と、オブジェクトを一緒に保存する必要があります(著者が本が有効な場合のみ保存する必要があります)。 http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/4fe057494c6e23e8ので、最終的に私はいくつかの作業コード持っていた可能性があり:

は、検索した後、溶液のためにたくさん(と無駄に他の多くのものを試してみてください)、私はこのメッセージを見つけ

class Book < ActiveRecord::Base 
    belongs_to :author 

    def author_with_lookup=(author) 
    author = Author.find_or_initialize_by_name(author) if author.is_a? String 
    self.author_without_lookup = author 
    end 
    alias_method_chain :author=, :lookup 
end 

にこの時間は、コンソールがいました私にすてきな:

>> book = Book.new(:name => "Alice's Adventures in Wonderland", :pub_year => 1865) 
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil> 
>> book.author = "Lewis Carroll"=> "Lewis Carroll" 
>> book 
=> #<Book id: nil, name: "Alice's Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil> 
>> book.author 
=> #<Author id: nil, name: "Lewis Carroll", created_at: nil, updated_at: nil> 

ここのトリックは、古いセッター(author_without_lookup)への代替名(この場合はauthor_with_lookupで)インターセプタを作成alias_method_chain、です。私はこの取り決めを理解するのに時間がかかったと告白します。誰かがそれを詳細に説明してくれたらうれしいですが、私にはこの種の問題に関する情報がないことに驚いたのです。私はただ1つの投稿を見つけるためにたくさんのGoogleを持っていなければなりません。タイトルは当初は問題に無関係だったようです。私はRailsが新しくなっています。だからあなたはどう思うのですか?これは悪い習慣ですか?

答えて

20

author=メソッドをオーバーライドする代わりに、仮想属性を作成することをお勧めします。

class Book < ActiveRecord::Base 
    belongs_to :author 

    def author_name=(author_name) 
    self.author = Author.find_or_initialize_by_name(author_name) 
    end 

    def author_name 
    author.name if author 
    end 
end 

次に、フォームフィールドに適用するようなクールな操作を行うことができます。

<%= f.text_field :author_name %> 

これはあなたの状況に適していますか?

+0

を使用してこの問題を解決し、私はこれを行うために思ったが、私は、重複する属性を望んでいませんでした。私が上で提案した方法は非常にうまくいっています。私はちょうどそれを共有したいと思った。私は確かにそれでtext_fieldトリックを作ることができます。しかし、あなたの返信をありがとう! = D –

+2

'delegate:name、:to:author::prefix => true'で' author_name'メソッドを置き換える方が正しいのでしょうか? –

+2

@Adam、それは確かにそれを行う別の方法です。私は通常、複数のメソッドを扱うときにのみ 'delegate'を使います。 1つしかない場合は、メソッドを直接定義する方が好きです。なぜなら、より明確に感じるからです。 – ryanb

6

アクセサーをオーバーライドするときは、オーバーライドしているアソシエーション生成アトリビュートの名前ではなく、write_attributeself[:the_attribute]=の実際のDBアトリビュートを設定する必要があります。これは私のために働く。

require 'rubygems' 
require 'active_record' 
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:") 
ActiveRecord::Schema.define do 
    create_table(:books) {|t| t.string :title } 
    create_table(:authors) {|t| t.string :name } 
end 

class Book < ActiveRecord::Base 
    belongs_to :author 

    def author=(author_name) 
    found_author = Author.find_by_name(author_name) 
    if found_author 
     self[:author_id] = found_author.id 
    else 
     build_author(:name => author_name) 
    end 
    end 
end 

class Author < ActiveRecord::Base 
end 

Author.create!(:name => "John Doe") 
Author.create!(:name => "Tolkien") 

b1 = Book.new(:author => "John Doe") 
p b1.author 
# => #<Author id: 1, name: "John Doe"> 

b2 = Book.new(:author => "Noone") 
p b2.author 
# => #<Author id: nil, name: "Noone"> 
b2.save 
p b2.author 
# => #<Author id: 3, name: "Noone"> 

ライアンベイツ氏が提案することを強くお勧めします。新しいauthor_nameアトリビュートを作成し、アソシエーションが生成したメソッドをそのままにしておきます。毛羽立ちが少なく、混乱が少ない。

+0

私が上で述べたように、あなたが提案した方法は、著者が私の場合ではない保存している(すなわちidを持っている)場合にのみ機能します。私は、本が保存されたときにのみ保存しなければならない新しい著者を持つことができます。 –

+0

私はあなたのコメントに基づいて少し書き直しました。それは今より意味をなさないでしょうか? –

+0

ああ、ありがとう、今これは私の期待どおりに動作します。しかし(すべての推奨事項にもかかわらず)私は元のソリューションを維持します。それにもかかわらず、いつか私の心を変えればよい選択肢です。 =] –

0

私はalias_method

class Book < ActiveRecord::Base 
    belongs_to :author 

    alias_method :set_author, :author= 
    def author=(author) 
    author = Author.find_or_initialize_by_name(author) if author.is_a? String 
    set_author(author) 
    end 
end