sequel 3.4.0 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/CHANGELOG +84 -0
  2. data/Rakefile +1 -1
  3. data/doc/cheat_sheet.rdoc +5 -2
  4. data/doc/opening_databases.rdoc +2 -0
  5. data/doc/release_notes/3.5.0.txt +510 -0
  6. data/lib/sequel/adapters/ado.rb +3 -1
  7. data/lib/sequel/adapters/ado/mssql.rb +2 -2
  8. data/lib/sequel/adapters/do.rb +2 -11
  9. data/lib/sequel/adapters/do/mysql.rb +7 -0
  10. data/lib/sequel/adapters/do/postgres.rb +2 -2
  11. data/lib/sequel/adapters/firebird.rb +3 -3
  12. data/lib/sequel/adapters/informix.rb +3 -3
  13. data/lib/sequel/adapters/jdbc/h2.rb +3 -3
  14. data/lib/sequel/adapters/jdbc/mssql.rb +7 -0
  15. data/lib/sequel/adapters/mysql.rb +60 -21
  16. data/lib/sequel/adapters/odbc.rb +1 -1
  17. data/lib/sequel/adapters/openbase.rb +3 -3
  18. data/lib/sequel/adapters/oracle.rb +1 -5
  19. data/lib/sequel/adapters/postgres.rb +3 -3
  20. data/lib/sequel/adapters/shared/mssql.rb +142 -33
  21. data/lib/sequel/adapters/shared/mysql.rb +54 -31
  22. data/lib/sequel/adapters/shared/oracle.rb +17 -6
  23. data/lib/sequel/adapters/shared/postgres.rb +7 -7
  24. data/lib/sequel/adapters/shared/progress.rb +3 -3
  25. data/lib/sequel/adapters/shared/sqlite.rb +3 -17
  26. data/lib/sequel/connection_pool.rb +4 -6
  27. data/lib/sequel/core.rb +29 -113
  28. data/lib/sequel/database.rb +14 -12
  29. data/lib/sequel/dataset.rb +8 -21
  30. data/lib/sequel/dataset/convenience.rb +1 -1
  31. data/lib/sequel/dataset/graph.rb +9 -2
  32. data/lib/sequel/dataset/sql.rb +170 -104
  33. data/lib/sequel/exceptions.rb +3 -0
  34. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  35. data/lib/sequel/extensions/named_timezones.rb +61 -0
  36. data/lib/sequel/extensions/schema_dumper.rb +7 -1
  37. data/lib/sequel/extensions/sql_expr.rb +122 -0
  38. data/lib/sequel/extensions/string_date_time.rb +4 -4
  39. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  40. data/lib/sequel/model/associations.rb +105 -45
  41. data/lib/sequel/model/base.rb +37 -28
  42. data/lib/sequel/plugins/active_model.rb +35 -0
  43. data/lib/sequel/plugins/association_dependencies.rb +96 -0
  44. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  45. data/lib/sequel/plugins/force_encoding.rb +61 -0
  46. data/lib/sequel/plugins/many_through_many.rb +32 -11
  47. data/lib/sequel/plugins/nested_attributes.rb +7 -2
  48. data/lib/sequel/plugins/subclasses.rb +45 -0
  49. data/lib/sequel/plugins/touch.rb +118 -0
  50. data/lib/sequel/plugins/typecast_on_load.rb +61 -0
  51. data/lib/sequel/sql.rb +31 -30
  52. data/lib/sequel/timezones.rb +161 -0
  53. data/lib/sequel/version.rb +1 -1
  54. data/spec/adapters/mssql_spec.rb +262 -0
  55. data/spec/adapters/mysql_spec.rb +46 -8
  56. data/spec/adapters/postgres_spec.rb +6 -3
  57. data/spec/adapters/spec_helper.rb +21 -0
  58. data/spec/adapters/sqlite_spec.rb +1 -1
  59. data/spec/core/connection_pool_spec.rb +1 -1
  60. data/spec/core/database_spec.rb +27 -1
  61. data/spec/core/dataset_spec.rb +63 -1
  62. data/spec/core/object_graph_spec.rb +1 -1
  63. data/spec/core/schema_spec.rb +1 -0
  64. data/spec/extensions/active_model_spec.rb +47 -0
  65. data/spec/extensions/association_dependencies_spec.rb +108 -0
  66. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  67. data/spec/extensions/force_encoding_spec.rb +75 -0
  68. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  69. data/spec/extensions/many_through_many_spec.rb +60 -2
  70. data/spec/extensions/named_timezones_spec.rb +72 -0
  71. data/spec/extensions/nested_attributes_spec.rb +29 -1
  72. data/spec/extensions/schema_dumper_spec.rb +10 -0
  73. data/spec/extensions/spec_helper.rb +1 -1
  74. data/spec/extensions/sql_expr_spec.rb +89 -0
  75. data/spec/extensions/subclasses_spec.rb +52 -0
  76. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  77. data/spec/extensions/touch_spec.rb +155 -0
  78. data/spec/extensions/typecast_on_load_spec.rb +60 -0
  79. data/spec/integration/database_test.rb +8 -0
  80. data/spec/integration/dataset_test.rb +9 -9
  81. data/spec/integration/plugin_test.rb +139 -0
  82. data/spec/integration/schema_test.rb +7 -7
  83. data/spec/integration/spec_helper.rb +32 -1
  84. data/spec/integration/timezone_test.rb +3 -3
  85. data/spec/integration/transaction_test.rb +1 -1
  86. data/spec/integration/type_test.rb +6 -6
  87. data/spec/model/association_reflection_spec.rb +18 -0
  88. data/spec/model/associations_spec.rb +169 -9
  89. data/spec/model/base_spec.rb +2 -0
  90. data/spec/model/eager_loading_spec.rb +82 -2
  91. data/spec/model/model_spec.rb +8 -1
  92. data/spec/model/record_spec.rb +52 -9
  93. metadata +33 -23
@@ -56,7 +56,7 @@ describe "Database schema parser" do
56
56
  sqls_should_be
57
57
  end
58
58
 
59
- specify "should parse primary keys from the schema properly" do
59
+ cspecify "should parse primary keys from the schema properly", [proc{|db| db.class.adapter_scheme != :jdbc}, :mssql] do
60
60
  INTEGRATION_DB.create_table!(:items){Integer :number}
61
61
  INTEGRATION_DB.schema(:items).collect{|k,v| k if v[:primary_key]}.compact.should == []
62
62
  INTEGRATION_DB.create_table!(:items){primary_key :number}
@@ -81,7 +81,7 @@ describe "Database schema parser" do
81
81
  INTEGRATION_DB.schema(:items).first.last[:ruby_default].should == 'blah'
82
82
  end
83
83
 
84
- specify "should parse types from the schema properly" do
84
+ cspecify "should parse types from the schema properly", [:do, :mysql], [:jdbc, :mysql] do
85
85
  INTEGRATION_DB.create_table!(:items){Integer :number}
86
86
  INTEGRATION_DB.schema(:items).first.last[:type].should == :integer
87
87
  INTEGRATION_DB.create_table!(:items){Fixnum :number}
@@ -183,7 +183,7 @@ describe "Database schema modifiers" do
183
183
  @ds.all.should == [{:number=>10, :name=>nil}]
184
184
  end
185
185
 
186
- specify "should add primary key columns to tables correctly" do
186
+ cspecify "should add primary key columns to tables correctly", :h2 do
187
187
  @db.create_table!(:items){Integer :number}
188
188
  @ds.insert(:number=>10)
189
189
  @db.alter_table(:items){add_primary_key :id}
@@ -220,7 +220,7 @@ describe "Database schema modifiers" do
220
220
  @ds.all.should == [{:n2=>'blah'}, {:n2=>'blah'}]
221
221
  end
222
222
 
223
- specify "should rename columns with not null constraints" do
223
+ cspecify "should rename columns with not null constraints", [:mysql, :mysql] do
224
224
  @db.create_table!(:items){String :n, :null=>false}
225
225
  @ds.insert(:n=>'blah')
226
226
  @db.alter_table(:items){rename_column :n, :n2}
@@ -231,7 +231,7 @@ describe "Database schema modifiers" do
231
231
  proc{@ds.insert}.should raise_error(Sequel::DatabaseError)
232
232
  end
233
233
 
234
- specify "should set column NULL/NOT NULL correctly" do
234
+ cspecify "should set column NULL/NOT NULL correctly", [:mysql, :mysql] do
235
235
  @db.create_table!(:items){Integer :id}
236
236
  @ds.insert(:id=>10)
237
237
  @db.alter_table(:items){set_column_allow_null :id, false}
@@ -263,7 +263,7 @@ describe "Database schema modifiers" do
263
263
  @ds.all.should == [{:id=>"10"}, {:id=>"20"}]
264
264
  end
265
265
 
266
- specify "should add unique constraints and foreign key table constraints correctly" do
266
+ cspecify "should add unique constraints and foreign key table constraints correctly", :sqlite do
267
267
  @db.create_table!(:items){Integer :id; Integer :item_id}
268
268
  @db.alter_table(:items) do
269
269
  add_unique_constraint [:item_id, :id]
@@ -273,7 +273,7 @@ describe "Database schema modifiers" do
273
273
  @ds.columns!.should == [:id, :item_id]
274
274
  end
275
275
 
276
- specify "should remove columns from tables correctly" do
276
+ cspecify "should remove columns from tables correctly", :h2, :mssql do
277
277
  @db.create_table!(:items) do
278
278
  primary_key :id
279
279
  String :name
@@ -22,7 +22,38 @@ class Spec::Example::ExampleGroup
22
22
  INTEGRATION_DB.loggers << Logger.new(STDOUT)
23
23
  yield
24
24
  ensure
25
- INTEGRATION_DB.loggers.clear
25
+ INTEGRATION_DB.loggers.pop
26
+ end
27
+ end
28
+
29
+ def self.cspecify(message, *checked, &block)
30
+ pending = false
31
+ checked.each do |c|
32
+ case c
33
+ when INTEGRATION_DB.database_type
34
+ pending = c
35
+ when Array
36
+ case c.length
37
+ when 1
38
+ pending = c if c.first == INTEGRATION_DB.class.adapter_scheme
39
+ when 2
40
+ if c.first.is_a?(Proc)
41
+ pending = c if c.first.call(INTEGRATION_DB) && c.last == INTEGRATION_DB.database_type
42
+ elsif c.last.is_a?(Proc)
43
+ pending = c if c.first == INTEGRATION_DB.class.adapter_scheme && c.last.call(INTEGRATION_DB)
44
+ else
45
+ pending = c if c.first == INTEGRATION_DB.class.adapter_scheme && c.last == INTEGRATION_DB.database_type
46
+ end
47
+ when 3
48
+ pending = c if c[0] == INTEGRATION_DB.class.adapter_scheme && c[1] == INTEGRATION_DB.database_type && c[2].call(INTEGRATION_DB)
49
+ end
50
+ end
51
+ break if pending
52
+ end
53
+ if pending
54
+ specify(message){pending("Not yet working on #{Array(pending).join(', ')}", &block)}
55
+ else
56
+ specify(message, &block)
26
57
  end
27
58
  end
28
59
  end
@@ -31,19 +31,19 @@ describe "Sequel timezone support" do
31
31
  Sequel.datetime_class = Time
32
32
  end
33
33
 
34
- specify "should support using UTC for database storage and local time for the application" do
34
+ cspecify "should support using UTC for database storage and local time for the application", [:do, proc{|db| db.database_type != :sqlite}] do
35
35
  Sequel.database_timezone = :utc
36
36
  Sequel.application_timezone = :local
37
37
  test_timezone
38
38
  end
39
39
 
40
- specify "should support using local time for database storage and UTC for the application" do
40
+ cspecify "should support using local time for database storage and UTC for the application", [:do, proc{|db| db.database_type != :sqlite}] do
41
41
  Sequel.database_timezone = :local
42
42
  Sequel.application_timezone = :utc
43
43
  test_timezone
44
44
  end
45
45
 
46
- specify "should support using UTC for both database storage and for application" do
46
+ cspecify "should support using UTC for both database storage and for application", [:do, proc{|db| db.database_type != :sqlite}] do
47
47
  Sequel.default_timezone = :utc
48
48
  test_timezone
49
49
  end
@@ -71,7 +71,7 @@ describe "Database transactions" do
71
71
  end
72
72
 
73
73
  if INTEGRATION_DB.supports_savepoints?
74
- specify "should support nested transactions through savepoints using the savepoint option" do
74
+ cspecify "should support nested transactions through savepoints using the savepoint option", [:jdbc, :sqlite] do
75
75
  @db = INTEGRATION_DB
76
76
  @db.transaction do
77
77
  @d << {:name => '1'}
@@ -6,7 +6,7 @@ describe "Supported types" do
6
6
  INTEGRATION_DB[:items]
7
7
  end
8
8
 
9
- specify "should support casting correctly" do
9
+ cspecify "should support casting correctly", [:sqlite, :sqlite] do
10
10
  ds = create_items_table_with_column(:number, Integer)
11
11
  ds.insert(:number => 1)
12
12
  ds.select(:number.cast_string.as(:n)).map(:n).should == %w'1'
@@ -45,7 +45,7 @@ describe "Supported types" do
45
45
  ds.all.should == [{:number=>2.1}]
46
46
  end
47
47
 
48
- specify "should support generic numeric type" do
48
+ cspecify "should support generic numeric type", [:odbc, :mssql] do
49
49
  ds = create_items_table_with_column(:number, Numeric, :size=>[15, 10])
50
50
  ds.insert(:number => BigDecimal.new('2.123456789'))
51
51
  ds.all.should == [{:number=>BigDecimal.new('2.123456789')}]
@@ -60,14 +60,14 @@ describe "Supported types" do
60
60
  ds.all.should == [{:name=>'Test User'}]
61
61
  end
62
62
 
63
- specify "should support generic date type" do
63
+ cspecify "should support generic date type", [:do, :sqlite], [:jdbc, :sqlite], :mssql do
64
64
  ds = create_items_table_with_column(:dat, Date)
65
65
  d = Date.today
66
66
  ds.insert(:dat => d)
67
67
  ds.first[:dat].should == d
68
68
  end
69
69
 
70
- specify "should support generic datetime type" do
70
+ cspecify "should support generic datetime type", [:do, :sqlite], [:jdbc, :sqlite] do
71
71
  ds = create_items_table_with_column(:tim, DateTime)
72
72
  t = DateTime.now
73
73
  ds.insert(:tim => t)
@@ -78,14 +78,14 @@ describe "Supported types" do
78
78
  ds.first[:tim].strftime('%Y%m%d%H%M%S').should == t.strftime('%Y%m%d%H%M%S')
79
79
  end
80
80
 
81
- specify "should support generic file type" do
81
+ cspecify "should support generic file type", [:do], :h2, [:odbc, :mssql] do
82
82
  ds = create_items_table_with_column(:name, File)
83
83
  ds.insert(:name => ("a\0"*300).to_sequel_blob)
84
84
  ds.all.should == [{:name=>("a\0"*300).to_sequel_blob}]
85
85
  ds.first[:name].should be_a_kind_of(::Sequel::SQL::Blob)
86
86
  end
87
87
 
88
- specify "should support generic boolean type" do
88
+ cspecify "should support generic boolean type", [:do, :sqlite], [:jdbc, :sqlite], [:odbc, :mssql] do
89
89
  ds = create_items_table_with_column(:number, TrueClass)
90
90
  ds.insert(:number => true)
91
91
  ds.all.should == [{:number=>true}]
@@ -73,6 +73,24 @@ describe Sequel::Model::Associations::AssociationReflection, "#reciprocal" do
73
73
  ParParentTwo.association_reflection(:par_parents).reciprocal.should == :par_parent_twos
74
74
  ParParentThree.association_reflection(:par_parents).reciprocal.should == :par_parent_threes
75
75
  end
76
+
77
+ it "should handle composite keys" do
78
+ ParParent.many_to_one :par_parent_two, :key=>[:a, :b], :primary_key=>[:c, :b]
79
+ ParParent.many_to_one :par_parent_three, :key=>[:d, :e], :primary_key=>[:c, :b]
80
+ ParParentTwo.one_to_many :par_parents, :primary_key=>[:c, :b], :key=>[:a, :b]
81
+ ParParentThree.one_to_many :par_parents, :primary_key=>[:c, :b], :key=>[:d, :e]
82
+
83
+ ParParentTwo.association_reflection(:par_parents).reciprocal.should == :par_parent_two
84
+ ParParentThree.association_reflection(:par_parents).reciprocal.should == :par_parent_three
85
+
86
+ ParParent.many_to_many :par_parent_twos, :left_key=>[:l1, :l2], :right_key=>[:r1, :r2], :left_primary_key=>[:pl1, :pl2], :right_primary_key=>[:pr1, :pr2], :join_table=>:jt
87
+ ParParent.many_to_many :par_parent_threes, :right_key=>[:l1, :l2], :left_key=>[:r1, :r2], :left_primary_key=>[:pl1, :pl2], :right_primary_key=>[:pr1, :pr2], :join_table=>:jt
88
+ ParParentTwo.many_to_many :par_parents, :right_key=>[:l1, :l2], :left_key=>[:r1, :r2], :right_primary_key=>[:pl1, :pl2], :left_primary_key=>[:pr1, :pr2], :join_table=>:jt
89
+ ParParentThree.many_to_many :par_parents, :left_key=>[:l1, :l2], :right_key=>[:r1, :r2], :right_primary_key=>[:pl1, :pl2], :left_primary_key=>[:pr1, :pr2], :join_table=>:jt
90
+
91
+ ParParentTwo.association_reflection(:par_parents).reciprocal.should == :par_parent_twos
92
+ ParParentThree.association_reflection(:par_parents).reciprocal.should == :par_parent_threes
93
+ end
76
94
 
77
95
  it "should figure out the reciprocal if the :reciprocal value is not present" do
78
96
  ParParent.many_to_one :par_parent_two
@@ -186,6 +186,19 @@ describe Sequel::Model, "many_to_one" do
186
186
  @c2.new(:id => 1, :blah => 567).parent
187
187
  MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE (nodes.pk = 567) LIMIT 1"]
188
188
  end
189
+
190
+ it "should support composite keys" do
191
+ @c2.many_to_one :parent, :class => @c2, :key=>[:id, :parent_id], :primary_key=>[:parent_id, :id]
192
+ @c2.new(:id => 1, :parent_id => 234).parent
193
+ MODEL_DB.sqls.should == ["SELECT * FROM nodes WHERE ((nodes.parent_id = 1) AND (nodes.id = 234)) LIMIT 1"]
194
+ end
195
+
196
+ it "should raise an Error unless same number of composite keys used" do
197
+ proc{@c2.many_to_one :parent, :class => @c2, :primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
198
+ proc{@c2.many_to_one :parent, :class => @c2, :key=>[:id, :parent_id], :primary_key=>:id}.should raise_error(Sequel::Error)
199
+ proc{@c2.many_to_one :parent, :class => @c2, :key=>:id, :primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
200
+ proc{@c2.many_to_one :parent, :class => @c2, :key=>[:id, :parent_id, :blah], :primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
201
+ end
189
202
 
190
203
  it "should use :select option if given" do
191
204
  @c2.many_to_one :parent, :class => @c2, :key => :blah, :select=>[:id, :name]
@@ -265,6 +278,21 @@ describe Sequel::Model, "many_to_one" do
265
278
  d.values.should == {:id => 1, :parent_id => 8}
266
279
  end
267
280
 
281
+ it "should have the setter method respect composite keys" do
282
+ @c2.many_to_one :parent, :class => @c2, :key=>[:id, :parent_id], :primary_key=>[:parent_id, :id]
283
+
284
+ d = @c2.new(:id => 1, :parent_id=> 234)
285
+ d.parent = @c2.new(:id => 4, :parent_id=>52)
286
+ d.values.should == {:id => 52, :parent_id => 4}
287
+
288
+ d.parent = nil
289
+ d.values.should == {:id => nil, :parent_id => nil}
290
+
291
+ e = @c2.new(:id => 6677, :parent_id=>8)
292
+ d.parent = e
293
+ d.values.should == {:id => 8, :parent_id => 6677}
294
+ end
295
+
268
296
  it "should not persist changes until saved" do
269
297
  @c2.many_to_one :parent, :class => @c2
270
298
 
@@ -520,13 +548,12 @@ describe Sequel::Model, "many_to_one" do
520
548
  end
521
549
 
522
550
  describe Sequel::Model, "one_to_many" do
523
-
524
- before(:each) do
551
+ before do
525
552
  MODEL_DB.reset
526
553
 
527
554
  @c1 = Class.new(Sequel::Model(:attributes)) do
528
555
  unrestrict_primary_key
529
- columns :id, :node_id
556
+ columns :id, :node_id, :y
530
557
  end
531
558
 
532
559
  @c2 = Class.new(Sequel::Model(:nodes)) do
@@ -535,7 +562,7 @@ describe Sequel::Model, "one_to_many" do
535
562
 
536
563
  def self.name; 'Node'; end
537
564
  def self.to_s; 'Node'; end
538
- columns :id
565
+ columns :id, :x
539
566
  end
540
567
  @dataset = @c2.dataset
541
568
 
@@ -599,6 +626,19 @@ describe Sequel::Model, "one_to_many" do
599
626
  a.should be_a_kind_of(Sequel::Dataset)
600
627
  a.sql.should == 'SELECT * FROM attributes WHERE (attributes.nodeid = 1234)'
601
628
  end
629
+
630
+ it "should support_composite keys" do
631
+ @c2.one_to_many :attributes, :class => @c1, :key =>[:node_id, :id], :primary_key=>[:id, :x]
632
+ @c2.load(:id => 1234, :x=>234).attributes_dataset.sql.should == 'SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (attributes.id = 234))'
633
+ end
634
+
635
+ it "should raise an Error unless same number of composite keys used" do
636
+ proc{@c2.one_to_many :attributes, :class => @c1, :key=>[:node_id, :id]}.should raise_error(Sequel::Error)
637
+ proc{@c2.one_to_many :attributes, :class => @c1, :primary_key=>[:node_id, :id]}.should raise_error(Sequel::Error)
638
+ proc{@c2.one_to_many :attributes, :class => @c1, :key=>[:node_id, :id], :primary_key=>:id}.should raise_error(Sequel::Error)
639
+ proc{@c2.one_to_many :attributes, :class => @c1, :key=>:id, :primary_key=>[:node_id, :id]}.should raise_error(Sequel::Error)
640
+ proc{@c2.one_to_many :attributes, :class => @c1, :key=>[:node_id, :id, :x], :primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
641
+ end
602
642
 
603
643
  it "should define an add_ method" do
604
644
  @c2.one_to_many :attributes, :class => @c1
@@ -633,6 +673,23 @@ describe Sequel::Model, "one_to_many" do
633
673
  MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = 5 WHERE (id = 2345)']
634
674
  end
635
675
 
676
+ it "should have add_ method respect composite keys" do
677
+ @c2.one_to_many :attributes, :class => @c1, :key =>[:node_id, :y], :primary_key=>[:id, :x]
678
+
679
+ n = @c2.load(:id => 1234, :x=>5)
680
+ a = @c1.load(:id => 2345)
681
+ a.should == n.add_attribute(a)
682
+ MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id = 1234|y = 5), (node_id = 1234|y = 5) WHERE \(id = 2345\)/
683
+ end
684
+
685
+ it "should have remove_ method respect composite keys" do
686
+ @c2.one_to_many :attributes, :class => @c1, :key =>[:node_id, :y], :primary_key=>[:id, :x]
687
+
688
+ n = @c2.load(:id => 1234, :x=>5)
689
+ a = @c1.load(:id => 2345, :node_id=>1234, :y=>5)
690
+ a.should == n.remove_attribute(a)
691
+ MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id|y) = NULL, (node_id|y) = NULL WHERE \(id = 2345\)/
692
+ end
636
693
 
637
694
  it "should raise an error in add_ and remove_ if the passed object returns false to save (is not valid)" do
638
695
  @c2.one_to_many :attributes, :class => @c1
@@ -643,6 +700,15 @@ describe Sequel::Model, "one_to_many" do
643
700
  proc{n.remove_attribute(a)}.should raise_error(Sequel::Error)
644
701
  end
645
702
 
703
+ it "should not validate the associated object in add_ and remove_ if the :validate=>false option is used" do
704
+ @c2.one_to_many :attributes, :class => @c1, :validate=>false
705
+ n = @c2.new(:id => 1234)
706
+ a = @c1.new(:id => 2345)
707
+ def a.valid?; false; end
708
+ n.add_attribute(a).should == a
709
+ n.remove_attribute(a).should == a
710
+ end
711
+
646
712
  it "should raise an error if the model object doesn't have a valid primary key" do
647
713
  @c2.one_to_many :attributes, :class => @c1
648
714
  a = @c2.new
@@ -914,6 +980,12 @@ describe Sequel::Model, "one_to_many" do
914
980
  @c2.new(:id => 1234, :xxx=>5).remove_all_attributes
915
981
  MODEL_DB.sqls.first.should == 'UPDATE attributes SET node_id = NULL WHERE (node_id = 5)'
916
982
  end
983
+
984
+ it "should have the remove_all_ method respect composite keys" do
985
+ @c2.one_to_many :attributes, :class => @c1, :key=>[:node_id, :y], :primary_key=>[:id, :x]
986
+ @c2.new(:id => 1234, :x=>5).remove_all_attributes
987
+ MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id|y) = NULL, (node_id|y) = NULL WHERE \(\(node_id = 1234\) AND \(y = 5\)\)/
988
+ end
917
989
 
918
990
  it "remove_all should set the cached instance variable to []" do
919
991
  @c2.one_to_many :attributes, :class => @c1
@@ -929,7 +1001,7 @@ describe Sequel::Model, "one_to_many" do
929
1001
  d = @c1.dataset
930
1002
  def d.fetch_rows(s); end
931
1003
  node.attributes.should == []
932
- def attrib.save; self end
1004
+ def attrib.save(*); self end
933
1005
  node.add_attribute(attrib)
934
1006
  node.associations[:attributes].should == [attrib]
935
1007
  node.remove_all_attributes.should == [attrib]
@@ -951,7 +1023,7 @@ describe Sequel::Model, "one_to_many" do
951
1023
  node = @c2.new(:id => 1234)
952
1024
  node.attributes.should == []
953
1025
  attrib.node.should == nil
954
- def attrib.save; self end
1026
+ def attrib.save(*); self end
955
1027
  node.add_attribute(attrib)
956
1028
  attrib.associations[:node].should == node
957
1029
  node.remove_all_attributes
@@ -1026,6 +1098,16 @@ describe Sequel::Model, "one_to_many" do
1026
1098
  @c2.new(:id => 621, :xxx=>5).attribute = attrib
1027
1099
  MODEL_DB.sqls.should == ['UPDATE attributes SET node_id = 5 WHERE (id = 3)',
1028
1100
  'UPDATE attributes SET node_id = NULL WHERE ((node_id = 5) AND (id != 3))']
1101
+ end
1102
+
1103
+ it "should have the setter method for the :one_to_one option respect composite keys" do
1104
+ @c2.one_to_many :attributes, :class => @c1, :one_to_one=>true, :key=>[:node_id, :y], :primary_key=>[:id, :x]
1105
+ attrib = @c1.load(:id=>3, :y=>6)
1106
+ d = @c1.dataset
1107
+ def d.fetch_rows(s); yield({:id=>3, :y=>6}) end
1108
+ @c2.load(:id => 1234, :x=>5).attribute = attrib
1109
+ MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id = 1234|y = 5), (node_id = 1234|y = 5) WHERE \(id = 3\)/
1110
+ MODEL_DB.sqls.last.should =~ /UPDATE attributes SET (node_id|y) = NULL, (node_id|y) = NULL WHERE \(\(\(node_id = 1234\) AND \(y = 5\)\) AND \(id != 3\)\)/
1029
1111
  end
1030
1112
 
1031
1113
  it "should raise an error if the one_to_one getter would be the same as the association name" do
@@ -1215,7 +1297,7 @@ describe Sequel::Model, "many_to_many" do
1215
1297
  attr_accessor :yyy
1216
1298
  def self.name; 'Attribute'; end
1217
1299
  def self.to_s; 'Attribute'; end
1218
- columns :id
1300
+ columns :id, :y
1219
1301
  def _refresh(ds)
1220
1302
  self.id = 1
1221
1303
  self
@@ -1228,7 +1310,7 @@ describe Sequel::Model, "many_to_many" do
1228
1310
 
1229
1311
  def self.name; 'Node'; end
1230
1312
  def self.to_s; 'Node'; end
1231
- columns :id
1313
+ columns :id, :x
1232
1314
  end
1233
1315
  @dataset = @c2.dataset
1234
1316
 
@@ -1323,6 +1405,24 @@ describe Sequel::Model, "many_to_many" do
1323
1405
  @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))'
1324
1406
  end
1325
1407
 
1408
+ it "should support composite keys" do
1409
+ @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]
1410
+ @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
+ end
1412
+
1413
+ it "should raise an Error unless same number of composite keys used" do
1414
+ proc{@c2.many_to_many :attributes, :class => @c1, :left_key=>[:node_id, :id]}.should raise_error(Sequel::Error)
1415
+ proc{@c2.many_to_many :attributes, :class => @c1, :left_primary_key=>[:node_id, :id]}.should raise_error(Sequel::Error)
1416
+ proc{@c2.many_to_many :attributes, :class => @c1, :left_key=>[:node_id, :id], :left_primary_key=>:id}.should raise_error(Sequel::Error)
1417
+ proc{@c2.many_to_many :attributes, :class => @c1, :left_key=>:id, :left_primary_key=>[:node_id, :id]}.should raise_error(Sequel::Error)
1418
+ proc{@c2.many_to_many :attributes, :class => @c1, :left_key=>[:node_id, :id, :x], :left_primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
1419
+
1420
+ proc{@c2.many_to_many :attributes, :class => @c1, :right_primary_key=>[:node_id, :id]}.should raise_error(Sequel::Error)
1421
+ proc{@c2.many_to_many :attributes, :class => @c1, :right_key=>[:node_id, :id], :right_primary_key=>:id}.should raise_error(Sequel::Error)
1422
+ proc{@c2.many_to_many :attributes, :class => @c1, :right_key=>:id, :left_primary_key=>[:node_id, :id]}.should raise_error(Sequel::Error)
1423
+ proc{@c2.many_to_many :attributes, :class => @c1, :right_key=>[:node_id, :id, :x], :right_primary_key=>[:parent_id, :id]}.should raise_error(Sequel::Error)
1424
+ end
1425
+
1326
1426
  it "should support a select option" do
1327
1427
  @c2.many_to_many :attributes, :class => @c1, :select => :blah
1328
1428
 
@@ -1434,6 +1534,26 @@ describe Sequel::Model, "many_to_many" do
1434
1534
  'INSERT INTO attributes_nodes (attribute_id, node_id) VALUES (8, 5)'
1435
1535
  ].should(include(MODEL_DB.sqls.first))
1436
1536
  end
1537
+
1538
+ it "should have the add_ method respect composite keys" do
1539
+ @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
+ n = @c2.load(:id => 1234, :x=>5)
1541
+ a = @c1.load(:id => 2345, :y=>8)
1542
+ a.should == n.add_attribute(a)
1543
+ m = /INSERT INTO attributes_nodes \((\w+), (\w+), (\w+), (\w+)\) VALUES \((\d+), (\d+), (\d+), (\d+)\)/.match(MODEL_DB.sqls.first)
1544
+ m.should_not == nil
1545
+ map = {'l1'=>1234, 'l2'=>5, 'r1'=>2345, 'r2'=>8}
1546
+ %w[l1 l2 r1 r2].each do |x|
1547
+ v = false
1548
+ 4.times do |i| i += 1
1549
+ if m[i] == x
1550
+ m[i+4].should == map[x].to_s
1551
+ v = true
1552
+ end
1553
+ end
1554
+ v.should == true
1555
+ end
1556
+ end
1437
1557
 
1438
1558
  it "should have the remove_ method respect the :left_primary_key and :right_primary_key options" do
1439
1559
  @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx, :right_primary_key=>:yyy
@@ -1443,6 +1563,14 @@ describe Sequel::Model, "many_to_many" do
1443
1563
  a.should == n.remove_attribute(a)
1444
1564
  MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE ((node_id = 5) AND (attribute_id = 8))'
1445
1565
  end
1566
+
1567
+ it "should have the remove_ method respect composite keys" do
1568
+ @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]
1569
+ n = @c2.load(:id => 1234, :x=>5)
1570
+ a = @c1.load(:id => 2345, :y=>8)
1571
+ a.should == n.remove_attribute(a)
1572
+ MODEL_DB.sqls.should == ["DELETE FROM attributes_nodes WHERE ((l1 = 1234) AND (l2 = 5) AND (r1 = 2345) AND (r2 = 8))"]
1573
+ end
1446
1574
 
1447
1575
  it "should raise an error if the model object doesn't have a valid primary key" do
1448
1576
  @c2.many_to_many :attributes, :class => @c1
@@ -1455,7 +1583,7 @@ describe Sequel::Model, "many_to_many" do
1455
1583
  proc{a.remove_all_attributes}.should raise_error(Sequel::Error)
1456
1584
  end
1457
1585
 
1458
- it "should save the associated object first if passed a new model object" do
1586
+ it "should save the associated object first in add_ if passed a new model object" do
1459
1587
  @c2.many_to_many :attributes, :class => @c1
1460
1588
  n = @c1.new
1461
1589
  a = @c2.load(:id=>123)
@@ -1464,6 +1592,32 @@ describe Sequel::Model, "many_to_many" do
1464
1592
  n.new?.should == false
1465
1593
  end
1466
1594
 
1595
+ it "should raise a ValidationFailed in add_ if the associated object is new and invalid" do
1596
+ @c2.many_to_many :attributes, :class => @c1
1597
+ n = @c1.new
1598
+ a = @c2.load(:id=>123)
1599
+ def n.valid?; false; end
1600
+ proc{a.add_attribute(n)}.should raise_error(Sequel::ValidationFailed)
1601
+ end
1602
+
1603
+ it "should raise an Error in add_ if the associated object is new and invalid and raise_on_save_failure is false" do
1604
+ @c2.many_to_many :attributes, :class => @c1
1605
+ n = @c1.new
1606
+ n.raise_on_save_failure = false
1607
+ a = @c2.load(:id=>123)
1608
+ def n.valid?; false; end
1609
+ proc{a.add_attribute(n)}.should raise_error(Sequel::Error)
1610
+ end
1611
+
1612
+ it "should not attempt to validate the associated object in add_ if the :validate=>false option is used" do
1613
+ @c2.many_to_many :attributes, :class => @c1, :validate=>false
1614
+ n = @c1.new
1615
+ a = @c2.load(:id=>123)
1616
+ def n.valid?; false; end
1617
+ a.add_attribute(n)
1618
+ n.new?.should == false
1619
+ end
1620
+
1467
1621
  it "should raise an error if trying to remove a model object that doesn't have a valid primary key" do
1468
1622
  @c2.many_to_many :attributes, :class => @c1
1469
1623
  n = @c1.new
@@ -1597,6 +1751,12 @@ describe Sequel::Model, "many_to_many" do
1597
1751
  @c2.new(:id => 1234, :xxx=>5).remove_all_attributes
1598
1752
  MODEL_DB.sqls.first.should == 'DELETE FROM attributes_nodes WHERE (node_id = 5)'
1599
1753
  end
1754
+
1755
+ it "should have the remove_all_ method respect composite keys" do
1756
+ @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>[:id, :x], :left_key=>[:l1, :l2]
1757
+ @c2.load(:id => 1234, :x=>5).remove_all_attributes
1758
+ MODEL_DB.sqls.should == ['DELETE FROM attributes_nodes WHERE ((l1 = 1234) AND (l2 = 5))']
1759
+ end
1600
1760
 
1601
1761
  it "remove_all should set the cached instance variable to []" do
1602
1762
  @c2.many_to_many :attributes, :class => @c1