Rails中的高效模型遍历(连接)

时间:2015-07-28 21:24:07

标签: ruby-on-rails activerecord

我有一个带有一组关系的rails应用程序。用户阅读和排名书籍,组织是用户的集合,他们共同拥有他们阅读/评级的书籍列表:

class Organization
  has_many :users, through: memberships
end
class Membership
  belongs_to :organization
  belongs_to :user
end
class User
  has_many :books, through: :readings
  has_many :organizations, through: :memberships
end
class Readings
  belongs_to :user
  belongs_to :book
end
class Book
  has_many :readings
end

我想在一个查询中查找组织已阅读和评级的所有书籍。类似的东西:

organization.members.books

我最好将它与will_paginate一起使用,并按Readings类的评级排序。知道如何在没有自定义SQL的情况下执行此操作吗?

2 个答案:

答案 0 :(得分:1)

Try the following relations:

class Organization < ActiveRecord::Base
  has_many :memberships
  has_many :users, through: :memberships
  has_many :books, -> { uniq }, through: :users
end
class Membership < ActiveRecord::Base
  belongs_to :organization
  belongs_to :user
end
class User < ActiveRecord::Base
  has_many :memberships
  has_many :readings
  has_many :organizations, through: :memberships
  has_many :books, through: :readings
end
class Reading < ActiveRecord::Base
  belongs_to :user
  belongs_to :book
end
class Book < ActiveRecord::Base
  has_many :readings
  has_many :users, through: :readings
  has_many :organizations, -> { uniq }, through: :users
end

Now you can call @organization.books to get all books for a specific organization.

I don't know exactly how you handle ratings, but you could add a scope called rated to your Book model and then call @organization.books.rated to get all rated books for a specific organization. Here is an example of what that scope might look like:

class Book < ActiveRecord::Base
  has_many :readings
  has_many :users, through: :readings
  has_many :organizations, through: :users

  scope :rated, -> { where.not(rating: nil) }
  scope :rated_above, ->(rating) { where('rating >= ?', rating) }
  scope :rated_below, ->(rating) { where('rating <= ?', rating) }
end

That is just an example assuming you use some integer based rating system where a nil rating means it is unrated. I also threw in the rated_above and rated_below scopes, which you may or may not find useful. You could use them like @organization.books.rated_above(6) to only get the books with a rating greater than or equal to 6. Again, these are just examples, you might need to change them to work with your rating implementation.


Update

In the case where your ratings are stored on the Reading model, you can change your Book model to the following:

class Book < ActiveRecord::Base
  has_many :readings
  has_many :users, through: :readings
  has_many :organizations, -> { uniq }, through: :users

  scope :rated, -> { with_ratings.having('COUNT(readings.rating) > 0') }
  scope :rated_above, ->(rating) { with_ratings.having('average_rating >= ?', rating) }
  scope :rated_below, ->(rating) { with_ratings.having('average_rating <= ?', rating) }

  private

  def self.with_readings
    includes(:readings).group('books.id')
  end

  def self.with_ratings
    with_readings.select('*, AVG(readings.rating) AS average_rating')
  end
end

I am not sure if there is a simpler approach, but it gets the job done. Now the scopes should work as expected. Additionally, you can sort by rating like this: @organization.books.rated.order('average_rating DESC')

答案 1 :(得分:0)

这应该为您提供组织在一个查询中阅读的所有书籍。不确定您的评分是如何实施的。

Book.joins(:readings => {:user => :memberships})
  .where(:readings => {:users => {:memberships => {:organization_id => @organization.id}}})