sequel 3.45.0 → 3.46.0

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