sequel 3.6.0 → 3.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.
- data/CHANGELOG +28 -0
- data/Rakefile +12 -15
- data/doc/release_notes/3.7.0.txt +179 -0
- data/doc/virtual_rows.rdoc +3 -0
- data/lib/sequel/adapters/mysql.rb +8 -5
- data/lib/sequel/adapters/shared/mssql.rb +24 -15
- data/lib/sequel/adapters/shared/mysql.rb +16 -0
- data/lib/sequel/adapters/shared/oracle.rb +18 -0
- data/lib/sequel/adapters/shared/postgres.rb +59 -1
- data/lib/sequel/dataset/convenience.rb +58 -11
- data/lib/sequel/dataset/features.rb +5 -0
- data/lib/sequel/dataset/query.rb +3 -3
- data/lib/sequel/dataset/sql.rb +19 -2
- data/lib/sequel/extensions/schema_dumper.rb +6 -1
- data/lib/sequel/model/base.rb +40 -16
- data/lib/sequel/plugins/validation_helpers.rb +7 -2
- data/lib/sequel/sql.rb +22 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +16 -0
- data/spec/core/dataset_spec.rb +187 -0
- data/spec/core/expression_filters_spec.rb +25 -0
- data/spec/extensions/schema_dumper_spec.rb +6 -0
- data/spec/extensions/validation_helpers_spec.rb +35 -0
- data/spec/integration/dataset_test.rb +37 -0
- data/spec/model/model_spec.rb +26 -0
- data/spec/model/record_spec.rb +65 -3
- metadata +4 -3
- data/spec/spec.opts +0 -0
@@ -153,6 +153,8 @@ module Sequel
|
|
153
153
|
#
|
154
154
|
# Possible Options:
|
155
155
|
# * :message - The message to use (default: 'is already taken')
|
156
|
+
# * :only_if_modified - Only check the uniqueness if the object is new or
|
157
|
+
# one of the columns has been modified.
|
156
158
|
def validates_unique(*atts)
|
157
159
|
opts = default_validation_helpers_options(:unique)
|
158
160
|
if atts.last.is_a?(Hash)
|
@@ -160,9 +162,12 @@ module Sequel
|
|
160
162
|
end
|
161
163
|
message = validation_error_message(opts[:message])
|
162
164
|
atts.each do |a|
|
163
|
-
|
165
|
+
arr = Array(a)
|
166
|
+
next if opts[:only_if_modified] && !new? && !arr.any?{|x| changed_columns.include?(x)}
|
167
|
+
ds = model.filter(arr.map{|x| [x, send(x)]})
|
164
168
|
ds = yield(ds) if block_given?
|
165
|
-
|
169
|
+
ds = ds.exclude(pk_hash) unless new?
|
170
|
+
errors.add(a, message) unless ds.count == 0
|
166
171
|
end
|
167
172
|
end
|
168
173
|
|
data/lib/sequel/sql.rb
CHANGED
@@ -446,6 +446,10 @@ module Sequel
|
|
446
446
|
new(:AND, new(:>=, l, r.begin), new(r.exclude_end? ? :< : :<=, l, r.end))
|
447
447
|
when Array, ::Sequel::Dataset, SQLArray
|
448
448
|
new(:IN, l, r)
|
449
|
+
when NegativeBooleanConstant
|
450
|
+
new(:"IS NOT", l, r.constant)
|
451
|
+
when BooleanConstant
|
452
|
+
new(:IS, l, r.constant)
|
449
453
|
when NilClass, TrueClass, FalseClass
|
450
454
|
new(:IS, l, r)
|
451
455
|
when Regexp
|
@@ -553,6 +557,20 @@ module Sequel
|
|
553
557
|
|
554
558
|
to_s_method :constant_sql, '@constant'
|
555
559
|
end
|
560
|
+
|
561
|
+
# Represents boolean constants such as NULL, NOTNULL, TRUE, and FALSE.
|
562
|
+
class BooleanConstant < Constant
|
563
|
+
# The underlying constant related for this object.
|
564
|
+
attr_reader :constant
|
565
|
+
|
566
|
+
to_s_method :boolean_constant_sql, '@constant'
|
567
|
+
end
|
568
|
+
|
569
|
+
# Represents inverse boolean constants (currently only NOTNULL). A
|
570
|
+
# special class to allow for special behavior
|
571
|
+
class NegativeBooleanConstant < BooleanConstant
|
572
|
+
to_s_method :negative_boolean_constant_sql, '@constant'
|
573
|
+
end
|
556
574
|
|
557
575
|
# Holds default generic constants that can be referenced. These
|
558
576
|
# are included in the Sequel top level module and are also available
|
@@ -562,6 +580,10 @@ module Sequel
|
|
562
580
|
CURRENT_DATE = Constant.new(:CURRENT_DATE)
|
563
581
|
CURRENT_TIME = Constant.new(:CURRENT_TIME)
|
564
582
|
CURRENT_TIMESTAMP = Constant.new(:CURRENT_TIMESTAMP)
|
583
|
+
SQLTRUE = TRUE = BooleanConstant.new(true)
|
584
|
+
SQLFALSE = FALSE = BooleanConstant.new(false)
|
585
|
+
NULL = BooleanConstant.new(nil)
|
586
|
+
NOTNULL = NegativeBooleanConstant.new(nil)
|
565
587
|
end
|
566
588
|
|
567
589
|
# Represents an SQL function call.
|
data/lib/sequel/version.rb
CHANGED
@@ -147,6 +147,10 @@ context "A PostgreSQL dataset" do
|
|
147
147
|
@d.lock('EXCLUSIVE'){@d.insert(:name=>'a')}.should == nil
|
148
148
|
POSTGRES_DB.transaction{@d.lock('EXCLUSIVE').should == nil; @d.insert(:name=>'a')}
|
149
149
|
end
|
150
|
+
|
151
|
+
specify "should raise an error if attempting to update a joined dataset with a single FROM table" do
|
152
|
+
proc{POSTGRES_DB[:test].join(:test2, [:name]).update(:name=>'a')}.should raise_error(Sequel::Error, 'Need multiple FROM tables if updating/deleting a dataset with JOINs')
|
153
|
+
end
|
150
154
|
end
|
151
155
|
|
152
156
|
context "A PostgreSQL dataset with a timestamp field" do
|
@@ -235,6 +239,18 @@ context "A PostgreSQL database" do
|
|
235
239
|
@db[:posts].order(:a).map(:a).should == [1, 2, 10, 20, 21]
|
236
240
|
end
|
237
241
|
|
242
|
+
specify "should support specifying Integer/Bignum/Fixnum types in primary keys and have them be auto incrementing" do
|
243
|
+
@db.create_table(:posts){primary_key :a, :type=>Integer}
|
244
|
+
@db[:posts].insert.should == 1
|
245
|
+
@db[:posts].insert.should == 2
|
246
|
+
@db.create_table!(:posts){primary_key :a, :type=>Fixnum}
|
247
|
+
@db[:posts].insert.should == 1
|
248
|
+
@db[:posts].insert.should == 2
|
249
|
+
@db.create_table!(:posts){primary_key :a, :type=>Bignum}
|
250
|
+
@db[:posts].insert.should == 1
|
251
|
+
@db[:posts].insert.should == 2
|
252
|
+
end
|
253
|
+
|
238
254
|
specify "should not raise an error if attempting to resetting the primary key sequence for a table without a primary key" do
|
239
255
|
@db.create_table(:posts){Integer :a}
|
240
256
|
@db.reset_primary_key_sequence(:posts).should == nil
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -1489,6 +1489,25 @@ context "Dataset#group_and_count" do
|
|
1489
1489
|
specify "should format column aliases in the select clause but not in the group clause" do
|
1490
1490
|
@ds.group_and_count(:name___n).sql.should ==
|
1491
1491
|
"SELECT name AS n, count(*) AS count FROM test GROUP BY name ORDER BY count"
|
1492
|
+
@ds.group_and_count(:name__n).sql.should ==
|
1493
|
+
"SELECT name.n, count(*) AS count FROM test GROUP BY name.n ORDER BY count"
|
1494
|
+
end
|
1495
|
+
|
1496
|
+
specify "should handle identifiers" do
|
1497
|
+
@ds.group_and_count(:name___n.identifier).sql.should ==
|
1498
|
+
"SELECT name___n, count(*) AS count FROM test GROUP BY name___n ORDER BY count"
|
1499
|
+
end
|
1500
|
+
|
1501
|
+
specify "should handle literal strings" do
|
1502
|
+
@ds.group_and_count("name".lit).sql.should ==
|
1503
|
+
"SELECT name, count(*) AS count FROM test GROUP BY name ORDER BY count"
|
1504
|
+
end
|
1505
|
+
|
1506
|
+
specify "should handle aliased expressions" do
|
1507
|
+
@ds.group_and_count(:name.as(:n)).sql.should ==
|
1508
|
+
"SELECT name AS n, count(*) AS count FROM test GROUP BY name ORDER BY count"
|
1509
|
+
@ds.group_and_count(:name.identifier.as(:n)).sql.should ==
|
1510
|
+
"SELECT name AS n, count(*) AS count FROM test GROUP BY name ORDER BY count"
|
1492
1511
|
end
|
1493
1512
|
end
|
1494
1513
|
|
@@ -2095,6 +2114,15 @@ context "Dataset compound operations" do
|
|
2095
2114
|
"SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
|
2096
2115
|
end
|
2097
2116
|
|
2117
|
+
specify "should support :alias option for specifying identifier" do
|
2118
|
+
@a.union(@b, :alias=>:xx).sql.should == \
|
2119
|
+
"SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS xx"
|
2120
|
+
@a.intersect(@b, :alias=>:xx).sql.should == \
|
2121
|
+
"SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)) AS xx"
|
2122
|
+
@a.except(@b, :alias=>:xx).sql.should == \
|
2123
|
+
"SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS xx"
|
2124
|
+
end
|
2125
|
+
|
2098
2126
|
specify "should support :from_self=>false option to not wrap the compound in a SELECT * FROM (...)" do
|
2099
2127
|
@b.union(@a, :from_self=>false).sql.should == \
|
2100
2128
|
"SELECT * FROM b WHERE (z = 2) UNION SELECT * FROM a WHERE (z = 1)"
|
@@ -3239,6 +3267,30 @@ describe Sequel::SQL::Constants do
|
|
3239
3267
|
@db.literal(Sequel::SQL::Constants::CURRENT_TIMESTAMP) == 'CURRENT_TIMESTAMP'
|
3240
3268
|
@db.literal(Sequel::CURRENT_TIMESTAMP) == 'CURRENT_TIMESTAMP'
|
3241
3269
|
end
|
3270
|
+
|
3271
|
+
it "should have NULL" do
|
3272
|
+
@db.literal(Sequel::SQL::Constants::NULL) == 'NULL'
|
3273
|
+
@db.literal(Sequel::NULL) == 'NULL'
|
3274
|
+
end
|
3275
|
+
|
3276
|
+
it "should have NOTNULL" do
|
3277
|
+
@db.literal(Sequel::SQL::Constants::NOTNULL) == 'NOT NULL'
|
3278
|
+
@db.literal(Sequel::NOTNULL) == 'NOT NULL'
|
3279
|
+
end
|
3280
|
+
|
3281
|
+
it "should have TRUE and SQLTRUE" do
|
3282
|
+
@db.literal(Sequel::SQL::Constants::TRUE) == '1'
|
3283
|
+
@db.literal(Sequel::TRUE) == '1'
|
3284
|
+
@db.literal(Sequel::SQL::Constants::SQLTRUE) == '1'
|
3285
|
+
@db.literal(Sequel::SQLTRUE) == '1'
|
3286
|
+
end
|
3287
|
+
|
3288
|
+
it "should have FALSE and SQLFALSE" do
|
3289
|
+
@db.literal(Sequel::SQL::Constants::FALSE) == '0'
|
3290
|
+
@db.literal(Sequel::FALSE) == '0'
|
3291
|
+
@db.literal(Sequel::SQL::Constants::SQLFALSE) == '0'
|
3292
|
+
@db.literal(Sequel::SQLFALSE) == '0'
|
3293
|
+
end
|
3242
3294
|
end
|
3243
3295
|
|
3244
3296
|
describe "Sequel timezone support" do
|
@@ -3385,3 +3437,138 @@ describe "Sequel timezone support" do
|
|
3385
3437
|
Sequel.typecast_timezone.should == :utc
|
3386
3438
|
end
|
3387
3439
|
end
|
3440
|
+
|
3441
|
+
context "Sequel::Dataset#select_map" do
|
3442
|
+
before do
|
3443
|
+
@ds = MockDatabase.new[:t]
|
3444
|
+
def @ds.fetch_rows(sql)
|
3445
|
+
db << sql
|
3446
|
+
yield({:c=>1})
|
3447
|
+
yield({:c=>2})
|
3448
|
+
end
|
3449
|
+
@ds.db.reset
|
3450
|
+
end
|
3451
|
+
|
3452
|
+
specify "should do select and map in one step" do
|
3453
|
+
@ds.select_map(:a).should == [1, 2]
|
3454
|
+
@ds.db.sqls.should == ['SELECT a FROM t']
|
3455
|
+
end
|
3456
|
+
|
3457
|
+
specify "should handle implicit qualifiers in arguments" do
|
3458
|
+
@ds.select_map(:a__b).should == [1, 2]
|
3459
|
+
@ds.db.sqls.should == ['SELECT a.b FROM t']
|
3460
|
+
end
|
3461
|
+
|
3462
|
+
specify "should handle implicit aliases in arguments" do
|
3463
|
+
@ds.select_map(:a___b).should == [1, 2]
|
3464
|
+
@ds.db.sqls.should == ['SELECT a AS b FROM t']
|
3465
|
+
end
|
3466
|
+
|
3467
|
+
specify "should handle other objects" do
|
3468
|
+
@ds.select_map("a".lit.as(:b)).should == [1, 2]
|
3469
|
+
@ds.db.sqls.should == ['SELECT a AS b FROM t']
|
3470
|
+
end
|
3471
|
+
|
3472
|
+
specify "should accept a block" do
|
3473
|
+
@ds.select_map{a(t__c)}.should == [1, 2]
|
3474
|
+
@ds.db.sqls.should == ['SELECT a(t.c) FROM t']
|
3475
|
+
end
|
3476
|
+
end
|
3477
|
+
|
3478
|
+
context "Sequel::Dataset#select_order_map" do
|
3479
|
+
before do
|
3480
|
+
@ds = MockDatabase.new[:t]
|
3481
|
+
def @ds.fetch_rows(sql)
|
3482
|
+
db << sql
|
3483
|
+
yield({:c=>1})
|
3484
|
+
yield({:c=>2})
|
3485
|
+
end
|
3486
|
+
@ds.db.reset
|
3487
|
+
end
|
3488
|
+
|
3489
|
+
specify "should do select and map in one step" do
|
3490
|
+
@ds.select_order_map(:a).should == [1, 2]
|
3491
|
+
@ds.db.sqls.should == ['SELECT a FROM t ORDER BY a']
|
3492
|
+
end
|
3493
|
+
|
3494
|
+
specify "should handle implicit qualifiers in arguments" do
|
3495
|
+
@ds.select_order_map(:a__b).should == [1, 2]
|
3496
|
+
@ds.db.sqls.should == ['SELECT a.b FROM t ORDER BY a.b']
|
3497
|
+
end
|
3498
|
+
|
3499
|
+
specify "should handle implicit aliases in arguments" do
|
3500
|
+
@ds.select_order_map(:a___b).should == [1, 2]
|
3501
|
+
@ds.db.sqls.should == ['SELECT a AS b FROM t ORDER BY a']
|
3502
|
+
end
|
3503
|
+
|
3504
|
+
specify "should handle implicit qualifiers and aliases in arguments" do
|
3505
|
+
@ds.select_order_map(:t__a___b).should == [1, 2]
|
3506
|
+
@ds.db.sqls.should == ['SELECT t.a AS b FROM t ORDER BY t.a']
|
3507
|
+
end
|
3508
|
+
|
3509
|
+
specify "should handle AliasedExpressions" do
|
3510
|
+
@ds.select_order_map("a".lit.as(:b)).should == [1, 2]
|
3511
|
+
@ds.db.sqls.should == ['SELECT a AS b FROM t ORDER BY a']
|
3512
|
+
end
|
3513
|
+
|
3514
|
+
specify "should accept a block" do
|
3515
|
+
@ds.select_order_map{a(t__c)}.should == [1, 2]
|
3516
|
+
@ds.db.sqls.should == ['SELECT a(t.c) FROM t ORDER BY a(t.c)']
|
3517
|
+
end
|
3518
|
+
end
|
3519
|
+
|
3520
|
+
context "Sequel::Dataset#select_hash" do
|
3521
|
+
before do
|
3522
|
+
@ds = MockDatabase.new[:t]
|
3523
|
+
def @ds.set_fr_yield(hs)
|
3524
|
+
@hs = hs
|
3525
|
+
end
|
3526
|
+
def @ds.fetch_rows(sql)
|
3527
|
+
db << sql
|
3528
|
+
@hs.each{|h| yield h}
|
3529
|
+
end
|
3530
|
+
@ds.db.reset
|
3531
|
+
end
|
3532
|
+
|
3533
|
+
specify "should do select and map in one step" do
|
3534
|
+
@ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
|
3535
|
+
@ds.select_hash(:a, :b).should == {1=>2, 3=>4}
|
3536
|
+
@ds.db.sqls.should == ['SELECT a, b FROM t']
|
3537
|
+
end
|
3538
|
+
|
3539
|
+
specify "should handle implicit qualifiers in arguments" do
|
3540
|
+
@ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
|
3541
|
+
@ds.select_hash(:t__a, :t__b).should == {1=>2, 3=>4}
|
3542
|
+
@ds.db.sqls.should == ['SELECT t.a, t.b FROM t']
|
3543
|
+
end
|
3544
|
+
|
3545
|
+
specify "should handle implicit aliases in arguments" do
|
3546
|
+
@ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
|
3547
|
+
@ds.select_hash(:c___a, :d___b).should == {1=>2, 3=>4}
|
3548
|
+
@ds.db.sqls.should == ['SELECT c AS a, d AS b FROM t']
|
3549
|
+
end
|
3550
|
+
|
3551
|
+
specify "should handle implicit qualifiers and aliases in arguments" do
|
3552
|
+
@ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
|
3553
|
+
@ds.select_hash(:t__c___a, :t__d___b).should == {1=>2, 3=>4}
|
3554
|
+
@ds.db.sqls.should == ['SELECT t.c AS a, t.d AS b FROM t']
|
3555
|
+
end
|
3556
|
+
end
|
3557
|
+
|
3558
|
+
context "Modifying joined datasets" do
|
3559
|
+
before do
|
3560
|
+
@ds = MockDatabase.new.from(:b, :c).join(:d, [:id]).where(:id => 2)
|
3561
|
+
@ds.meta_def(:supports_modifying_joins?){true}
|
3562
|
+
@ds.db.reset
|
3563
|
+
end
|
3564
|
+
|
3565
|
+
specify "should allow deleting from joined datasets" do
|
3566
|
+
@ds.delete
|
3567
|
+
@ds.db.sqls.should == ['DELETE FROM b, c WHERE (id = 2)']
|
3568
|
+
end
|
3569
|
+
|
3570
|
+
specify "should allow updating joined datasets" do
|
3571
|
+
@ds.update(:a=>1)
|
3572
|
+
@ds.db.sqls.should == ['UPDATE b, c INNER JOIN d USING (id) SET a = 1 WHERE (id = 2)']
|
3573
|
+
end
|
3574
|
+
end
|
@@ -399,6 +399,31 @@ context "Blockless Ruby Filters" do
|
|
399
399
|
y.lit.should == y
|
400
400
|
end
|
401
401
|
|
402
|
+
it "should return have .sql_literal operate like .to_s" do
|
403
|
+
y = :x + 1
|
404
|
+
y.sql_literal(@d).should == '(x + 1)'
|
405
|
+
y.sql_literal(@d).should == y.to_s(@d)
|
406
|
+
y.sql_literal(@d).should == @d.literal(y)
|
407
|
+
end
|
408
|
+
|
409
|
+
it "should support SQL::Constants" do
|
410
|
+
@d.l({:x => Sequel::NULL}).should == '(x IS NULL)'
|
411
|
+
@d.l({:x => Sequel::NOTNULL}).should == '(x IS NOT NULL)'
|
412
|
+
@d.l({:x => Sequel::TRUE}).should == '(x IS TRUE)'
|
413
|
+
@d.l({:x => Sequel::FALSE}).should == '(x IS FALSE)'
|
414
|
+
@d.l({:x => Sequel::SQLTRUE}).should == '(x IS TRUE)'
|
415
|
+
@d.l({:x => Sequel::SQLFALSE}).should == '(x IS FALSE)'
|
416
|
+
end
|
417
|
+
|
418
|
+
it "should support negation of SQL::Constants" do
|
419
|
+
@d.l(~{:x => Sequel::NULL}).should == '(x IS NOT NULL)'
|
420
|
+
@d.l(~{:x => Sequel::NOTNULL}).should == '(x IS NULL)'
|
421
|
+
@d.l(~{:x => Sequel::TRUE}).should == '(x IS NOT TRUE)'
|
422
|
+
@d.l(~{:x => Sequel::FALSE}).should == '(x IS NOT FALSE)'
|
423
|
+
@d.l(~{:x => Sequel::SQLTRUE}).should == '(x IS NOT TRUE)'
|
424
|
+
@d.l(~{:x => Sequel::SQLFALSE}).should == '(x IS NOT FALSE)'
|
425
|
+
end
|
426
|
+
|
402
427
|
it "should raise an error if trying to create an invalid complex expression" do
|
403
428
|
proc{Sequel::SQL::ComplexExpression.new(:BANG, 1, 2)}.should raise_error(Sequel::Error)
|
404
429
|
end
|
@@ -80,6 +80,8 @@ describe "Sequel::Database dump methods" do
|
|
80
80
|
[:c2, {:db_type=>'datetime', :allow_null=>false}]]
|
81
81
|
when :t5
|
82
82
|
[[:c1, {:db_type=>'blahblah', :allow_null=>true}]]
|
83
|
+
when :t6
|
84
|
+
[[:c1, {:db_type=>'bigint', :primary_key=>true, :allow_null=>true}]]
|
83
85
|
end
|
84
86
|
end
|
85
87
|
end
|
@@ -88,6 +90,10 @@ describe "Sequel::Database dump methods" do
|
|
88
90
|
@d.dump_table_schema(:t1).should == "create_table(:t1) do\n primary_key :c1\n String :c2, :size=>20\nend"
|
89
91
|
end
|
90
92
|
|
93
|
+
it "should dump non-Integer primary key columns with explicit :type" do
|
94
|
+
@d.dump_table_schema(:t6).should == "create_table(:t6) do\n primary_key :c1, :type=>Bignum\nend"
|
95
|
+
end
|
96
|
+
|
91
97
|
it "should use a composite primary_key calls if there is a composite primary key" do
|
92
98
|
@d.dump_table_schema(:t2).should == "create_table(:t2) do\n Integer :c1, :null=>false\n BigDecimal :c2, :null=>false\n \n primary_key [:c1, :c2]\nend"
|
93
99
|
end
|
@@ -377,4 +377,39 @@ describe "Sequel::Plugins::ValidationHelpers" do
|
|
377
377
|
MODEL_DB.sqls.should == ["SELECT COUNT(*) AS count FROM items WHERE ((username = '0records') AND active) LIMIT 1",
|
378
378
|
"SELECT COUNT(*) AS count FROM items WHERE (((username = '0records') AND active) AND (id != 3)) LIMIT 1"]
|
379
379
|
end
|
380
|
+
|
381
|
+
it "should support :only_if_modified option for validates_unique, and not check uniqueness for existing records if values haven't changed" do
|
382
|
+
@c.columns(:id, :username, :password)
|
383
|
+
@c.set_dataset MODEL_DB[:items]
|
384
|
+
@c.set_validations{validates_unique([:username, :password], :only_if_modified=>true)}
|
385
|
+
|
386
|
+
@c.dataset.extend(Module.new {
|
387
|
+
def fetch_rows (sql)
|
388
|
+
@db << sql
|
389
|
+
yield({:v => 0})
|
390
|
+
end
|
391
|
+
})
|
392
|
+
|
393
|
+
MODEL_DB.reset
|
394
|
+
@c.new(:username => "0records", :password => "anothertest").should be_valid
|
395
|
+
MODEL_DB.sqls.should == ["SELECT COUNT(*) AS count FROM items WHERE ((username = '0records') AND (password = 'anothertest')) LIMIT 1"]
|
396
|
+
MODEL_DB.reset
|
397
|
+
m = @c.load(:id=>3, :username => "0records", :password => "anothertest")
|
398
|
+
m.should be_valid
|
399
|
+
MODEL_DB.sqls.should == []
|
400
|
+
|
401
|
+
m.username = '1'
|
402
|
+
m.should be_valid
|
403
|
+
MODEL_DB.sqls.should == ["SELECT COUNT(*) AS count FROM items WHERE (((username = '1') AND (password = 'anothertest')) AND (id != 3)) LIMIT 1"]
|
404
|
+
|
405
|
+
m = @c.load(:id=>3, :username => "0records", :password => "anothertest")
|
406
|
+
MODEL_DB.reset
|
407
|
+
m.password = '1'
|
408
|
+
m.should be_valid
|
409
|
+
MODEL_DB.sqls.should == ["SELECT COUNT(*) AS count FROM items WHERE (((username = '0records') AND (password = '1')) AND (id != 3)) LIMIT 1"]
|
410
|
+
MODEL_DB.reset
|
411
|
+
m.username = '2'
|
412
|
+
m.should be_valid
|
413
|
+
MODEL_DB.sqls.should == ["SELECT COUNT(*) AS count FROM items WHERE (((username = '2') AND (password = '1')) AND (id != 3)) LIMIT 1"]
|
414
|
+
end
|
380
415
|
end
|
@@ -870,3 +870,40 @@ describe "Dataset defaults and overrides" do
|
|
870
870
|
@ds.all.should == [{:a=>10}, {:a=>10}]
|
871
871
|
end
|
872
872
|
end
|
873
|
+
|
874
|
+
if INTEGRATION_DB.dataset.supports_modifying_joins?
|
875
|
+
describe "Modifying joined datasets" do
|
876
|
+
before do
|
877
|
+
@db = INTEGRATION_DB
|
878
|
+
@db.create_table!(:a){Integer :a; Integer :d}
|
879
|
+
@db.create_table!(:b){Integer :b; Integer :e}
|
880
|
+
@db.create_table!(:c){Integer :c; Integer :f}
|
881
|
+
@ds = @db.from(:a, :b).join(:c, :c=>:e.identifier).where(:d=>:b, :f=>6)
|
882
|
+
@db[:a].insert(1, 2)
|
883
|
+
@db[:a].insert(3, 4)
|
884
|
+
@db[:b].insert(2, 5)
|
885
|
+
@db[:c].insert(5, 6)
|
886
|
+
@db[:b].insert(4, 7)
|
887
|
+
@db[:c].insert(7, 8)
|
888
|
+
end
|
889
|
+
after do
|
890
|
+
@db.drop_table(:a, :b, :c)
|
891
|
+
end
|
892
|
+
|
893
|
+
it "#update should allow updating joined datasets" do
|
894
|
+
@ds.update(:a=>10)
|
895
|
+
@ds.all.should == [{:c=>5, :b=>2, :a=>10, :d=>2, :e=>5, :f=>6}]
|
896
|
+
@db[:a].order(:a).all.should == [{:a=>3, :d=>4}, {:a=>10, :d=>2}]
|
897
|
+
@db[:b].order(:b).all.should == [{:b=>2, :e=>5}, {:b=>4, :e=>7}]
|
898
|
+
@db[:c].order(:c).all.should == [{:c=>5, :f=>6}, {:c=>7, :f=>8}]
|
899
|
+
end
|
900
|
+
|
901
|
+
it "#delete should allow deleting from joined datasets" do
|
902
|
+
@ds.delete
|
903
|
+
@ds.all.should == []
|
904
|
+
@db[:a].order(:a).all.should == [{:a=>3, :d=>4}]
|
905
|
+
@db[:b].order(:b).all.should == [{:b=>2, :e=>5}, {:b=>4, :e=>7}]
|
906
|
+
@db[:c].order(:c).all.should == [{:c=>5, :f=>6}, {:c=>7, :f=>8}]
|
907
|
+
end
|
908
|
+
end
|
909
|
+
end
|