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.
- checksums.yaml +4 -4
- data/CHANGELOG +26 -0
- data/Rakefile +1 -1
- data/doc/release_notes/4.24.0.txt +99 -0
- data/doc/sql.rdoc +10 -1
- data/lib/sequel/adapters/jdbc.rb +7 -0
- data/lib/sequel/adapters/jdbc/cubrid.rb +1 -1
- data/lib/sequel/adapters/jdbc/db2.rb +1 -1
- data/lib/sequel/adapters/jdbc/derby.rb +1 -1
- data/lib/sequel/adapters/jdbc/h2.rb +1 -1
- data/lib/sequel/adapters/jdbc/hsqldb.rb +1 -1
- data/lib/sequel/adapters/jdbc/mssql.rb +1 -1
- data/lib/sequel/adapters/jdbc/mysql.rb +2 -2
- data/lib/sequel/adapters/jdbc/oracle.rb +1 -1
- data/lib/sequel/adapters/jdbc/sqlanywhere.rb +1 -1
- data/lib/sequel/adapters/jdbc/sqlite.rb +1 -1
- data/lib/sequel/adapters/postgres.rb +14 -6
- data/lib/sequel/adapters/shared/mssql.rb +1 -1
- data/lib/sequel/core.rb +12 -1
- data/lib/sequel/database/connecting.rb +1 -2
- data/lib/sequel/extensions/pg_inet_ops.rb +200 -0
- data/lib/sequel/plugins/association_pks.rb +63 -18
- data/lib/sequel/plugins/auto_validations.rb +43 -9
- data/lib/sequel/plugins/class_table_inheritance.rb +236 -179
- data/lib/sequel/plugins/update_refresh.rb +26 -1
- data/lib/sequel/plugins/validation_helpers.rb +7 -2
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/oracle_spec.rb +1 -1
- data/spec/adapters/postgres_spec.rb +61 -0
- data/spec/core_extensions_spec.rb +5 -1
- data/spec/extensions/association_pks_spec.rb +73 -1
- data/spec/extensions/auto_validations_spec.rb +34 -0
- data/spec/extensions/class_table_inheritance_spec.rb +58 -54
- data/spec/extensions/pg_inet_ops_spec.rb +101 -0
- data/spec/extensions/spec_helper.rb +5 -5
- data/spec/extensions/update_refresh_spec.rb +12 -0
- data/spec/extensions/validation_helpers_spec.rb +7 -0
- data/spec/integration/plugin_test.rb +48 -13
- metadata +6 -4
- data/lib/sequel/adapters/db2.rb +0 -229
- 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)
|
data/lib/sequel/version.rb
CHANGED
@@ -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 =
|
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
|
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=>'
|
61
|
-
Manager[1].must_equal
|
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
|
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=>'
|
83
|
-
|
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
|
86
|
-
a.values.must_equal(:id=>1, :name=>'A', :kind=>'
|
87
|
-
a.refresh.values.must_equal(:id=>1, :name=>'A', :kind=>'
|
88
|
-
@db.sqls.must_equal ["SELECT
|
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
|
-
|
115
|
-
Employee.
|
116
|
-
|
117
|
-
Manager.
|
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
|
-
|
137
|
-
@db.sqls.must_equal ["INSERT INTO employees (kind) VALUES ('
|
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=>'
|
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
|
-
|
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=>'
|
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=>'
|
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(
|
190
|
-
@db.sqls.must_equal ["SELECT
|
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=>'
|
199
|
+
Employee.dataset._fetch = {:id=>1, :kind=>'Ceo'}
|
197
200
|
Manager.dataset._fetch = {:id=>1, :num_staff=>2}
|
198
|
-
|
201
|
+
@db.fetch = {:id=>1, :num_managers=>3}
|
199
202
|
e = Employee.all.first
|
200
|
-
e.must_be_kind_of(
|
201
|
-
@db.sqls.must_equal ["SELECT
|
202
|
-
e.num_staff
|
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
|
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
|
-
|
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 =
|
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
|
-
|
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|
|
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 =
|
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'|'
|
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
|
-
|
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=>'
|
265
|
-
Staff.load(:manager_id=>3).manager.must_equal
|
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
|
-
|
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
|