sequel 3.4.0 → 3.5.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 (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