sequel 4.6.0 → 4.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +32 -0
- data/doc/association_basics.rdoc +18 -0
- data/doc/migration.rdoc +30 -0
- data/doc/release_notes/4.7.0.txt +103 -0
- data/doc/security.rdoc +5 -0
- data/doc/sql.rdoc +21 -12
- data/doc/validations.rdoc +10 -2
- data/doc/virtual_rows.rdoc +22 -29
- data/lib/sequel/adapters/jdbc.rb +4 -1
- data/lib/sequel/adapters/jdbc/h2.rb +5 -0
- data/lib/sequel/adapters/odbc.rb +0 -1
- data/lib/sequel/adapters/postgres.rb +8 -1
- data/lib/sequel/adapters/shared/db2.rb +5 -0
- data/lib/sequel/adapters/shared/mssql.rb +1 -1
- data/lib/sequel/adapters/shared/oracle.rb +5 -0
- data/lib/sequel/adapters/shared/postgres.rb +5 -0
- data/lib/sequel/adapters/shared/sqlanywhere.rb +1 -1
- data/lib/sequel/adapters/shared/sqlite.rb +6 -1
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +1 -1
- data/lib/sequel/database/schema_methods.rb +1 -0
- data/lib/sequel/database/transactions.rb +11 -26
- data/lib/sequel/dataset/actions.rb +3 -3
- data/lib/sequel/dataset/features.rb +5 -0
- data/lib/sequel/dataset/sql.rb +16 -1
- data/lib/sequel/model/associations.rb +22 -7
- data/lib/sequel/model/base.rb +2 -2
- data/lib/sequel/plugins/auto_validations.rb +5 -1
- data/lib/sequel/plugins/instance_hooks.rb +21 -3
- data/lib/sequel/plugins/pg_array_associations.rb +21 -5
- data/lib/sequel/plugins/update_or_create.rb +60 -0
- data/lib/sequel/plugins/validation_helpers.rb +5 -2
- data/lib/sequel/sql.rb +55 -9
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +25 -4
- data/spec/core/database_spec.rb +1 -1
- data/spec/core/dataset_spec.rb +1 -1
- data/spec/core/expression_filters_spec.rb +53 -1
- data/spec/extensions/auto_validations_spec.rb +18 -0
- data/spec/extensions/instance_hooks_spec.rb +14 -0
- data/spec/extensions/pg_array_associations_spec.rb +40 -0
- data/spec/extensions/to_dot_spec.rb +1 -1
- data/spec/extensions/update_or_create_spec.rb +81 -0
- data/spec/extensions/validation_helpers_spec.rb +15 -11
- data/spec/integration/associations_test.rb +1 -1
- data/spec/integration/database_test.rb +8 -0
- data/spec/integration/dataset_test.rb +15 -10
- data/spec/integration/type_test.rb +4 -0
- data/spec/model/associations_spec.rb +20 -0
- data/spec/spec_config.rb +1 -1
- metadata +364 -360
@@ -0,0 +1,60 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Plugins
|
3
|
+
# The update_or_create plugin adds a couple of methods that make it easier
|
4
|
+
# to deal with objects which may or may not yet exist in the database.
|
5
|
+
# The first method is update_or_create, which updates an object if it
|
6
|
+
# exists in the database, or creates the object if it does not.
|
7
|
+
#
|
8
|
+
# You can call create_or_update with a block:
|
9
|
+
#
|
10
|
+
# Album.update_or_create(:name=>'Hello') do |album|
|
11
|
+
# album.num_copies_sold = 1000
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# or provide two hashes, with the second one being the attributes
|
15
|
+
# to set.
|
16
|
+
#
|
17
|
+
# Album.update_or_create({:name=>'Hello'}, {:num_copies_sold=>1000})
|
18
|
+
#
|
19
|
+
# In both cases, this will check the database to find the album with
|
20
|
+
# the name "Hello". If such an album exists, it will be updated to set
|
21
|
+
# num_copies_sold to 1000. If no such album exists, an album with the
|
22
|
+
# name "Hello" and num_copies_sold 1000 will be created.
|
23
|
+
#
|
24
|
+
# The second method is find_or_new, which returns the object from the
|
25
|
+
# database if it exists, or returns a new (unsaved) object if not. It
|
26
|
+
# has the same API as update_or_create, and operates identically to
|
27
|
+
# update_or_create except that it doesn't persist any changes.
|
28
|
+
#
|
29
|
+
# Usage:
|
30
|
+
#
|
31
|
+
# # Make all model subclass support update_or_create
|
32
|
+
# Sequel::Model.plugin :update_or_create
|
33
|
+
#
|
34
|
+
# # Make the Album class support update_or_create
|
35
|
+
# Album.plugin :update_or_create
|
36
|
+
module UpdateOrCreate
|
37
|
+
module ClassMethods
|
38
|
+
# Attempt to find an record with the +attrs+, which should be a
|
39
|
+
# hash with column symbol keys. If such an record exists, update it
|
40
|
+
# with the values given in +set_attrs+. If no such record exists,
|
41
|
+
# create a new record with the columns specified by both +attrs+ and
|
42
|
+
# +set_attrs+, with the ones in +set_attrs+ taking priority. If
|
43
|
+
# a block is given, the object is yielded to the block before the
|
44
|
+
# object is saved.
|
45
|
+
def update_or_create(attrs, set_attrs=nil, &block)
|
46
|
+
find_or_new(attrs, set_attrs, &block).save_changes
|
47
|
+
end
|
48
|
+
|
49
|
+
# Operates the same as +update_or_create+, but returns the objects
|
50
|
+
# without persisting changes (no UPDATE/INSERT queries).
|
51
|
+
def find_or_new(attrs, set_attrs=nil, &block)
|
52
|
+
obj = find(attrs) || new(attrs)
|
53
|
+
obj.set(set_attrs) if set_attrs
|
54
|
+
yield obj if block_given?
|
55
|
+
obj
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -202,6 +202,8 @@ module Sequel
|
|
202
202
|
# since it can deal with a grouping of multiple attributes.
|
203
203
|
#
|
204
204
|
# Possible Options:
|
205
|
+
# :dataset :: The base dataset to use for the unique query, defaults to the
|
206
|
+
# model's dataset.
|
205
207
|
# :message :: The message to use (default: 'is already taken')
|
206
208
|
# :only_if_modified :: Only check the uniqueness if the object is new or
|
207
209
|
# one of the columns has been modified.
|
@@ -231,12 +233,13 @@ module Sequel
|
|
231
233
|
arr = Array(a)
|
232
234
|
next if arr.any?{|x| errors.on(x)}
|
233
235
|
next if opts[:only_if_modified] && !new? && !arr.any?{|x| changed_columns.include?(x)}
|
236
|
+
ds = opts[:dataset] || model.dataset
|
234
237
|
ds = if where
|
235
|
-
where.call(
|
238
|
+
where.call(ds, self, arr)
|
236
239
|
else
|
237
240
|
vals = arr.map{|x| send(x)}
|
238
241
|
next if vals.any?{|v| v.nil?}
|
239
|
-
|
242
|
+
ds.where(arr.zip(vals))
|
240
243
|
end
|
241
244
|
ds = yield(ds) if block_given?
|
242
245
|
ds = ds.exclude(pk_hash) unless new?
|
data/lib/sequel/sql.rb
CHANGED
@@ -48,6 +48,17 @@ module Sequel
|
|
48
48
|
t = now
|
49
49
|
local(t.year, t.month, t.day, hour, minute, second, usec)
|
50
50
|
end
|
51
|
+
|
52
|
+
# Return a string in HH:MM:SS format representing the time.
|
53
|
+
def to_s(*args)
|
54
|
+
if args.empty?
|
55
|
+
strftime('%H:%M:%S')
|
56
|
+
else
|
57
|
+
# Superclass may have defined a method that takes a format string,
|
58
|
+
# and we shouldn't override in that case.
|
59
|
+
super
|
60
|
+
end
|
61
|
+
end
|
51
62
|
end
|
52
63
|
|
53
64
|
# The SQL module holds classes whose instances represent SQL fragments.
|
@@ -1200,6 +1211,10 @@ module Sequel
|
|
1200
1211
|
|
1201
1212
|
# Represents an SQL function call.
|
1202
1213
|
class Function < GenericExpression
|
1214
|
+
WILDCARD = LiteralString.new('*').freeze
|
1215
|
+
DISTINCT = ["DISTINCT ".freeze].freeze
|
1216
|
+
COMMA_ARRAY = [LiteralString.new(', ').freeze].freeze
|
1217
|
+
|
1203
1218
|
# The SQL function to call
|
1204
1219
|
attr_reader :f
|
1205
1220
|
|
@@ -1211,6 +1226,28 @@ module Sequel
|
|
1211
1226
|
@f, @args = f, args
|
1212
1227
|
end
|
1213
1228
|
|
1229
|
+
# If no arguments are given, return a new function with the wildcard prepended to the arguments.
|
1230
|
+
#
|
1231
|
+
# Sequel.function(:count).* # count(*)
|
1232
|
+
# Sequel.function(:count, 1).* # count(*, 1)
|
1233
|
+
def *(ce=(arg=false;nil))
|
1234
|
+
if arg == false
|
1235
|
+
Function.new(f, WILDCARD, *args)
|
1236
|
+
else
|
1237
|
+
super(ce)
|
1238
|
+
end
|
1239
|
+
end
|
1240
|
+
|
1241
|
+
# Return a new function with DISTINCT before the method arguments.
|
1242
|
+
def distinct
|
1243
|
+
Function.new(f, PlaceholderLiteralString.new(DISTINCT + COMMA_ARRAY * (args.length-1), args))
|
1244
|
+
end
|
1245
|
+
|
1246
|
+
# Create a WindowFunction using the receiver and the appropriate options for the window.
|
1247
|
+
def over(opts=OPTS)
|
1248
|
+
WindowFunction.new(self, Window.new(opts))
|
1249
|
+
end
|
1250
|
+
|
1214
1251
|
to_s_method :function_sql
|
1215
1252
|
end
|
1216
1253
|
|
@@ -1245,6 +1282,12 @@ module Sequel
|
|
1245
1282
|
def initialize(value)
|
1246
1283
|
@value = value
|
1247
1284
|
end
|
1285
|
+
|
1286
|
+
# Create a Function using this identifier as the functions name, with
|
1287
|
+
# the given args.
|
1288
|
+
def function(*args)
|
1289
|
+
Function.new(self, *args)
|
1290
|
+
end
|
1248
1291
|
|
1249
1292
|
to_s_method :quote_identifier, '@value'
|
1250
1293
|
end
|
@@ -1399,6 +1442,12 @@ module Sequel
|
|
1399
1442
|
@table, @column = table, column
|
1400
1443
|
end
|
1401
1444
|
|
1445
|
+
# Create a Function using this identifier as the functions name, with
|
1446
|
+
# the given args.
|
1447
|
+
def function(*args)
|
1448
|
+
Function.new(self, *args)
|
1449
|
+
end
|
1450
|
+
|
1402
1451
|
to_s_method :qualified_identifier_sql, "@table, @column"
|
1403
1452
|
end
|
1404
1453
|
|
@@ -1584,12 +1633,8 @@ module Sequel
|
|
1584
1633
|
#
|
1585
1634
|
# For a more detailed explanation, see the {Virtual Rows guide}[rdoc-ref:doc/virtual_rows.rdoc].
|
1586
1635
|
class VirtualRow < BasicObject
|
1587
|
-
WILDCARD = LiteralString.new('*').freeze
|
1588
1636
|
QUESTION_MARK = LiteralString.new('?').freeze
|
1589
|
-
COMMA_SEPARATOR = LiteralString.new(', ').freeze
|
1590
1637
|
DOUBLE_UNDERSCORE = '__'.freeze
|
1591
|
-
DISTINCT = ["DISTINCT ".freeze].freeze
|
1592
|
-
COMMA_ARRAY = [COMMA_SEPARATOR].freeze
|
1593
1638
|
|
1594
1639
|
include OperatorBuilders
|
1595
1640
|
|
@@ -1616,13 +1661,14 @@ module Sequel
|
|
1616
1661
|
else
|
1617
1662
|
case args.shift
|
1618
1663
|
when :*
|
1619
|
-
Function.new(m,
|
1664
|
+
Function.new(m, *args).*
|
1620
1665
|
when :distinct
|
1621
|
-
Function.new(m,
|
1666
|
+
Function.new(m, *args).distinct
|
1622
1667
|
when :over
|
1623
|
-
opts = args.shift ||
|
1624
|
-
|
1625
|
-
|
1668
|
+
opts = args.shift || OPTS
|
1669
|
+
f = Function.new(m, *::Kernel.Array(opts[:args]))
|
1670
|
+
f = f.* if opts[:*]
|
1671
|
+
f.over(opts)
|
1626
1672
|
else
|
1627
1673
|
Kernel.raise(Error, 'unsupported VirtualRow method argument used with block')
|
1628
1674
|
end
|
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 = 7
|
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
|
@@ -170,6 +170,16 @@ describe "A PostgreSQL database" do
|
|
170
170
|
@db.server_version.should > 70000
|
171
171
|
end
|
172
172
|
|
173
|
+
specify "should support functions with and without quoting" do
|
174
|
+
ds = @db[:public__testfk]
|
175
|
+
ds.insert
|
176
|
+
ds.get{sum(1)}.should == 1
|
177
|
+
ds.get{Sequel.function('pg_catalog.sum', 1)}.should == 1
|
178
|
+
ds.get{sum.function(1)}.should == 1
|
179
|
+
ds.get{pg_catalog__sum.function(1)}.should == 1
|
180
|
+
ds.delete
|
181
|
+
end
|
182
|
+
|
173
183
|
specify "should support a :qualify option to tables and views" do
|
174
184
|
@db.tables(:qualify=>true).should include(Sequel.qualify(:public, :testfk))
|
175
185
|
begin
|
@@ -1345,13 +1355,13 @@ if DB.dataset.supports_window_functions?
|
|
1345
1355
|
end
|
1346
1356
|
|
1347
1357
|
specify "should give correct results for window functions" do
|
1348
|
-
@ds.window(:win, :partition=>:group_id, :order=>:id).select(:id){sum(:over
|
1358
|
+
@ds.window(:win, :partition=>:group_id, :order=>:id).select(:id){sum(:amount).over(:window=>win)}.all.should ==
|
1349
1359
|
[{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>111000, :id=>6}]
|
1350
|
-
@ds.window(:win, :partition=>:group_id).select(:id){sum(:over
|
1360
|
+
@ds.window(:win, :partition=>:group_id).select(:id){sum(:amount).over(:window=>win, :order=>id)}.all.should ==
|
1351
1361
|
[{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1000, :id=>4}, {:sum=>11000, :id=>5}, {:sum=>111000, :id=>6}]
|
1352
|
-
@ds.window(:win, {}).select(:id){sum(:over
|
1362
|
+
@ds.window(:win, {}).select(:id){sum(:amount).over(:window=>:win, :order=>id)}.all.should ==
|
1353
1363
|
[{:sum=>1, :id=>1}, {:sum=>11, :id=>2}, {:sum=>111, :id=>3}, {:sum=>1111, :id=>4}, {:sum=>11111, :id=>5}, {:sum=>111111, :id=>6}]
|
1354
|
-
@ds.window(:win, :partition=>:group_id).select(:id){sum(:over
|
1364
|
+
@ds.window(:win, :partition=>:group_id).select(:id){sum(:amount).over(:window=>:win, :order=>id, :frame=>:all)}.all.should ==
|
1355
1365
|
[{:sum=>111, :id=>1}, {:sum=>111, :id=>2}, {:sum=>111, :id=>3}, {:sum=>111000, :id=>4}, {:sum=>111000, :id=>5}, {:sum=>111000, :id=>6}]
|
1356
1366
|
end
|
1357
1367
|
end
|
@@ -1456,6 +1466,17 @@ if DB.adapter_scheme == :postgres
|
|
1456
1466
|
@ds.all.should == @ds.use_cursor.all
|
1457
1467
|
end
|
1458
1468
|
|
1469
|
+
specify "should not swallow errors if closing cursor raises an error" do
|
1470
|
+
proc do
|
1471
|
+
@db.synchronize do |c|
|
1472
|
+
@ds.use_cursor.each do |r|
|
1473
|
+
@db.run "CLOSE sequel_cursor"
|
1474
|
+
raise ArgumentError
|
1475
|
+
end
|
1476
|
+
end
|
1477
|
+
end.should raise_error(ArgumentError)
|
1478
|
+
end
|
1479
|
+
|
1459
1480
|
specify "should respect the :rows_per_fetch option" do
|
1460
1481
|
@db.sqls.clear
|
1461
1482
|
@ds.use_cursor.all
|
data/spec/core/database_spec.rb
CHANGED
@@ -825,7 +825,7 @@ shared_examples_for "Database#transaction" do
|
|
825
825
|
@db.sqls.should == ['BEGIN', 'BEGIN -- test', 'DROP TABLE test;', 'COMMIT -- test', 'COMMIT']
|
826
826
|
end
|
827
827
|
|
828
|
-
if (!defined?(RUBY_ENGINE) or RUBY_ENGINE == 'ruby' or RUBY_ENGINE == 'rbx') and RUBY_VERSION
|
828
|
+
if (!defined?(RUBY_ENGINE) or RUBY_ENGINE == 'ruby' or RUBY_ENGINE == 'rbx') and !RUBY_VERSION.start_with?('1.9')
|
829
829
|
specify "should handle Thread#kill for transactions inside threads" do
|
830
830
|
q = Queue.new
|
831
831
|
q1 = Queue.new
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -3582,7 +3582,7 @@ describe "Sequel::Dataset#qualify" do
|
|
3582
3582
|
|
3583
3583
|
specify "should handle SQL::WindowFunctions" do
|
3584
3584
|
meta_def(@ds, :supports_window_functions?){true}
|
3585
|
-
@ds.select{sum(:over
|
3585
|
+
@ds.select{sum(:a).over(:partition=>:b, :order=>:c)}.qualify.sql.should == 'SELECT sum(t.a) OVER (PARTITION BY t.b ORDER BY t.c) FROM t'
|
3586
3586
|
end
|
3587
3587
|
|
3588
3588
|
specify "should handle SQL::DelayedEvaluation" do
|
@@ -419,8 +419,15 @@ describe Sequel::SQL::VirtualRow do
|
|
419
419
|
@d.l{version{}}.should == 'version()'
|
420
420
|
end
|
421
421
|
|
422
|
-
it "should treat methods with a block and a leading argument :* as a function call with the SQL wildcard" do
|
422
|
+
it "should treat methods with a block and a leading argument :* as a function call starting with the SQL wildcard" do
|
423
423
|
@d.l{count(:*){}}.should == 'count(*)'
|
424
|
+
@d.l{count(:*, 1){}}.should == 'count(*, 1)'
|
425
|
+
end
|
426
|
+
|
427
|
+
it "should support * method on functions to add * as the first argument" do
|
428
|
+
@d.l{count{}.*}.should == 'count(*)'
|
429
|
+
@d.l{count(1).*}.should == 'count(*, 1)'
|
430
|
+
@d.literal(Sequel.expr{count(1) * 2}).should == '(count(1) * 2)'
|
424
431
|
end
|
425
432
|
|
426
433
|
it "should treat methods with a block and a leading argument :distinct as a function call with DISTINCT and the additional method arguments" do
|
@@ -428,6 +435,11 @@ describe Sequel::SQL::VirtualRow do
|
|
428
435
|
@d.l{count(:distinct, column1, column2){}}.should == 'count(DISTINCT "column1", "column2")'
|
429
436
|
end
|
430
437
|
|
438
|
+
it "should support distinct methods on functions to use DISTINCT before the arguments" do
|
439
|
+
@d.l{count(column1).distinct}.should == 'count(DISTINCT "column1")'
|
440
|
+
@d.l{count(column1, column2).distinct}.should == 'count(DISTINCT "column1", "column2")'
|
441
|
+
end
|
442
|
+
|
431
443
|
it "should raise an error if an unsupported argument is used with a block" do
|
432
444
|
proc{@d.where{count(:blah){}}}.should raise_error(Sequel::Error)
|
433
445
|
end
|
@@ -479,6 +491,11 @@ describe Sequel::SQL::VirtualRow do
|
|
479
491
|
@d.l{count(:over, :* =>true, :partition=>a, :order=>b, :window=>:win, :frame=>:rows){}}.should == 'count(*) OVER ("win" PARTITION BY "a" ORDER BY "b" ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)'
|
480
492
|
end
|
481
493
|
|
494
|
+
it "should support over method on functions to create window functions" do
|
495
|
+
@d.l{rank{}.over}.should == 'rank() OVER ()'
|
496
|
+
@d.l{sum(c).over(:partition=>a, :order=>b, :window=>:win, :frame=>:rows)}.should == 'sum("c") OVER ("win" PARTITION BY "a" ORDER BY "b" ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)'
|
497
|
+
end
|
498
|
+
|
482
499
|
it "should raise an error if window functions are not supported" do
|
483
500
|
class << @d; remove_method :supports_window_functions? end
|
484
501
|
meta_def(@d, :supports_window_functions?){false}
|
@@ -486,6 +503,25 @@ describe Sequel::SQL::VirtualRow do
|
|
486
503
|
proc{Sequel.mock.dataset.filter{count(:over, :* =>true, :partition=>a, :order=>b, :window=>:win, :frame=>:rows){}}.sql}.should raise_error(Sequel::Error)
|
487
504
|
end
|
488
505
|
|
506
|
+
it "should support function method on identifiers to create functions" do
|
507
|
+
@d.l{rank.function}.should == 'rank()'
|
508
|
+
@d.l{sum.function(c)}.should == 'sum("c")'
|
509
|
+
@d.l{sum.function(c, 1)}.should == 'sum("c", 1)'
|
510
|
+
end
|
511
|
+
|
512
|
+
it "should support function method on qualified identifiers to create functions" do
|
513
|
+
@d.l{sch__rank.function}.should == 'sch.rank()'
|
514
|
+
@d.l{sch__sum.function(c)}.should == 'sch.sum("c")'
|
515
|
+
@d.l{sch__sum.function(c, 1)}.should == 'sch.sum("c", 1)'
|
516
|
+
@d.l{Sequel.qualify(sch__sum, :x__y).function(c, 1)}.should == 'sch.sum.x.y("c", 1)'
|
517
|
+
end
|
518
|
+
|
519
|
+
it "should handle quoted function names" do
|
520
|
+
def @d.supports_quoted_function_names?; true; end
|
521
|
+
@d.l{rank.function}.should == '"rank"()'
|
522
|
+
@d.l{sch__rank.function}.should == '"sch"."rank"()'
|
523
|
+
end
|
524
|
+
|
489
525
|
it "should deal with classes without requiring :: prefix" do
|
490
526
|
@d.l{date < Date.today}.should == "(\"date\" < '#{Date.today}')"
|
491
527
|
@d.l{date < Sequel::CURRENT_DATE}.should == "(\"date\" < CURRENT_DATE)"
|
@@ -903,6 +939,22 @@ describe "Sequel::SQLTime" do
|
|
903
939
|
@db.literal(Sequel::SQLTime.create(1, 2, 3)).should == "'01:02:03.000000'"
|
904
940
|
@db.literal(Sequel::SQLTime.create(1, 2, 3, 500000)).should == "'01:02:03.500000'"
|
905
941
|
end
|
942
|
+
|
943
|
+
specify "#to_s should include hour, minute, and second by default" do
|
944
|
+
Sequel::SQLTime.create(1, 2, 3).to_s.should == "01:02:03"
|
945
|
+
Sequel::SQLTime.create(1, 2, 3, 500000).to_s.should == "01:02:03"
|
946
|
+
end
|
947
|
+
|
948
|
+
specify "#to_s should handle arguments with super" do
|
949
|
+
t = Sequel::SQLTime.create(1, 2, 3)
|
950
|
+
begin
|
951
|
+
Time.now.to_s('%F')
|
952
|
+
rescue
|
953
|
+
proc{t.to_s('%F')}.should raise_error
|
954
|
+
else
|
955
|
+
proc{t.to_s('%F')}.should_not raise_error
|
956
|
+
end
|
957
|
+
end
|
906
958
|
end
|
907
959
|
|
908
960
|
describe "Sequel::SQL::Wrapper" do
|
@@ -106,6 +106,24 @@ describe "Sequel::Plugins::AutoValidations" do
|
|
106
106
|
@m.errors.should == {[:name, :num]=>["is already taken"]}
|
107
107
|
end
|
108
108
|
|
109
|
+
it "should work correctly in STI subclasses" do
|
110
|
+
@c.plugin(:single_table_inheritance, :num, :model_map=>{1=>@c}, :key_map=>proc{[1, 2]})
|
111
|
+
sc = Class.new(@c)
|
112
|
+
@m = sc.new
|
113
|
+
@m.valid?.should == false
|
114
|
+
@m.errors.should == {:d=>["is not present"], :name=>["is not present"]}
|
115
|
+
|
116
|
+
@m.set(:d=>'/', :num=>'a', :name=>'1')
|
117
|
+
@m.valid?.should == false
|
118
|
+
@m.errors.should == {:d=>["is not a valid date"], :num=>["is not a valid integer"]}
|
119
|
+
|
120
|
+
@m.db.sqls
|
121
|
+
@m.set(:d=>Date.today, :num=>1)
|
122
|
+
@m.valid?.should == false
|
123
|
+
@m.errors.should == {[:name, :num]=>["is already taken"]}
|
124
|
+
@m.db.sqls.should == ["SELECT count(*) AS count FROM test WHERE ((name = '1') AND (num = 1)) LIMIT 1"]
|
125
|
+
end
|
126
|
+
|
109
127
|
it "should work correctly when changing the dataset" do
|
110
128
|
@c.set_dataset(@c.db[:foo])
|
111
129
|
@c.new.valid?.should == true
|
@@ -177,6 +177,20 @@ describe "InstanceHooks plugin" do
|
|
177
177
|
@r.should == [2, 1, 4, 3]
|
178
178
|
end
|
179
179
|
|
180
|
+
it "should not clear validations hooks on successful save" do
|
181
|
+
@x.after_validation_hook{@x.errors.add(:id, 'a') if @x.id == 1; r 1}
|
182
|
+
@x.before_validation_hook{r 2}
|
183
|
+
@x.save.should == nil
|
184
|
+
@r.should == [2, 1]
|
185
|
+
@x.save.should == nil
|
186
|
+
@r.should == [2, 1, 2, 1]
|
187
|
+
@x.id = 2
|
188
|
+
@x.save.should == @x
|
189
|
+
@r.should == [2, 1, 2, 1, 2, 1]
|
190
|
+
@x.save.should == @x
|
191
|
+
@r.should == [2, 1, 2, 1, 2, 1]
|
192
|
+
end
|
193
|
+
|
180
194
|
it "should not allow addition of instance hooks to frozen instances" do
|
181
195
|
@x.after_destroy_hook{r 1}
|
182
196
|
@x.before_destroy_hook{r 2}
|
@@ -644,4 +644,44 @@ describe Sequel::Model, "pg_array_associations" do
|
|
644
644
|
@o2.add_artist(@c1.load(:id=>1))
|
645
645
|
DB.sqls.should == ["UPDATE artists SET tag_ids = ARRAY[2]::int8[] WHERE (id = 1)"]
|
646
646
|
end
|
647
|
+
|
648
|
+
it "should not validate the current/associated object in add_ and remove_ if the :validate=>false option is used" do
|
649
|
+
@c1.pg_array_to_many :tags, :clone=>:tags, :validate=>false, :save_after_modify=>true
|
650
|
+
@c2.many_to_pg_array :artists, :clone=>:artists, :validate=>false
|
651
|
+
a = @c1.load(:id=>1)
|
652
|
+
t = @c2.load(:id=>2)
|
653
|
+
def a.validate() errors.add(:id, 'foo') end
|
654
|
+
a.associations[:tags] = []
|
655
|
+
a.add_tag(t).should == t
|
656
|
+
a.tags.should == [t]
|
657
|
+
a.remove_tag(t).should == t
|
658
|
+
a.tags.should == []
|
659
|
+
|
660
|
+
t.associations[:artists] = []
|
661
|
+
t.add_artist(a).should == a
|
662
|
+
t.artists.should == [a]
|
663
|
+
t.remove_artist(a).should == a
|
664
|
+
t.artists.should == []
|
665
|
+
end
|
666
|
+
|
667
|
+
it "should not raise exception in add_ and remove_ if the :raise_on_save_failure=>false option is used" do
|
668
|
+
@c1.pg_array_to_many :tags, :clone=>:tags, :raise_on_save_failure=>false, :save_after_modify=>true
|
669
|
+
@c2.many_to_pg_array :artists, :clone=>:artists, :raise_on_save_failure=>false
|
670
|
+
a = @c1.load(:id=>1)
|
671
|
+
t = @c2.load(:id=>2)
|
672
|
+
def a.validate() errors.add(:id, 'foo') end
|
673
|
+
a.associations[:tags] = []
|
674
|
+
a.add_tag(t).should == nil
|
675
|
+
a.tags.should == []
|
676
|
+
a.associations[:tags] = [t]
|
677
|
+
a.remove_tag(t).should == nil
|
678
|
+
a.tags.should == [t]
|
679
|
+
|
680
|
+
t.associations[:artists] = []
|
681
|
+
t.add_artist(a).should == nil
|
682
|
+
t.artists.should == []
|
683
|
+
t.associations[:artists] = [a]
|
684
|
+
t.remove_artist(a).should == nil
|
685
|
+
t.artists.should == [a]
|
686
|
+
end
|
647
687
|
end
|