sequel 3.5.0 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
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