Browse files

Add filter by association support to the many_through_many plugin

  • Loading branch information...
1 parent 934b2db commit a01651416d020269629c87d05a2a97862fdcee53 @jeremyevans committed Apr 29, 2011
View
24 lib/sequel/plugins/many_through_many.rb
@@ -116,6 +116,7 @@ def calculate_edges
nil
end
end
+
module ClassMethods
# Create a many_through_many association. Arguments:
# * name - Same as associate, the name of the association.
@@ -218,6 +219,29 @@ def def_many_through_many(opts)
def_association_dataset_methods(opts)
end
end
+
+ module DatasetMethods
+ private
+
+ # Use a subquery to filter rows to those related to the given associated object
+ def many_through_many_association_filter_expression(ref, obj)
+ lpks = ref[:left_primary_keys]
+ lpks = lpks.first if lpks.length == 1
+ edges = ref.edges
+ first, rest = edges.first, edges[1..-1]
+ last = edges.last
+ ds = model.db[first[:table]].select(*Array(first[:right]).map{|x| ::Sequel::SQL::QualifiedIdentifier.new(first[:table], x)})
+ rest.each{|e| ds = ds.join(e[:table], e.fetch(:only_conditions, (Array(e[:right]).zip(Array(e[:left])) + e[:conditions])), :table_alias=>ds.unused_table_alias(e[:table]), &e[:block])}
+ last_alias = if rest.empty?
+ first[:table]
+ else
+ last_join = ds.opts[:join].last
+ last_join.table_alias || last_join.table
+ end
+ ds = ds.where(Array(ref[:final_edge][:left]).map{|x| ::Sequel::SQL::QualifiedIdentifier.new(last_alias, x)}.zip(ref.right_primary_keys.map{|k| obj.send(k)}))
+ SQL::BooleanExpression.from_value_pairs(lpks=>ds)
+ end
+ end
end
end
end
View
21 spec/extensions/many_through_many_spec.rb
@@ -8,6 +8,7 @@ class ::Artist < Sequel::Model
plugin :many_through_many
end
class ::Tag < Sequel::Model
+ columns :id, :h1, :h2
end
MODEL_DB.reset
@c1 = Artist
@@ -111,6 +112,26 @@ class ::Album < Sequel::Model
n.tags.should == [@c2.load(:id=>1)]
end
+ it "should allowing filtering by many_through_many associations" do
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
+ @c1.filter(:tags=>@c2.load(:id=>1234)).sql.should == 'SELECT * FROM artists WHERE (id IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums ON (albums.id = albums_artists.album_id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) WHERE (albums_tags.tag_id = 1234)))'
+ end
+
+ it "should allowing filtering by many_through_many associations with a single through table" do
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id]]
+ @c1.filter(:tags=>@c2.load(:id=>1234)).sql.should == 'SELECT * FROM artists WHERE (id IN (SELECT albums_artists.artist_id FROM albums_artists WHERE (albums_artists.album_id = 1234)))'
+ end
+
+ it "should allowing filtering by many_through_many associations with aliased tables" do
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums_artists, :id, :id], [:albums_artists, :album_id, :tag_id]]
+ @c1.filter(:tags=>@c2.load(:id=>1234)).sql.should == 'SELECT * FROM artists WHERE (id IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums_artists AS albums_artists_0 ON (albums_artists_0.id = albums_artists.album_id) INNER JOIN albums_artists AS albums_artists_1 ON (albums_artists_1.album_id = albums_artists_0.id) WHERE (albums_artists_1.tag_id = 1234)))'
+ end
+
+ it "should allowing filtering by many_through_many associations with composite keys" do
+ @c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
+ @c1.filter(:tags=>@c2.load(:h1=>1234, :h2=>85)).sql.should == 'SELECT * FROM artists WHERE ((id, yyy) IN (SELECT albums_artists.b1, albums_artists.b2 FROM albums_artists INNER JOIN albums ON ((albums.d1 = albums_artists.c1) AND (albums.d2 = albums_artists.c2)) INNER JOIN albums_tags ON ((albums_tags.f1 = albums.e1) AND (albums_tags.f2 = albums.e2)) WHERE ((albums_tags.g1 = 1234) AND (albums_tags.g2 = 85))))'
+ end
+
it "should support a :conditions option" do
@c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :conditions=>{:a=>32}
n = @c1.load(:id => 1234)
View
15 spec/integration/plugin_test.rb
@@ -205,6 +205,11 @@ class ::Artist < Sequel::Model(@db)
Artist.filter(:artists__id=>2).eager_graph(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'A C'
Artist.filter(:artists__id=>3).eager_graph(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'B C'
Artist.filter(:artists__id=>4).eager_graph(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'B D'
+
+ Artist.filter(:albums=>@album1).all.map{|a| a.name}.sort.should == %w'1 2'
+ Artist.filter(:albums=>@album2).all.map{|a| a.name}.sort.should == %w'3 4'
+ Artist.filter(:albums=>@album3).all.map{|a| a.name}.sort.should == %w'2 3'
+ Artist.filter(:albums=>@album4).all.map{|a| a.name}.sort.should == %w'1 4'
end
specify "should handle typical case with 3 join tables" do
@@ -223,6 +228,11 @@ class ::Artist < Sequel::Model(@db)
Artist.filter(:artists__id=>2).eager_graph(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'1 2 3'
Artist.filter(:artists__id=>3).eager_graph(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'2 3 4'
Artist.filter(:artists__id=>4).eager_graph(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'1 3 4'
+
+ Artist.filter(:related_artists=>@artist1).all.map{|a| a.name}.sort.should == %w'1 2 4'
+ Artist.filter(:related_artists=>@artist2).all.map{|a| a.name}.sort.should == %w'1 2 3'
+ Artist.filter(:related_artists=>@artist3).all.map{|a| a.name}.sort.should == %w'2 3 4'
+ Artist.filter(:related_artists=>@artist4).all.map{|a| a.name}.sort.should == %w'1 3 4'
end
specify "should handle extreme case with 5 join tables" do
@@ -250,6 +260,11 @@ class ::Artist < Sequel::Model(@db)
Artist.filter(:artists__id=>2).eager_graph(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'A B C D'
Artist.filter(:artists__id=>3).eager_graph(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'A B D'
Artist.filter(:artists__id=>4).eager_graph(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'B D'
+
+ Artist.filter(:related_albums=>@album1).all.map{|a| a.name}.sort.should == %w'1 2 3'
+ Artist.filter(:related_albums=>@album2).all.map{|a| a.name}.sort.should == %w'1 2 3 4'
+ Artist.filter(:related_albums=>@album3).all.map{|a| a.name}.sort.should == %w'1 2'
+ Artist.filter(:related_albums=>@album4).all.map{|a| a.name}.sort.should == %w'2 3 4'
end
end
View
4 spec/model/associations_spec.rb
@@ -2826,8 +2826,8 @@ def self.to_s; 'Node'; end
end
it "should raise for an invalid association type" do
- @Album.plugin :many_through_many
- @Album.many_through_many :mtmtags, [[:album_id, :album_tags, :tag_id]], :class=>@Tag
+ @Album.many_to_many :iatags, :clone=>:tags
+ @Album.association_reflection(:iatags)[:type] = :foo
proc{@Album.filter(:mtmtags=>@Tag.load(:id=>3)).sql}.should raise_error(Sequel::Error)
end

0 comments on commit a016514

Please sign in to comment.