sequel 3.45.0 → 3.46.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 (67) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +34 -0
  3. data/README.rdoc +6 -0
  4. data/Rakefile +46 -33
  5. data/doc/release_notes/3.46.0.txt +122 -0
  6. data/doc/schema_modification.rdoc +42 -6
  7. data/doc/security.rdoc +379 -0
  8. data/doc/transactions.rdoc +1 -1
  9. data/lib/sequel/adapters/jdbc/as400.rb +1 -0
  10. data/lib/sequel/adapters/jdbc/h2.rb +11 -0
  11. data/lib/sequel/adapters/mysql2.rb +3 -9
  12. data/lib/sequel/adapters/postgres.rb +34 -2
  13. data/lib/sequel/adapters/shared/cubrid.rb +5 -0
  14. data/lib/sequel/adapters/shared/mssql.rb +27 -3
  15. data/lib/sequel/adapters/shared/mysql.rb +25 -4
  16. data/lib/sequel/adapters/shared/sqlite.rb +12 -1
  17. data/lib/sequel/connection_pool.rb +3 -3
  18. data/lib/sequel/connection_pool/sharded_threaded.rb +7 -8
  19. data/lib/sequel/connection_pool/threaded.rb +7 -8
  20. data/lib/sequel/core.rb +5 -2
  21. data/lib/sequel/database.rb +1 -1
  22. data/lib/sequel/database/connecting.rb +7 -7
  23. data/lib/sequel/database/features.rb +88 -0
  24. data/lib/sequel/database/misc.rb +14 -64
  25. data/lib/sequel/database/query.rb +0 -332
  26. data/lib/sequel/database/schema_generator.rb +36 -3
  27. data/lib/sequel/database/schema_methods.rb +48 -12
  28. data/lib/sequel/database/transactions.rb +344 -0
  29. data/lib/sequel/dataset/actions.rb +24 -9
  30. data/lib/sequel/dataset/mutation.rb +20 -0
  31. data/lib/sequel/dataset/query.rb +0 -17
  32. data/lib/sequel/dataset/sql.rb +7 -0
  33. data/lib/sequel/exceptions.rb +10 -6
  34. data/lib/sequel/extensions/_pretty_table.rb +2 -2
  35. data/lib/sequel/extensions/looser_typecasting.rb +10 -0
  36. data/lib/sequel/extensions/migration.rb +5 -2
  37. data/lib/sequel/model.rb +1 -1
  38. data/lib/sequel/model/associations.rb +16 -14
  39. data/lib/sequel/model/base.rb +14 -2
  40. data/lib/sequel/plugins/composition.rb +3 -3
  41. data/lib/sequel/plugins/dirty.rb +6 -6
  42. data/lib/sequel/plugins/hook_class_methods.rb +3 -0
  43. data/lib/sequel/plugins/serialization.rb +7 -17
  44. data/lib/sequel/plugins/string_stripper.rb +2 -1
  45. data/lib/sequel/plugins/validation_helpers.rb +1 -1
  46. data/lib/sequel/sql.rb +3 -0
  47. data/lib/sequel/version.rb +1 -1
  48. data/spec/adapters/mssql_spec.rb +21 -0
  49. data/spec/adapters/postgres_spec.rb +35 -8
  50. data/spec/core/database_spec.rb +4 -0
  51. data/spec/core/dataset_spec.rb +48 -2
  52. data/spec/core/schema_generator_spec.rb +10 -1
  53. data/spec/core/schema_spec.rb +69 -0
  54. data/spec/extensions/composition_spec.rb +21 -2
  55. data/spec/extensions/dirty_spec.rb +17 -10
  56. data/spec/extensions/eager_each_spec.rb +4 -1
  57. data/spec/extensions/looser_typecasting_spec.rb +16 -19
  58. data/spec/extensions/migration_spec.rb +7 -1
  59. data/spec/extensions/serialization_spec.rb +22 -0
  60. data/spec/extensions/single_table_inheritance_spec.rb +3 -2
  61. data/spec/extensions/validation_helpers_spec.rb +6 -0
  62. data/spec/integration/dataset_test.rb +5 -0
  63. data/spec/integration/schema_test.rb +16 -0
  64. data/spec/model/associations_spec.rb +40 -0
  65. data/spec/model/base_spec.rb +21 -1
  66. data/spec/model/record_spec.rb +3 -0
  67. metadata +14 -10
@@ -161,7 +161,7 @@ module Sequel
161
161
  # Check if value is an instance of a class
162
162
  def validates_type(klass, atts, opts={})
163
163
  klass = klass.to_s.constantize if klass.is_a?(String) || klass.is_a?(Symbol)
164
- validatable_attributes_for_type(:type, atts, opts){|a,v,m| validation_error_message(m, klass) if v && !v.is_a?(klass)}
164
+ validatable_attributes_for_type(:type, atts, opts){|a,v,m| validation_error_message(m, klass) if !v.nil? && !v.is_a?(klass)}
165
165
  end
166
166
 
167
167
  # Check attribute value(s) is not considered blank by the database, but allow false values.
@@ -89,6 +89,9 @@ module Sequel
89
89
  # Create a to_s instance method that takes a dataset, and calls
90
90
  # the method provided on the dataset with args as the argument (self by default).
91
91
  # Used to DRY up some code.
92
+ #
93
+ # Do not call this method with untrusted input, as that can result in
94
+ # arbitrary code execution.
92
95
  def to_s_method(meth, args=:self) # :nodoc:
93
96
  class_eval("def to_s(ds) ds.#{meth}(#{args}) end", __FILE__, __LINE__)
94
97
  class_eval("def to_s_append(ds, sql) ds.#{meth}_append(sql, #{args}) end", __FILE__, __LINE__)
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 3
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 45
6
+ MINOR = 46
7
7
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
8
8
  # releases that fix regressions from previous versions.
9
9
  TINY = 0
@@ -55,6 +55,27 @@ describe "A MSSQL database" do
55
55
  end
56
56
  end
57
57
 
58
+ describe "MSSQL" do
59
+ before(:all) do
60
+ @db = MSSQL_DB
61
+ @db.create_table!(:test3){Integer :v3}
62
+ @db.create_table!(:test4){Integer :v4}
63
+ @db[:test3].import([:v3], [[1], [2]])
64
+ @db[:test4].import([:v4], [[1], [3]])
65
+ end
66
+ after(:all) do
67
+ @db.drop_table?(:test3, :test4)
68
+ end
69
+
70
+ specify "should should support CROSS APPLY" do
71
+ @db[:test3].cross_apply(@db[:test4].where(:test3__v3=>:test4__v4)).select_order_map([:v3, :v4]).should == [[1,1]]
72
+ end
73
+
74
+ specify "should should support OUTER APPLY" do
75
+ @db[:test3].outer_apply(@db[:test4].where(:test3__v3=>:test4__v4)).select_order_map([:v3, :v4]).should == [[1,1], [2, nil]]
76
+ end
77
+ end
78
+
58
79
  # This spec is currently disabled as the SQL Server 2008 R2 Express doesn't support
59
80
  # full text searching. Even if full text searching is supported,
60
81
  # you may need to create a full text catalog on the database first via:
@@ -440,8 +440,8 @@ describe "A PostgreSQL dataset with a timestamp field" do
440
440
  before(:all) do
441
441
  @db = POSTGRES_DB
442
442
  @db.create_table! :test3 do
443
- integer :value
444
- timestamp :time
443
+ Date :date
444
+ DateTime :time
445
445
  end
446
446
  @d = @db[:test3]
447
447
  end
@@ -457,8 +457,8 @@ describe "A PostgreSQL dataset with a timestamp field" do
457
457
 
458
458
  cspecify "should store milliseconds in time fields for Time objects", :do, :swift do
459
459
  t = Time.now
460
- @d << {:value=>1, :time=>t}
461
- t2 = @d[:value =>1][:time]
460
+ @d << {:time=>t}
461
+ t2 = @d.get(:time)
462
462
  @d.literal(t2).should == @d.literal(t)
463
463
  t2.strftime('%Y-%m-%d %H:%M:%S').should == t.strftime('%Y-%m-%d %H:%M:%S')
464
464
  (t2.is_a?(Time) ? t2.usec : t2.strftime('%N').to_i/1000).should == t.usec
@@ -466,8 +466,8 @@ describe "A PostgreSQL dataset with a timestamp field" do
466
466
 
467
467
  cspecify "should store milliseconds in time fields for DateTime objects", :do, :swift do
468
468
  t = DateTime.now
469
- @d << {:value=>1, :time=>t}
470
- t2 = @d[:value =>1][:time]
469
+ @d << {:time=>t}
470
+ t2 = @d.get(:time)
471
471
  @d.literal(t2).should == @d.literal(t)
472
472
  t2.strftime('%Y-%m-%d %H:%M:%S').should == t.strftime('%Y-%m-%d %H:%M:%S')
473
473
  (t2.is_a?(Time) ? t2.usec : t2.strftime('%N').to_i/1000).should == t.strftime('%N').to_i/1000
@@ -475,7 +475,7 @@ describe "A PostgreSQL dataset with a timestamp field" do
475
475
 
476
476
  if POSTGRES_DB.adapter_scheme == :postgres
477
477
  specify "should handle infinite timestamps if convert_infinite_timestamps is set" do
478
- @d << {:time=>Sequel.cast('infinity', :timestamp)}
478
+ @d << {:time=>Sequel.cast('infinity', DateTime)}
479
479
  @db.convert_infinite_timestamps = :nil
480
480
  @db[:test3].get(:time).should == nil
481
481
  @db.convert_infinite_timestamps = :string
@@ -483,7 +483,7 @@ describe "A PostgreSQL dataset with a timestamp field" do
483
483
  @db.convert_infinite_timestamps = :float
484
484
  @db[:test3].get(:time).should == 1.0/0.0
485
485
 
486
- @d.update(:time=>Sequel.cast('-infinity', :timestamp))
486
+ @d.update(:time=>Sequel.cast('-infinity', DateTime))
487
487
  @db.convert_infinite_timestamps = :nil
488
488
  @db[:test3].get(:time).should == nil
489
489
  @db.convert_infinite_timestamps = :string
@@ -500,6 +500,33 @@ describe "A PostgreSQL dataset with a timestamp field" do
500
500
  c.new(:time=>1.0/0.0).time.should == 1.0/0.0
501
501
  c.new(:time=>-1.0/0.0).time.should == -1.0/0.0
502
502
  end
503
+
504
+ specify "should handle infinite dates if convert_infinite_timestamps is set" do
505
+ @d << {:date=>Sequel.cast('infinity', Date)}
506
+ @db.convert_infinite_timestamps = :nil
507
+ @db[:test3].get(:date).should == nil
508
+ @db.convert_infinite_timestamps = :string
509
+ @db[:test3].get(:date).should == 'infinity'
510
+ @db.convert_infinite_timestamps = :float
511
+ @db[:test3].get(:date).should == 1.0/0.0
512
+
513
+ @d.update(:date=>Sequel.cast('-infinity', :timestamp))
514
+ @db.convert_infinite_timestamps = :nil
515
+ @db[:test3].get(:date).should == nil
516
+ @db.convert_infinite_timestamps = :string
517
+ @db[:test3].get(:date).should == '-infinity'
518
+ @db.convert_infinite_timestamps = :float
519
+ @db[:test3].get(:date).should == -1.0/0.0
520
+ end
521
+
522
+ specify "should handle conversions from infinite strings/floats in models" do
523
+ c = Class.new(Sequel::Model(:test3))
524
+ @db.convert_infinite_timestamps = :float
525
+ c.new(:date=>'infinity').date.should == 'infinity'
526
+ c.new(:date=>'-infinity').date.should == '-infinity'
527
+ c.new(:date=>1.0/0.0).date.should == 1.0/0.0
528
+ c.new(:date=>-1.0/0.0).date.should == -1.0/0.0
529
+ end
503
530
  end
504
531
 
505
532
  specify "explain and analyze should not raise errors" do
@@ -1663,6 +1663,10 @@ describe "Database#each_server with do/jdbc adapter connection string without :a
1663
1663
  end
1664
1664
  hosts.sort.should == [1, 3]
1665
1665
  end
1666
+
1667
+ specify "should raise if not given a block" do
1668
+ proc{Sequel.mock.each_server}.should raise_error(Sequel::Error)
1669
+ end
1666
1670
  end
1667
1671
 
1668
1672
  describe "Database#each_server" do
@@ -2465,8 +2465,7 @@ end
2465
2465
 
2466
2466
  describe "Dataset #first and #last" do
2467
2467
  before do
2468
- @db = Sequel.mock(:fetch=>proc{|s| {:s=>s}})
2469
- @d = @db[:test]
2468
+ @d = Sequel.mock(:fetch=>proc{|s| {:s=>s}})[:test]
2470
2469
  end
2471
2470
 
2472
2471
  specify "should return a single record if no argument is given" do
@@ -2504,6 +2503,10 @@ describe "Dataset #first and #last" do
2504
2503
  i = rand(10) + 10
2505
2504
  r = @d.order(:a).last(i){z > 26}.should == [{:s=>"SELECT * FROM test WHERE (z > 26) ORDER BY a DESC LIMIT #{i}"}]
2506
2505
  end
2506
+
2507
+ specify "should return nil if no records match" do
2508
+ Sequel.mock[:t].first.should == nil
2509
+ end
2507
2510
 
2508
2511
  specify "#last should raise if no order is given" do
2509
2512
  proc {@d.last}.should raise_error(Sequel::Error)
@@ -2520,6 +2523,44 @@ describe "Dataset #first and #last" do
2520
2523
  end
2521
2524
  end
2522
2525
 
2526
+ describe "Dataset #first!" do
2527
+ before do
2528
+ @db = Sequel.mock(:fetch=>proc{|s| {:s=>s}})
2529
+ @d = @db[:test]
2530
+ end
2531
+
2532
+ specify "should return a single record if no argument is given" do
2533
+ @d.order(:a).first!.should == {:s=>'SELECT * FROM test ORDER BY a LIMIT 1'}
2534
+ end
2535
+
2536
+ specify "should return the first! matching record if argument is not an Integer" do
2537
+ @d.order(:a).first!(:z => 26).should == {:s=>'SELECT * FROM test WHERE (z = 26) ORDER BY a LIMIT 1'}
2538
+ @d.order(:a).first!('z = ?', 15).should == {:s=>'SELECT * FROM test WHERE (z = 15) ORDER BY a LIMIT 1'}
2539
+ end
2540
+
2541
+ specify "should set the limit and return an array of records if the given number is > 1" do
2542
+ i = rand(10) + 10
2543
+ r = @d.order(:a).first!(i).should == [{:s=>"SELECT * FROM test ORDER BY a LIMIT #{i}"}]
2544
+ end
2545
+
2546
+ specify "should return the first! matching record if a block is given without an argument" do
2547
+ @d.first!{z > 26}.should == {:s=>'SELECT * FROM test WHERE (z > 26) LIMIT 1'}
2548
+ end
2549
+
2550
+ specify "should combine block and standard argument filters if argument is not an Integer" do
2551
+ @d.first!(:y=>25){z > 26}.should == {:s=>'SELECT * FROM test WHERE ((z > 26) AND (y = 25)) LIMIT 1'}
2552
+ end
2553
+
2554
+ specify "should filter and return an array of records if an Integer argument is provided and a block is given" do
2555
+ i = rand(10) + 10
2556
+ r = @d.order(:a).first!(i){z > 26}.should == [{:s=>"SELECT * FROM test WHERE (z > 26) ORDER BY a LIMIT #{i}"}]
2557
+ end
2558
+
2559
+ specify "should raise NoMatchingRow exception if no rows match" do
2560
+ proc{Sequel.mock[:t].first!}.should raise_error(Sequel::NoMatchingRow)
2561
+ end
2562
+ end
2563
+
2523
2564
  describe "Dataset compound operations" do
2524
2565
  before do
2525
2566
  @a = Sequel::Dataset.new(nil).from(:a).filter(:z => 1)
@@ -4038,6 +4079,11 @@ describe "Sequel::Dataset#select_map" do
4038
4079
  @ds.db.sqls.should == ['SELECT c, c FROM t']
4039
4080
  end
4040
4081
 
4082
+ specify "should raise an error for plain strings" do
4083
+ proc{@ds.select_map(['c', :c])}.should raise_error(Sequel::Error)
4084
+ @ds.db.sqls.should == []
4085
+ end
4086
+
4041
4087
  specify "should accept a block" do
4042
4088
  @ds.select_map{a(t__c)}.should == [1, 2]
4043
4089
  @ds.db.sqls.should == ['SELECT a(t.c) FROM t']
@@ -121,6 +121,10 @@ describe Sequel::Schema::AlterTableGenerator do
121
121
  add_foreign_key :node_id, :nodes
122
122
  add_primary_key [:aaa, :bbb]
123
123
  add_foreign_key [:node_id, :prop_id], :nodes_props
124
+ add_foreign_key [:node_id, :prop_id], :nodes_props, :name => :fkey
125
+ drop_foreign_key :node_id
126
+ drop_foreign_key [:node_id, :prop_id]
127
+ drop_foreign_key [:node_id, :prop_id], :name => :fkey
124
128
  end
125
129
  end
126
130
 
@@ -144,7 +148,12 @@ describe Sequel::Schema::AlterTableGenerator do
144
148
  {:op => :add_column, :name => :id, :type => Integer, :primary_key=>true, :auto_increment=>true},
145
149
  {:op => :add_column, :name => :node_id, :type => Integer, :table=>:nodes},
146
150
  {:op => :add_constraint, :type => :primary_key, :columns => [:aaa, :bbb]},
147
- {:op => :add_constraint, :type => :foreign_key, :columns => [:node_id, :prop_id], :table => :nodes_props}
151
+ {:op => :add_constraint, :type => :foreign_key, :columns => [:node_id, :prop_id], :table => :nodes_props},
152
+ {:op => :add_constraint, :type => :foreign_key, :columns => [:node_id, :prop_id], :table => :nodes_props, :name => :fkey},
153
+ {:op => :drop_constraint, :type => :foreign_key, :columns => [:node_id]},
154
+ {:op => :drop_column, :name => :node_id},
155
+ {:op => :drop_constraint, :type => :foreign_key, :columns => [:node_id, :prop_id]},
156
+ {:op => :drop_constraint, :type => :foreign_key, :columns => [:node_id, :prop_id], :name => :fkey},
148
157
  ]
149
158
  end
150
159
  end
@@ -113,6 +113,22 @@ describe "DB#create_table" do
113
113
  @db.sqls.should == ['CREATE TABLE cats (id serial PRIMARY KEY)']
114
114
  end
115
115
 
116
+ specify "should allow naming primary key constraint with :primary_key_constraint_name option" do
117
+ @db.create_table(:cats) do
118
+ primary_key :id, :primary_key_constraint_name=>:foo
119
+ end
120
+ @db.sqls.should == ['CREATE TABLE cats (id integer CONSTRAINT foo PRIMARY KEY AUTOINCREMENT)']
121
+ end
122
+
123
+ specify "should handling splitting named column constraints into table constraints if unsupported" do
124
+ def @db.supports_named_column_constraints?; false end
125
+ @db.create_table(:cats) do
126
+ primary_key :id, :primary_key_constraint_name=>:foo
127
+ foreign_key :cat_id, :cats, :unique=>true, :unique_constraint_name=>:bar, :foreign_key_constraint_name=>:baz, :deferrable=>true, :key=>:foo_id, :on_delete=>:cascade, :on_update=>:restrict
128
+ end
129
+ @db.sqls.should == ['CREATE TABLE cats (id integer AUTOINCREMENT, cat_id integer, CONSTRAINT foo PRIMARY KEY (id), CONSTRAINT baz FOREIGN KEY (cat_id) REFERENCES cats(foo_id) ON DELETE CASCADE ON UPDATE RESTRICT DEFERRABLE INITIALLY DEFERRED, CONSTRAINT bar UNIQUE (cat_id))']
130
+ end
131
+
116
132
  specify "should accept and literalize default values" do
117
133
  @db.create_table(:cats) do
118
134
  integer :id, :default => 123
@@ -147,6 +163,13 @@ describe "DB#create_table" do
147
163
  @db.sqls.should == ["CREATE TABLE cats (id integer, name text UNIQUE)"]
148
164
  end
149
165
 
166
+ specify "should allow naming unique constraint with :unique_constraint_name option" do
167
+ @db.create_table(:cats) do
168
+ text :name, :unique => true, :unique_constraint_name=>:foo
169
+ end
170
+ @db.sqls.should == ["CREATE TABLE cats (name text CONSTRAINT foo UNIQUE)"]
171
+ end
172
+
150
173
  specify "should handle not deferred unique constraints" do
151
174
  @db.create_table(:cats) do
152
175
  integer :id
@@ -227,6 +250,13 @@ describe "DB#create_table" do
227
250
  @db.sqls.should == ["CREATE TABLE cats (project_id integer DEFAULT 3 REFERENCES projects)"]
228
251
  end
229
252
 
253
+ specify "should allowing naming foreign key constraint with :foreign_key_constraint_name option" do
254
+ @db.create_table(:cats) do
255
+ foreign_key :project_id, :projects, :foreign_key_constraint_name=>:foo
256
+ end
257
+ @db.sqls.should == ["CREATE TABLE cats (project_id integer CONSTRAINT foo REFERENCES projects)"]
258
+ end
259
+
230
260
  specify "should raise an error if the table argument to foreign_key isn't a hash, symbol, or nil" do
231
261
  proc{@db.create_table(:cats){foreign_key :project_id, Object.new, :default=>3}}.should raise_error(Sequel::Error)
232
262
  end
@@ -949,6 +979,45 @@ describe "DB#alter_table" do
949
979
  @db.sqls.should == ["ALTER TABLE cats DROP CONSTRAINT valid_score CASCADE"]
950
980
  end
951
981
 
982
+ specify "should support drop_foreign_key" do
983
+ def @db.foreign_key_list(table_name)
984
+ [{:name=>:cats_node_id_fkey, :columns=>[:node_id]}]
985
+ end
986
+ @db.alter_table(:cats) do
987
+ drop_foreign_key :node_id
988
+ end
989
+ @db.sqls.should == ["ALTER TABLE cats DROP CONSTRAINT cats_node_id_fkey", "ALTER TABLE cats DROP COLUMN node_id"]
990
+ end
991
+
992
+ specify "should support drop_foreign_key with composite foreign keys" do
993
+ def @db.foreign_key_list(table_name)
994
+ [{:name=>:cats_node_id_prop_id_fkey, :columns=>[:node_id, :prop_id]}]
995
+ end
996
+ @db.alter_table(:cats) do
997
+ drop_foreign_key [:node_id, :prop_id]
998
+ end
999
+ @db.sqls.should == ["ALTER TABLE cats DROP CONSTRAINT cats_node_id_prop_id_fkey"]
1000
+
1001
+ @db.alter_table(:cats) do
1002
+ drop_foreign_key [:node_id, :prop_id], :name => :cfk
1003
+ end
1004
+ @db.sqls.should == ["ALTER TABLE cats DROP CONSTRAINT cfk"]
1005
+ end
1006
+
1007
+ specify "should have drop_foreign_key raise Error if no name is found" do
1008
+ def @db.foreign_key_list(table_name)
1009
+ [{:name=>:cats_node_id_fkey, :columns=>[:foo_id]}]
1010
+ end
1011
+ lambda{@db.alter_table(:cats){drop_foreign_key :node_id}}.should raise_error(Sequel::Error)
1012
+ end
1013
+
1014
+ specify "should have drop_foreign_key raise Error if multiple foreign keys found" do
1015
+ def @db.foreign_key_list(table_name)
1016
+ [{:name=>:cats_node_id_fkey, :columns=>[:node_id]}, {:name=>:cats_node_id_fkey2, :columns=>[:node_id]}]
1017
+ end
1018
+ lambda{@db.alter_table(:cats){drop_foreign_key :node_id}}.should raise_error(Sequel::Error)
1019
+ end
1020
+
952
1021
  specify "should support drop_index" do
953
1022
  @db.alter_table(:cats) do
954
1023
  drop_index :name
@@ -90,13 +90,32 @@ describe "Composition plugin" do
90
90
  called.should == true
91
91
  end
92
92
 
93
- it "should clear compositions cache when reloading" do
93
+ it "should clear compositions cache when using set_values" do
94
94
  @c.composition :date, :composer=>proc{}, :decomposer=>proc{called = true}
95
95
  @o.date = Date.new(3, 4, 5)
96
- @o.reload
96
+ @o.set_values(:id=>1)
97
97
  @o.compositions.should == {}
98
98
  end
99
99
 
100
+ it "should clear compositions cache when refreshing" do
101
+ @c.composition :date, :composer=>proc{}, :decomposer=>proc{called = true}
102
+ @o.date = Date.new(3, 4, 5)
103
+ @o.refresh
104
+ @o.compositions.should == {}
105
+ end
106
+
107
+ it "should clear compositions cache when refreshing after save" do
108
+ @c.composition :date, :composer=>proc{}, :decomposer=>proc{called = true}
109
+ @c.create(:date=>Date.new(3, 4, 5)).compositions.should == {}
110
+ end
111
+
112
+ it "should clear compositions cache when saving with insert_select" do
113
+ def (@c.instance_dataset).supports_insert_select?() true end
114
+ def (@c.instance_dataset).insert_select(*) {:id=>1} end
115
+ @c.composition :date, :composer=>proc{}, :decomposer=>proc{called = true}
116
+ @c.create(:date=>Date.new(3, 4, 5)).compositions.should == {}
117
+ end
118
+
100
119
  it "should instantiate compositions lazily" do
101
120
  @c.composition :date, :mapping=>[:year, :month, :day]
102
121
  @o.compositions.should == {}
@@ -88,6 +88,11 @@ describe "Sequel::Plugins::Dirty" do
88
88
  @o.values.has_key?(:missing_changed).should == false
89
89
  end
90
90
 
91
+ it "set_values should clear the cached initial values" do
92
+ @o.set_values(:id=>1)
93
+ @o.column_changes.should == {}
94
+ end
95
+
91
96
  it "refresh should clear the cached initial values" do
92
97
  @o.refresh
93
98
  @o.column_changes.should == {}
@@ -116,16 +121,6 @@ describe "Sequel::Plugins::Dirty" do
116
121
  o.column_change(:initial).should == [v, 'a']
117
122
  end
118
123
  end
119
-
120
- it "save should clear the cached initial values" do
121
- @o.save
122
- @o.column_changes.should == {}
123
- end
124
-
125
- it "save_changes should clear the cached initial values" do
126
- @o.save_changes
127
- @o.column_changes.should == {}
128
- end
129
124
  end
130
125
 
131
126
  describe "with new instance" do
@@ -136,6 +131,18 @@ describe "Sequel::Plugins::Dirty" do
136
131
  end
137
132
 
138
133
  it_should_behave_like "dirty plugin"
134
+
135
+ it "save should clear the cached initial values" do
136
+ @o.save
137
+ @o.column_changes.should == {}
138
+ end
139
+
140
+ it "save_changes should clear the cached initial values" do
141
+ def (@c.instance_dataset).supports_insert_select?() true end
142
+ def (@c.instance_dataset).insert_select(*) {:id=>1} end
143
+ @o.save
144
+ @o.column_changes.should == {}
145
+ end
139
146
  end
140
147
 
141
148
  describe "with existing instance" do
@@ -17,7 +17,10 @@ describe "Sequel::Plugins::EagerEach" do
17
17
  ds.each{|c| a << c}
18
18
  a.should == [@c.load(:id=>1, :parent_id=>nil), @c.load(:id=>2, :parent_id=>nil)]
19
19
  a.map{|c| c.associations[:children]}.should == [[@c.load(:id=>3, :parent_id=>1), @c.load(:id=>4, :parent_id=>1)], [@c.load(:id=>5, :parent_id=>2), @c.load(:id=>6, :parent_id=>2)]]
20
- @c.db.sqls.should == ['SELECT * FROM items', 'SELECT * FROM items WHERE (items.parent_id IN (1, 2))']
20
+ sqls = @c.db.sqls
21
+ sqls.shift.should == 'SELECT * FROM items'
22
+ ['SELECT * FROM items WHERE (items.parent_id IN (1, 2))',
23
+ 'SELECT * FROM items WHERE (items.parent_id IN (2, 1))'].should include(sqls.pop)
21
24
  end
22
25
 
23
26
  it "should make #each on an eager_graph dataset do eager loading" do
@@ -4,36 +4,33 @@ describe "LooserTypecasting Extension" do
4
4
  before do
5
5
  @db = Sequel::Database.new({})
6
6
  def @db.schema(*args)
7
- [[:id, {}], [:z, {:type=>:float}], [:b, {:type=>:integer}]]
7
+ [[:id, {}], [:z, {:type=>:float}], [:b, {:type=>:integer}], [:d, {:type=>:decimal}]]
8
8
  end
9
9
  @c = Class.new(Sequel::Model(@db[:items]))
10
+ @db.extension(:looser_typecasting)
10
11
  @c.instance_eval do
11
- @columns = [:id, :b, :z]
12
+ @columns = [:id, :b, :z, :d]
12
13
  def columns; @columns; end
13
14
  end
14
15
  end
15
16
 
16
- specify "Should use to_i instead of Integer() for typecasting integers" do
17
- proc{@c.new(:b=>'a')}.should raise_error(Sequel::InvalidValue)
18
- @db.extension(:looser_typecasting)
17
+ specify "should not raise errors for invalid strings in integer columns" do
19
18
  @c.new(:b=>'a').b.should == 0
20
-
21
- o = Object.new
22
- def o.to_i
23
- 1
24
- end
25
- @c.new(:b=>o).b.should == 1
19
+ @c.new(:b=>'a').b.should be_a_kind_of(Integer)
26
20
  end
27
21
 
28
- specify "Should use to_f instead of Float() for typecasting floats" do
29
- proc{@c.new(:z=>'a')}.should raise_error(Sequel::InvalidValue)
30
- @db.extension(:looser_typecasting)
22
+ specify "should not raise errors for invalid strings in float columns" do
31
23
  @c.new(:z=>'a').z.should == 0.0
24
+ @c.new(:z=>'a').z.should be_a_kind_of(Float)
25
+ end
32
26
 
33
- o = Object.new
34
- def o.to_f
35
- 1.0
36
- end
37
- @c.new(:z=>o).z.should == 1.0
27
+ specify "should not raise errors for invalid strings in decimal columns" do
28
+ @c.new(:d=>'a').d.should == 0.0
29
+ @c.new(:d=>'a').d.should be_a_kind_of(BigDecimal)
30
+ end
31
+
32
+ specify "should not affect conversions of other types in decimal columns" do
33
+ @c.new(:d=>1).d.should == 1
34
+ @c.new(:d=>'a').d.should be_a_kind_of(BigDecimal)
38
35
  end
39
36
  end