sequel 4.23.0 → 4.24.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 (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