sequel 4.6.0 → 4.7.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 +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
|