sequel 4.23.0 → 4.24.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +26 -0
  3. data/Rakefile +1 -1
  4. data/doc/release_notes/4.24.0.txt +99 -0
  5. data/doc/sql.rdoc +10 -1
  6. data/lib/sequel/adapters/jdbc.rb +7 -0
  7. data/lib/sequel/adapters/jdbc/cubrid.rb +1 -1
  8. data/lib/sequel/adapters/jdbc/db2.rb +1 -1
  9. data/lib/sequel/adapters/jdbc/derby.rb +1 -1
  10. data/lib/sequel/adapters/jdbc/h2.rb +1 -1
  11. data/lib/sequel/adapters/jdbc/hsqldb.rb +1 -1
  12. data/lib/sequel/adapters/jdbc/mssql.rb +1 -1
  13. data/lib/sequel/adapters/jdbc/mysql.rb +2 -2
  14. data/lib/sequel/adapters/jdbc/oracle.rb +1 -1
  15. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +1 -1
  16. data/lib/sequel/adapters/jdbc/sqlite.rb +1 -1
  17. data/lib/sequel/adapters/postgres.rb +14 -6
  18. data/lib/sequel/adapters/shared/mssql.rb +1 -1
  19. data/lib/sequel/core.rb +12 -1
  20. data/lib/sequel/database/connecting.rb +1 -2
  21. data/lib/sequel/extensions/pg_inet_ops.rb +200 -0
  22. data/lib/sequel/plugins/association_pks.rb +63 -18
  23. data/lib/sequel/plugins/auto_validations.rb +43 -9
  24. data/lib/sequel/plugins/class_table_inheritance.rb +236 -179
  25. data/lib/sequel/plugins/update_refresh.rb +26 -1
  26. data/lib/sequel/plugins/validation_helpers.rb +7 -2
  27. data/lib/sequel/version.rb +1 -1
  28. data/spec/adapters/oracle_spec.rb +1 -1
  29. data/spec/adapters/postgres_spec.rb +61 -0
  30. data/spec/core_extensions_spec.rb +5 -1
  31. data/spec/extensions/association_pks_spec.rb +73 -1
  32. data/spec/extensions/auto_validations_spec.rb +34 -0
  33. data/spec/extensions/class_table_inheritance_spec.rb +58 -54
  34. data/spec/extensions/pg_inet_ops_spec.rb +101 -0
  35. data/spec/extensions/spec_helper.rb +5 -5
  36. data/spec/extensions/update_refresh_spec.rb +12 -0
  37. data/spec/extensions/validation_helpers_spec.rb +7 -0
  38. data/spec/integration/plugin_test.rb +48 -13
  39. metadata +6 -4
  40. data/lib/sequel/adapters/db2.rb +0 -229
  41. data/lib/sequel/adapters/dbi.rb +0 -102
@@ -19,6 +19,20 @@ module Sequel
19
19
  #
20
20
  # # Make the Album class refresh after update
21
21
  # Album.plugin :update_refresh
22
+ #
23
+ # As a performance optimisation, if you know only specific
24
+ # columns will have changed, you can specify them to the
25
+ # +columns+ option. This can be a performance gain if it
26
+ # would avoid pointlessly comparing many other columns.
27
+ # Note that this option currently only has an effect if the
28
+ # dataset # supports RETURNING.
29
+ #
30
+ # # Only include the artist column in RETURNING
31
+ # Album.plugin :update_refresh, :columns => :artist
32
+ #
33
+ # # Only include the artist and title columns in RETURNING
34
+ # Album.plugin :update_refresh, :columns => [ :artist, :title ]
35
+ #
22
36
  module UpdateRefresh
23
37
  module InstanceMethods
24
38
  def after_update
@@ -33,7 +47,7 @@ module Sequel
33
47
  def _update_without_checking(columns)
34
48
  ds = _update_dataset
35
49
  if ds.supports_returning?(:update)
36
- ds = ds.opts[:returning] ? ds : ds.returning
50
+ ds = ds.opts[:returning] ? ds : ds.returning(*self.class.update_refresh_columns)
37
51
  rows = ds.update(columns)
38
52
  n = rows.length
39
53
  if n == 1
@@ -45,6 +59,17 @@ module Sequel
45
59
  end
46
60
  end
47
61
  end
62
+
63
+ module ClassMethods
64
+ attr_reader :update_refresh_columns
65
+ end
66
+
67
+ def self.configure(model, opts=OPTS)
68
+ model.instance_eval do
69
+ @update_refresh_columns = Array(opts[:columns]) || []
70
+ end
71
+ end
72
+
48
73
  end
49
74
  end
50
75
  end
@@ -30,6 +30,9 @@ module Sequel
30
30
  # Sequel will attempt to insert a NULL value into the database, instead of using the
31
31
  # database's default.
32
32
  # :allow_nil :: Whether to skip the validation if the value is nil.
33
+ # :from :: Set to :values to pull column values from the values hash instead of calling the related method.
34
+ # Allows for setting up methods on the underlying column values, in the cases where the model
35
+ # transforms the underlying value before returning it, such as when using serialization.
33
36
  # :message :: The message to use. Can be a string which is used directly, or a
34
37
  # proc which is called. If the validation method takes a argument before the array of attributes,
35
38
  # that argument is passed as an argument to the proc.
@@ -227,6 +230,7 @@ module Sequel
227
230
  opts = Hash[opts].merge!(atts.pop)
228
231
  end
229
232
  message = validation_error_message(opts[:message])
233
+ from_values = opts[:from] == :values
230
234
  where = opts[:where]
231
235
  atts.each do |a|
232
236
  arr = Array(a)
@@ -236,7 +240,7 @@ module Sequel
236
240
  ds = if where
237
241
  where.call(ds, self, arr)
238
242
  else
239
- vals = arr.map{|x| get_column_value(x)}
243
+ vals = arr.map{|x| from_values ? values[x] : get_column_value(x)}
240
244
  next if vals.any?(&:nil?)
241
245
  ds.where(arr.zip(vals))
242
246
  end
@@ -265,9 +269,10 @@ module Sequel
265
269
  # an error message for that attributes.
266
270
  def validatable_attributes(atts, opts)
267
271
  am, an, ab, m = opts.values_at(:allow_missing, :allow_nil, :allow_blank, :message)
272
+ from_values = opts[:from] == :values
268
273
  Array(atts).each do |a|
269
274
  next if am && !values.has_key?(a)
270
- v = get_column_value(a)
275
+ v = from_values ? values[a] : get_column_value(a)
271
276
  next if an && v.nil?
272
277
  next if ab && v.respond_to?(:blank?) && v.blank?
273
278
  if message = yield(a, v, m)
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 4
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 23
6
+ MINOR = 24
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
@@ -150,7 +150,7 @@ describe "An Oracle database" do
150
150
  {:date_created=>nil, :name => 'abc', :value => 123}
151
151
  ]
152
152
 
153
- @d.filter(:name => 'abc').limit(1).to_a.must_equal [
153
+ @d.filter(:name => 'abc').order(:value).limit(1).to_a.must_equal [
154
154
  {:date_created=>nil, :name => 'abc', :value => 123}
155
155
  ]
156
156
 
@@ -1913,6 +1913,19 @@ if DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG && DB.server_versio
1913
1913
  i = 0
1914
1914
  @db.listen('foo2', :timeout=>0.001, :loop=>proc{i+=1; throw :stop if i > 3}){|ev, pid, payload| called = true}.must_equal nil
1915
1915
  i.must_equal 4
1916
+
1917
+ called = false
1918
+ i = 0
1919
+ @db.listen('foo2', :timeout=>proc{i+=1; 0.001}){|ev, pid, payload| called = true}.must_equal nil
1920
+ called.must_equal false
1921
+ i.must_equal 1
1922
+
1923
+ i = 0
1924
+ t = 0
1925
+ @db.listen('foo2', :timeout=>proc{t+=1; 0.001}, :loop=>proc{i+=1; throw :stop if i > 3}){|ev, pid, payload| called = true}.must_equal nil
1926
+ called.must_equal false
1927
+ t.must_equal 4
1928
+
1916
1929
  end unless RUBY_PLATFORM =~ /mingw/ # Ruby freezes on this spec on this platform/version
1917
1930
  end
1918
1931
  end
@@ -2940,6 +2953,54 @@ describe 'PostgreSQL inet/cidr types' do
2940
2953
  c.create(:i=>@ipv6, :c=>@ipv6nm).values.values_at(:i, :c).must_equal [@ipv6, @ipv6nm]
2941
2954
  end
2942
2955
  end
2956
+
2957
+ it 'operations/functions with pg_inet_ops' do
2958
+ Sequel.extension :pg_inet_ops
2959
+
2960
+ @db.get(Sequel.pg_inet_op('1.2.3.4') << '1.2.3.0/24').must_equal true
2961
+ @db.get(Sequel.pg_inet_op('1.2.3.4') << '1.2.3.4/32').must_equal false
2962
+ @db.get(Sequel.pg_inet_op('1.2.3.4') << '1.2.2.0/24').must_equal false
2963
+ @db.get(Sequel.pg_inet_op('1.2.3.4').contained_by('1.2.3.0/24')).must_equal true
2964
+ @db.get(Sequel.pg_inet_op('1.2.3.4').contained_by('1.2.3.4/32')).must_equal false
2965
+ @db.get(Sequel.pg_inet_op('1.2.3.4').contained_by('1.2.2.0/24')).must_equal false
2966
+
2967
+ @db.get(Sequel.pg_inet_op('1.2.3.4').contained_by_or_equals('1.2.3.0/24')).must_equal true
2968
+ @db.get(Sequel.pg_inet_op('1.2.3.4').contained_by_or_equals('1.2.3.4/32')).must_equal true
2969
+ @db.get(Sequel.pg_inet_op('1.2.3.4').contained_by_or_equals('1.2.2.0/24')).must_equal false
2970
+
2971
+ @db.get(Sequel.pg_inet_op('1.2.3.0/24') >> '1.2.3.4').must_equal true
2972
+ @db.get(Sequel.pg_inet_op('1.2.3.0/24') >> '1.2.2.4').must_equal false
2973
+ @db.get(Sequel.pg_inet_op('1.2.3.0/24').contains('1.2.3.4')).must_equal true
2974
+ @db.get(Sequel.pg_inet_op('1.2.3.0/24').contains('1.2.2.4')).must_equal false
2975
+
2976
+ @db.get(Sequel.pg_inet_op('1.2.3.0/24').contains_or_equals('1.2.3.4')).must_equal true
2977
+ @db.get(Sequel.pg_inet_op('1.2.3.0/24').contains_or_equals('1.2.2.4')).must_equal false
2978
+ @db.get(Sequel.pg_inet_op('1.2.3.0/24').contains_or_equals('1.2.3.0/24')).must_equal true
2979
+
2980
+ @db.get(Sequel.pg_inet_op('1.2.3.0/32') + 1).must_equal IPAddr.new('1.2.3.1/32')
2981
+ @db.get(Sequel.pg_inet_op('1.2.3.1/32') - 1).must_equal IPAddr.new('1.2.3.0/32')
2982
+ @db.get(Sequel.pg_inet_op('1.2.3.1/32') - '1.2.3.0/32').must_equal 1
2983
+ @db.get(Sequel.pg_inet_op('1.2.3.0/32') & '1.2.0.4/32').must_equal IPAddr.new('1.2.0.0/32')
2984
+ @db.get(Sequel.pg_inet_op('1.2.0.0/32') | '0.0.3.4/32').must_equal IPAddr.new('1.2.3.4/32')
2985
+ @db.get(~Sequel.pg_inet_op('0.0.0.0/32')).must_equal IPAddr.new('255.255.255.255/32')
2986
+
2987
+ @db.get(Sequel.pg_inet_op('1.2.3.4/24').abbrev).must_equal '1.2.3.4/24'
2988
+ @db.get(Sequel.pg_inet_op('1.2.3.4/24').broadcast).must_equal IPAddr.new('1.2.3.255/24')
2989
+ @db.get(Sequel.pg_inet_op('1.2.3.4/24').family).must_equal 4
2990
+ @db.get(Sequel.pg_inet_op('1.2.3.4/24').host).must_equal '1.2.3.4'
2991
+ @db.get(Sequel.pg_inet_op('1.2.3.4/24').hostmask).must_equal IPAddr.new('0.0.0.255/32')
2992
+ @db.get(Sequel.pg_inet_op('1.2.3.4/24').masklen).must_equal 24
2993
+ @db.get(Sequel.pg_inet_op('1.2.3.4/24').netmask).must_equal IPAddr.new('255.255.255.0/32')
2994
+ @db.get(Sequel.pg_inet_op('1.2.3.4/24').network).must_equal IPAddr.new('1.2.3.0/24')
2995
+ @db.get(Sequel.pg_inet_op('1.2.3.4/24').set_masklen(16)).must_equal IPAddr.new('1.2.3.4/16')
2996
+ @db.get(Sequel.pg_inet_op('1.2.3.4/32').text).must_equal '1.2.3.4/32'
2997
+
2998
+ if @db.server_version >= 90400
2999
+ @db.get(Sequel.pg_inet_op('1.2.3.0/24').contains_or_contained_by('1.2.0.0/16')).must_equal true
3000
+ @db.get(Sequel.pg_inet_op('1.2.0.0/16').contains_or_contained_by('1.2.3.0/24')).must_equal true
3001
+ @db.get(Sequel.pg_inet_op('1.3.0.0/16').contains_or_contained_by('1.2.3.0/24')).must_equal false
3002
+ end
3003
+ end
2943
3004
  end
2944
3005
 
2945
3006
  describe 'PostgreSQL range types' do
@@ -630,7 +630,7 @@ end
630
630
  describe "Postgres extensions integration" do
631
631
  before do
632
632
  @db = Sequel.mock
633
- Sequel.extension(:pg_array, :pg_array_ops, :pg_hstore, :pg_hstore_ops, :pg_json, :pg_json_ops, :pg_range, :pg_range_ops, :pg_row, :pg_row_ops)
633
+ Sequel.extension(:pg_array, :pg_array_ops, :pg_hstore, :pg_hstore_ops, :pg_json, :pg_json_ops, :pg_range, :pg_range_ops, :pg_row, :pg_row_ops, :pg_inet_ops)
634
634
  end
635
635
 
636
636
  it "Symbol#pg_array should return an ArrayOp" do
@@ -645,6 +645,10 @@ describe "Postgres extensions integration" do
645
645
  @db.literal(:a.hstore['a']).must_equal "(a -> 'a')"
646
646
  end
647
647
 
648
+ it "Symbol#pg_inet should return an InetOp" do
649
+ @db.literal(:a.pg_inet.contains(:b)).must_equal "(a >> b)"
650
+ end
651
+
648
652
  it "Symbol#pg_json should return an JSONOp" do
649
653
  @db.literal(:a.pg_json[%w'a b']).must_equal "(a #> ARRAY['a','b'])"
650
654
  @db.literal(:a.pg_json.extract('a')).must_equal "json_extract_path(a, 'a')"
@@ -2,8 +2,10 @@ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
2
 
3
3
  describe "Sequel::Plugins::AssociationPks" do
4
4
  before do
5
- @db = Sequel.mock(:fetch=>proc do |sql|
5
+ @db = Sequel.mock(:autoid=>1, :fetch=>proc do |sql|
6
6
  case sql
7
+ when /SELECT \* FROM (?:artists|albums) WHERE \(id = (\d+)\) LIMIT 1/
8
+ {:id=>$1.to_i}
7
9
  when "SELECT id FROM albums WHERE (albums.artist_id = 1)"
8
10
  [{:id=>1}, {:id=>2}, {:id=>3}]
9
11
  when /SELECT tag_id FROM albums_tags WHERE \(album_id = (\d)\)/
@@ -278,4 +280,74 @@ describe "Sequel::Plugins::AssociationPks" do
278
280
  Hash[match[1].split(', ').zip(match[2].split(', '))].must_equal("first"=>"12", "last"=>"12", "album_id"=>"2")
279
281
  sqls.length.must_equal 6
280
282
  end
283
+
284
+ it "should handle delaying setting of association pks until after saving for new objects, if :delay plugin option is used" do
285
+ @Artist.one_to_many :albums, :clone=>:albums, :delay_pks=>true
286
+ @Album.many_to_many :tags, :clone=>:tags, :delay_pks=>true
287
+
288
+ ar = @Artist.new
289
+ ar.album_pks.must_equal []
290
+ ar.album_pks = [1,2,3]
291
+ ar.album_pks.must_equal [1,2,3]
292
+ @db.sqls.must_equal []
293
+
294
+ ar.save
295
+ @db.sqls.must_equal [
296
+ "INSERT INTO artists DEFAULT VALUES",
297
+ "UPDATE albums SET artist_id = 1 WHERE (id IN (1, 2, 3))",
298
+ "UPDATE albums SET artist_id = NULL WHERE ((albums.artist_id = 1) AND (id NOT IN (1, 2, 3)))",
299
+ "SELECT * FROM artists WHERE (id = 1) LIMIT 1",
300
+ ]
301
+
302
+ al = @Album.new
303
+ al.tag_pks.must_equal []
304
+ al.tag_pks = [1,2]
305
+ al.tag_pks.must_equal [1, 2]
306
+ @db.sqls.must_equal []
307
+
308
+ al.save
309
+ @db.sqls.must_equal [
310
+ "INSERT INTO albums DEFAULT VALUES",
311
+ "DELETE FROM albums_tags WHERE ((album_id = 2) AND (tag_id NOT IN (1, 2)))",
312
+ "SELECT tag_id FROM albums_tags WHERE (album_id = 2)",
313
+ "BEGIN",
314
+ "INSERT INTO albums_tags (album_id, tag_id) VALUES (2, 1)",
315
+ "COMMIT",
316
+ "SELECT * FROM albums WHERE (id = 2) LIMIT 1"
317
+ ]
318
+ end
319
+
320
+ it "should handle delaying setting of association pks until after saving for existing objects, if :delay=>:all plugin option is used" do
321
+ @Artist.one_to_many :albums, :clone=>:albums, :delay_pks=>:always
322
+ @Album.many_to_many :tags, :clone=>:tags, :delay_pks=>:always
323
+
324
+ ar = @Artist.load(:id=>1)
325
+ ar.album_pks.must_equal [1,2,3]
326
+ @db.sqls
327
+ ar.album_pks = [2,4]
328
+ ar.album_pks.must_equal [2,4]
329
+ @db.sqls.must_equal []
330
+
331
+ ar.save_changes
332
+ @db.sqls.must_equal [
333
+ "UPDATE albums SET artist_id = 1 WHERE (id IN (2, 4))",
334
+ "UPDATE albums SET artist_id = NULL WHERE ((albums.artist_id = 1) AND (id NOT IN (2, 4)))"
335
+ ]
336
+
337
+ al = @Album.load(:id=>1)
338
+ al.tag_pks.must_equal [1,2]
339
+ @db.sqls
340
+ al.tag_pks = [2,3]
341
+ al.tag_pks.must_equal [2,3]
342
+ @db.sqls.must_equal []
343
+
344
+ al.save_changes
345
+ @db.sqls.must_equal [
346
+ "DELETE FROM albums_tags WHERE ((album_id = 1) AND (tag_id NOT IN (2, 3)))",
347
+ "SELECT tag_id FROM albums_tags WHERE (album_id = 1)",
348
+ "BEGIN",
349
+ "INSERT INTO albums_tags (album_id, tag_id) VALUES (1, 3)",
350
+ "COMMIT",
351
+ ]
352
+ end
281
353
  end
@@ -48,6 +48,25 @@ describe "Sequel::Plugins::AutoValidations" do
48
48
  @m.errors.must_equal(:name=>["is longer than 50 characters"])
49
49
  end
50
50
 
51
+ it "should handle simple unique indexes correctly" do
52
+ def (@c.db).indexes(t, *)
53
+ raise if t.is_a?(Sequel::Dataset)
54
+ return [] if t != :test
55
+ {:a=>{:columns=>[:name], :unique=>true}}
56
+ end
57
+ @c.plugin :auto_validations
58
+ @m.set(:name=>'foo', :d=>Date.today)
59
+ @m.valid?.must_equal false
60
+ @m.errors.must_equal(:name=>["is already taken"])
61
+ end
62
+
63
+ it "should validate using the underlying column values" do
64
+ @c.send(:define_method, :name){super() * 2}
65
+ @c.db.fetch = {:v=>0}
66
+ @m.set(:d=>Date.today, :num=>1, :name=>'b'*26)
67
+ @m.valid?.must_equal true
68
+ end
69
+
51
70
  it "should handle databases that don't support index parsing" do
52
71
  def (@m.db).supports_index_parsing?() false end
53
72
  @m.model.send(:setup_auto_validations)
@@ -155,4 +174,19 @@ describe "Sequel::Plugins::AutoValidations" do
155
174
  @c.set_dataset(@c.db[:foo])
156
175
  @c.new.valid?.must_equal true
157
176
  end
177
+
178
+ it "should support setting validator options" do
179
+ sc = Class.new(@c)
180
+ sc.plugin :auto_validations, :max_length_opts=> {:message=> 'ml_message'}, :schema_types_opts=> {:message=> 'st_message'}, :explicit_not_null_opts=> {:message=> 'enn_message'}, :unique_opts=> {:message=> 'u_message'}
181
+
182
+ @m = sc.new
183
+ @m.set(:name=>'a'*51, :d => '/', :nnd => nil, :num=>1)
184
+ @m.valid?.must_equal false
185
+ @m.errors.must_equal(:name=>["ml_message"], :d=>["st_message"], :nnd=>["enn_message"])
186
+
187
+ @m = sc.new
188
+ @m.set(:name=>1, :num=>1, :d=>Date.today)
189
+ @m.valid?.must_equal false
190
+ @m.errors.must_equal([:name, :num]=>["u_message"])
191
+ end
158
192
  end
@@ -35,6 +35,8 @@ describe "class_table_inheritance plugin" do
35
35
  end
36
36
  class ::Executive < Manager
37
37
  end
38
+ class ::Ceo < Executive
39
+ end
38
40
  class ::Staff < Employee
39
41
  many_to_one :manager
40
42
  end
@@ -42,57 +44,59 @@ describe "class_table_inheritance plugin" do
42
44
  @db.sqls
43
45
  end
44
46
  after do
47
+ Object.send(:remove_const, :Ceo)
45
48
  Object.send(:remove_const, :Executive)
46
49
  Object.send(:remove_const, :Manager)
47
50
  Object.send(:remove_const, :Staff)
48
51
  Object.send(:remove_const, :Employee)
49
52
  end
50
53
 
51
- it "should have simple_table = nil for all classes" do
52
- Employee.simple_table.must_equal nil
54
+ it "should have simple_table = nil for all subclasses" do
53
55
  Manager.simple_table.must_equal nil
54
56
  Executive.simple_table.must_equal nil
57
+ Ceo.simple_table.must_equal nil
55
58
  Staff.simple_table.must_equal nil
56
59
  end
57
60
 
58
61
  it "should have working row_proc if using set_dataset in subclass to remove columns" do
59
62
  Manager.set_dataset(Manager.dataset.select(*(Manager.columns - [:blah])))
60
- Manager.dataset._fetch = {:id=>1, :kind=>'Executive'}
61
- Manager[1].must_equal Executive.load(:id=>1, :kind=>'Executive')
63
+ Manager.dataset._fetch = {:id=>1, :kind=>'Ceo'}
64
+ Manager[1].must_equal Ceo.load(:id=>1, :kind=>'Ceo')
62
65
  end
63
66
 
64
67
  it "should use a joined dataset in subclasses" do
65
- Employee.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind FROM employees'
68
+ Employee.dataset.sql.must_equal 'SELECT * FROM employees'
66
69
  Manager.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)'
67
70
  Executive.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id)'
71
+ Ceo.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id) WHERE (employees.kind IN (\'Ceo\'))'
68
72
  Staff.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id)'
69
73
  end
70
74
 
71
75
  it "should return rows with the correct class based on the polymorphic_key value" do
72
- @ds._fetch = [{:kind=>'Employee'}, {:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Staff'}]
73
- Employee.all.collect{|x| x.class}.must_equal [Employee, Manager, Executive, Staff]
76
+ @ds._fetch = [{:kind=>'Employee'}, {:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Ceo'}, {:kind=>'Staff'}]
77
+ Employee.all.collect{|x| x.class}.must_equal [Employee, Manager, Executive, Ceo, Staff]
74
78
  end
75
79
 
76
80
  it "should return rows with the correct class based on the polymorphic_key value for subclasses" do
77
- Manager.dataset._fetch = [{:kind=>'Manager'}, {:kind=>'Executive'}]
78
- Manager.all.collect{|x| x.class}.must_equal [Manager, Executive]
81
+ Manager.dataset._fetch = [{:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Ceo'}]
82
+ Manager.all.collect{|x| x.class}.must_equal [Manager, Executive, Ceo]
79
83
  end
80
84
 
81
85
  it "should have refresh return all columns in subclass after loading from superclass" do
82
- Employee.dataset._fetch = [{:id=>1, :name=>'A', :kind=>'Executive'}]
83
- Executive.instance_dataset._fetch = [{:id=>1, :name=>'A', :kind=>'Executive', :num_staff=>3, :num_managers=>2}]
86
+ Employee.dataset._fetch = [{:id=>1, :name=>'A', :kind=>'Ceo'}]
87
+ Ceo.instance_dataset._fetch = [{:id=>1, :name=>'A', :kind=>'Ceo', :num_staff=>3, :num_managers=>2}]
84
88
  a = Employee.first
85
- a.class.must_equal Executive
86
- a.values.must_equal(:id=>1, :name=>'A', :kind=>'Executive')
87
- a.refresh.values.must_equal(:id=>1, :name=>'A', :kind=>'Executive', :num_staff=>3, :num_managers=>2)
88
- @db.sqls.must_equal ["SELECT employees.id, employees.name, employees.kind FROM employees LIMIT 1",
89
- "SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id) WHERE (executives.id = 1) LIMIT 1"]
89
+ a.class.must_equal Ceo
90
+ a.values.must_equal(:id=>1, :name=>'A', :kind=>'Ceo')
91
+ a.refresh.values.must_equal(:id=>1, :name=>'A', :kind=>'Ceo', :num_staff=>3, :num_managers=>2)
92
+ @db.sqls.must_equal ["SELECT * FROM employees LIMIT 1",
93
+ "SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id) WHERE ((employees.kind IN ('Ceo')) AND (executives.id = 1)) LIMIT 1"]
90
94
  end
91
95
 
92
96
  it "should return rows with the current class if cti_key is nil" do
93
97
  Employee.plugin(:class_table_inheritance)
94
- Employee.dataset._fetch = [{:kind=>'Employee'}, {:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Staff'}]
95
- Employee.all.collect{|x| x.class}.must_equal [Employee, Employee, Employee, Employee]
98
+ Employee.dataset._fetch = [{:kind=>'Employee'}, {:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Ceo'}, {:kind=>'Staff'}]
99
+ Employee.all.collect{|x| x.class}.must_equal [Employee, Employee, Employee, Employee, Employee]
96
100
  end
97
101
 
98
102
  it "should return rows with the current class if cti_key is nil in subclasses" do
@@ -106,15 +110,17 @@ describe "class_table_inheritance plugin" do
106
110
  end
107
111
 
108
112
  it "should handle a model map with integer values" do
109
- Employee.plugin(:class_table_inheritance, :key=>:kind, :model_map=>{0=>:Employee, 1=>:Manager, 2=>:Executive})
113
+ Employee.plugin(:class_table_inheritance, :key=>:kind, :model_map=>{0=>:Employee, 1=>:Manager, 2=>:Executive, 3=>:Ceo})
114
+ Object.send(:remove_const, :Ceo)
110
115
  Object.send(:remove_const, :Executive)
111
116
  Object.send(:remove_const, :Manager)
112
117
  class ::Manager < Employee; end
113
118
  class ::Executive < Manager; end
114
- Employee.dataset._fetch = [{:kind=>nil},{:kind=>0},{:kind=>1}, {:kind=>2}]
115
- Employee.all.collect{|x| x.class}.must_equal [Employee, Employee, Manager, Executive]
116
- Manager.dataset._fetch = [{:kind=>nil},{:kind=>0},{:kind=>1}, {:kind=>2}]
117
- Manager.all.collect{|x| x.class}.must_equal [Manager, Employee, Manager, Executive]
119
+ class ::Ceo < Executive; end
120
+ Employee.dataset._fetch = [{:kind=>nil},{:kind=>0},{:kind=>1}, {:kind=>2}, {:kind=>3}]
121
+ Employee.all.collect{|x| x.class}.must_equal [Employee, Employee, Manager, Executive, Ceo]
122
+ Manager.dataset._fetch = [{:kind=>nil},{:kind=>0},{:kind=>1}, {:kind=>2}, {:kind=>3}]
123
+ Manager.all.collect{|x| x.class}.must_equal [Manager, Employee, Manager, Executive, Ceo]
118
124
  end
119
125
 
120
126
  it "should fallback to the main class if the given class does not exist" do
@@ -123,8 +129,8 @@ describe "class_table_inheritance plugin" do
123
129
  end
124
130
 
125
131
  it "should fallback to the main class if the given class does not exist in subclasses" do
126
- Manager.dataset._fetch = [{:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Blah'}]
127
- Manager.all.collect{|x| x.class}.must_equal [Manager, Executive, Manager]
132
+ Manager.dataset._fetch = [{:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Ceo'}, {:kind=>'Blah'}]
133
+ Manager.all.collect{|x| x.class}.must_equal [Manager, Executive, Ceo, Manager]
128
134
  end
129
135
 
130
136
  it "should sets the model class name for the key when creating new parent class records" do
@@ -133,8 +139,8 @@ describe "class_table_inheritance plugin" do
133
139
  end
134
140
 
135
141
  it "should sets the model class name for the key when creating new subclass records" do
136
- Executive.create
137
- @db.sqls.must_equal ["INSERT INTO employees (kind) VALUES ('Executive')",
142
+ Ceo.create
143
+ @db.sqls.must_equal ["INSERT INTO employees (kind) VALUES ('Ceo')",
138
144
  "INSERT INTO managers (id) VALUES (1)",
139
145
  "INSERT INTO executives (id) VALUES (1)"]
140
146
  end
@@ -163,45 +169,42 @@ describe "class_table_inheritance plugin" do
163
169
  @db.sqls.must_equal ["INSERT INTO employees (kind) VALUES ('Employee')"]
164
170
  end
165
171
 
166
- it "should raise an error if attempting to create an anonymous subclass" do
167
- proc{Class.new(Manager)}.must_raise(Sequel::Error)
168
- end
169
-
170
172
  it "should allow specifying a map of names to tables to override implicit mapping" do
171
173
  Manager.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)'
172
174
  Staff.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id)'
173
175
  end
174
176
 
175
177
  it "should lazily load attributes for columns in subclass tables" do
176
- Manager.instance_dataset._fetch = Manager.dataset._fetch = {:id=>1, :name=>'J', :kind=>'Executive', :num_staff=>2}
178
+ Manager.instance_dataset._fetch = Manager.dataset._fetch = {:id=>1, :name=>'J', :kind=>'Ceo', :num_staff=>2}
177
179
  m = Manager[1]
178
180
  @db.sqls.must_equal ['SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id) WHERE (managers.id = 1) LIMIT 1']
179
- Executive.instance_dataset._fetch = Executive.dataset._fetch = {:num_managers=>3}
181
+ @db.fetch = {:num_managers=>3}
182
+ m.must_be_kind_of Ceo
180
183
  m.num_managers.must_equal 3
181
184
  @db.sqls.must_equal ['SELECT executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id) WHERE (executives.id = 1) LIMIT 1']
182
- m.values.must_equal(:id=>1, :name=>'J', :kind=>'Executive', :num_staff=>2, :num_managers=>3)
185
+ m.values.must_equal(:id=>1, :name=>'J', :kind=>'Ceo', :num_staff=>2, :num_managers=>3)
183
186
  end
184
187
 
185
188
  it "should lazily load columns in middle classes correctly when loaded from parent class" do
186
- Employee.dataset._fetch = {:id=>1, :kind=>'Executive'}
189
+ Employee.dataset._fetch = {:id=>1, :kind=>'Ceo'}
187
190
  Manager.dataset._fetch = {:num_staff=>2}
188
191
  e = Employee[1]
189
- e.must_be_kind_of(Executive)
190
- @db.sqls.must_equal ["SELECT employees.id, employees.name, employees.kind FROM employees WHERE (id = 1) LIMIT 1"]
192
+ e.must_be_kind_of(Ceo)
193
+ @db.sqls.must_equal ["SELECT * FROM employees WHERE (id = 1) LIMIT 1"]
191
194
  e.num_staff.must_equal 2
192
195
  @db.sqls.must_equal ["SELECT managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id) WHERE (managers.id = 1) LIMIT 1"]
193
196
  end
194
197
 
195
198
  it "should eagerly load lazily columns in subclasses when loaded from parent class" do
196
- Employee.dataset._fetch = {:id=>1, :kind=>'Executive'}
199
+ Employee.dataset._fetch = {:id=>1, :kind=>'Ceo'}
197
200
  Manager.dataset._fetch = {:id=>1, :num_staff=>2}
198
- Executive.dataset._fetch = {:id=>1, :num_managers=>3}
201
+ @db.fetch = {:id=>1, :num_managers=>3}
199
202
  e = Employee.all.first
200
- e.must_be_kind_of(Executive)
201
- @db.sqls.must_equal ["SELECT employees.id, employees.name, employees.kind FROM employees"]
202
- e.num_staff#.must_equal 2
203
+ e.must_be_kind_of(Ceo)
204
+ @db.sqls.must_equal ["SELECT * FROM employees"]
205
+ e.num_staff.must_equal 2
203
206
  @db.sqls.must_equal ["SELECT managers.id, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id) WHERE (managers.id IN (1))"]
204
- e.num_managers#.must_equal 3
207
+ e.num_managers.must_equal 3
205
208
  @db.sqls.must_equal ['SELECT executives.id, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id) WHERE (executives.id IN (1))']
206
209
  end
207
210
 
@@ -213,62 +216,63 @@ describe "class_table_inheritance plugin" do
213
216
  end
214
217
 
215
218
  it "should use the correct primary key (which should have the same name in all subclasses)" do
216
- [Employee, Manager, Executive, Staff].each{|c| c.primary_key.must_equal :id}
219
+ [Employee, Manager, Executive, Ceo, Staff].each{|c| c.primary_key.must_equal :id}
217
220
  end
218
221
 
219
222
  it "should have table_name return the table name of the most specific table" do
220
223
  Employee.table_name.must_equal :employees
221
224
  Manager.table_name.must_equal :managers
222
225
  Executive.table_name.must_equal :executives
226
+ Ceo.table_name.must_equal :executives
223
227
  Staff.table_name.must_equal :staff
224
228
  end
225
229
 
226
230
  it "should delete the correct rows from all tables when deleting" do
227
- Executive.load(:id=>1).delete
231
+ Ceo.load(:id=>1).delete
228
232
  @db.sqls.must_equal ["DELETE FROM executives WHERE (id = 1)", "DELETE FROM managers WHERE (id = 1)", "DELETE FROM employees WHERE (id = 1)"]
229
233
  end
230
234
 
231
235
  it "should not allow deletion of frozen object" do
232
- o = Executive.load(:id=>1)
236
+ o = Ceo.load(:id=>1)
233
237
  o.freeze
234
238
  proc{o.delete}.must_raise(Sequel::Error)
235
239
  @db.sqls.must_equal []
236
240
  end
237
241
 
238
242
  it "should insert the correct rows into all tables when inserting" do
239
- Executive.create(:num_managers=>3, :num_staff=>2, :name=>'E')
243
+ Ceo.create(:num_managers=>3, :num_staff=>2, :name=>'E')
240
244
  sqls = @db.sqls
241
245
  sqls.length.must_equal 3
242
- sqls[0].must_match(/INSERT INTO employees \((name|kind), (name|kind)\) VALUES \('(E|Executive)', '(E|Executive)'\)/)
246
+ sqls[0].must_match(/INSERT INTO employees \((name|kind), (name|kind)\) VALUES \('(E|Ceo)', '(E|Ceo)'\)/)
243
247
  sqls[1].must_match(/INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \([12], [12]\)/)
244
248
  sqls[2].must_match(/INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([13], [13]\)/)
245
249
  end
246
250
 
247
251
  it "should insert the correct rows into all tables with a given primary key" do
248
- e = Executive.new(:num_managers=>3, :num_staff=>2, :name=>'E')
252
+ e = Ceo.new(:num_managers=>3, :num_staff=>2, :name=>'E')
249
253
  e.id = 2
250
254
  e.save
251
255
  sqls = @db.sqls
252
256
  sqls.length.must_equal 3
253
- sqls[0].must_match(/INSERT INTO employees \((name|kind|id), (name|kind|id), (name|kind|id)\) VALUES \(('E'|'Executive'|2), ('E'|'Executive'|2), ('E'|'Executive'|2)\)/)
257
+ sqls[0].must_match(/INSERT INTO employees \((name|kind|id), (name|kind|id), (name|kind|id)\) VALUES \(('E'|'Ceo'|2), ('E'|'Ceo'|2), ('E'|'Ceo'|2)\)/)
254
258
  sqls[1].must_match(/INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \(2, 2\)/)
255
259
  sqls[2].must_match(/INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([23], [23]\)/)
256
260
  end
257
261
 
258
262
  it "should update the correct rows in all tables when updating" do
259
- Executive.load(:id=>2).update(:num_managers=>3, :num_staff=>2, :name=>'E')
263
+ Ceo.load(:id=>2).update(:num_managers=>3, :num_staff=>2, :name=>'E')
260
264
  @db.sqls.must_equal ["UPDATE employees SET name = 'E' WHERE (id = 2)", "UPDATE managers SET num_staff = 2 WHERE (id = 2)", "UPDATE executives SET num_managers = 3 WHERE (id = 2)"]
261
265
  end
262
266
 
263
267
  it "should handle many_to_one relationships correctly" do
264
- Manager.dataset._fetch = {:id=>3, :name=>'E', :kind=>'Executive', :num_managers=>3}
265
- Staff.load(:manager_id=>3).manager.must_equal Executive.load(:id=>3, :name=>'E', :kind=>'Executive', :num_managers=>3)
268
+ Manager.dataset._fetch = {:id=>3, :name=>'E', :kind=>'Ceo', :num_managers=>3}
269
+ Staff.load(:manager_id=>3).manager.must_equal Ceo.load(:id=>3, :name=>'E', :kind=>'Ceo', :num_managers=>3)
266
270
  @db.sqls.must_equal ['SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id) WHERE (managers.id = 3) LIMIT 1']
267
271
  end
268
272
 
269
273
  it "should handle one_to_many relationships correctly" do
270
274
  Staff.dataset._fetch = {:id=>1, :name=>'S', :kind=>'Staff', :manager_id=>3}
271
- Executive.load(:id=>3).staff_members.must_equal [Staff.load(:id=>1, :name=>'S', :kind=>'Staff', :manager_id=>3)]
275
+ Ceo.load(:id=>3).staff_members.must_equal [Staff.load(:id=>1, :name=>'S', :kind=>'Staff', :manager_id=>3)]
272
276
  @db.sqls.must_equal ['SELECT employees.id, employees.name, employees.kind, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id) WHERE (staff.manager_id = 3)']
273
277
  end
274
278
  end