Вопрос

i have been banging my head against the wall for the past few hours with this problem, i can't seem to figure out myself. Okay i have two models that look like this

class Book < ActiveRecord::Base
  has_and_belongs_to_many :categories
end

class Category < ActiveRecord::Base
  has_and_belongs_to_many :books
end

Categories have many books and book have several categories. And what i want to do is to create this thing that will point at book who is the best in that category. For example:

Da vinci code <- Drama <- Best(Drama)
Lord of the Rings <- Fantasy <- Best(Fantasy)
Hobbit <- Fantasy
Twilight <- Stupidity <- Best(Stupidity)

But i cant quite grasp how can i accomplish this. In form for this Best option i have checkbox, and for categories i have dropdown with multiselect. And if there is several categories selected and Best checked than what i want, is if there was another Best book than this book now becomes "not" the best book, and the newly checked and saved becomes Best!

I figured out a workaround for now, but it does not seem "Raily" for me. Workaround is to add Best column for Categories with Book id and use after_save callback. something like this:

def best_book
    if self.best
      Category.where(id: self.category_ids).update_all(default: self.id)
      Category.where.not(id: self.category_ids, best: self.id).update_all(best: nil)
    else
      Category.where(best: self.id).update_all(best: nil)
    end
  end
Это было полезно?

Решение

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.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top