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