Это не вопрос, а скорее отчет о том, как я решил проблему с write_attribute
, когда атрибут является объектом, в Rails 'Active Record
. Я надеюсь, что это может быть полезно для других, сталкивающихся с одной и той же проблемой.
Позвольте мне объяснить пример. Предположим, у вас есть два класса: Book
и Author
:
class Book < ActiveRecord::Base
belongs_to :author
end
class Author < ActiveRecord::Base
has_many :books
end
Очень просто. Но по какой-либо причине вам нужно переопределить метод Author
= на Book
. Поскольку я новичок в Rails, я следовал предложению Сэма Руби на Agile Web Development с Rails: используйте частный метод attribute_writer
. Итак, моя первая попытка:
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 Adventures in Wonderland", :pub_year => 1865)
=> #<Book id: nil, name: "Alice 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 Adventures in Wonderland", pub_year: 1865, author_id: nil, created_at: nil, updated_at: nil>
>> book.author
=> nil
Кажется, что Rails не признает, что это объект и ничего не делает: после атрибуции автор все еще ноль! Конечно, я мог бы попробовать write_attribute(:author_id, author.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 Adventures in Wonderland", :pub_year => 1865)
=> #<Book id: nil, name: "Alice 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 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>
Трюк здесь alias_method_chain
, который создает перехватчик (в данном случае author_with_lookup
) и альтернативное имя для старого установщика (author_without_lookup
). Я признаюсь, что потребовалось некоторое время, чтобы понять эту договоренность, и я был бы рад, если кто-то захочет объяснить это подробно, но меня удивило отсутствие информации об этой проблеме. Мне нужно много google, чтобы найти только одно сообщение, которое по названию казалось первоначально не связанным с проблемой. Я новичок в Rails, так что вы думаете, ребята: это плохая практика?