Đây không phải là một câu hỏi, đó là một báo cáo về cách tôi giải quyết vấn đề với write_attribute
khi thuộc tính là một đối tượng, trên Rails 'Active Record
. Tôi hy vọng điều này có thể hữu ích cho những người khác phải đối mặt với cùng một vấn đề.Vấn đề với ghi đè setter trên ActiveRecord
Hãy để tôi giải thích bằng ví dụ. Giả sử bạn có hai lớp, Book
và Author
:
class Book < ActiveRecord::Base
belongs_to :author
end
class Author < ActiveRecord::Base
has_many :books
end
Rất đơn giản. Nhưng, vì lý do gì đó, bạn cần ghi đè phương thức author
= trên Book
. Vì tôi mới sử dụng Rails, tôi đã theo đề xuất của Sam Ruby về Phát triển Web Agile với Rails: sử dụng phương thức riêng tư attribute_writer
. Vì vậy, lần thử đầu tiên của tôi là:
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
Thật không may, điều này không có tác dụng. Đó là những gì tôi nhận được từ bảng điều khiển:
>> 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
Dường như Rails không nhận ra nó là một đối tượng và không có gì: sau khi attribuition, tác giả vẫn còn không! Tất nhiên, tôi có thể thử write_attribute(:author_id, author.id)
, nhưng nó không giúp đỡ khi tác giả chưa được lưu (nó vẫn không có id!) Và tôi cần các đối tượng được lưu lại với nhau (tác giả phải được lưu chỉ khi sách hợp lệ).
Sau khi tìm kiếm rất nhiều cho một giải pháp (và thử nhiều thứ khác một cách vô ích), tôi thấy tin nhắn này: http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/4fe057494c6e23e8, vì vậy cuối cùng tôi có thể có một số mã làm việc:
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
thời gian này, giao diện điều khiển là tốt với tôi:
>> 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>
Bí quyết ở đây là alias_method_chain
, tạo ra một đánh chặn (trong trường hợp này author_with_lookup
) và một cái tên thay thế cho các setter cũ (author_without_lookup
). Tôi thú nhận phải mất một thời gian để hiểu sự sắp xếp này và tôi rất vui nếu có ai đó quan tâm giải thích chi tiết, nhưng điều làm tôi ngạc nhiên là thiếu thông tin về vấn đề này. Tôi phải google rất nhiều để tìm thấy chỉ là một bài, mà theo tiêu đề dường như ban đầu không liên quan đến vấn đề. Tôi mới làm quen với Rails, vậy bạn nghĩ gì về chàng trai: đây có phải là một hành động tồi tệ?
Tôi nghĩ để làm điều này, nhưng tôi không muốn thuộc tính trùng lặp. Phương pháp tôi đề xuất ở trên đang hoạt động rất tốt; Tôi chỉ muốn chia sẻ nó. Tôi thực sự có thể làm cho các trick text_field với nó. Nhưng cảm ơn bạn đã trả lời! = D –
Sẽ không chính xác hơn nếu thay thế phương thức 'author_name' bằng' delegate: name,: to =>: author,: prefix => true'? –
@Adam, Đó chắc chắn là một cách thay thế để làm điều đó. Tôi thường chỉ sử dụng 'delegate' khi giao dịch với nhiều phương thức. Nếu chỉ có một tôi thích xác định phương pháp trực tiếp bởi vì tôi cảm thấy nó rõ ràng hơn. – ryanb