You should change your schema to use has_many :through - this will allow you to put data into the join model.
models:
class Book < ActiveRecord::Base
has_many :book_categories, :dependent => :destroy
has_many :categories, :through => :book_categories
end
class Category < ActiveRecord::Base
has_many :book_categories, :dependent => :destroy
has_many :books, :through => :book_categories
end
class BookCategory < ActiveRecord::Base
belongs_to :book
belongs_to :category
end
So, now, BookCategory has the responsibility for storing whether that book is the best one for that category.
You could do this in a variety of ways. One is to have a boolean field "best" in books_categories.
You can then add this to get the best book for a category.
#in Category
def best_book
self.books.first(:conditions => ["books_categories.best = ?", true]
end
What's happening here is that rails will automatically do a JOIN to get from categories to books via books_categories, and we're applying a condition to the join table.
All that's left is to ensure that only one book can be best for a given category. I would do this with an after_save method in BookCategory:
class BookCategory < ActiveRecord::Base
after_save :ensure_only_one_best_per_category
def ensure_only_one_best_per_category
if self.best
BookCategory.all(:conditions => ["category_id = ? and best = ? id <> ?", self.category_id, true, self.id]).each{|bc| bc.update_attributes(:best => false)}
end
end
This system is easily expanded upon should you wish to, eg, rate books in various categories, instead of simply saying whether they are the "best" or not: in this case you would replace the best
boolean field with a rating
float field or something.