Reputation: 3021
I have an ActiveRecord model called Books
which has a has_one
association on authors
and a has_many
association on publishers
. So the following code is all good
books
.publishers
Now I have another AR model, digital_publishers
which is similar but which I would like to transparently use if the book's author responds to digital?
- let me explain with some code
normal_book = Book.find(1)
normal_book.author.digital? #=> false
normal_book.publishers #=> [Publisher1, Publisher2, ...]
digital_book = Book.find(2)
digital_book.digital? #=> true
digital_book.publishers #=> I want to use the DigitalPublishers class here
So if the book's author is digital (the author is set through a has_one :author
association so it's not as simple as having a has_many with a SQL condition on the books table), I still want to be able to call .publishers
on it, but have that return a list of DigitalPublishers
, so I want some condition on my has_many
association that first checks if the book is digital, and if it is, use the DigitalPublishers
class instead of the Publishers
class.
I tried using an after_find
callback using the following code:
after_find :alias_digital_publisher
def alias_digital_publisher
if self.author.digital?
def publishers
return self.digital_publishers
end
end
end
But this didn't seem to do the trick. Also, I'm using Rails 2.3.
Upvotes: 2
Views: 562
Reputation: 5294
My solution is very simple.
class Book < ActiveRecord::Base
has_many :normal_publishers, :class_name => 'Publisher'
has_many :digital_publishers, :class_name => 'DigitalPublisher'
def publishers
if self.digital?
self.digital_publishers
else
self.normal_publishers
end
end
end
You can still chain some methods, like book.publishers.count
, book.publishers.find(...)
.
If you need book.publisher_ids
, book.publisher_ids=
, book.publishers=
, you can define these methods like book.publishers
.
The above code works on Rails 2.3.12.
UPDATE: Sorry, I noticed klochner's alternate solution after I had posted this.
Upvotes: 1
Reputation: 8125
I need more information about the project to really make a recommendation, but here are some thoughts to consider:
I'm guessing a publisher may be linked to more than one book, so it doesn't make a lot of sense that they belong_to books. I would consider
#Book.rb
has_many :publishers, :through=>:publications
Either use Single Table Inheritance (STI) for digital publishers with a type
column of DigitalPublisher
, or just add a boolean indicating whether a publisher is digital.
This way you can just call book.publishers
, and you would get publishers that may or may not be digital, depending on which were assigned.
The trick is that you would need to ensure that only digital publishers are assigned to books with a digital author. This makes sense to me though.
def book_publishers
author.digital? ? digital_publishers : publshers
end
I'm not really a fan of this option, I think you're better off having all the publishers in one table.
Upvotes: 3
Reputation: 8892
Have a look at this section from Rails Guides v-2.3.11. In particular note the following:
The after_initialize and after_find callbacks are a bit different from the others. They have no before_* counterparts, and the only way to register them is by defining them as regular methods. If you try to register after_initialize or after_find using macro-style class methods, they will just be ignored.
Basically, try defining your after_find
as
def after_find
...
end
If that doesn't work, it might be because the book's fields haven't been initialized, so try after_initialize
instead.
Upvotes: 1