活动记录:查询属于同一父级的不同关联

时间:2016-05-24 18:21:25

标签: ruby-on-rails ruby ruby-on-rails-4 activerecord

我有一个Rails应用程序,用户可以选择他们正在观看的节目,以及他们已经观看过的剧集。 ActiveRecord关联看起来像这样:

class User
  has_many :episodes_joins, :dependent => :delete_all, class_name: 'UsersEpisodesJoin'
  has_many :episodes, through: :episodes_joins
  has_many :shows_joins, :dependent => :delete_all, class_name: 'UsersShowsJoin'
  has_many :shows, through: :shows_joins
end

class Show
  has_many :episodes, dependent: :destroy
  has_many :users_shows_joins, :dependent => :delete_all
  has_many :users, through: :users_shows_joins
end

class Episode
  belongs_to :show
  has_many :users_episodes_joins, :dependent => :delete_all
  has_many :users, through: :users_episodes_joins
end

class UsersShowsJoin
  belongs_to :user
  belongs_to :show
end

class UsersEpisodesJoin
  belongs_to :user
  belongs_to :episode
end

我希望允许用户过滤"特殊剧集"每个节目单独("特别剧集"有episode.special?设置为true)。为此,我考虑在名为filter_specials的UsersShowsJoins表中添加一个布尔列。我现在想做这样的事情:

episodes = user.episodes.where(special: false).to_a
user.episodes.where(special: true) do |epi|
  show_join = UsersShowsJoins.where(user_id: user.id, show_id: epi.show.id).first
  episodes << epi if show_join.nil? || !show_join.filter_specials?
end
# do something with 'episodes',
# which contains all non-special episodes,
# as well as special episodes of shows for which the user is not filtering these

我知道这是一个非常虚伪和缓慢的实现,会执行大量的数据库查询,而且我确信有更好的方法可以做到这一点,甚至可能只需要一个查询。

此外,我希望能够在数据库中查询用户选择的所有节目,并为同一用户预加载具有UsersEpisodesJoins行的相应剧集。类似的东西:

shows = user.shows.all
h = {}
shows.each do |show|
  episodes = user.episodes.where(show_id: show.id).all
  h[show] = episodes
end

# do something with h,
# which contains a user's watched episodes for each selected show

如何有效地编写这些查询,以免我在复杂查询中遇到N + 1问题?

1 个答案:

答案 0 :(得分:2)

您应该能够像这样编写特殊剧集过滤器:

show_joins = UsersShowsJoins.joins(:shows, :users).where(episodes: { special: true})

这将按照他们的共同关系加入showsusers,并过滤special设置为true的剧集。

根据您想要使用的对象类型作为主要对象,您可以编写各种变体:

shows = Show.joins(:users, :episodes).where(episodes: {special: true})

或:

episodes = Episode.joins(shows: :users).where(special: true)

对于第二个查询,您可以使用:

user.shows.includes(:episodes).all

这应预先加载用户正在观看的节目的剧集。如果您愿意,可以添加where条件,group - 甚至order条件,如下所示:

user.shows.includes(:episodes).order("shows.title")

按节目标题排序结果(假设有一个节目标题字段)。

Active Record Query Interface指南在Joining Tables部分中有一些很好的示例。值得一读的是如何有效地进行这样复杂的查询。