spec/sequel_model/associations_spec.rb in sequel-2.6.0 vs spec/sequel_model/associations_spec.rb in sequel-2.7.0

- old
+ new

@@ -44,11 +44,11 @@ end describe Sequel::Model, "many_to_one" do before do MODEL_DB.reset - + @c2 = Class.new(Sequel::Model(:nodes)) do unrestrict_primary_key columns :id, :parent_id, :par_parent_id, :blah end @@ -103,10 +103,16 @@ p.values.should == {:x => 1, :id => 1} MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.id = 567) LIMIT 1"] end + it "should use :primary_key option if given" do + @c2.many_to_one :parent, :class => @c2, :key => :blah, :primary_key => :pk + @c2.new(:id => 1, :blah => 567).parent + MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.pk = 567) LIMIT 1"] + end + it "should use :select option if given" do @c2.many_to_one :parent, :class => @c2, :key => :blah, :select=>[:id, :name] @c2.new(:id => 1, :blah => 567).parent MODEL_DB.sqls.should == ["SELECT id, name FROM nodes WHERE (nodes.id = 567) LIMIT 1"] end @@ -155,10 +161,25 @@ e = @c2.new(:id => 6677) d.parent = e d.values.should == {:id => 1, :parent_id => 6677} end + it "should have the setter method respect the :primary_key option" do + @c2.many_to_one :parent, :class => @c2, :primary_key=>:blah + + d = @c2.new(:id => 1) + d.parent = @c2.new(:id => 4321, :blah=>444) + d.values.should == {:id => 1, :parent_id => 444} + + d.parent = nil + d.values.should == {:id => 1, :parent_id => nil} + + e = @c2.new(:id => 6677, :blah=>8) + d.parent = e + d.values.should == {:id => 1, :parent_id => 8} + end + it "should not persist changes until saved" do @c2.many_to_one :parent, :class => @c2 d = @c2.load(:id => 1) MODEL_DB.reset @@ -287,10 +308,18 @@ @c2.many_to_one :parent, :class => @c2, :read_only=>true @c2.instance_methods.collect{|x| x.to_s}.should(include('parent')) @c2.instance_methods.collect{|x| x.to_s}.should_not(include('parent=')) end + it "should not add associations methods directly to class" do + @c2.many_to_one :parent, :class => @c2 + @c2.instance_methods.collect{|x| x.to_s}.should(include('parent')) + @c2.instance_methods.collect{|x| x.to_s}.should(include('parent=')) + @c2.instance_methods(false).collect{|x| x.to_s}.should_not(include('parent')) + @c2.instance_methods(false).collect{|x| x.to_s}.should_not(include('parent=')) + end + it "should raise an error if trying to set a model object that doesn't have a valid primary key" do @c2.many_to_one :parent, :class => @c2 p = @c2.new c = @c2.load(:id=>123) proc{c.parent = p}.should raise_error(Sequel::Error) @@ -518,10 +547,22 @@ MODEL_DB.reset a.should == n.remove_attribute(a) MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE (id = 2345)'] end + it "should have add_ method respect the :primary_key option" do + @c2.one_to_many :attributes, :class => @c1, :primary_key=>:xxx + + n = @c2.new(:id => 1234, :xxx=>5) + a = @c1.new(:id => 2345) + a.save! + MODEL_DB.reset + a.should == n.add_attribute(a) + MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = 5 WHERE (id = 2345)'] + end + + it "should raise an error in add_ and remove_ if the passed object returns false to save (is not valid)" do @c2.one_to_many :attributes, :class => @c1 n = @c2.new(:id => 1234) a = @c1.new(:id => 2345) def a.valid?; false; end @@ -538,10 +579,16 @@ proc{a.add_attribute(n)}.should raise_error(Sequel::Error) proc{a.remove_attribute(n)}.should raise_error(Sequel::Error) proc{a.remove_all_attributes}.should raise_error(Sequel::Error) end + it "should use :primary_key option if given" do + @c1.one_to_many :nodes, :class => @c2, :primary_key => :node_id, :key=>:id + n = @c1.load(:id => 1234, :node_id=>4321) + n.nodes_dataset.sql.should == "SELECT * FROM nodes WHERE (nodes.id = 4321)" + end + it "should support a select option" do @c2.one_to_many :attributes, :class => @c1, :select => [:id, :name] n = @c2.new(:id => 1234) n.attributes_dataset.sql.should == "SELECT id, name FROM attributes WHERE (attributes.node_id = 1234)" @@ -725,10 +772,26 @@ im.should_not(include('add_attribute')) im.should_not(include('remove_attribute')) im.should_not(include('remove_all_attributes')) end + it "should not add associations methods directly to class" do + @c2.one_to_many :attributes, :class => @c1 + im = @c2.instance_methods.collect{|x| x.to_s} + im.should(include('attributes')) + im.should(include('attributes_dataset')) + im.should(include('add_attribute')) + im.should(include('remove_attribute')) + im.should(include('remove_all_attributes')) + im2 = @c2.instance_methods(false).collect{|x| x.to_s} + im2.should_not(include('attributes')) + im2.should_not(include('attributes_dataset')) + im2.should_not(include('add_attribute')) + im2.should_not(include('remove_attribute')) + im2.should_not(include('remove_all_attributes')) + end + it "should have has_many alias" do @c2.has_many :attributes, :class => @c1 n = @c2.new(:id => 1234) atts = n.attributes @@ -775,10 +838,16 @@ @c2.one_to_many :attributes, :class => @c1 @c2.new(:id => 1234).remove_all_attributes MODEL_DB.sqls.first.should == 'UPDATE attributes SET node_id = NULL WHERE (node_id = 1234)' end + it "should have the remove_all_ method respect the :primary_key option" do + @c2.one_to_many :attributes, :class => @c1, :primary_key=>:xxx + @c2.new(:id => 1234, :xxx=>5).remove_all_attributes + MODEL_DB.sqls.first.should == 'UPDATE attributes SET node_id = NULL WHERE (node_id = 5)' + end + it "remove_all should set the cached instance variable to []" do @c2.one_to_many :attributes, :class => @c1 node = @c2.new(:id => 1234) node.remove_all_attributes node.associations[:attributes].should == [] @@ -826,11 +895,11 @@ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (attributes.node_id = 1234)'] att.should be_a_kind_of(@c1) att.values.should == {} end - it "should not add a getter method if the :one_to_one option is true and :read_only option is true" do + it "should not add a setter method if the :one_to_one option is true and :read_only option is true" do @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true, :read_only=>true im = @c2.instance_methods.collect{|x| x.to_s} im.should(include('attribute')) im.should_not(include('attribute=')) end @@ -859,10 +928,29 @@ MODEL_DB.sqls.length.should == 2 MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id = 1234, id = 3|id = 3, node_id = 1234) WHERE \(id = 3\)/ MODEL_DB.sqls.last.should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 1234) AND (id != 3))' end + it "should have the setter method for the :one_to_one option respect the :primary_key option" do + @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true, :primary_key=>:xxx + attrib = @c1.new(:id=>3) + d = @c1.dataset + def d.fetch_rows(s); yield({:id=>3}) end + @c2.new(:id => 1234, :xxx=>5).attribute = attrib + ['INSERT INTO attributes (node_id, id) VALUES (5, 3)', + 'INSERT INTO attributes (id, node_id) VALUES (3, 5)'].should(include(MODEL_DB.sqls.first)) + MODEL_DB.sqls.last.should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 5) AND (id != 3))' + MODEL_DB.sqls.length.should == 2 + @c2.new(:id => 321, :xxx=>5).attribute.should == attrib + MODEL_DB.sqls.clear + attrib = @c1.load(:id=>3) + @c2.new(:id => 621, :xxx=>5).attribute = attrib + MODEL_DB.sqls.length.should == 2 + MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id = 5, id = 3|id = 3, node_id = 5) WHERE \(id = 3\)/ + MODEL_DB.sqls.last.should == 'UPDATE attributes SET node_id = NULL WHERE ((node_id = 5) AND (id != 3))' + end + it "should raise an error if the one_to_one getter would be the same as the association name" do proc{@c2.one_to_many :song, :class => @c1, :one_to_one=>true}.should raise_error(Sequel::Error) end it "should not create remove_ and remove_all methods if :one_to_one option is used" do @@ -871,11 +959,11 @@ @c2.new.should_not(respond_to(:remove_all_attributes)) end it "should make non getter and setter methods private if :one_to_one option is used" do @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true do |ds| end - meths = @c2.private_instance_methods(false).collect{|x| x.to_s} + meths = @c2.private_instance_methods.collect{|x| x.to_s} meths.should(include("attributes")) meths.should(include("add_attribute")) meths.should(include("attributes_dataset")) end @@ -992,10 +1080,11 @@ before(:each) do MODEL_DB.reset @c1 = Class.new(Sequel::Model(:attributes)) do unrestrict_primary_key + attr_accessor :yyy def self.name; 'Attribute'; end def self.to_s; 'Attribute'; end columns :id end @@ -1079,10 +1168,15 @@ a = n.attributes_dataset a.should be_a_kind_of(Sequel::Dataset) a.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 1234)) ORDER BY blah1, blah2' end + it "should support :left_primary_key and :right_primary_key options" do + @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx, :right_primary_key=>:yyy + @c2.new(:id => 1234, :xxx=>5).attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.yyy) AND (attributes_nodes.node_id = 5))' + end + it "should support a select option" do @c2.many_to_many :attributes, :class => @c1, :select => :blah n = @c2.new(:id => 1234) a = n.attributes_dataset @@ -1180,10 +1274,30 @@ a = @c1.new(:id => 2345) a.should == n.remove_attribute(a) MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 2345))' end + it "should have the add_ method respect the :left_primary_key and :right_primary_key options" do + @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx, :right_primary_key=>:yyy + + n = @c2.new(:id => 1234, :xxx=>5) + a = @c1.new(:id => 2345, :yyy=>8) + a.should == n.add_attribute(a) + ['INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (5, 8)', + 'INSERT INTO attributes_nodes (attribute_id, node_id) VALUES (8, 5)' + ].should(include(MODEL_DB.sqls.first)) + end + + it "should have the remove_ method respect the :left_primary_key and :right_primary_key options" do + @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx, :right_primary_key=>:yyy + + n = @c2.new(:id => 1234, :xxx=>5) + a = @c1.new(:id => 2345, :yyy=>8) + a.should == n.remove_attribute(a) + MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE ((node_id = 5) AND (attribute_id = 8))' + end + it "should raise an error if the model object doesn't have a valid primary key" do @c2.many_to_many :attributes, :class => @c1 a = @c2.new n = @c1.load(:id=>123) proc{a.attributes_dataset}.should raise_error(Sequel::Error) @@ -1298,10 +1412,26 @@ im.should_not(include('add_attribute')) im.should_not(include('remove_attribute')) im.should_not(include('remove_all_attributes')) end + it "should not add associations methods directly to class" do + @c2.many_to_many :attributes, :class => @c1 + im = @c2.instance_methods.collect{|x| x.to_s} + im.should(include('attributes')) + im.should(include('attributes_dataset')) + im.should(include('add_attribute')) + im.should(include('remove_attribute')) + im.should(include('remove_all_attributes')) + im2 = @c2.instance_methods(false).collect{|x| x.to_s} + im2.should_not(include('attributes')) + im2.should_not(include('attributes_dataset')) + im2.should_not(include('add_attribute')) + im2.should_not(include('remove_attribute')) + im2.should_not(include('remove_all_attributes')) + end + it "should have has_and_belongs_to_many alias" do @c2.has_and_belongs_to_many :attributes, :class => @c1 n = @c2.new(:id => 1234) a = n.attributes_dataset @@ -1313,10 +1443,16 @@ @c2.many_to_many :attributes, :class => @c1 @c2.new(:id => 1234).remove_all_attributes MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE (node_id = 1234)' end + it "should have the remove_all_ method respect the :left_primary_key option" do + @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx + @c2.new(:id => 1234, :xxx=>5).remove_all_attributes + MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE (node_id = 5)' + end + it "remove_all should set the cached instance variable to []" do @c2.many_to_many :attributes, :class => @c1 node = @c2.new(:id => 1234) node.remove_all_attributes node.associations[:attributes].should == [] @@ -1461,10 +1597,24 @@ p.associations[:attributes] = [c] p.should_receive(:br).once.with(c).and_return(false) p.remove_attribute(c).should == nil p.attributes.should == [c] end + + it "should support a :uniq option that removes duplicates from the association" do + h = [] + @c2.many_to_many :attributes, :class => @c1, :uniq=>true + @c1.class_eval do + def @dataset.fetch_rows(sql) + yield({:id=>20}) + yield({:id=>30}) + yield({:id=>20}) + yield({:id=>30}) + end + end + @c2.load(:id=>10, :parent_id=>20).attributes.should == [@c1.load(:id=>20), @c1.load(:id=>30)] + end end describe Sequel::Model, " association reflection methods" do before do MODEL_DB.reset @@ -1511,7 +1661,18 @@ @c1.associations.should == [] @c1.associate :many_to_one, :parent, :class => @c1 @c1.associations.should == [:parent] @c1.associate :one_to_many, :children, :class => @c1 @c1.associations.sort_by{|x|x.to_s}.should == [:children, :parent] + end + + it "association reflections should be copied upon subclasing" do + @c1.associate :many_to_one, :parent, :class => @c1 + c = Class.new(@c1) + @c1.associations.should == [:parent] + c.associations.should == [:parent] + c.associate :many_to_one, :parent2, :class => @c1 + @c1.associations.should == [:parent] + c.associations.sort_by{|x| x.to_s}.should == [:parent, :parent2] + c.instance_methods.map{|x| x.to_s}.should include('parent') end end