Ruby on rails 在Rails 3多对多关联中,基于对象关联条件查询对象的最有效方法是什么?
我有一个多对多模型关系:Ruby on rails 在Rails 3多对多关联中,基于对象关联条件查询对象的最有效方法是什么?,ruby-on-rails,ruby,oracle,ruby-on-rails-3,Ruby On Rails,Ruby,Oracle,Ruby On Rails 3,我有一个多对多模型关系: class Movie has_many :movie_genres has_many :genres, :through => :movie_genres class Genre has_many :movie_genres has_many :movies, :through => :movie_genres class MovieGenre belongs_to :movie belongs_to :genre 我想查询所有具有特定类型但不与其他
class Movie
has_many :movie_genres
has_many :genres, :through => :movie_genres
class Genre
has_many :movie_genres
has_many :movies, :through => :movie_genres
class MovieGenre
belongs_to :movie
belongs_to :genre
我想查询所有具有特定类型但不与其他类型关联的电影。示例:所有动作片而非剧情片
我所做的是:
action_movies = Genre.find_by_name('action').movies
drama_movies = Genre.find_by_name('drama').movies
action_not_drama_movies = action_movies - drama_movies
有没有更有效的方法?需要注意的是,查询可能会变得更复杂,比如:所有动作片而非戏剧片,或者所有浪漫和喜剧片看不到一个查询就可以完成查询的方法(无论如何也不能不使用子查询)。但我认为这是一个让它变得更好的方法:
scope :by_genres, lambda { |genres|
genres = [genres] unless genres.is_a? Array
joins(:genres).where(genres: { name: genre }).uniq
}
scope :except_ids, lambda { |ids|
where("movies.id NOT IN (?)", ids)
}
scope :intersect_ids, lambda { |ids|
where("movies.id IN (?)", ids)
}
## all movies that are action but not drama
action_ids = Movie.by_genres("action").ids
drama_movies = Movie.by_genres("drama").except_ids(action_ids)
## all movies that are both action and dramas
action_ids = Movie.by_genres("action").ids
drama_movies = Movie.by_genres("drama").intersect_ids(action_ids)
## all movies that are either action or drama
action_or_drama_movies = Movie.by_genres(["action", "drama"])
在Rails中,可以使用原始SQL进行除法和相交。但我认为这通常不是一个好主意,因为它仍然需要多个查询,并且可能使代码依赖于所使用的数据库
我最初的回答相当幼稚。我将把它留在这里,这样其他人就不会犯同样的错误: 使用
连接
,只需一次查询即可获得:
Movie.joins(:genres).where("genres.name = ?", "action").where("genres.name != ?", "drama")
正如评论中提到的,这将获得所有动作片和戏剧片 可能使用范围更好,下面是示例和说明(但未测试),在电影模型中创建范围如下 Movie.rb
scope :action_movies, joins(movie_genre: :genre).select('movies.*','genres.*').where('genres.name = ?', 'action')
scope :drama_movies, joins(movie_genre: :genre).select('movies.*','genres.*').where('genres.name = ?', 'drama')
在控制器中,您可以按如下方式调用
@action_movies = Movie.action_movies
@drama_movies = Movie.drama_movies
@action_not_drama_movies = @action_movies - @drama_movies
编辑动态范围
若您想要动态,那个么可以使用块将参数发送到下面的作用域is作用域
scope :get_movies, lambda { |genre_request|
joins(movie_genre: :genre).select('movies.*','genres.*').where('genres.name = ?', genre_request)
}
类型请求=作用域的参数变量
在控制器中
@action_movies = Movie.get_movies('action')
@drama_movies = Movie.get_movies('drama')
通过sql语句从中的动作电影集中删除动作电影,您确实可以通过不必为所有动作电影和戏剧电影实例化
电影
实例来提高效率
基本构建块是一个动态范围,类似于
您可以使用该范围作为构建块来获取所需的电影
所有动作片而非戏剧片:
Movie
.of_genre('action')
.where("movies.id NOT IN (#{Movie.of_genre('drama').to_sql}")
这将导致一个sql子查询。使用连接会更好,但对于大多数情况来说,它应该足够好,并且比使用连接选项更好
如果你的应用程序中有rails 5应用程序,你甚至可以键入
Movie
.of_genre('action')
.where.not(id: Movie.of_genre('drama'))
所有动作片而非剧情片或所有浪漫喜剧片
因为它是rails 3应用程序,所以您必须手动键入并移动大部分sql,并且不能充分利用范围。或
方法仅在rails 5中引入。因此,这意味着必须键入:
Movie
.joins(:genres)
.where("(genres.name = 'action' AND movies.id NOT IN (#{Movie.of_genre('drama').to_sql}) OR genres.name IN ('romance', 'comedy')" )
同样,如果将它放在rails 5应用程序的位置,这会简单得多
Movie
.of_genre('action')
.where.not(id: Movie.of_genre('drama'))
.or(Movie.of_genre(['romance', 'comedy']))
嘿谢谢你的回答。我在使用它时遇到的问题是,在我的结果中,如果那些电影也有动作类型的话,我仍然会得到戏剧类型的电影。哦,真的。抱歉,我想不出一种方法可以在一个查询中完成。我将使用的查询将是动态的。我只是以动作和戏剧为例。因此,以这种方式使用作用域对我来说可能不太合适。不过我会记下这个,谢谢!我只是为你的案件增加了更多的范围我认为你所增加的是非常有用的。但我仍然在寻找查询的改进。不过,还是对你的答案投了赞成票:)嗨,这一个和我最后使用的最接近。它在一个查询中实现了我想要的。谢谢!
Movie
.of_genre('action')
.where.not(id: Movie.of_genre('drama'))
.or(Movie.of_genre(['romance', 'comedy']))