sequel 3.5.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. data/CHANGELOG +108 -0
  2. data/README.rdoc +25 -14
  3. data/Rakefile +20 -1
  4. data/doc/advanced_associations.rdoc +61 -64
  5. data/doc/cheat_sheet.rdoc +16 -7
  6. data/doc/opening_databases.rdoc +3 -3
  7. data/doc/prepared_statements.rdoc +1 -1
  8. data/doc/reflection.rdoc +2 -1
  9. data/doc/release_notes/3.6.0.txt +366 -0
  10. data/doc/schema.rdoc +19 -14
  11. data/lib/sequel/adapters/amalgalite.rb +5 -27
  12. data/lib/sequel/adapters/jdbc.rb +13 -3
  13. data/lib/sequel/adapters/jdbc/h2.rb +17 -0
  14. data/lib/sequel/adapters/jdbc/mysql.rb +20 -7
  15. data/lib/sequel/adapters/mysql.rb +4 -3
  16. data/lib/sequel/adapters/oracle.rb +1 -1
  17. data/lib/sequel/adapters/postgres.rb +87 -28
  18. data/lib/sequel/adapters/shared/mssql.rb +47 -6
  19. data/lib/sequel/adapters/shared/mysql.rb +12 -31
  20. data/lib/sequel/adapters/shared/postgres.rb +15 -12
  21. data/lib/sequel/adapters/shared/sqlite.rb +18 -0
  22. data/lib/sequel/adapters/sqlite.rb +1 -16
  23. data/lib/sequel/connection_pool.rb +1 -1
  24. data/lib/sequel/core.rb +1 -1
  25. data/lib/sequel/database.rb +1 -1
  26. data/lib/sequel/database/schema_generator.rb +2 -0
  27. data/lib/sequel/database/schema_sql.rb +1 -1
  28. data/lib/sequel/dataset.rb +5 -179
  29. data/lib/sequel/dataset/actions.rb +123 -0
  30. data/lib/sequel/dataset/convenience.rb +18 -10
  31. data/lib/sequel/dataset/features.rb +65 -0
  32. data/lib/sequel/dataset/prepared_statements.rb +29 -23
  33. data/lib/sequel/dataset/query.rb +429 -0
  34. data/lib/sequel/dataset/sql.rb +67 -435
  35. data/lib/sequel/model/associations.rb +77 -13
  36. data/lib/sequel/model/base.rb +30 -8
  37. data/lib/sequel/model/errors.rb +4 -4
  38. data/lib/sequel/plugins/caching.rb +38 -15
  39. data/lib/sequel/plugins/force_encoding.rb +18 -7
  40. data/lib/sequel/plugins/hook_class_methods.rb +4 -0
  41. data/lib/sequel/plugins/many_through_many.rb +1 -1
  42. data/lib/sequel/plugins/nested_attributes.rb +40 -11
  43. data/lib/sequel/plugins/serialization.rb +17 -3
  44. data/lib/sequel/plugins/validation_helpers.rb +65 -18
  45. data/lib/sequel/sql.rb +23 -1
  46. data/lib/sequel/version.rb +1 -1
  47. data/spec/adapters/mssql_spec.rb +96 -10
  48. data/spec/adapters/mysql_spec.rb +19 -0
  49. data/spec/adapters/postgres_spec.rb +65 -2
  50. data/spec/adapters/sqlite_spec.rb +10 -0
  51. data/spec/core/core_sql_spec.rb +9 -0
  52. data/spec/core/database_spec.rb +8 -4
  53. data/spec/core/dataset_spec.rb +122 -29
  54. data/spec/core/expression_filters_spec.rb +17 -0
  55. data/spec/extensions/caching_spec.rb +43 -3
  56. data/spec/extensions/force_encoding_spec.rb +43 -1
  57. data/spec/extensions/nested_attributes_spec.rb +55 -2
  58. data/spec/extensions/validation_helpers_spec.rb +71 -0
  59. data/spec/integration/associations_test.rb +281 -0
  60. data/spec/integration/dataset_test.rb +383 -9
  61. data/spec/integration/eager_loader_test.rb +0 -65
  62. data/spec/integration/model_test.rb +110 -0
  63. data/spec/integration/plugin_test.rb +306 -0
  64. data/spec/integration/prepared_statement_test.rb +32 -0
  65. data/spec/integration/schema_test.rb +8 -3
  66. data/spec/integration/spec_helper.rb +1 -25
  67. data/spec/model/association_reflection_spec.rb +38 -0
  68. data/spec/model/associations_spec.rb +184 -8
  69. data/spec/model/eager_loading_spec.rb +23 -0
  70. data/spec/model/model_spec.rb +8 -0
  71. data/spec/model/record_spec.rb +84 -1
  72. metadata +9 -2
@@ -23,11 +23,43 @@ describe "Prepared Statements and Bound Arguments" do
23
23
  @ds.filter(:number=>@ds.ba(:$n)).call(:all, :n=>10).should == [{:id=>1, :number=>10}]
24
24
  @ds.filter(:number=>@ds.ba(:$n)).call(:first, :n=>10).should == {:id=>1, :number=>10}
25
25
  end
26
+
27
+ specify "should support blocks for select and all" do
28
+ @ds.filter(:number=>@ds.ba(:$n)).call(:select, :n=>10){|r| r[:number] *= 2}.should == [{:id=>1, :number=>20}]
29
+ @ds.filter(:number=>@ds.ba(:$n)).call(:all, :n=>10){|r| r[:number] *= 2}.should == [{:id=>1, :number=>20}]
30
+ end
31
+
32
+ specify "should support binding variables before the call with #bind" do
33
+ @ds.filter(:number=>@ds.ba(:$n)).bind(:n=>10).call(:select).should == [{:id=>1, :number=>10}]
34
+ @ds.filter(:number=>@ds.ba(:$n)).bind(:n=>10).call(:all).should == [{:id=>1, :number=>10}]
35
+ @ds.filter(:number=>@ds.ba(:$n)).bind(:n=>10).call(:first).should == {:id=>1, :number=>10}
36
+
37
+ @ds.bind(:n=>10).filter(:number=>@ds.ba(:$n)).call(:select).should == [{:id=>1, :number=>10}]
38
+ @ds.bind(:n=>10).filter(:number=>@ds.ba(:$n)).call(:all).should == [{:id=>1, :number=>10}]
39
+ @ds.bind(:n=>10).filter(:number=>@ds.ba(:$n)).call(:first).should == {:id=>1, :number=>10}
40
+ end
41
+
42
+ specify "should allow overriding variables specified with #bind" do
43
+ @ds.filter(:number=>@ds.ba(:$n)).bind(:n=>1).call(:select, :n=>10).should == [{:id=>1, :number=>10}]
44
+ @ds.filter(:number=>@ds.ba(:$n)).bind(:n=>1).call(:all, :n=>10).should == [{:id=>1, :number=>10}]
45
+ @ds.filter(:number=>@ds.ba(:$n)).bind(:n=>1).call(:first, :n=>10).should == {:id=>1, :number=>10}
46
+
47
+ @ds.filter(:number=>@ds.ba(:$n)).bind(:n=>1).bind(:n=>10).call(:select).should == [{:id=>1, :number=>10}]
48
+ @ds.filter(:number=>@ds.ba(:$n)).bind(:n=>1).bind(:n=>10).call(:all).should == [{:id=>1, :number=>10}]
49
+ @ds.filter(:number=>@ds.ba(:$n)).bind(:n=>1).bind(:n=>10).call(:first).should == {:id=>1, :number=>10}
50
+ end
26
51
 
27
52
  specify "should support placeholder literal strings" do
28
53
  @ds.filter("number = ?", @ds.ba(:$n)).call(:select, :n=>10).should == [{:id=>1, :number=>10}]
29
54
  end
30
55
 
56
+ specify "should support named placeholder literal strings and handle multiple named placeholders correctly" do
57
+ @ds.filter("number = :n", :n=>@ds.ba(:$n)).call(:select, :n=>10).should == [{:id=>1, :number=>10}]
58
+ @ds.insert(:number=>20)
59
+ @ds.insert(:number=>30)
60
+ @ds.filter("number > :n1 AND number < :n2 AND number = :n3", :n3=>@ds.ba(:$n3), :n2=>@ds.ba(:$n2), :n1=>@ds.ba(:$n1)).call(:select, :n3=>20, :n2=>30, :n1=>10).should == [{:id=>2, :number=>20}]
61
+ end
62
+
31
63
  specify "should support datasets with static sql and placeholders" do
32
64
  INTEGRATION_DB["SELECT * FROM items WHERE number = ?", @ds.ba(:$n)].call(:select, :n=>10).should == [{:id=>1, :number=>10}]
33
65
  end
@@ -29,10 +29,8 @@ describe "Database schema parser" do
29
29
  INTEGRATION_DB.schema(:items, :reload=>true)
30
30
  clear_sqls
31
31
  INTEGRATION_DB.schema(:items)
32
- sqls_should_be
33
32
  clear_sqls
34
33
  INTEGRATION_DB.schema(:items, :reload=>true)
35
- sqls_should_be "PRAGMA table_info('items')"
36
34
  end
37
35
 
38
36
  specify "should raise an error when the table doesn't exist" do
@@ -53,7 +51,6 @@ describe "Database schema parser" do
53
51
  col_info[:type].should == :integer
54
52
  clear_sqls
55
53
  INTEGRATION_DB.schema(:items)
56
- sqls_should_be
57
54
  end
58
55
 
59
56
  cspecify "should parse primary keys from the schema properly", [proc{|db| db.class.adapter_scheme != :jdbc}, :mssql] do
@@ -161,6 +158,14 @@ describe "Database schema modifiers" do
161
158
  @ds.insert([10])
162
159
  @ds.columns!.should == [:number]
163
160
  end
161
+
162
+ specify "should allow creating indexes with tables" do
163
+ @db.create_table!(:items){Integer :number; index :number}
164
+ @db.table_exists?(:items).should == true
165
+ @db.schema(:items, :reload=>true).map{|x| x.first}.should == [:number]
166
+ @ds.insert([10])
167
+ @ds.columns!.should == [:number]
168
+ end
164
169
 
165
170
  specify "should handle foreign keys correctly when creating tables" do
166
171
  @db.create_table!(:items) do
@@ -64,30 +64,6 @@ if defined?(INTEGRATION_DB) || defined?(INTEGRATION_URL) || ENV['SEQUEL_INTEGRAT
64
64
  INTEGRATION_DB = Sequel.connect(url)
65
65
  #INTEGRATION_DB.instance_variable_set(:@server_version, 80100)
66
66
  end
67
- class Spec::Example::ExampleGroup
68
- def sqls_should_be(*args)
69
- end
70
- end
71
67
  else
72
- sql_logger = Object.new
73
- def sql_logger.info(str)
74
- $sqls << str
75
- end
76
- INTEGRATION_DB = Sequel.sqlite('', :loggers=>[sql_logger], :quote_identifiers=>false)
77
- class Spec::Example::ExampleGroup
78
- def sqls_should_be(*sqls)
79
- sqls.zip($sqls).each do |should_be, is|
80
- case should_be
81
- when String
82
- is.should == should_be
83
- when Regexp
84
- is.should =~ should_be
85
- else
86
- raise ArgumentError, "need String or RegExp"
87
- end
88
- end
89
- $sqls.length.should == sqls.length
90
- clear_sqls
91
- end
92
- end
68
+ INTEGRATION_DB = Sequel.sqlite('', :quote_identifiers=>false)
93
69
  end
@@ -135,3 +135,41 @@ describe Sequel::Model::Associations::AssociationReflection, "#select" do
135
135
  end
136
136
  end
137
137
 
138
+ describe Sequel::Model::Associations::AssociationReflection, "#can_have_associated_objects?" do
139
+ it "should be true for any given object (for backward compatibility)" do
140
+ Sequel::Model::Associations::AssociationReflection.new.can_have_associated_objects?(Object.new).should == true
141
+ end
142
+ end
143
+
144
+ describe Sequel::Model::Associations::AssociationReflection, "#associated_object_keys" do
145
+ before do
146
+ @c = Class.new(Sequel::Model)
147
+ class ::ParParent < Sequel::Model; end
148
+ end
149
+
150
+ it "should use the primary keys for a many_to_one association" do
151
+ @c.many_to_one :c, :class=>ParParent
152
+ @c.association_reflection(:c).associated_object_keys.should == [:id]
153
+ @c.many_to_one :c, :class=>ParParent, :primary_key=>:d_id
154
+ @c.association_reflection(:c).associated_object_keys.should == [:d_id]
155
+ @c.many_to_one :c, :class=>ParParent, :key=>[:c_id1, :c_id2], :primary_key=>[:id1, :id2]
156
+ @c.association_reflection(:c).associated_object_keys.should == [:id1, :id2]
157
+ end
158
+ it "should use the keys for a one_to_many association" do
159
+ ParParent.one_to_many :cs, :class=>ParParent
160
+ ParParent.association_reflection(:cs).associated_object_keys.should == [:par_parent_id]
161
+ @c.one_to_many :cs, :class=>ParParent, :key=>:d_id
162
+ @c.association_reflection(:cs).associated_object_keys.should == [:d_id]
163
+ @c.one_to_many :cs, :class=>ParParent, :key=>[:c_id1, :c_id2], :primary_key=>[:id1, :id2]
164
+ @c.association_reflection(:cs).associated_object_keys.should == [:c_id1, :c_id2]
165
+ end
166
+ it "should use the right primary keys for a many_to_many association" do
167
+ @c.many_to_many :cs, :class=>ParParent
168
+ @c.association_reflection(:cs).associated_object_keys.should == [:id]
169
+ @c.many_to_many :cs, :class=>ParParent, :right_primary_key=>:d_id
170
+ @c.association_reflection(:cs).associated_object_keys.should == [:d_id]
171
+ @c.many_to_many :cs, :class=>ParParent, :right_key=>[:c_id1, :c_id2], :right_primary_key=>[:id1, :id2]
172
+ @c.association_reflection(:cs).associated_object_keys.should == [:id1, :id2]
173
+ end
174
+ end
175
+
@@ -193,6 +193,12 @@ describe Sequel::Model, "many_to_one" do
193
193
  MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.parent_id = 1) AND (nodes.id = 234)) LIMIT 1"]
194
194
  end
195
195
 
196
+ it "should not issue query if not all keys have values" do
197
+ @c2.many_to_one :parent, :class => @c2, :key=>[:id, :parent_id], :primary_key=>[:parent_id, :id]
198
+ @c2.new(:id => 1, :parent_id => nil).parent.should == nil
199
+ MODEL_DB.sqls.should == []
200
+ end
201
+
196
202
  it "should raise an Error unless same number of composite keys used" do
197
203
  proc{@c2.many_to_one :parent, :class => @c2, :primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
198
204
  proc{@c2.many_to_one :parent, :class => @c2, :key=>[:id, :parent_id], :primary_key=>:id}.should raise_error(Sequel::Error)
@@ -552,11 +558,13 @@ describe Sequel::Model, "one_to_many" do
552
558
  MODEL_DB.reset
553
559
 
554
560
  @c1 = Class.new(Sequel::Model(:attributes)) do
561
+ def _refresh(ds); end
555
562
  unrestrict_primary_key
556
563
  columns :id, :node_id, :y
557
564
  end
558
565
 
559
566
  @c2 = Class.new(Sequel::Model(:nodes)) do
567
+ def _refresh(ds); end
560
568
  unrestrict_primary_key
561
569
  attr_accessor :xxx
562
570
 
@@ -567,6 +575,7 @@ describe Sequel::Model, "one_to_many" do
567
575
  @dataset = @c2.dataset
568
576
 
569
577
  @c2.dataset.extend(Module.new {
578
+ def empty?; false; end
570
579
  def fetch_rows(sql)
571
580
  @db << sql
572
581
  yield Hash.new
@@ -574,6 +583,7 @@ describe Sequel::Model, "one_to_many" do
574
583
  })
575
584
 
576
585
  @c1.dataset.extend(Module.new {
586
+ def empty?; opts.has_key?(:empty) ? (super; true) : false; end
577
587
  def fetch_rows(sql)
578
588
  @db << sql
579
589
  yield Hash.new
@@ -632,6 +642,12 @@ describe Sequel::Model, "one_to_many" do
632
642
  @c2.load(:id => 1234, :x=>234).attributes_dataset.sql.should == 'SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (attributes.id = 234))'
633
643
  end
634
644
 
645
+ it "should not issue query if not all keys have values" do
646
+ @c2.one_to_many :attributes, :class => @c1, :key =>[:node_id, :id], :primary_key=>[:id, :x]
647
+ @c2.load(:id => 1234, :x=>nil).attributes.should == []
648
+ MODEL_DB.sqls.should == []
649
+ end
650
+
635
651
  it "should raise an Error unless same number of composite keys used" do
636
652
  proc{@c2.one_to_many :attributes, :class => @c1, :key=>[:node_id, :id]}.should raise_error(Sequel::Error)
637
653
  proc{@c2.one_to_many :attributes, :class => @c1, :primary_key=>[:node_id, :id]}.should raise_error(Sequel::Error)
@@ -640,7 +656,7 @@ describe Sequel::Model, "one_to_many" do
640
656
  proc{@c2.one_to_many :attributes, :class => @c1, :key=>[:node_id, :id, :x], :primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
641
657
  end
642
658
 
643
- it "should define an add_ method" do
659
+ it "should define an add_ method that works on existing records" do
644
660
  @c2.one_to_many :attributes, :class => @c1
645
661
 
646
662
  n = @c2.new(:id => 1234)
@@ -648,20 +664,77 @@ describe Sequel::Model, "one_to_many" do
648
664
  a.save
649
665
  MODEL_DB.reset
650
666
  a.should == n.add_attribute(a)
667
+ a.values.should == {:node_id => 1234, :id => 2345}
651
668
  MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = 1234 WHERE (id = 2345)']
652
669
  end
653
670
 
654
- it "should define a remove_ method" do
671
+ it "should define an add_ method that works on new records" do
655
672
  @c2.one_to_many :attributes, :class => @c1
656
673
 
657
674
  n = @c2.new(:id => 1234)
658
- a = @c1.new(:id => 2345)
675
+ a = @c1.new(:id => 234)
676
+ # do not save
677
+ MODEL_DB.reset
678
+ a.should == n.add_attribute(a)
679
+ MODEL_DB.sqls.first.should =~ /INSERT INTO attributes \((node_)?id, (node_)?id\) VALUES \(1?234, 1?234\)/
680
+ a.values.should == {:node_id => 1234, :id => 234}
681
+ end
682
+
683
+ it "should define a remove_ method that works on existing records" do
684
+ @c2.one_to_many :attributes, :class => @c1
685
+
686
+ n = @c2.new(:id => 1234)
687
+ a = @c1.new(:id => 2345, :node_id => 1234)
659
688
  a.save
660
689
  MODEL_DB.reset
661
690
  a.should == n.remove_attribute(a)
691
+ a.values.should == {:node_id => nil, :id => 2345}
662
692
  MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = NULL WHERE (id = 2345)']
663
693
  end
694
+
695
+ it "should have the remove_ method raise an error if the passed object is not already associated" do
696
+ @c2.one_to_many :attributes, :class => @c1
697
+ @c1.dataset.opts[:empty] = true
698
+
699
+ n = @c2.new(:id => 1234)
700
+ a = @c1.load(:id => 2345, :node_id => 1234)
701
+ MODEL_DB.reset
702
+ proc{n.remove_attribute(a)}.should raise_error(Sequel::Error)
703
+ MODEL_DB.sqls.should == ["SELECT 1 FROM attributes WHERE ((attributes.node_id = 1234) AND (id = 2345)) LIMIT 1"]
704
+ end
705
+
706
+ it "should accept a hash for the add_ method and create a new record" do
707
+ @c2.one_to_many :attributes, :class => @c1
708
+ n = @c2.new(:id => 1234)
709
+ MODEL_DB.reset
710
+ @c1.load(:node_id => 1234, :id => 234).should == n.add_attribute(:id => 234)
711
+ MODEL_DB.sqls.first.should =~ /INSERT INTO attributes \((node_)?id, (node_)?id\) VALUES \(1?234, 1?234\)/
712
+ end
713
+
714
+ it "should raise an error in the add_ method if the passed associated object is not of the correct type" do
715
+ @c2.one_to_many :attributes, :class => @c1
716
+ proc{@c2.new(:id => 1234).add_attribute(@c2.new)}.should raise_error(Sequel::Error)
717
+ end
718
+
719
+ it "should accept a primary key for the remove_ method and remove an existing record" do
720
+ @c2.one_to_many :attributes, :class => @c1
721
+ n = @c2.new(:id => 1234)
722
+ ds = @c1.dataset
723
+ def ds.fetch_rows(sql)
724
+ db << sql
725
+ yield({:id=>234, :node_id=>1234})
726
+ end
727
+ MODEL_DB.reset
728
+ @c1.load(:node_id => nil, :id => 234).should == n.remove_attribute(234)
729
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (id = 234)) LIMIT 1',
730
+ 'UPDATE attributes SET node_id = NULL WHERE (id = 234)']
731
+ end
664
732
 
733
+ it "should raise an error in the remove_ method if the passed associated object is not of the correct type" do
734
+ @c2.one_to_many :attributes, :class => @c1
735
+ proc{@c2.new(:id => 1234).remove_attribute(@c2.new)}.should raise_error(Sequel::Error)
736
+ end
737
+
665
738
  it "should have add_ method respect the :primary_key option" do
666
739
  @c2.one_to_many :attributes, :class => @c1, :primary_key=>:xxx
667
740
 
@@ -672,6 +745,21 @@ describe Sequel::Model, "one_to_many" do
672
745
  a.should == n.add_attribute(a)
673
746
  MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = 5 WHERE (id = 2345)']
674
747
  end
748
+
749
+ it "should have add_ method not add the same object to the cached association array if the object is already in the array" do
750
+ @c2.one_to_many :attributes, :class => @c1
751
+
752
+ n = @c2.new(:id => 1234)
753
+ a = @c1.new(:id => 2345)
754
+ a.save
755
+ MODEL_DB.reset
756
+ n.associations[:attributes] = []
757
+ a.should == n.add_attribute(a)
758
+ a.should == n.add_attribute(a)
759
+ a.values.should == {:node_id => 1234, :id => 2345}
760
+ MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = 1234 WHERE (id = 2345)'] * 2
761
+ n.attributes.should == [a]
762
+ end
675
763
 
676
764
  it "should have add_ method respect composite keys" do
677
765
  @c2.one_to_many :attributes, :class => @c1, :key =>[:node_id, :y], :primary_key=>[:id, :x]
@@ -691,6 +779,22 @@ describe Sequel::Model, "one_to_many" do
691
779
  MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id|y) = NULL, (node_id|y) = NULL WHERE \(id = 2345\)/
692
780
  end
693
781
 
782
+ it "should accept a array of composite primary key values for the remove_ method and remove an existing record" do
783
+ @c1.set_primary_key :id, :y
784
+ @c2.one_to_many :attributes, :class => @c1, :key=>:node_id, :primary_key=>:id
785
+ n = @c2.new(:id => 123)
786
+ ds = @c1.dataset
787
+ def ds.fetch_rows(sql)
788
+ db << sql
789
+ yield({:id=>234, :node_id=>123, :y=>5})
790
+ end
791
+ MODEL_DB.reset
792
+ @c1.load(:node_id => nil, :y => 5, :id => 234).should == n.remove_attribute([234, 5])
793
+ MODEL_DB.sqls.length.should == 2
794
+ MODEL_DB.sqls.first.should =~ /SELECT \* FROM attributes WHERE \(\(attributes.node_id = 123\) AND \(\((id|y) = (234|5)\) AND \((id|y) = (234|5)\)\)\) LIMIT 1/
795
+ MODEL_DB.sqls.last.should =~ /UPDATE attributes SET node_id = NULL WHERE \(\((id|y) = (234|5)\) AND \((id|y) = (234|5)\)\)/
796
+ end
797
+
694
798
  it "should raise an error in add_ and remove_ if the passed object returns false to save (is not valid)" do
695
799
  @c2.one_to_many :attributes, :class => @c1
696
800
  n = @c2.new(:id => 1234)
@@ -714,7 +818,6 @@ describe Sequel::Model, "one_to_many" do
714
818
  a = @c2.new
715
819
  n = @c1.load(:id=>123)
716
820
  proc{a.attributes_dataset}.should raise_error(Sequel::Error)
717
- proc{a.attributes}.should raise_error(Sequel::Error)
718
821
  proc{a.add_attribute(n)}.should raise_error(Sequel::Error)
719
822
  proc{a.remove_attribute(n)}.should raise_error(Sequel::Error)
720
823
  proc{a.remove_all_attributes}.should raise_error(Sequel::Error)
@@ -1056,6 +1159,7 @@ describe Sequel::Model, "one_to_many" do
1056
1159
  @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true
1057
1160
  attrib = @c1.new(:id=>3)
1058
1161
  d = @c1.dataset
1162
+ @c1.class_eval{remove_method :_refresh}
1059
1163
  def d.fetch_rows(s); yield({:id=>3}) end
1060
1164
  @c2.new(:id => 1234).attribute = attrib
1061
1165
  ['INSERT INTO attributes (node_id, id) VALUES (1234, 3)',
@@ -1086,6 +1190,7 @@ describe Sequel::Model, "one_to_many" do
1086
1190
  @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true, :primary_key=>:xxx
1087
1191
  attrib = @c1.new(:id=>3)
1088
1192
  d = @c1.dataset
1193
+ @c1.class_eval{remove_method :_refresh}
1089
1194
  def d.fetch_rows(s); yield({:id=>3}) end
1090
1195
  @c2.new(:id => 1234, :xxx=>5).attribute = attrib
1091
1196
  ['INSERT INTO attributes (node_id, id) VALUES (5, 3)',
@@ -1410,6 +1515,12 @@ describe Sequel::Model, "many_to_many" do
1410
1515
  @c2.load(:id => 1234, :x=>5).attributes_dataset.sql.should == 'SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.r1 = attributes.id) AND (attributes_nodes.r2 = attributes.y) AND (attributes_nodes.l1 = 1234) AND (attributes_nodes.l2 = 5))'
1411
1516
  end
1412
1517
 
1518
+ it "should not issue query if not all keys have values" do
1519
+ @c2.many_to_many :attributes, :class => @c1, :left_key=>[:l1, :l2], :right_key=>[:r1, :r2], :left_primary_key=>[:id, :x], :right_primary_key=>[:id, :y]
1520
+ @c2.load(:id => 1234, :x=>nil).attributes.should == []
1521
+ MODEL_DB.sqls.should == []
1522
+ end
1523
+
1413
1524
  it "should raise an Error unless same number of composite keys used" do
1414
1525
  proc{@c2.many_to_many :attributes, :class => @c1, :left_key=>[:node_id, :id]}.should raise_error(Sequel::Error)
1415
1526
  proc{@c2.many_to_many :attributes, :class => @c1, :left_primary_key=>[:node_id, :id]}.should raise_error(Sequel::Error)
@@ -1504,7 +1615,7 @@ describe Sequel::Model, "many_to_many" do
1504
1615
  @c2.new(:id => 1234).attributes_dataset.opts[:eager].should == {:attributes=>nil}
1505
1616
  end
1506
1617
 
1507
- it "should define an add_ method" do
1618
+ it "should define an add_ method that works on existing records" do
1508
1619
  @c2.many_to_many :attributes, :class => @c1
1509
1620
 
1510
1621
  n = @c2.load(:id => 1234)
@@ -1515,7 +1626,18 @@ describe Sequel::Model, "many_to_many" do
1515
1626
  ].should(include(MODEL_DB.sqls.first))
1516
1627
  end
1517
1628
 
1518
- it "should define a remove_ method" do
1629
+ it "should allow passing a hash to the add_ method which creates a new record" do
1630
+ @c2.many_to_many :attributes, :class => @c1
1631
+
1632
+ n = @c2.load(:id => 1234)
1633
+ @c1.load(:id => 1).should == n.add_attribute(:id => 1)
1634
+ MODEL_DB.sqls.first.should == 'INSERT INTO attributes (id) VALUES (1)'
1635
+ ['INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (1234, 1)',
1636
+ 'INSERT INTO attributes_nodes (attribute_id, node_id) VALUES (1, 1234)'
1637
+ ].should(include(MODEL_DB.sqls.last))
1638
+ end
1639
+
1640
+ it "should define a remove_ method that works on existing records" do
1519
1641
  @c2.many_to_many :attributes, :class => @c1
1520
1642
 
1521
1643
  n = @c2.new(:id => 1234)
@@ -1524,6 +1646,30 @@ describe Sequel::Model, "many_to_many" do
1524
1646
  MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 2345))'
1525
1647
  end
1526
1648
 
1649
+ it "should raise an error in the add_ method if the passed associated object is not of the correct type" do
1650
+ @c2.many_to_many :attributes, :class => @c1
1651
+ proc{@c2.new(:id => 1234).add_attribute(@c2.new)}.should raise_error(Sequel::Error)
1652
+ end
1653
+
1654
+ it "should accept a primary key for the remove_ method and remove an existing record" do
1655
+ @c2.many_to_many :attributes, :class => @c1
1656
+ n = @c2.new(:id => 1234)
1657
+ ds = @c1.dataset
1658
+ def ds.fetch_rows(sql)
1659
+ db << sql
1660
+ yield({:id=>234})
1661
+ end
1662
+ MODEL_DB.reset
1663
+ @c1.load(:id => 234).should == n.remove_attribute(234)
1664
+ MODEL_DB.sqls.should == ['SELECT * FROM attributes WHERE (id = 234) LIMIT 1',
1665
+ 'DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 234))']
1666
+ end
1667
+
1668
+ it "should raise an error in the remove_ method if the passed associated object is not of the correct type" do
1669
+ @c2.many_to_many :attributes, :class => @c1
1670
+ proc{@c2.new(:id => 1234).remove_attribute(@c2.new)}.should raise_error(Sequel::Error)
1671
+ end
1672
+
1527
1673
  it "should have the add_ method respect the :left_primary_key and :right_primary_key options" do
1528
1674
  @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx, :right_primary_key=>:yyy
1529
1675
 
@@ -1535,6 +1681,17 @@ describe Sequel::Model, "many_to_many" do
1535
1681
  ].should(include(MODEL_DB.sqls.first))
1536
1682
  end
1537
1683
 
1684
+ it "should have add_ method not add the same object to the cached association array if the object is already in the array" do
1685
+ @c2.many_to_many :attributes, :class => @c1
1686
+
1687
+ n = @c2.load(:id => 1234).set(:xxx=>5)
1688
+ a = @c1.load(:id => 2345).set(:yyy=>8)
1689
+ n.associations[:attributes] = []
1690
+ a.should == n.add_attribute(a)
1691
+ a.should == n.add_attribute(a)
1692
+ n.attributes.should == [a]
1693
+ end
1694
+
1538
1695
  it "should have the add_ method respect composite keys" do
1539
1696
  @c2.many_to_many :attributes, :class => @c1, :left_key=>[:l1, :l2], :right_key=>[:r1, :r2], :left_primary_key=>[:id, :x], :right_primary_key=>[:id, :y]
1540
1697
  n = @c2.load(:id => 1234, :x=>5)
@@ -1572,12 +1729,27 @@ describe Sequel::Model, "many_to_many" do
1572
1729
  MODEL_DB.sqls.should == ["DELETE FROM attributes_nodes WHERE ((l1 = 1234) AND (l2 = 5) AND (r1 = 2345) AND (r2 = 8))"]
1573
1730
  end
1574
1731
 
1732
+ it "should accept a array of composite primary key values for the remove_ method and remove an existing record" do
1733
+ @c1.set_primary_key [:id, :y]
1734
+ @c2.many_to_many :attributes, :class => @c1
1735
+ n = @c2.new(:id => 1234)
1736
+ ds = @c1.dataset
1737
+ def ds.fetch_rows(sql)
1738
+ db << sql
1739
+ yield({:id=>234, :y=>8})
1740
+ end
1741
+ MODEL_DB.reset
1742
+ @c1.load(:id => 234, :y=>8).should == n.remove_attribute([234, 8])
1743
+ MODEL_DB.sqls.length.should == 2
1744
+ MODEL_DB.sqls.first.should =~ /SELECT \* FROM attributes WHERE \(\((id|y) = (234|8)\) AND \((id|y) = (234|8)\)\) LIMIT 1/
1745
+ MODEL_DB.sqls.last.should == 'DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 234))'
1746
+ end
1747
+
1575
1748
  it "should raise an error if the model object doesn't have a valid primary key" do
1576
1749
  @c2.many_to_many :attributes, :class => @c1
1577
1750
  a = @c2.new
1578
1751
  n = @c1.load(:id=>123)
1579
1752
  proc{a.attributes_dataset}.should raise_error(Sequel::Error)
1580
- proc{a.attributes}.should raise_error(Sequel::Error)
1581
1753
  proc{a.add_attribute(n)}.should raise_error(Sequel::Error)
1582
1754
  proc{a.remove_attribute(n)}.should raise_error(Sequel::Error)
1583
1755
  proc{a.remove_all_attributes}.should raise_error(Sequel::Error)
@@ -1957,7 +2129,6 @@ describe Sequel::Model, "many_to_many" do
1957
2129
  end
1958
2130
 
1959
2131
  it "should support a :uniq option that removes duplicates from the association" do
1960
- h = []
1961
2132
  @c2.many_to_many :attributes, :class => @c1, :uniq=>true
1962
2133
  @c1.class_eval do
1963
2134
  def @dataset.fetch_rows(sql)
@@ -1969,6 +2140,11 @@ describe Sequel::Model, "many_to_many" do
1969
2140
  end
1970
2141
  @c2.load(:id=>10, :parent_id=>20).attributes.should == [@c1.load(:id=>20), @c1.load(:id=>30)]
1971
2142
  end
2143
+
2144
+ it "should support a :distinct option that uses the DISTINCT clause" do
2145
+ @c2.many_to_many :attributes, :class => @c1, :distinct=>true
2146
+ @c2.load(:id=>10).attributes_dataset.sql.should == "SELECT DISTINCT attributes.* FROM attributes INNER JOIN attributes_nodes ON ((attributes_nodes.attribute_id = attributes.id) AND (attributes_nodes.node_id = 10))"
2147
+ end
1972
2148
  end
1973
2149
 
1974
2150
  describe Sequel::Model, " association reflection methods" do