sequel 3.0.0 → 3.1.0

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