Reputation: 405
This is a library system, people can borrow books here. And each book belongs to a category. We'd like to give people some suggestions according to what kind of books they borrowed most.
Here are four models:
class Person < AR
has_many :borrows
end
class Borrow < AR
belongs_to :person
belongs_to :book
end
class Category < AR
has_many :books
end
class Book < AR
has_many :borrows
belongs_to :category
end
And I wrote SQL to find the books
SELECT * FROM books WHERE category_id =
(SELECT category_id FROM books WHERE id IN
(SELECT book_id FROM borrows WHERE person_id =10000)
GROUP BY category_id ORDER BY count(*) DESC LIMIT 1)
AND id NOT IN
(SELECT book_id FROM borrows WHERE person_id =10000)
This seems to be working, but I wonder how could I write the finder in the Rails way...
Upvotes: 3
Views: 515
Reputation: 2195
You can do following things, write following in person.rb
has_many :books, :through => :borrows
has_many :categories_of_books, :through => :books, :source => :category
&
def suggested_books
Book.where("category_id IN (?) AND id NOT IN (?)", self.categories_of_books, self.books)
end
Though it results in more than 1 query, but its clean, you just have to do:
@user.suggested_books
Upvotes: 2
Reputation: 8892
With active record, you can eliminate two of the three subqueries in favor of joins:
Book.where(
category_id: Category.limit(1)
.joins(:books => :borrows)
.where("borrows.person_id = ?", 10000)
.group("categories.id")
.order("COUNT(*) DESC")
.pluck("categories.id")
).joins(:borrows).where("borrows.person_id != ?", 10000)
Still not the best solution because it generates two separate queries (one for the inner query on Category
). Depending on your needs, this may not be so bad, if, say, you decide to use the result of the inner query (the most borrowed category of the user in question) for something else.
Upvotes: 1
Reputation: 413
May be something like that :
@person = Person.find(10000)
@categories = @person.books.map{|b| b.category}.uniq!
@suggestions = @categories.map{|c| c.books} - @person.books
In order to have '@person.books' working, you have to add in your Person model :
has_many :books, :through => :borrows
Upvotes: 0