sequel 3.0.0 → 3.1.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 (87) hide show
  1. data/CHANGELOG +100 -0
  2. data/README.rdoc +3 -3
  3. data/bin/sequel +102 -19
  4. data/doc/reflection.rdoc +83 -0
  5. data/doc/release_notes/3.1.0.txt +406 -0
  6. data/lib/sequel/adapters/ado.rb +11 -0
  7. data/lib/sequel/adapters/amalgalite.rb +5 -20
  8. data/lib/sequel/adapters/do.rb +44 -36
  9. data/lib/sequel/adapters/firebird.rb +29 -43
  10. data/lib/sequel/adapters/jdbc.rb +17 -27
  11. data/lib/sequel/adapters/mysql.rb +35 -40
  12. data/lib/sequel/adapters/odbc.rb +4 -23
  13. data/lib/sequel/adapters/oracle.rb +22 -19
  14. data/lib/sequel/adapters/postgres.rb +6 -15
  15. data/lib/sequel/adapters/shared/mssql.rb +1 -1
  16. data/lib/sequel/adapters/shared/mysql.rb +29 -10
  17. data/lib/sequel/adapters/shared/oracle.rb +6 -8
  18. data/lib/sequel/adapters/shared/postgres.rb +28 -72
  19. data/lib/sequel/adapters/shared/sqlite.rb +5 -3
  20. data/lib/sequel/adapters/sqlite.rb +5 -20
  21. data/lib/sequel/adapters/utils/savepoint_transactions.rb +80 -0
  22. data/lib/sequel/adapters/utils/unsupported.rb +0 -12
  23. data/lib/sequel/core.rb +12 -3
  24. data/lib/sequel/core_sql.rb +1 -8
  25. data/lib/sequel/database.rb +107 -43
  26. data/lib/sequel/database/schema_generator.rb +1 -0
  27. data/lib/sequel/database/schema_methods.rb +38 -4
  28. data/lib/sequel/dataset.rb +6 -0
  29. data/lib/sequel/dataset/convenience.rb +2 -2
  30. data/lib/sequel/dataset/graph.rb +2 -2
  31. data/lib/sequel/dataset/prepared_statements.rb +3 -8
  32. data/lib/sequel/dataset/sql.rb +93 -19
  33. data/lib/sequel/extensions/blank.rb +2 -1
  34. data/lib/sequel/extensions/inflector.rb +4 -3
  35. data/lib/sequel/extensions/migration.rb +13 -2
  36. data/lib/sequel/extensions/pagination.rb +4 -0
  37. data/lib/sequel/extensions/pretty_table.rb +4 -0
  38. data/lib/sequel/extensions/query.rb +4 -0
  39. data/lib/sequel/extensions/schema_dumper.rb +100 -24
  40. data/lib/sequel/extensions/string_date_time.rb +3 -4
  41. data/lib/sequel/model.rb +2 -1
  42. data/lib/sequel/model/associations.rb +96 -38
  43. data/lib/sequel/model/base.rb +14 -14
  44. data/lib/sequel/model/plugins.rb +32 -21
  45. data/lib/sequel/plugins/caching.rb +13 -15
  46. data/lib/sequel/plugins/identity_map.rb +107 -0
  47. data/lib/sequel/plugins/lazy_attributes.rb +65 -0
  48. data/lib/sequel/plugins/many_through_many.rb +188 -0
  49. data/lib/sequel/plugins/schema.rb +13 -0
  50. data/lib/sequel/plugins/serialization.rb +53 -37
  51. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  52. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  53. data/lib/sequel/plugins/validation_class_methods.rb +28 -7
  54. data/lib/sequel/plugins/validation_helpers.rb +31 -24
  55. data/lib/sequel/sql.rb +16 -0
  56. data/lib/sequel/version.rb +1 -1
  57. data/spec/adapters/ado_spec.rb +47 -1
  58. data/spec/adapters/firebird_spec.rb +39 -36
  59. data/spec/adapters/mysql_spec.rb +25 -9
  60. data/spec/adapters/postgres_spec.rb +11 -24
  61. data/spec/core/database_spec.rb +54 -13
  62. data/spec/core/dataset_spec.rb +147 -29
  63. data/spec/core/object_graph_spec.rb +6 -1
  64. data/spec/core/schema_spec.rb +34 -0
  65. data/spec/core/spec_helper.rb +0 -2
  66. data/spec/extensions/caching_spec.rb +7 -0
  67. data/spec/extensions/identity_map_spec.rb +158 -0
  68. data/spec/extensions/lazy_attributes_spec.rb +113 -0
  69. data/spec/extensions/many_through_many_spec.rb +813 -0
  70. data/spec/extensions/migration_spec.rb +4 -4
  71. data/spec/extensions/schema_dumper_spec.rb +114 -13
  72. data/spec/extensions/schema_spec.rb +19 -3
  73. data/spec/extensions/serialization_spec.rb +28 -0
  74. data/spec/extensions/single_table_inheritance_spec.rb +25 -1
  75. data/spec/extensions/spec_helper.rb +2 -7
  76. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  77. data/spec/extensions/validation_class_methods_spec.rb +10 -5
  78. data/spec/integration/dataset_test.rb +39 -6
  79. data/spec/integration/eager_loader_test.rb +7 -7
  80. data/spec/integration/spec_helper.rb +0 -1
  81. data/spec/integration/transaction_test.rb +28 -1
  82. data/spec/model/association_reflection_spec.rb +29 -3
  83. data/spec/model/associations_spec.rb +1 -0
  84. data/spec/model/eager_loading_spec.rb +70 -1
  85. data/spec/model/plugins_spec.rb +236 -50
  86. data/spec/model/spec_helper.rb +0 -2
  87. metadata +18 -5
@@ -135,7 +135,7 @@ context "Sequel::Migrator" do
135
135
  File.open('001_create_sessions.rb', 'w') {|f| f << MIGRATION_001}
136
136
  File.open('002_create_nodes.rb', 'w') {|f| f << MIGRATION_002}
137
137
  File.open('003_create_users.rb', 'w') {|f| f << MIGRATION_003}
138
- File.open('005_create_attributes.rb', 'w') {|f| f << MIGRATION_005}
138
+ File.open('005_5_create_attributes.rb', 'w') {|f| f << MIGRATION_005}
139
139
 
140
140
  @db[:schema_info].version = nil
141
141
  end
@@ -149,7 +149,7 @@ context "Sequel::Migrator" do
149
149
  File.delete('001_create_sessions.rb')
150
150
  File.delete('002_create_nodes.rb')
151
151
  File.delete('003_create_users.rb')
152
- File.delete('005_create_attributes.rb')
152
+ File.delete('005_5_create_attributes.rb')
153
153
  end
154
154
 
155
155
  specify "should return the list of files for a specified version range" do
@@ -159,8 +159,8 @@ context "Sequel::Migrator" do
159
159
  Sequel::Migrator.migration_files('.', 1..3).should == \
160
160
  ['./001_create_sessions.rb', './002_create_nodes.rb', './003_create_users.rb']
161
161
 
162
- Sequel::Migrator.migration_files('.', 3..5).should == \
163
- ['./003_create_users.rb', './005_create_attributes.rb']
162
+ Sequel::Migrator.migration_files('.', 3..6).should == \
163
+ ['./003_create_users.rb', './005_5_create_attributes.rb']
164
164
 
165
165
  Sequel::Migrator.migration_files('.', 7..8).should == []
166
166
  end
@@ -78,11 +78,6 @@ describe "Sequel::Database dump methods" do
78
78
  when :t3
79
79
  [[:c1, {:db_type=>'date', :default=>"'now()'", :allow_null=>true}],
80
80
  [:c2, {:db_type=>'datetime', :allow_null=>false}]]
81
- when :t4
82
- [[:c1, {:db_type=>'boolean', :default=>"false", :allow_null=>true}],
83
- [:c2, {:db_type=>'boolean', :default=>"true", :allow_null=>true}],
84
- [:c3, {:db_type=>'varchar', :default=>"'blah'", :allow_null=>true}],
85
- [:c4, {:db_type=>'integer', :default=>"35", :allow_null=>true}]]
86
81
  when :t5
87
82
  [[:c1, {:db_type=>'blahblah', :allow_null=>true}]]
88
83
  end
@@ -102,7 +97,7 @@ describe "Sequel::Database dump methods" do
102
97
  {:i1=>{:columns=>[:c1], :unique=>false},
103
98
  :t1_c2_c1_index=>{:columns=>[:c2, :c1], :unique=>true}}
104
99
  end
105
- @d.dump_table_schema(:t1).should == "create_table(:t1) do\n primary_key :c1\n String :c2, :size=>20\n \n index [:c1], :name=>:i1\n index [:c2, :c1], :unique=>true\nend"
100
+ @d.dump_table_schema(:t1).should == "create_table(:t1, :ignore_index_errors=>true) do\n primary_key :c1\n String :c2, :size=>20\n \n index [:c1], :name=>:i1\n index [:c2, :c1], :unique=>true\nend"
106
101
  end
107
102
 
108
103
  it "should support dumping the whole database as a migration" do
@@ -129,6 +124,31 @@ end
129
124
  END_MIG
130
125
  end
131
126
 
127
+ it "should sort table names when dumping a migration" do
128
+ @d.meta_def(:tables){[:t2, :t1]}
129
+ @d.dump_schema_migration.should == <<-END_MIG
130
+ Class.new(Sequel::Migration) do
131
+ def up
132
+ create_table(:t1) do
133
+ primary_key :c1
134
+ String :c2, :size=>20
135
+ end
136
+
137
+ create_table(:t2) do
138
+ Integer :c1, :null=>false
139
+ BigDecimal :c2, :null=>false
140
+
141
+ primary_key [:c1, :c2]
142
+ end
143
+ end
144
+
145
+ def down
146
+ drop_table(:t1, :t2)
147
+ end
148
+ end
149
+ END_MIG
150
+ end
151
+
132
152
  it "should honor the :same_db option to not convert types" do
133
153
  @d.dump_table_schema(:t1, :same_db=>true).should == "create_table(:t1) do\n primary_key :c1\n column :c2, \"varchar(20)\"\nend"
134
154
  @d.dump_schema_migration(:same_db=>true).should == <<-END_MIG
@@ -192,24 +212,105 @@ END_MIG
192
212
  @d.dump_indexes_migration.should == <<-END_MIG
193
213
  Class.new(Sequel::Migration) do
194
214
  def up
195
- add_index :t1, [:c1], :name=>:i1
196
- add_index :t1, [:c2, :c1], :unique=>true
215
+ add_index :t1, [:c1], :ignore_errors=>true, :name=>:i1
216
+ add_index :t1, [:c2, :c1], :ignore_errors=>true, :unique=>true
197
217
  end
198
218
 
199
219
  def down
200
- drop_index :t1, [:c1], :name=>:i1
201
- drop_index :t1, [:c2, :c1], :unique=>true
220
+ drop_index :t1, [:c1], :ignore_errors=>true, :name=>:i1
221
+ drop_index :t1, [:c2, :c1], :ignore_errors=>true, :unique=>true
202
222
  end
203
223
  end
204
224
  END_MIG
205
225
  end
206
226
 
207
227
  it "should handle not null values and defaults" do
208
- @d.dump_table_schema(:t3).should == "create_table(:t3) do\n Date :c1, :default=>\"'now()'\".lit\n DateTime :c2, :null=>false\nend"
228
+ @d.dump_table_schema(:t3).should == "create_table(:t3) do\n Date :c1\n DateTime :c2, :null=>false\nend"
209
229
  end
210
-
230
+
231
+ it "should handle converting many default formats" do
232
+ m = @d.method(:column_schema_to_ruby_default)
233
+ m.call("adf", :string, :same_db=>true).inspect.should == '"adf".lit'
234
+ p = lambda{|d,t| m.call(d,t,{})}
235
+ p[nil, :integer].should == nil
236
+ p['1', :integer].should == 1
237
+ p['-1', :integer].should == -1
238
+ p['1.0', :float].should == 1.0
239
+ p['-1.0', :float].should == -1.0
240
+ p['1.0', :decimal].should == BigDecimal.new('1.0')
241
+ p['-1.0', :decimal].should == BigDecimal.new('-1.0')
242
+ p['1', :boolean].should == true
243
+ p['0', :boolean].should == false
244
+ p['true', :boolean].should == true
245
+ p['false', :boolean].should == false
246
+ p["'t'", :boolean].should == true
247
+ p["'f'", :boolean].should == false
248
+ p["'a'", :string].should == 'a'
249
+ p["'a'", :blob].should == 'a'.to_sequel_blob
250
+ p["'a'", :blob].should be_a_kind_of(Sequel::SQL::Blob)
251
+ p["''", :string].should == ''
252
+ p["'\\a''b'", :string].should == "\\a'b"
253
+ p["'NULL'", :string].should == "NULL"
254
+ p["'2009-10-29'", :date].should == Date.new(2009,10,29)
255
+ p["CURRENT_TIMESTAMP", :date].should == nil
256
+ p["today()", :date].should == nil
257
+ p["'2009-10-29T10:20:30-07:00'", :datetime].should == DateTime.parse('2009-10-29T10:20:30-07:00')
258
+ p["'2009-10-29 10:20:30'", :datetime].should == DateTime.parse('2009-10-29 10:20:30')
259
+ p["'10:20:30'", :time].should == Time.parse('10:20:30')
260
+ p["NaN", :float].should == nil
261
+ end
262
+
211
263
  it "should handle converting common defaults" do
212
- @d.dump_table_schema(:t4).should == "create_table(:t4) do\n TrueClass :c1, :default=>false\n TrueClass :c2, :default=>true\n String :c3, :default=>\"'blah'\".lit\n Integer :c4, :default=>35\nend"
264
+ @d.meta_def(:schema) do |t, *os|
265
+ [[:c1, {:db_type=>'boolean', :default=>"false", :type=>:boolean, :allow_null=>true}],
266
+ [:c2, {:db_type=>'varchar', :default=>"'blah'", :type=>:string, :allow_null=>true}],
267
+ [:c3, {:db_type=>'integer', :default=>"-1", :type=>:integer, :allow_null=>true}],
268
+ [:c4, {:db_type=>'float', :default=>"1.0", :type=>:float, :allow_null=>true}],
269
+ [:c5, {:db_type=>'decimal', :default=>"100.50", :type=>:decimal, :allow_null=>true}],
270
+ [:c6, {:db_type=>'blob', :default=>"'blah'", :type=>:blob, :allow_null=>true}],
271
+ [:c7, {:db_type=>'date', :default=>"'2008-10-29'", :type=>:date, :allow_null=>true}],
272
+ [:c8, {:db_type=>'datetime', :default=>"'2008-10-29 10:20:30'", :type=>:datetime, :allow_null=>true}],
273
+ [:c9, {:db_type=>'time', :default=>"'10:20:30'", :type=>:time, :allow_null=>true}],
274
+ [:c10, {:db_type=>'interval', :default=>"'6 weeks'", :type=>:interval, :allow_null=>true}]]
275
+ end
276
+ @d.dump_table_schema(:t4).gsub(/[+-]\d\d:\d\d"\)/, '")').should == "create_table(:t4) do\n TrueClass :c1, :default=>false\n String :c2, :default=>\"blah\"\n Integer :c3, :default=>-1\n Float :c4, :default=>1.0\n BigDecimal :c5, :default=>BigDecimal.new(\"0.1005E3\")\n File :c6, :default=>Sequel::SQL::Blob.new(\"blah\")\n Date :c7, :default=>Date.parse(\"2008-10-29\")\n DateTime :c8, :default=>DateTime.parse(\"2008-10-29T10:20:30\")\n Time :c9, :default=>Time.parse(\"10:20:30\"), :only_time=>true\n String :c10\nend"
277
+ @d.dump_table_schema(:t4, :same_db=>true).gsub(/[+-]\d\d:\d\d"\)/, '")').should == "create_table(:t4) do\n column :c1, \"boolean\", :default=>false\n column :c2, \"varchar\", :default=>\"blah\"\n column :c3, \"integer\", :default=>-1\n column :c4, \"float\", :default=>1.0\n column :c5, \"decimal\", :default=>BigDecimal.new(\"0.1005E3\")\n column :c6, \"blob\", :default=>Sequel::SQL::Blob.new(\"blah\")\n column :c7, \"date\", :default=>Date.parse(\"2008-10-29\")\n column :c8, \"datetime\", :default=>DateTime.parse(\"2008-10-29T10:20:30\")\n column :c9, \"time\", :default=>Time.parse(\"10:20:30\")\n column :c10, \"interval\", :default=>\"'6 weeks'\".lit\nend"
278
+ end
279
+
280
+ it "should handle converting PostgreSQL specific default formats" do
281
+ m = @d.method(:column_schema_to_ruby_default)
282
+ @d.meta_def(:database_type){:postgres}
283
+ p = lambda{|d,t| m.call(d,t,{})}
284
+ p["''::text", :string].should == ""
285
+ p["'\\a''b'::character varying", :string].should == "\\a'b"
286
+ p["'a'::bpchar", :string].should == "a"
287
+ p["(-1)", :integer].should == -1
288
+ p["(-1.0)", :float].should == -1.0
289
+ p['(-1.0)', :decimal].should == BigDecimal.new('-1.0')
290
+ p["'a'::bytea", :blob].should == 'a'.to_sequel_blob
291
+ p["'a'::bytea", :blob].should be_a_kind_of(Sequel::SQL::Blob)
292
+ p["'2009-10-29'::date", :date].should == Date.new(2009,10,29)
293
+ p["'2009-10-29 10:20:30.241343'::timestamp without time zone", :datetime].should == DateTime.parse('2009-10-29 10:20:30.241343')
294
+ p["'10:20:30'::time without time zone", :time].should == Time.parse('10:20:30')
295
+ end
296
+
297
+ it "should handle converting MySQL specific default formats" do
298
+ m = @d.method(:column_schema_to_ruby_default)
299
+ @d.meta_def(:database_type){:mysql}
300
+ p = lambda{|d,t| m.call(d,t,{})}
301
+ s = lambda{|d,t| m.call(d,t,{:same_db=>true})}
302
+ p["\\a'b", :string].should == "\\a'b"
303
+ p["a", :string].should == "a"
304
+ p["NULL", :string].should == "NULL"
305
+ p["-1", :float].should == -1.0
306
+ p['-1', :decimal].should == BigDecimal.new('-1.0')
307
+ p["2009-10-29", :date].should == Date.new(2009,10,29)
308
+ p["2009-10-29 10:20:30", :datetime].should == DateTime.parse('2009-10-29 10:20:30')
309
+ p["10:20:30", :time].should == Time.parse('10:20:30')
310
+ p["CURRENT_DATE", :date].should == nil
311
+ p["CURRENT_TIMESTAMP", :datetime].should == nil
312
+ s["CURRENT_DATE", :date].inspect.should == "\"CURRENT_DATE\".lit"
313
+ s["CURRENT_TIMESTAMP", :datetime].inspect.should == "\"CURRENT_TIMESTAMP\".lit"
213
314
  end
214
315
 
215
316
  it "should convert unknown database types to strings" do
@@ -95,8 +95,7 @@ describe Sequel::Model, "drop_table" do
95
95
  end
96
96
 
97
97
  describe Sequel::Model, "create_table!" do
98
-
99
- before(:each) do
98
+ before do
100
99
  MODEL_DB.reset
101
100
  @model = Class.new(Sequel::Model(:items))
102
101
  end
@@ -104,8 +103,25 @@ describe Sequel::Model, "create_table!" do
104
103
  it "should drop table if it exists and then create the table" do
105
104
  @model.should_receive(:drop_table).and_return(true)
106
105
  @model.should_receive(:create_table).and_return(true)
107
-
108
106
  @model.create_table!
109
107
  end
108
+ end
110
109
 
110
+ describe Sequel::Model, "create_table?" do
111
+ before do
112
+ MODEL_DB.reset
113
+ @model = Class.new(Sequel::Model(:items))
114
+ end
115
+
116
+ it "should not create the table if it already exists" do
117
+ @model.should_receive(:table_exists?).and_return(true)
118
+ @model.should_not_receive(:create_table)
119
+ @model.create_table?.should == nil
120
+ end
121
+
122
+ it "should create the table if it doesn't exist" do
123
+ @model.should_receive(:table_exists?).and_return(false)
124
+ @model.should_receive(:create_table).and_return(3)
125
+ @model.create_table?.should == 3
126
+ end
111
127
  end
@@ -14,6 +14,16 @@ describe "Serialization plugin" do
14
14
  end
15
15
  MODEL_DB.reset
16
16
  end
17
+
18
+ it "should allow setting additional serializable attributes via plugin :serialization call" do
19
+ @c.plugin :serialization, :yaml, :abc
20
+ @c.create(:abc => 1, :def=> 2)
21
+ MODEL_DB.sqls.last.should =~ /INSERT INTO items \((abc, def|def, abc)\) VALUES \(('--- 1\n', 2|2, '--- 1\n')\)/
22
+
23
+ @c.plugin :serialization, :marshal, :def
24
+ @c.create(:abc => 1, :def=> 1)
25
+ MODEL_DB.sqls.last.should =~ /INSERT INTO items \((abc, def|def, abc)\) VALUES \(('--- 1\n', 'BAhpBg==\n'|'BAhpBg==\n', '--- 1\n')\)/
26
+ end
17
27
 
18
28
  it "should allow serializing attributes to yaml" do
19
29
  @c.plugin :serialization, :yaml, :abc
@@ -26,6 +36,16 @@ describe "Serialization plugin" do
26
36
  ]
27
37
  end
28
38
 
39
+ it "serialization_format should be the serialization format used" do
40
+ @c.plugin :serialization, :yaml, :abc
41
+ @c.serialization_format.should == :yaml
42
+ end
43
+
44
+ it "serialized_columns should be the columns serialized" do
45
+ @c.plugin :serialization, :yaml, :abc
46
+ @c.serialized_columns.should == [:abc]
47
+ end
48
+
29
49
  it "should allow serializing attributes to marshal" do
30
50
  @c.plugin :serialization, :marshal, :abc
31
51
  @c.create(:abc => 1)
@@ -115,4 +135,12 @@ describe "Serialization plugin" do
115
135
  o.refresh
116
136
  o.deserialized_values.length.should == 0
117
137
  end
138
+
139
+ it "should raise an error if calling internal serialization methods with bad columns" do
140
+ @c.set_primary_key :id
141
+ @c.plugin :serialization
142
+ o = @c.load(:id => 1, :abc => "--- 1\n", :def => "--- hello\n")
143
+ lambda{o.send(:serialize_value, :abc, 1)}.should raise_error(Sequel::Error)
144
+ lambda{o.send(:deserialize_value, :abc, "--- hello\n")}.should raise_error(Sequel::Error)
145
+ end
118
146
  end
@@ -13,13 +13,37 @@ describe Sequel::Model, "#sti_key" do
13
13
  end
14
14
  @ds = StiTest.dataset
15
15
  MODEL_DB.reset
16
- end
16
+ end
17
+ after do
18
+ Object.send(:remove_const, :StiTestSub1)
19
+ Object.send(:remove_const, :StiTestSub2)
20
+ Object.send(:remove_const, :StiTest)
21
+ end
17
22
 
18
23
  specify "should have simple_table = nil" do
19
24
  StiTest.simple_table.should == nil
20
25
  StiTestSub1.simple_table.should == nil
21
26
  end
22
27
 
28
+ it "should allow changing the inheritance column via a plugin :single_table_inheritance call" do
29
+ StiTest.plugin :single_table_inheritance, :blah
30
+ Object.send(:remove_const, :StiTestSub1)
31
+ Object.send(:remove_const, :StiTestSub2)
32
+ class ::StiTestSub1 < StiTest
33
+ end
34
+ class ::StiTestSub2 < StiTest
35
+ end
36
+ def @ds.fetch_rows(sql)
37
+ yield({:blah=>'StiTest'})
38
+ yield({:blah=>'StiTestSub1'})
39
+ yield({:blah=>'StiTestSub2'})
40
+ end
41
+ StiTest.all.collect{|x| x.class}.should == [StiTest, StiTestSub1, StiTestSub2]
42
+ StiTest.dataset.sql.should == "SELECT * FROM sti_tests"
43
+ StiTestSub1.dataset.sql.should == "SELECT * FROM sti_tests WHERE (blah = 'StiTestSub1')"
44
+ StiTestSub2.dataset.sql.should == "SELECT * FROM sti_tests WHERE (blah = 'StiTestSub2')"
45
+ end
46
+
23
47
  it "should return rows with the correct class based on the polymorphic_key value" do
24
48
  def @ds.fetch_rows(sql)
25
49
  yield({:kind=>'StiTest'})
@@ -8,13 +8,8 @@ unless Sequel.const_defined?('Model')
8
8
  require 'sequel/model'
9
9
  end
10
10
 
11
- Sequel.virtual_row_instance_eval = true
12
-
13
- extensions = %w'string_date_time inflector pagination query pretty_table blank migration schema_dumper'
14
- plugins = {:hook_class_methods=>[], :schema=>[], :validation_class_methods=>[]}
15
-
16
- extensions.each{|e| require "sequel/extensions/#{e}"}
17
- plugins.each{|p, opts| Sequel::Model.plugin(p, *opts)}
11
+ Sequel.extension(*%w'string_date_time inflector pagination query pretty_table blank migration schema_dumper')
12
+ {:hook_class_methods=>[], :schema=>[], :validation_class_methods=>[]}.each{|p, opts| Sequel::Model.plugin(p, *opts)}
18
13
 
19
14
  class MockDataset < Sequel::Dataset
20
15
  def insert(*args)
@@ -0,0 +1,65 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe "Sequel::Plugins::TacticalEagerLoading" do
4
+ before do
5
+ class ::TaticalEagerLoadingModel < Sequel::Model
6
+ plugin :tactical_eager_loading
7
+ columns :id, :parent_id
8
+ many_to_one :parent, :class=>self
9
+ one_to_many :children, :class=>self, :key=>:parent_id
10
+ ds = dataset
11
+ def ds.fetch_rows(sql)
12
+ execute(sql)
13
+ where = @opts[:where]
14
+ if !where
15
+ yield(:id=>1, :parent_id=>101)
16
+ yield(:id=>2, :parent_id=>102)
17
+ yield(:id=>101, :parent_id=>nil)
18
+ yield(:id=>102, :parent_id=>nil)
19
+ elsif where.args.first.column == :id
20
+ Array(where.args.last).each do |x|
21
+ yield(:id=>x, :parent_id=>nil)
22
+ end
23
+ elsif where.args.first.column == :parent_id
24
+ Array(where.args.last).each do |x|
25
+ yield(:id=>x-100, :parent_id=>x) if x > 100
26
+ end
27
+ end
28
+ end
29
+ end
30
+ @c = ::TaticalEagerLoadingModel
31
+ @ds = TaticalEagerLoadingModel.dataset
32
+ MODEL_DB.reset
33
+ end
34
+ after do
35
+ Object.send(:remove_const, :TaticalEagerLoadingModel)
36
+ end
37
+
38
+ it "Dataset#all should set the retrieved_by and reteived_with attributes" do
39
+ ts = @c.all
40
+ ts.map{|x| [x.retrieved_by, x.retrieved_with]}.should == [[@ds,ts], [@ds,ts], [@ds,ts], [@ds,ts]]
41
+ end
42
+
43
+ it "Dataset#all shouldn't raise an error if a Sequel::Model instance is not returned" do
44
+ proc{@c.naked.all}.should_not raise_error
45
+ end
46
+
47
+ it "association getter methods should eagerly load the association if the association isn't cached" do
48
+ MODEL_DB.sqls.length.should == 0
49
+ ts = @c.all
50
+ MODEL_DB.sqls.length.should == 1
51
+ ts.map{|x| x.parent}.should == [ts[2], ts[3], nil, nil]
52
+ MODEL_DB.sqls.length.should == 2
53
+ ts.map{|x| x.children}.should == [[], [], [ts[0]], [ts[1]]]
54
+ MODEL_DB.sqls.length.should == 3
55
+ end
56
+
57
+ it "association getter methods should not eagerly load the association if the association is cached" do
58
+ MODEL_DB.sqls.length.should == 0
59
+ ts = @c.all
60
+ MODEL_DB.sqls.length.should == 1
61
+ ts.map{|x| x.parent}.should == [ts[2], ts[3], nil, nil]
62
+ @ds.should_not_receive(:eager_load)
63
+ ts.map{|x| x.parent}.should == [ts[2], ts[3], nil, nil]
64
+ end
65
+ end
@@ -116,7 +116,8 @@ end
116
116
 
117
117
  describe Sequel::Model do
118
118
  before do
119
- @c = Class.new(Sequel::Model) do
119
+ @c = Class.new(Sequel::Model)
120
+ @c.class_eval do
120
121
  columns :score
121
122
  validates_each :score do |o, a, v|
122
123
  o.errors[a] << 'too low' if v < 87
@@ -551,12 +552,14 @@ end
551
552
 
552
553
  context "Superclass validations" do
553
554
  before do
554
- @c1 = Class.new(Sequel::Model) do
555
+ @c1 = Class.new(Sequel::Model)
556
+ @c1.class_eval do
555
557
  columns :value
556
558
  validates_length_of :value, :minimum => 5
557
559
  end
558
560
 
559
- @c2 = Class.new(@c1) do
561
+ @c2 = Class.new(@c1)
562
+ @c2.class_eval do
560
563
  columns :value
561
564
  validates_format_of :value, :with => /^[a-z]+$/
562
565
  end
@@ -601,7 +604,8 @@ end
601
604
 
602
605
  context ".validates with block" do
603
606
  specify "should support calling .each" do
604
- @c = Class.new(Sequel::Model) do
607
+ @c = Class.new(Sequel::Model)
608
+ @c.class_eval do
605
609
  columns :vvv
606
610
  validates do
607
611
  each :vvv do |o, a, v|
@@ -938,7 +942,8 @@ end
938
942
 
939
943
  describe "Model#save" do
940
944
  before do
941
- @c = Class.new(Sequel::Model(:people)) do
945
+ @c = Class.new(Sequel::Model(:people))
946
+ @c.class_eval do
942
947
  columns :id
943
948
 
944
949
  validates_each :id do |o, a, v|
@@ -120,7 +120,9 @@ describe Sequel::Dataset do
120
120
  proc {@d.literal(true)}.should_not raise_error
121
121
  proc {@d.literal(false)}.should_not raise_error
122
122
  end
123
+ end
123
124
 
125
+ describe Sequel::Database do
124
126
  specify "should correctly escape strings" do
125
127
  INTEGRATION_DB.get("\\dingo".as(:a)) == "\\dingo"
126
128
  end
@@ -132,6 +134,14 @@ describe Sequel::Dataset do
132
134
  specify "should properly escape binary data" do
133
135
  INTEGRATION_DB.get("\1\2\3".to_sequel_blob.as(:a)) == "\1\2\3"
134
136
  end
137
+
138
+ specify "should have a working table_exists?" do
139
+ t = :basdfdsafsaddsaf
140
+ INTEGRATION_DB.drop_table(t) rescue nil
141
+ INTEGRATION_DB.table_exists?(t).should == false
142
+ INTEGRATION_DB.create_table(t){Integer :a}
143
+ INTEGRATION_DB.table_exists?(t).should == true
144
+ end
135
145
  end
136
146
 
137
147
  context "An SQLite dataset" do
@@ -208,7 +218,7 @@ describe "Simple Dataset operations in transactions" do
208
218
  specify "should insert correctly with a primary key specified inside a transaction" do
209
219
  INTEGRATION_DB.transaction do
210
220
  @ds.insert(:id=>100, :number=>20)
211
- sqls_should_be('Transaction.begin', /INSERT INTO items_insert_in_transaction \((number, id|id, number)\) VALUES \((100, 20|20, 100)\)/)
221
+ sqls_should_be('BEGIN', /INSERT INTO items_insert_in_transaction \((number, id|id, number)\) VALUES \((100, 20|20, 100)\)/)
212
222
  @ds.count.should == 1
213
223
  @ds.order(:id).all.should == [{:id=>100, :number=>20}]
214
224
  end
@@ -217,7 +227,7 @@ describe "Simple Dataset operations in transactions" do
217
227
  specify "should have insert return primary key value inside a transaction" do
218
228
  INTEGRATION_DB.transaction do
219
229
  @ds.insert(:number=>20).should == 1
220
- sqls_should_be('Transaction.begin', /INSERT INTO items_insert_in_transaction \(number\) VALUES \(20\)/)
230
+ sqls_should_be('BEGIN', /INSERT INTO items_insert_in_transaction \(number\) VALUES \(20\)/)
221
231
  @ds.count.should == 1
222
232
  @ds.order(:id).all.should == [{:id=>1, :number=>20}]
223
233
  end
@@ -228,16 +238,12 @@ describe "Dataset UNION, EXCEPT, and INTERSECT" do
228
238
  before do
229
239
  INTEGRATION_DB.create_table!(:i1){integer :number}
230
240
  INTEGRATION_DB.create_table!(:i2){integer :number}
231
- INTEGRATION_DB.create_table!(:i3){integer :number}
232
241
  @ds1 = INTEGRATION_DB[:i1]
233
242
  @ds1.insert(:number=>10)
234
243
  @ds1.insert(:number=>20)
235
244
  @ds2 = INTEGRATION_DB[:i2]
236
245
  @ds2.insert(:number=>10)
237
246
  @ds2.insert(:number=>30)
238
- @ds3 = INTEGRATION_DB[:i3]
239
- @ds3.insert(:number=>10)
240
- @ds3.insert(:number=>40)
241
247
  clear_sqls
242
248
  end
243
249
 
@@ -249,7 +255,34 @@ describe "Dataset UNION, EXCEPT, and INTERSECT" do
249
255
  end
250
256
  end
251
257
 
258
+ specify "should give the correct results for UNION, EXCEPT, and INTERSECT when used with ordering and limits" do
259
+ @ds1.insert(:number=>8)
260
+ @ds2.insert(:number=>9)
261
+ @ds1.insert(:number=>38)
262
+ @ds2.insert(:number=>39)
263
+
264
+ @ds1.order(:number.desc).union(@ds2).order(:number).map{|x| x[:number].to_s}.should == %w'8 9 10 20 30 38 39'
265
+ @ds1.union(@ds2.order(:number.desc)).order(:number).map{|x| x[:number].to_s}.should == %w'8 9 10 20 30 38 39'
266
+
267
+ @ds1.order(:number.desc).limit(1).union(@ds2).order(:number).map{|x| x[:number].to_s}.should == %w'9 10 30 38 39'
268
+ @ds2.order(:number.desc).limit(1).union(@ds1).order(:number).map{|x| x[:number].to_s}.should == %w'8 10 20 38 39'
269
+
270
+ @ds1.union(@ds2.order(:number).limit(1)).order(:number).map{|x| x[:number].to_s}.should == %w'8 9 10 20 38'
271
+ @ds2.union(@ds1.order(:number).limit(1)).order(:number).map{|x| x[:number].to_s}.should == %w'8 9 10 30 39'
272
+
273
+ @ds1.union(@ds2).limit(2).order(:number).map{|x| x[:number].to_s}.should == %w'8 9'
274
+ @ds2.union(@ds1).order(:number.desc).limit(2).map{|x| x[:number].to_s}.should == %w'39 38'
275
+
276
+ @ds1.order(:number.desc).limit(2).union(@ds2.order(:number.desc).limit(2)).order(:number).limit(3).map{|x| x[:number].to_s}.should == %w'20 30 38'
277
+ @ds2.order(:number).limit(2).union(@ds1.order(:number).limit(2)).order(:number.desc).limit(3).map{|x| x[:number].to_s}.should == %w'10 9 8'
278
+ end
279
+
252
280
  specify "should give the correct results for compound UNION, EXCEPT, and INTERSECT" do
281
+ INTEGRATION_DB.create_table!(:i3){integer :number}
282
+ @ds3 = INTEGRATION_DB[:i3]
283
+ @ds3.insert(:number=>10)
284
+ @ds3.insert(:number=>40)
285
+
253
286
  @ds1.union(@ds2).union(@ds3).order(:number).map{|x| x[:number].to_s}.should == %w'10 20 30 40'
254
287
  @ds1.union(@ds2.union(@ds3)).order(:number).map{|x| x[:number].to_s}.should == %w'10 20 30 40'
255
288
  unless defined?(Sequel::Dataset::UnsupportedIntersectExcept) and @ds1.class.ancestors.include?(Sequel::Dataset::UnsupportedIntersectExcept)