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