(This message was originally written on StackOverflow -- the
formatting is much prettier there --
http://stackoverflow.com/questions/1283046/problem-with-setter-override-on-activerecord)
This is not exactly a question, it''s rather a report on how I solved
an issue with write_attribute when the attribute is an object, on
Rails'' Active Record. I hope this can be useful to others facing the
same problem.
Let me explain with an example. Suppose you have two classes, Book and
Author:
class Book < ActiveRecord::Base
belongs_to :author
end
class Author < ActiveRecord::Base
has_many :books
end
Very simple. But, for whatever reason, you need to override the
author= method on Book. As I''m new to Rails, I''ve followed the
Sam
Ruby''s suggestion on Agile Web Development with Rails: use
attribute_writer private method. So, my first try was:
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
Unfortunately, this does not work. That''s what I get from console:
>> 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
It seems that Rails does not recognize it is an object and makes
nothing: after the attribuition, author is still nil! Of course, I
could try write_attribute(:author_id, author.id), but it does not help
when the author is not saved yet (it still has no id!) and I need the
objects be saved together (author must be saved only if book is
valid).
After search a lot for a solution (and try many other things in vain),
I found this message:
http://groups.google.com/group/rubyonrails-talk/browse%5Fthread/thread/4fe057494c6e23e8,
so finally I could had some working code:
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
This time, the console was nice to me:
>> 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>
The trick here is the alias_method_chain, that creates an interceptor
(in this case author_with_lookup) and an alternative name to the old
setter (author_without_lookup). I confess it took some time to
understand this arrangement and I''d be glad if someone care to explain
it in detail, but what surprised me was the lack of information about
this kind of problem. I have to google a lot to find just one post,
that by the title seemed initially unrelated to the problem. I''m new
to Rails, so what do you think guys: is this a bad practice?