sequel 5.21.0 → 5.26.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 +80 -0
- data/README.rdoc +1 -1
- data/doc/dataset_filtering.rdoc +15 -0
- data/doc/opening_databases.rdoc +3 -0
- data/doc/postgresql.rdoc +2 -2
- data/doc/release_notes/5.22.0.txt +48 -0
- data/doc/release_notes/5.23.0.txt +56 -0
- data/doc/release_notes/5.24.0.txt +56 -0
- data/doc/release_notes/5.25.0.txt +32 -0
- data/doc/release_notes/5.26.0.txt +35 -0
- data/doc/testing.rdoc +1 -0
- data/lib/sequel/adapters/jdbc.rb +7 -1
- data/lib/sequel/adapters/jdbc/postgresql.rb +1 -13
- data/lib/sequel/adapters/jdbc/sqlite.rb +29 -0
- data/lib/sequel/adapters/mysql2.rb +0 -1
- data/lib/sequel/adapters/shared/mssql.rb +11 -9
- data/lib/sequel/adapters/shared/postgres.rb +42 -12
- data/lib/sequel/adapters/shared/sqlite.rb +16 -2
- data/lib/sequel/adapters/tinytds.rb +12 -0
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
- data/lib/sequel/database/logging.rb +7 -1
- data/lib/sequel/database/schema_generator.rb +11 -2
- data/lib/sequel/database/schema_methods.rb +2 -0
- data/lib/sequel/dataset.rb +4 -2
- data/lib/sequel/dataset/actions.rb +3 -2
- data/lib/sequel/dataset/query.rb +4 -0
- data/lib/sequel/dataset/sql.rb +11 -7
- data/lib/sequel/extensions/named_timezones.rb +51 -9
- data/lib/sequel/extensions/pg_array.rb +4 -0
- data/lib/sequel/extensions/pg_json.rb +88 -17
- data/lib/sequel/extensions/pg_json_ops.rb +124 -0
- data/lib/sequel/extensions/pg_range.rb +12 -2
- data/lib/sequel/extensions/pg_row.rb +3 -1
- data/lib/sequel/extensions/sql_comments.rb +2 -2
- data/lib/sequel/model/base.rb +12 -5
- data/lib/sequel/plugins/association_multi_add_remove.rb +83 -0
- data/lib/sequel/plugins/association_proxies.rb +3 -2
- data/lib/sequel/plugins/caching.rb +3 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +10 -0
- data/lib/sequel/plugins/csv_serializer.rb +26 -9
- data/lib/sequel/plugins/dirty.rb +3 -9
- data/lib/sequel/plugins/insert_conflict.rb +72 -0
- data/lib/sequel/plugins/nested_attributes.rb +7 -0
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +89 -30
- data/lib/sequel/plugins/sharding.rb +11 -5
- data/lib/sequel/plugins/static_cache.rb +8 -3
- data/lib/sequel/plugins/static_cache_cache.rb +53 -0
- data/lib/sequel/plugins/typecast_on_load.rb +3 -2
- data/lib/sequel/sql.rb +18 -4
- data/lib/sequel/timezones.rb +50 -11
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +174 -0
- data/spec/bin_spec.rb +2 -2
- data/spec/core/database_spec.rb +50 -0
- data/spec/core/dataset_spec.rb +33 -1
- data/spec/core/expression_filters_spec.rb +32 -3
- data/spec/core/schema_spec.rb +18 -0
- data/spec/core/spec_helper.rb +1 -1
- data/spec/core_extensions_spec.rb +1 -1
- data/spec/extensions/association_multi_add_remove_spec.rb +1041 -0
- data/spec/extensions/dirty_spec.rb +33 -0
- data/spec/extensions/insert_conflict_spec.rb +103 -0
- data/spec/extensions/named_timezones_spec.rb +109 -2
- data/spec/extensions/nested_attributes_spec.rb +48 -0
- data/spec/extensions/pg_auto_constraint_validations_spec.rb +37 -0
- data/spec/extensions/pg_json_ops_spec.rb +67 -0
- data/spec/extensions/pg_json_spec.rb +12 -0
- data/spec/extensions/pg_range_spec.rb +90 -9
- data/spec/extensions/sharding_spec.rb +8 -0
- data/spec/extensions/spec_helper.rb +9 -2
- data/spec/extensions/static_cache_cache_spec.rb +35 -0
- data/spec/guards_helper.rb +1 -1
- data/spec/integration/dataset_test.rb +24 -8
- data/spec/integration/plugin_test.rb +27 -0
- data/spec/integration/schema_test.rb +16 -2
- data/spec/model/spec_helper.rb +1 -1
- metadata +32 -2
data/spec/bin_spec.rb
CHANGED
@@ -25,7 +25,7 @@ DB2 = Sequel.connect("#{CONN_PREFIX}#{BIN_SPEC_DB2}", :test=>false)
|
|
25
25
|
|
26
26
|
ENV['MT_NO_PLUGINS'] = '1' # Work around stupid autoloading of plugins
|
27
27
|
gem 'minitest'
|
28
|
-
require 'minitest/autorun'
|
28
|
+
require 'minitest/global_expectations/autorun'
|
29
29
|
|
30
30
|
describe "bin/sequel" do
|
31
31
|
def bin(opts={})
|
@@ -86,7 +86,7 @@ Begin creating indexes
|
|
86
86
|
Finished creating indexes
|
87
87
|
Begin adding foreign key constraints
|
88
88
|
Finished adding foreign key constraints
|
89
|
-
Database copy finished in \\d
|
89
|
+
Database copy finished in \\d+\\.\\d+ seconds
|
90
90
|
END
|
91
91
|
DB2.tables.sort_by{|t| t.to_s}.must_equal [:a, :b]
|
92
92
|
DB[:a].all.must_equal [{:a=>1, :name=>'foo'}]
|
data/spec/core/database_spec.rb
CHANGED
@@ -264,6 +264,25 @@ describe "Database#log_connection_yield" do
|
|
264
264
|
@o.logs.first.first.must_equal :info
|
265
265
|
@o.logs.first.last.must_match(/\A\(\d\.\d{6}s\) blah; \[1, 2\]\z/)
|
266
266
|
end
|
267
|
+
|
268
|
+
it "should log without a logger defined by forcing skip_logging? to return false" do
|
269
|
+
@db.logger = nil
|
270
|
+
@db.extend(Module.new do
|
271
|
+
def skip_logging?
|
272
|
+
false
|
273
|
+
end
|
274
|
+
|
275
|
+
def log_duration(*)
|
276
|
+
self.did_log = true
|
277
|
+
end
|
278
|
+
|
279
|
+
attr_accessor :did_log
|
280
|
+
end)
|
281
|
+
|
282
|
+
@db.log_connection_yield('some sql', @conn) {}
|
283
|
+
|
284
|
+
@db.did_log.must_equal true
|
285
|
+
end
|
267
286
|
end
|
268
287
|
|
269
288
|
describe "Database#uri" do
|
@@ -2073,6 +2092,18 @@ describe "Database#typecast_value" do
|
|
2073
2092
|
proc{@db.typecast_value(:datetime, 4)}.must_raise(Sequel::InvalidValue)
|
2074
2093
|
end
|
2075
2094
|
|
2095
|
+
it "should raise an InvalidValue when given an invalid timezone value" do
|
2096
|
+
begin
|
2097
|
+
Sequel.default_timezone = :blah
|
2098
|
+
proc{@db.typecast_value(:datetime, [2019, 2, 3, 4, 5, 6])}.must_raise(Sequel::InvalidValue)
|
2099
|
+
Sequel.datetime_class = DateTime
|
2100
|
+
proc{@db.typecast_value(:datetime, [2019, 2, 3, 4, 5, 6])}.must_raise(Sequel::InvalidValue)
|
2101
|
+
ensure
|
2102
|
+
Sequel.default_timezone = nil
|
2103
|
+
Sequel.datetime_class = Time
|
2104
|
+
end
|
2105
|
+
end
|
2106
|
+
|
2076
2107
|
it "should handle integers with leading 0 as base 10" do
|
2077
2108
|
@db.typecast_value(:integer, "013").must_equal 13
|
2078
2109
|
@db.typecast_value(:integer, "08").must_equal 8
|
@@ -2317,6 +2348,17 @@ describe "Database#typecast_value" do
|
|
2317
2348
|
@db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14).must_equal Time.local(2011, 10, 11, 12, 13, 14)
|
2318
2349
|
@db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'nanos'=>500000000).must_equal Time.local(2011, 10, 11, 12, 13, 14, 500000)
|
2319
2350
|
|
2351
|
+
@db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :offset=>Rational(1, 2)).must_equal Time.new(2011, 10, 11, 12, 13, 14, 43200)
|
2352
|
+
@db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :nanos=>500000000, :offset=>Rational(1, 2)).must_equal Time.new(2011, 10, 11, 12, 13, 14.5, 43200)
|
2353
|
+
@db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'offset'=>Rational(1, 2)).must_equal Time.new(2011, 10, 11, 12, 13, 14, 43200)
|
2354
|
+
@db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'nanos'=>500000000, 'offset'=>Rational(1, 2)).must_equal Time.new(2011, 10, 11, 12, 13, 14.5, 43200)
|
2355
|
+
|
2356
|
+
Sequel.default_timezone = :utc
|
2357
|
+
@db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14).must_equal Time.utc(2011, 10, 11, 12, 13, 14)
|
2358
|
+
@db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :nanos=>500000000).must_equal Time.utc(2011, 10, 11, 12, 13, 14, 500000)
|
2359
|
+
@db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14).must_equal Time.utc(2011, 10, 11, 12, 13, 14)
|
2360
|
+
@db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'nanos'=>500000000).must_equal Time.utc(2011, 10, 11, 12, 13, 14, 500000)
|
2361
|
+
|
2320
2362
|
Sequel.datetime_class = DateTime
|
2321
2363
|
@db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14).must_equal DateTime.civil(2011, 10, 11, 12, 13, 14)
|
2322
2364
|
@db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :nanos=>500000000).must_equal DateTime.civil(2011, 10, 11, 12, 13, Rational(29, 2))
|
@@ -2326,8 +2368,16 @@ describe "Database#typecast_value" do
|
|
2326
2368
|
@db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :nanos=>500000000, :offset=>Rational(1, 2)).must_equal DateTime.civil(2011, 10, 11, 12, 13, Rational(29, 2), Rational(1, 2))
|
2327
2369
|
@db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'offset'=>Rational(1, 2)).must_equal DateTime.civil(2011, 10, 11, 12, 13, 14, Rational(1, 2))
|
2328
2370
|
@db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'nanos'=>500000000, 'offset'=>Rational(1, 2)).must_equal DateTime.civil(2011, 10, 11, 12, 13, Rational(29, 2), Rational(1, 2))
|
2371
|
+
|
2372
|
+
Sequel.default_timezone = :local
|
2373
|
+
offset = Rational(Time.local(2011, 10, 11, 12, 13, 14).utc_offset, 86400)
|
2374
|
+
@db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14).must_equal DateTime.civil(2011, 10, 11, 12, 13, 14, offset)
|
2375
|
+
@db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :nanos=>500000000).must_equal DateTime.civil(2011, 10, 11, 12, 13, Rational(29, 2), offset)
|
2376
|
+
@db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14).must_equal DateTime.civil(2011, 10, 11, 12, 13, 14, offset)
|
2377
|
+
@db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'nanos'=>500000000).must_equal DateTime.civil(2011, 10, 11, 12, 13, Rational(29, 2), offset)
|
2329
2378
|
ensure
|
2330
2379
|
Sequel.datetime_class = Time
|
2380
|
+
Sequel.default_timezone = nil
|
2331
2381
|
end
|
2332
2382
|
end
|
2333
2383
|
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -3434,6 +3434,16 @@ describe "Dataset#multi_insert" do
|
|
3434
3434
|
'COMMIT']
|
3435
3435
|
end
|
3436
3436
|
|
3437
|
+
it "should handle :return=>:primary_key option if dataset has a row_proc" do
|
3438
|
+
@db.autoid = 1
|
3439
|
+
@ds.with_row_proc(lambda{|h| Object.new}).multi_insert(@list, :return=>:primary_key).must_equal [1, 2, 3]
|
3440
|
+
@db.sqls.must_equal ['BEGIN',
|
3441
|
+
"INSERT INTO items (name) VALUES ('abc')",
|
3442
|
+
"INSERT INTO items (name) VALUES ('def')",
|
3443
|
+
"INSERT INTO items (name) VALUES ('ghi')",
|
3444
|
+
'COMMIT']
|
3445
|
+
end
|
3446
|
+
|
3437
3447
|
with_symbol_splitting "should handle splittable symbols for tables" do
|
3438
3448
|
@ds = @ds.from(:sch__tab)
|
3439
3449
|
@ds.multi_insert(@list)
|
@@ -3715,7 +3725,7 @@ end
|
|
3715
3725
|
|
3716
3726
|
describe "Dataset default #fetch_rows, #insert, #update, #delete, #truncate, #execute" do
|
3717
3727
|
before do
|
3718
|
-
@db = Sequel.mock(:servers=>{:read_only=>{}}, :autoid=>1)
|
3728
|
+
@db = Sequel.mock(:servers=>{:read_only=>{}, :r1=>{}}, :autoid=>1)
|
3719
3729
|
@ds = @db[:items]
|
3720
3730
|
end
|
3721
3731
|
|
@@ -3753,6 +3763,18 @@ describe "Dataset default #fetch_rows, #insert, #update, #delete, #truncate, #ex
|
|
3753
3763
|
@ds.for_update.send(:execute, 'SELECT 1')
|
3754
3764
|
@db.sqls.must_equal ["SELECT 1"]
|
3755
3765
|
end
|
3766
|
+
|
3767
|
+
[:execute, :execute_dui, :execute_insert, :execute_ddl].each do |meth|
|
3768
|
+
it "##{meth} should respect explicit :server option" do
|
3769
|
+
@ds.send(meth, 'SELECT 1', :server=>:r1)
|
3770
|
+
@db.sqls.must_equal ["SELECT 1 -- r1"]
|
3771
|
+
end
|
3772
|
+
|
3773
|
+
it "##{meth} should respect dataset's :server option if :server option not given" do
|
3774
|
+
@ds.server(:r1).send(meth, 'SELECT 1')
|
3775
|
+
@db.sqls.must_equal ["SELECT 1 -- r1"]
|
3776
|
+
end
|
3777
|
+
end
|
3756
3778
|
end
|
3757
3779
|
|
3758
3780
|
describe "Dataset#with_sql_*" do
|
@@ -4483,6 +4505,16 @@ describe "Sequel timezone support" do
|
|
4483
4505
|
proc{Sequel.database_to_application_timestamp(Object.new)}.must_raise(Sequel::InvalidValue)
|
4484
4506
|
end
|
4485
4507
|
|
4508
|
+
it "should raise an InvalidValue error when the Time class is used and when a bad application timezone is used when attempting to convert timestamps" do
|
4509
|
+
Sequel.application_timezone = :blah
|
4510
|
+
proc{Sequel.database_to_application_timestamp('2009-06-01 10:20:30')}.must_raise(Sequel::InvalidValue)
|
4511
|
+
end
|
4512
|
+
|
4513
|
+
it "should raise an InvalidValue error when the Time class is used and when a bad database timezone is used when attempting to convert timestamps" do
|
4514
|
+
Sequel.database_timezone = :blah
|
4515
|
+
proc{Sequel.database_to_application_timestamp('2009-06-01 10:20:30')}.must_raise(Sequel::InvalidValue)
|
4516
|
+
end
|
4517
|
+
|
4486
4518
|
it "should raise an InvalidValue error when the DateTime class is used and when a bad application timezone is used when attempting to convert timestamps" do
|
4487
4519
|
Sequel.application_timezone = :blah
|
4488
4520
|
Sequel.datetime_class = DateTime
|
@@ -214,8 +214,13 @@ describe "Blockless Ruby Filters" do
|
|
214
214
|
@d.lit(1 + Sequel.lit('?', :x)).must_equal '(1 + x)'
|
215
215
|
end
|
216
216
|
|
217
|
-
it "should
|
218
|
-
|
217
|
+
it "should not break Date/DateTime equality" do
|
218
|
+
(Date.today == Sequel.expr(:x)).must_equal false
|
219
|
+
(DateTime.now == Sequel.expr(:x)).must_equal false
|
220
|
+
end
|
221
|
+
|
222
|
+
it "should have coerce return array if called on a non-numeric" do
|
223
|
+
Sequel.expr(:x).coerce(:a).must_equal [Sequel.expr(:x), :a]
|
219
224
|
end
|
220
225
|
|
221
226
|
it "should support AND conditions via &" do
|
@@ -526,11 +531,35 @@ describe "Blockless Ruby Filters" do
|
|
526
531
|
dsc.new(@d.db).literal(Sequel.trim(:a)).must_equal 'trimFOO(lower(a))'
|
527
532
|
end
|
528
533
|
|
529
|
-
it "should endless ranges" do
|
534
|
+
it "should handle endless ranges" do
|
530
535
|
endless = eval('1..')
|
531
536
|
@d.l{x =~ endless}.must_equal '(x >= 1)'
|
532
537
|
@d.l(:x => endless).must_equal '(x >= 1)'
|
538
|
+
|
539
|
+
endless = eval('1...')
|
540
|
+
@d.l{x =~ endless}.must_equal '(x >= 1)'
|
541
|
+
@d.l(:x => endless).must_equal '(x >= 1)'
|
533
542
|
end if RUBY_VERSION >= '2.6'
|
543
|
+
|
544
|
+
it "should handle startless ranges" do
|
545
|
+
endless = eval('..1')
|
546
|
+
@d.l{x =~ endless}.must_equal '(x <= 1)'
|
547
|
+
@d.l(:x => endless).must_equal '(x <= 1)'
|
548
|
+
|
549
|
+
endless = eval('...1')
|
550
|
+
@d.l{x =~ endless}.must_equal '(x < 1)'
|
551
|
+
@d.l(:x => endless).must_equal '(x < 1)'
|
552
|
+
end if RUBY_VERSION >= '2.7'
|
553
|
+
|
554
|
+
it "should handle startless, endless ranges" do
|
555
|
+
endless = eval('nil..nil')
|
556
|
+
@d.l{x =~ endless}.must_equal '(1 = 1)'
|
557
|
+
@d.l(:x => endless).must_equal '(1 = 1)'
|
558
|
+
|
559
|
+
endless = eval('nil...nil')
|
560
|
+
@d.l{x =~ endless}.must_equal '(1 = 1)'
|
561
|
+
@d.l(:x => endless).must_equal '(1 = 1)'
|
562
|
+
end if RUBY_VERSION >= '2.7'
|
534
563
|
end
|
535
564
|
|
536
565
|
describe Sequel::SQL::VirtualRow do
|
data/spec/core/schema_spec.rb
CHANGED
@@ -240,6 +240,24 @@ describe "DB#create_table" do
|
|
240
240
|
@db.sqls.must_equal ["CREATE TABLE cats (id integer, name text, UNIQUE (name) DEFERRABLE INITIALLY IMMEDIATE)"]
|
241
241
|
end
|
242
242
|
|
243
|
+
it "should handle deferred unique column constraints" do
|
244
|
+
@db.create_table(:cats) do
|
245
|
+
integer :id, :unique=>true, :unique_deferrable=>true
|
246
|
+
integer :i, :unique=>true, :unique_deferrable=>:immediate
|
247
|
+
integer :j, :unique=>true, :unique_deferrable=>false
|
248
|
+
end
|
249
|
+
@db.sqls.must_equal ["CREATE TABLE cats (id integer UNIQUE DEFERRABLE INITIALLY DEFERRED, i integer UNIQUE DEFERRABLE INITIALLY IMMEDIATE, j integer UNIQUE NOT DEFERRABLE)"]
|
250
|
+
end
|
251
|
+
|
252
|
+
it "should handle deferred primary key column constraints" do
|
253
|
+
@db.create_table(:cats) do
|
254
|
+
integer :id, :primary_key=>true, :primary_key_deferrable=>true
|
255
|
+
integer :i, :primary_key=>true, :primary_key_deferrable=>:immediate
|
256
|
+
integer :j, :primary_key=>true, :primary_key_deferrable=>false
|
257
|
+
end
|
258
|
+
@db.sqls.must_equal ["CREATE TABLE cats (id integer PRIMARY KEY DEFERRABLE INITIALLY DEFERRED, i integer PRIMARY KEY DEFERRABLE INITIALLY IMMEDIATE, j integer PRIMARY KEY NOT DEFERRABLE)"]
|
259
|
+
end
|
260
|
+
|
243
261
|
it "should accept unsigned definition" do
|
244
262
|
@db.create_table(:cats) do
|
245
263
|
integer :value, :unsigned => true
|
data/spec/core/spec_helper.rb
CHANGED
@@ -10,7 +10,7 @@ require_relative "../../lib/sequel/core"
|
|
10
10
|
|
11
11
|
ENV['MT_NO_PLUGINS'] = '1' # Work around stupid autoloading of plugins
|
12
12
|
gem 'minitest'
|
13
|
-
require 'minitest/autorun'
|
13
|
+
require 'minitest/global_expectations/autorun'
|
14
14
|
require 'minitest/hooks/default'
|
15
15
|
require 'minitest/shared_description'
|
16
16
|
|
@@ -16,7 +16,7 @@ Sequel.extension :virtual_row_method_block
|
|
16
16
|
|
17
17
|
ENV['MT_NO_PLUGINS'] = '1' # Work around stupid autoloading of plugins
|
18
18
|
gem 'minitest'
|
19
|
-
require 'minitest/autorun'
|
19
|
+
require 'minitest/global_expectations/autorun'
|
20
20
|
require 'minitest/hooks/default'
|
21
21
|
|
22
22
|
require_relative "deprecation_helper.rb"
|
@@ -0,0 +1,1041 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
|
3
|
+
describe "association_multi_add_remove plugin - one_to_many" do
|
4
|
+
before do
|
5
|
+
@c1 = Class.new(Sequel::Model(:attributes)) do
|
6
|
+
unrestrict_primary_key
|
7
|
+
columns :id, :node_id, :y, :z
|
8
|
+
end
|
9
|
+
|
10
|
+
@c2 = Class.new(Sequel::Model(:nodes)) do
|
11
|
+
plugin :association_multi_add_remove
|
12
|
+
|
13
|
+
def _refresh(ds); end
|
14
|
+
unrestrict_primary_key
|
15
|
+
attr_accessor :xxx
|
16
|
+
|
17
|
+
def self.name; 'Node'; end
|
18
|
+
def self.to_s; 'Node'; end
|
19
|
+
|
20
|
+
columns :id, :x
|
21
|
+
end
|
22
|
+
@dataset = @c2.dataset = @c2.dataset.with_fetch({})
|
23
|
+
@c1.dataset = @c1.dataset.with_fetch(proc { |sql| sql =~ /SELECT 1/ ? { a: 1 } : {} })
|
24
|
+
DB.reset
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should define an add_*s method that works on existing records" do
|
28
|
+
@c2.one_to_many :attributes, class: @c1
|
29
|
+
|
30
|
+
n = @c2.load(id: 1234)
|
31
|
+
a1 = @c1.load(id: 2345)
|
32
|
+
a2 = @c1.load(id: 3456)
|
33
|
+
[a1, a2].must_equal n.add_attributes([a1, a2])
|
34
|
+
a1.values.must_equal(:node_id => 1234, id: 2345)
|
35
|
+
a2.values.must_equal(:node_id => 1234, id: 3456)
|
36
|
+
DB.sqls.must_equal [
|
37
|
+
'BEGIN',
|
38
|
+
'UPDATE attributes SET node_id = 1234 WHERE (id = 2345)',
|
39
|
+
'UPDATE attributes SET node_id = 1234 WHERE (id = 3456)',
|
40
|
+
'COMMIT'
|
41
|
+
]
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should not define add/remove methods with the same name as the ones defined by default " do
|
45
|
+
@c2.one_to_many :sheep, class: @c1, :key=>:node_id
|
46
|
+
|
47
|
+
n = @c2.load(id: 1234)
|
48
|
+
a1 = @c1.load(id: 2345)
|
49
|
+
a1.must_be_same_as n.add_sheep(a1)
|
50
|
+
a1.values.must_equal(:node_id => 1234, id: 2345)
|
51
|
+
DB.sqls.must_equal ['UPDATE attributes SET node_id = 1234 WHERE (id = 2345)']
|
52
|
+
a1.must_be_same_as n.remove_sheep(a1)
|
53
|
+
a1.values.must_equal(:node_id => nil, id: 2345)
|
54
|
+
DB.sqls.must_equal [
|
55
|
+
"SELECT 1 AS one FROM attributes WHERE ((attributes.node_id = 1234) AND (id = 2345)) LIMIT 1",
|
56
|
+
'UPDATE attributes SET node_id = NULL WHERE (id = 2345)',
|
57
|
+
]
|
58
|
+
n.respond_to?(:sheep=).must_equal false
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should support :multi_add_method" do
|
62
|
+
@c2.one_to_many :attributes, class: @c1, :multi_add_method=>:add_multiple_attributes
|
63
|
+
|
64
|
+
n = @c2.load(id: 1234)
|
65
|
+
a1 = @c1.load(id: 2345)
|
66
|
+
a2 = @c1.load(id: 3456)
|
67
|
+
[a1, a2].must_equal n.add_multiple_attributes([a1, a2])
|
68
|
+
a1.values.must_equal(:node_id => 1234, id: 2345)
|
69
|
+
a2.values.must_equal(:node_id => 1234, id: 3456)
|
70
|
+
DB.sqls.must_equal [
|
71
|
+
'BEGIN',
|
72
|
+
'UPDATE attributes SET node_id = 1234 WHERE (id = 2345)',
|
73
|
+
'UPDATE attributes SET node_id = 1234 WHERE (id = 3456)',
|
74
|
+
'COMMIT'
|
75
|
+
]
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should define an add_*s method that works on new records" do
|
79
|
+
@c2.one_to_many :attributes, :class => @c1
|
80
|
+
|
81
|
+
n = @c2.load(:id => 1234)
|
82
|
+
a1 = @c1.new(:id => 234)
|
83
|
+
a2 = @c1.new(:id => 345)
|
84
|
+
@c1.dataset = @c1.dataset.with_fetch([
|
85
|
+
[{ :id=>234, :node_id=>1234 }], [{ :id=>345, :node_id=>1234 }]
|
86
|
+
])
|
87
|
+
[a1, a2].must_equal n.add_attributes([a1, a2])
|
88
|
+
DB.sqls.must_equal [
|
89
|
+
'BEGIN',
|
90
|
+
"INSERT INTO attributes (id, node_id) VALUES (234, 1234)",
|
91
|
+
"SELECT * FROM attributes WHERE id = 234",
|
92
|
+
"INSERT INTO attributes (id, node_id) VALUES (345, 1234)",
|
93
|
+
"SELECT * FROM attributes WHERE id = 345",
|
94
|
+
'COMMIT'
|
95
|
+
]
|
96
|
+
a1.values.must_equal(:node_id => 1234, :id => 234)
|
97
|
+
a2.values.must_equal(:node_id => 1234, :id => 345)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should define a remove_*s method that works on existing records" do
|
101
|
+
@c2.one_to_many :attributes, :class => @c1
|
102
|
+
|
103
|
+
n = @c2.load(:id => 1234)
|
104
|
+
a1 = @c1.load(:id => 2345, :node_id => 1234)
|
105
|
+
a2 = @c1.load(:id => 3456, :node_id => 1234)
|
106
|
+
[a1, a2].must_equal n.remove_attributes([a1, a2])
|
107
|
+
a1.values.must_equal(:node_id => nil, :id => 2345)
|
108
|
+
a2.values.must_equal(:node_id => nil, :id => 3456)
|
109
|
+
DB.sqls.must_equal [
|
110
|
+
'BEGIN',
|
111
|
+
"SELECT 1 AS one FROM attributes WHERE ((attributes.node_id = 1234) AND (id = 2345)) LIMIT 1",
|
112
|
+
'UPDATE attributes SET node_id = NULL WHERE (id = 2345)',
|
113
|
+
"SELECT 1 AS one FROM attributes WHERE ((attributes.node_id = 1234) AND (id = 3456)) LIMIT 1",
|
114
|
+
'UPDATE attributes SET node_id = NULL WHERE (id = 3456)',
|
115
|
+
'COMMIT'
|
116
|
+
]
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should support :multi_remove_method" do
|
120
|
+
@c2.one_to_many :attributes, :class => @c1, :multi_remove_method=>:remove_multiple_attributes
|
121
|
+
|
122
|
+
n = @c2.load(:id => 1234)
|
123
|
+
a1 = @c1.load(:id => 2345, :node_id => 1234)
|
124
|
+
a2 = @c1.load(:id => 3456, :node_id => 1234)
|
125
|
+
[a1, a2].must_equal n.remove_multiple_attributes([a1, a2])
|
126
|
+
a1.values.must_equal(:node_id => nil, :id => 2345)
|
127
|
+
a2.values.must_equal(:node_id => nil, :id => 3456)
|
128
|
+
DB.sqls.must_equal [
|
129
|
+
'BEGIN',
|
130
|
+
"SELECT 1 AS one FROM attributes WHERE ((attributes.node_id = 1234) AND (id = 2345)) LIMIT 1",
|
131
|
+
'UPDATE attributes SET node_id = NULL WHERE (id = 2345)',
|
132
|
+
"SELECT 1 AS one FROM attributes WHERE ((attributes.node_id = 1234) AND (id = 3456)) LIMIT 1",
|
133
|
+
'UPDATE attributes SET node_id = NULL WHERE (id = 3456)',
|
134
|
+
'COMMIT'
|
135
|
+
]
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should have the remove_*s method raise an error if the passed objects are not already associated" do
|
139
|
+
@c2.one_to_many :attributes, :class => @c1
|
140
|
+
|
141
|
+
n = @c2.new(:id => 1234)
|
142
|
+
a1 = @c1.load(:id => 2345, :node_id => 1234)
|
143
|
+
a2 = @c1.load(:id => 3456, :node_id => 1234)
|
144
|
+
@c1.dataset = @c1.dataset.with_fetch([])
|
145
|
+
proc{n.remove_attributes([a1, a2])}.must_raise(Sequel::Error)
|
146
|
+
DB.sqls.must_equal [
|
147
|
+
'BEGIN',
|
148
|
+
"SELECT 1 AS one FROM attributes WHERE ((attributes.node_id = 1234) AND (id = 2345)) LIMIT 1",
|
149
|
+
'ROLLBACK'
|
150
|
+
]
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should accept hashes for the add_*s method and create a new records" do
|
154
|
+
@c2.one_to_many :attributes, :class => @c1
|
155
|
+
n = @c2.new(:id => 1234)
|
156
|
+
DB.reset
|
157
|
+
@c1.dataset = @c1.dataset.with_fetch([
|
158
|
+
[{ :node_id => 1234, :id => 234 }], [{ :node_id => 1234, :id => 345 }]
|
159
|
+
])
|
160
|
+
n.add_attributes([{ :id => 234 }, { :id => 345 }]).must_equal [
|
161
|
+
@c1.load(:node_id => 1234, :id => 234),
|
162
|
+
@c1.load(:node_id => 1234, :id => 345)
|
163
|
+
]
|
164
|
+
DB.sqls.must_equal [
|
165
|
+
'BEGIN',
|
166
|
+
"INSERT INTO attributes (id, node_id) VALUES (234, 1234)",
|
167
|
+
"SELECT * FROM attributes WHERE id = 234",
|
168
|
+
"INSERT INTO attributes (id, node_id) VALUES (345, 1234)",
|
169
|
+
"SELECT * FROM attributes WHERE id = 345",
|
170
|
+
'COMMIT'
|
171
|
+
]
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should accept primary keys for the add_*s method" do
|
175
|
+
@c2.one_to_many :attributes, :class => @c1
|
176
|
+
n = @c2.new(:id => 1234)
|
177
|
+
@c1.dataset = @c1.dataset.with_fetch([
|
178
|
+
[{ :node_id => nil, :id => 234 }], [{ :node_id => nil, :id => 345 }]
|
179
|
+
])
|
180
|
+
n.add_attributes([234, 345]).must_equal [
|
181
|
+
@c1.load(:node_id => 1234, :id => 234),
|
182
|
+
@c1.load(:node_id => 1234, :id => 345)
|
183
|
+
]
|
184
|
+
DB.sqls.must_equal [
|
185
|
+
'BEGIN',
|
186
|
+
"SELECT * FROM attributes WHERE id = 234",
|
187
|
+
"UPDATE attributes SET node_id = 1234 WHERE (id = 234)",
|
188
|
+
"SELECT * FROM attributes WHERE id = 345",
|
189
|
+
"UPDATE attributes SET node_id = 1234 WHERE (id = 345)",
|
190
|
+
'COMMIT'
|
191
|
+
]
|
192
|
+
end
|
193
|
+
|
194
|
+
it "should raise an error if the primary key passed to the add_*s method does not match an existing record" do
|
195
|
+
@c2.one_to_many :attributes, :class => @c1
|
196
|
+
n = @c2.new(:id => 1234)
|
197
|
+
@c1.dataset = @c1.dataset.with_fetch([])
|
198
|
+
proc{n.add_attributes([234, 345])}.must_raise(Sequel::NoMatchingRow)
|
199
|
+
DB.sqls.must_equal [
|
200
|
+
'BEGIN',
|
201
|
+
"SELECT * FROM attributes WHERE id = 234",
|
202
|
+
'ROLLBACK'
|
203
|
+
]
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should raise an error in the add_*s method if the passed associated objects are not of the correct type" do
|
207
|
+
@c2.one_to_many :attributes, :class => @c1
|
208
|
+
proc{@c2.new(:id => 1234).add_attributes([@c2.new, @c2.new])}.must_raise(Sequel::Error)
|
209
|
+
end
|
210
|
+
|
211
|
+
it "should accept primary keys for the remove_*s method and remove existing records" do
|
212
|
+
@c2.one_to_many :attributes, :class => @c1
|
213
|
+
n = @c2.new(:id => 1234)
|
214
|
+
@c1.dataset = @c1.dataset.with_fetch([
|
215
|
+
[{ :id=>234, :node_id=>1234 }], [{ :id=>345, :node_id=>1234 }]
|
216
|
+
])
|
217
|
+
n.remove_attributes([234, 345]).must_equal [
|
218
|
+
@c1.load(:node_id => nil, :id => 234),
|
219
|
+
@c1.load(:node_id => nil, :id => 345)
|
220
|
+
]
|
221
|
+
DB.sqls.must_equal [
|
222
|
+
'BEGIN',
|
223
|
+
'SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (attributes.id = 234)) LIMIT 1',
|
224
|
+
'UPDATE attributes SET node_id = NULL WHERE (id = 234)',
|
225
|
+
'SELECT * FROM attributes WHERE ((attributes.node_id = 1234) AND (attributes.id = 345)) LIMIT 1',
|
226
|
+
'UPDATE attributes SET node_id = NULL WHERE (id = 345)',
|
227
|
+
'COMMIT'
|
228
|
+
]
|
229
|
+
end
|
230
|
+
|
231
|
+
it "should raise an error in the remove_*s method if the passed associated objects are not of the correct type" do
|
232
|
+
@c2.one_to_many :attributes, :class => @c1
|
233
|
+
proc{@c2.new(:id => 1234).remove_attributes([@c2.new, @c2.new])}.must_raise(Sequel::Error)
|
234
|
+
end
|
235
|
+
|
236
|
+
it "should have add_*s method respect the :primary_key option" do
|
237
|
+
@c2.one_to_many :attributes, :class => @c1, :primary_key=>:xxx
|
238
|
+
|
239
|
+
n = @c2.new(:id => 1234, :xxx=>5)
|
240
|
+
a1 = @c1.load(:id => 2345)
|
241
|
+
a2 = @c1.load(:id => 3456)
|
242
|
+
n.add_attributes([a1, a2]).must_equal [a1, a2]
|
243
|
+
DB.sqls.must_equal [
|
244
|
+
'BEGIN',
|
245
|
+
'UPDATE attributes SET node_id = 5 WHERE (id = 2345)',
|
246
|
+
'UPDATE attributes SET node_id = 5 WHERE (id = 3456)',
|
247
|
+
'COMMIT'
|
248
|
+
]
|
249
|
+
end
|
250
|
+
|
251
|
+
it "should have add_*s method not add the same objects to the cached association array if the objects are already in the array" do
|
252
|
+
@c2.one_to_many :attributes, :class => @c1
|
253
|
+
|
254
|
+
n = @c2.new(:id => 1234)
|
255
|
+
a1 = @c1.load(:id => 2345)
|
256
|
+
a2 = @c1.load(:id => 3456)
|
257
|
+
n.associations[:attributes] = []
|
258
|
+
[a1, a2].must_equal n.add_attributes([a1, a2])
|
259
|
+
[a1, a2].must_equal n.add_attributes([a1, a2])
|
260
|
+
a1.values.must_equal(:node_id => 1234, :id => 2345)
|
261
|
+
a2.values.must_equal(:node_id => 1234, :id => 3456)
|
262
|
+
n.attributes.must_equal [a1, a2]
|
263
|
+
DB.sqls.must_equal [
|
264
|
+
'BEGIN',
|
265
|
+
'UPDATE attributes SET node_id = 1234 WHERE (id = 2345)',
|
266
|
+
'UPDATE attributes SET node_id = 1234 WHERE (id = 3456)',
|
267
|
+
'COMMIT'
|
268
|
+
] * 2
|
269
|
+
end
|
270
|
+
|
271
|
+
it "should have add_*s method respect composite keys" do
|
272
|
+
@c2.one_to_many :attributes, :class => @c1, :key =>[:node_id, :y], :primary_key=>[:id, :x]
|
273
|
+
|
274
|
+
n = @c2.load(:id => 1234, :x=>5)
|
275
|
+
a1 = @c1.load(:id => 2345)
|
276
|
+
a2 = @c1.load(:id => 3456)
|
277
|
+
n.add_attributes([a1, a2]).must_equal [a1, a2]
|
278
|
+
DB.sqls.must_equal [
|
279
|
+
'BEGIN',
|
280
|
+
"UPDATE attributes SET node_id = 1234, y = 5 WHERE (id = 2345)",
|
281
|
+
"UPDATE attributes SET node_id = 1234, y = 5 WHERE (id = 3456)",
|
282
|
+
'COMMIT'
|
283
|
+
]
|
284
|
+
end
|
285
|
+
|
286
|
+
it "should have add_*s method accept composite keys" do
|
287
|
+
@c1.dataset = @c1.dataset.with_fetch([
|
288
|
+
[{ :id=>2345, :node_id=>1234, :z=>8, :y=>5 }],
|
289
|
+
[{ :id=>3456, :node_id=>1234, :z=>9, :y=>5 }]
|
290
|
+
])
|
291
|
+
@c1.set_primary_key [:id, :z]
|
292
|
+
@c2.one_to_many :attributes, :class => @c1, :key =>[:node_id, :y], :primary_key=>[:id, :x]
|
293
|
+
|
294
|
+
n = @c2.load(:id => 1234, :x=>5)
|
295
|
+
a1 = @c1.load(:id => 2345, :z => 8, :node_id => 1234, :y=>5)
|
296
|
+
a2 = @c1.load(:id => 3456, :z => 9, :node_id => 1234, :y=>5)
|
297
|
+
n.add_attributes([[2345, 8], [3456, 9]]).must_equal [a1, a2]
|
298
|
+
DB.sqls.must_equal [
|
299
|
+
'BEGIN',
|
300
|
+
"SELECT * FROM attributes WHERE ((id = 2345) AND (z = 8)) LIMIT 1",
|
301
|
+
"UPDATE attributes SET node_id = 1234, y = 5 WHERE ((id = 2345) AND (z = 8))",
|
302
|
+
"SELECT * FROM attributes WHERE ((id = 3456) AND (z = 9)) LIMIT 1",
|
303
|
+
"UPDATE attributes SET node_id = 1234, y = 5 WHERE ((id = 3456) AND (z = 9))",
|
304
|
+
'COMMIT'
|
305
|
+
]
|
306
|
+
end
|
307
|
+
|
308
|
+
it "should have remove_*s method respect composite keys" do
|
309
|
+
@c2.one_to_many :attributes, :class => @c1, :key =>[:node_id, :y], :primary_key=>[:id, :x]
|
310
|
+
|
311
|
+
n = @c2.load(:id => 1234, :x=>5)
|
312
|
+
a1 = @c1.load(:id => 2345, :node_id=>1234, :y=>5)
|
313
|
+
a2 = @c1.load(:id => 3456, :node_id=>1234, :y=>5)
|
314
|
+
n.remove_attributes([a1, a2]).must_equal [a1, a2]
|
315
|
+
DB.sqls.must_equal [
|
316
|
+
'BEGIN',
|
317
|
+
"SELECT 1 AS one FROM attributes WHERE ((attributes.node_id = 1234) AND (attributes.y = 5) AND (id = 2345)) LIMIT 1",
|
318
|
+
"UPDATE attributes SET node_id = NULL, y = NULL WHERE (id = 2345)",
|
319
|
+
"SELECT 1 AS one FROM attributes WHERE ((attributes.node_id = 1234) AND (attributes.y = 5) AND (id = 3456)) LIMIT 1",
|
320
|
+
"UPDATE attributes SET node_id = NULL, y = NULL WHERE (id = 3456)",
|
321
|
+
'COMMIT'
|
322
|
+
]
|
323
|
+
end
|
324
|
+
|
325
|
+
it "should accept a array of composite primary keys values for the remove_*s method and remove existing records" do
|
326
|
+
@c1.dataset = @c1.dataset.with_fetch([
|
327
|
+
[{ :id=>234, :node_id=>123, :y=>5 }], [{ :id=>345, :node_id=>123, :y=>6 }]
|
328
|
+
])
|
329
|
+
@c1.set_primary_key [:id, :y]
|
330
|
+
@c2.one_to_many :attributes, :class => @c1, :key=>:node_id, :primary_key=>:id
|
331
|
+
n = @c2.new(:id => 123)
|
332
|
+
n.remove_attributes([[234, 5], [345, 6]]).must_equal [
|
333
|
+
@c1.load(:node_id => nil, :y => 5, :id => 234),
|
334
|
+
@c1.load(:node_id => nil, :y => 6, :id => 345)
|
335
|
+
]
|
336
|
+
DB.sqls.must_equal [
|
337
|
+
'BEGIN',
|
338
|
+
"SELECT * FROM attributes WHERE ((attributes.node_id = 123) AND (attributes.id = 234) AND (attributes.y = 5)) LIMIT 1",
|
339
|
+
"UPDATE attributes SET node_id = NULL WHERE ((id = 234) AND (y = 5))",
|
340
|
+
"SELECT * FROM attributes WHERE ((attributes.node_id = 123) AND (attributes.id = 345) AND (attributes.y = 6)) LIMIT 1",
|
341
|
+
"UPDATE attributes SET node_id = NULL WHERE ((id = 345) AND (y = 6))",
|
342
|
+
'COMMIT'
|
343
|
+
]
|
344
|
+
end
|
345
|
+
|
346
|
+
it "should raise an error in add_*s and remove_*s if the passed objects return false to save (are not valid)" do
|
347
|
+
@c2.one_to_many :attributes, :class => @c1
|
348
|
+
n = @c2.new(:id => 1234)
|
349
|
+
a1 = @c1.new(:id => 2345)
|
350
|
+
a2 = @c1.new(:id => 3456)
|
351
|
+
def a1.validate() errors.add(:id, 'foo') end
|
352
|
+
def a2.validate() errors.add(:id, 'bar') end
|
353
|
+
proc{n.add_attributes([a1, a2])}.must_raise(Sequel::ValidationFailed)
|
354
|
+
proc{n.remove_attributes([a1, a2])}.must_raise(Sequel::ValidationFailed)
|
355
|
+
end
|
356
|
+
|
357
|
+
it "should not validate the associated objects in add_*s and remove_*s if the :validate=>false option is used" do
|
358
|
+
@c2.one_to_many :attributes, :class => @c1, :validate=>false
|
359
|
+
n = @c2.new(:id => 1234)
|
360
|
+
a1 = @c1.new(:id => 2345)
|
361
|
+
a2 = @c1.new(:id => 3456)
|
362
|
+
def a1.validate() errors.add(:id, 'foo') end
|
363
|
+
def a2.validate() errors.add(:id, 'bar') end
|
364
|
+
n.add_attributes([a1, a2]).must_equal [a1, a2]
|
365
|
+
n.remove_attributes([a1, a2]).must_equal [a1, a2]
|
366
|
+
end
|
367
|
+
|
368
|
+
it "should not raise exception in add_*s and remove_*s if the :raise_on_save_failure=>false option is used" do
|
369
|
+
@c2.one_to_many :attributes, :class => @c1, :raise_on_save_failure=>false
|
370
|
+
n = @c2.new(:id => 1234)
|
371
|
+
a1 = @c1.new(:id => 2345)
|
372
|
+
a2 = @c1.new(:id => 3456)
|
373
|
+
def a1.validate() errors.add(:id, 'foo') end
|
374
|
+
def a2.validate() errors.add(:id, 'bar') end
|
375
|
+
n.associations[:attributes] = []
|
376
|
+
n.add_attributes([a1, a2]).must_equal []
|
377
|
+
n.associations[:attributes].must_equal []
|
378
|
+
n.remove_attributes([a1, a2]).must_equal []
|
379
|
+
n.associations[:attributes].must_equal []
|
380
|
+
end
|
381
|
+
|
382
|
+
it "should add item to cache if it exists when calling add_*s" do
|
383
|
+
@c2.one_to_many :attributes, :class => @c1
|
384
|
+
n = @c2.new(:id => 123)
|
385
|
+
a1 = @c1.load(:id => 234)
|
386
|
+
a2 = @c1.load(:id => 345)
|
387
|
+
arr = []
|
388
|
+
n.associations[:attributes] = arr
|
389
|
+
n.add_attributes([a1, a2])
|
390
|
+
arr.must_equal [a1, a2]
|
391
|
+
end
|
392
|
+
|
393
|
+
it "should set object to item's reciprocal cache when calling add_*s" do
|
394
|
+
@c2.one_to_many :attributes, :class => @c1
|
395
|
+
@c1.many_to_one :node, :class => @c2
|
396
|
+
|
397
|
+
n = @c2.new(:id => 123)
|
398
|
+
a1 = @c1.new(:id => 234)
|
399
|
+
a2 = @c1.new(:id => 345)
|
400
|
+
n.add_attributes([a1, a2])
|
401
|
+
a1.node.must_equal n
|
402
|
+
a2.node.must_equal n
|
403
|
+
end
|
404
|
+
|
405
|
+
it "should remove item from cache if it exists when calling remove_*s" do
|
406
|
+
@c2.one_to_many :attributes, :class => @c1
|
407
|
+
|
408
|
+
n = @c2.load(:id => 123)
|
409
|
+
a1 = @c1.load(:id => 234)
|
410
|
+
a2 = @c1.load(:id => 345)
|
411
|
+
arr = [a1, a2]
|
412
|
+
n.associations[:attributes] = arr
|
413
|
+
n.remove_attributes([a1, a2])
|
414
|
+
arr.must_equal []
|
415
|
+
end
|
416
|
+
|
417
|
+
it "should remove item's reciprocal cache calling remove_*s" do
|
418
|
+
@c2.one_to_many :attributes, :class => @c1
|
419
|
+
@c1.many_to_one :node, :class => @c2
|
420
|
+
|
421
|
+
n = @c2.new(:id => 123)
|
422
|
+
a1 = @c1.new(:id => 234)
|
423
|
+
a2 = @c1.new(:id => 345)
|
424
|
+
a1.associations[:node] = n
|
425
|
+
a2.associations[:node] = n
|
426
|
+
a1.node.must_equal n
|
427
|
+
a2.node.must_equal n
|
428
|
+
n.remove_attributes([a1, a2])
|
429
|
+
a1.node.must_be_nil
|
430
|
+
a2.node.must_be_nil
|
431
|
+
end
|
432
|
+
|
433
|
+
it "should not create the add_*s or remove_*s methods if :read_only option is used" do
|
434
|
+
@c2.one_to_many :attributes, :class => @c1, :read_only=>true
|
435
|
+
im = @c2.instance_methods
|
436
|
+
im.wont_include(:add_attributes)
|
437
|
+
im.wont_include(:remove_attributes)
|
438
|
+
end
|
439
|
+
|
440
|
+
it "should not add associations methods directly to class" do
|
441
|
+
@c2.one_to_many :attributes, :class => @c1
|
442
|
+
im = @c2.instance_methods
|
443
|
+
im.must_include(:add_attributes)
|
444
|
+
im.must_include(:remove_attributes)
|
445
|
+
im2 = @c2.instance_methods(false)
|
446
|
+
im2.wont_include(:add_attributes)
|
447
|
+
im2.wont_include(:remove_attributes)
|
448
|
+
end
|
449
|
+
|
450
|
+
it "should call an _add_ method internally to add attributes" do
|
451
|
+
@c2.one_to_many :attributes, :class => @c1
|
452
|
+
@c2.private_instance_methods.must_include(:_add_attribute)
|
453
|
+
p = @c2.load(:id=>10)
|
454
|
+
c1 = @c1.load(:id=>123)
|
455
|
+
c2 = @c1.load(:id=>234)
|
456
|
+
def p._add_attribute(x)
|
457
|
+
(@x ||= []) << x
|
458
|
+
end
|
459
|
+
def c1._node_id=; raise; end
|
460
|
+
def c2._node_id=; raise; end
|
461
|
+
p.add_attributes([c1, c2])
|
462
|
+
p.instance_variable_get(:@x).must_equal [c1, c2]
|
463
|
+
end
|
464
|
+
|
465
|
+
it "should allow additional arguments given to the add_*s method and pass them onwards to the _add_ method" do
|
466
|
+
@c2.one_to_many :attributes, :class => @c1
|
467
|
+
p = @c2.load(:id=>10)
|
468
|
+
c1 = @c1.load(:id=>123)
|
469
|
+
c2 = @c1.load(:id=>234)
|
470
|
+
def p._add_attribute(x,*y)
|
471
|
+
(@x ||= []) << x
|
472
|
+
(@y ||= []) << y
|
473
|
+
end
|
474
|
+
def c1._node_id=; raise; end
|
475
|
+
def c2._node_id=; raise; end
|
476
|
+
p.add_attributes([c1, c2], :foo, :bar=>:baz)
|
477
|
+
p.instance_variable_get(:@x).must_equal [c1, c2]
|
478
|
+
p.instance_variable_get(:@y).must_equal [
|
479
|
+
[:foo,{:bar=>:baz}], [:foo,{:bar=>:baz}]
|
480
|
+
]
|
481
|
+
end
|
482
|
+
|
483
|
+
it "should call a _remove_ method internally to remove attributes" do
|
484
|
+
@c2.one_to_many :attributes, :class => @c1
|
485
|
+
@c2.private_instance_methods.must_include(:_remove_attribute)
|
486
|
+
p = @c2.load(:id=>10)
|
487
|
+
c1 = @c1.load(:id=>123)
|
488
|
+
c2 = @c1.load(:id=>234)
|
489
|
+
def p._remove_attribute(x)
|
490
|
+
(@x ||= []) << x
|
491
|
+
end
|
492
|
+
def c1._node_id=; raise; end
|
493
|
+
def c2._node_id=; raise; end
|
494
|
+
p.remove_attributes([c1, c2])
|
495
|
+
p.instance_variable_get(:@x).must_equal [c1, c2]
|
496
|
+
end
|
497
|
+
|
498
|
+
it "should allow additional arguments given to the remove_*s method and pass them onwards to the _remove_ method" do
|
499
|
+
@c2.one_to_many :attributes, :class => @c1, :reciprocal=>nil
|
500
|
+
p = @c2.load(:id=>10)
|
501
|
+
c1 = @c1.load(:id=>123)
|
502
|
+
c2 = @c1.load(:id=>234)
|
503
|
+
def p._remove_attribute(x,*y)
|
504
|
+
(@x ||= []) << x
|
505
|
+
(@y ||= []) << y
|
506
|
+
end
|
507
|
+
def c1._node_id=; raise; end
|
508
|
+
def c2._node_id=; raise; end
|
509
|
+
p.remove_attributes([c1, c2], :foo, :bar=>:baz)
|
510
|
+
p.instance_variable_get(:@x).must_equal [c1, c2]
|
511
|
+
p.instance_variable_get(:@y).must_equal [
|
512
|
+
[:foo,{:bar=>:baz}], [:foo,{:bar=>:baz}]
|
513
|
+
]
|
514
|
+
end
|
515
|
+
|
516
|
+
it "should support (before|after)_(add|remove) callbacks for (add|remove)_*s methods" do
|
517
|
+
h = []
|
518
|
+
@c2.one_to_many :attributes, :class => @c1, :before_add=>[proc{|x,y| h << x.pk; h << -y.pk}, :blah], :after_add=>proc{h << 3}, :before_remove=>:blah, :after_remove=>[:blahr]
|
519
|
+
@c2.class_eval do
|
520
|
+
self::Foo = h
|
521
|
+
def _add_attribute(v)
|
522
|
+
model::Foo << 4
|
523
|
+
end
|
524
|
+
def _remove_attribute(v)
|
525
|
+
model::Foo << 5
|
526
|
+
end
|
527
|
+
def blah(x)
|
528
|
+
model::Foo << x.pk
|
529
|
+
end
|
530
|
+
def blahr(x)
|
531
|
+
model::Foo << 6
|
532
|
+
end
|
533
|
+
end
|
534
|
+
p = @c2.load(:id=>10)
|
535
|
+
c1 = @c1.load(:id=>123)
|
536
|
+
c2 = @c1.load(:id=>234)
|
537
|
+
h.must_equal []
|
538
|
+
p.add_attributes([c1, c2])
|
539
|
+
h.must_equal [
|
540
|
+
10, -123, 123, 4, 3,
|
541
|
+
10, -234, 234, 4, 3
|
542
|
+
]
|
543
|
+
p.remove_attributes([c1, c2])
|
544
|
+
h.must_equal [
|
545
|
+
10, -123, 123, 4, 3,
|
546
|
+
10, -234, 234, 4, 3,
|
547
|
+
123, 5, 6,
|
548
|
+
234, 5, 6
|
549
|
+
]
|
550
|
+
end
|
551
|
+
|
552
|
+
it "should raise error and not call internal add_*s or remove_*s method if before callback calls cancel_action if raise_on_save_failure is true" do
|
553
|
+
p = @c2.load(:id=>10)
|
554
|
+
c1 = @c1.load(:id=>123)
|
555
|
+
c2 = @c1.load(:id=>234)
|
556
|
+
@c2.one_to_many :attributes, :class => @c1, :before_add=>:ba, :before_remove=>:br
|
557
|
+
def p.ba(o); cancel_action; end
|
558
|
+
def p._add_attribute; raise; end
|
559
|
+
def p._remove_attribute; raise; end
|
560
|
+
p.associations[:attributes] = []
|
561
|
+
proc{p.add_attributes([c1, c2])}.must_raise(Sequel::HookFailed)
|
562
|
+
p.attributes.must_equal []
|
563
|
+
p.associations[:attributes] = [c1, c2]
|
564
|
+
def p.br(o); cancel_action; end
|
565
|
+
proc{p.remove_attributes([c1, c2])}.must_raise(Sequel::HookFailed)
|
566
|
+
p.attributes.must_equal [c1, c2]
|
567
|
+
end
|
568
|
+
|
569
|
+
it "should return nil and not call internal add_*s or remove_*s method if before callback calls cancel_action if raise_on_save_failure is false" do
|
570
|
+
p = @c2.load(:id=>10)
|
571
|
+
c1 = @c1.load(:id=>123)
|
572
|
+
c2 = @c1.load(:id=>234)
|
573
|
+
p.raise_on_save_failure = false
|
574
|
+
@c2.one_to_many :attributes, :class => @c1, :before_add=>:ba, :before_remove=>:br
|
575
|
+
def p.ba(o); cancel_action; end
|
576
|
+
def p._add_attribute; raise; end
|
577
|
+
def p._remove_attribute; raise; end
|
578
|
+
p.associations[:attributes] = []
|
579
|
+
p.add_attributes([c1, c2]).must_equal []
|
580
|
+
p.attributes.must_equal []
|
581
|
+
p.associations[:attributes] = [c1, c2]
|
582
|
+
def p.br(o); cancel_action; end
|
583
|
+
p.remove_attributes([c1, c2]).must_equal []
|
584
|
+
p.attributes.must_equal [c1, c2]
|
585
|
+
end
|
586
|
+
|
587
|
+
it "should define a setter that works on existing records" do
|
588
|
+
@c2.one_to_many :attributes, class: @c1
|
589
|
+
|
590
|
+
n = @c2.load(id: 1234)
|
591
|
+
a1 = @c1.load(id: 2345, node_id: 1234)
|
592
|
+
a2 = @c1.load(id: 3456, node_id: 1234)
|
593
|
+
a3 = @c1.load(id: 4567)
|
594
|
+
|
595
|
+
n.associations[:attributes] = [a1, a2]
|
596
|
+
|
597
|
+
[a2, a3].must_equal(n.attributes = [a2, a3])
|
598
|
+
a1.values.must_equal(node_id: nil, id: 2345)
|
599
|
+
a2.values.must_equal(node_id: 1234, id: 3456)
|
600
|
+
a3.values.must_equal(node_id: 1234, id: 4567)
|
601
|
+
DB.sqls.must_equal [
|
602
|
+
'BEGIN',
|
603
|
+
'SELECT 1 AS one FROM attributes WHERE ((attributes.node_id = 1234) AND (id = 2345)) LIMIT 1',
|
604
|
+
'UPDATE attributes SET node_id = NULL WHERE (id = 2345)',
|
605
|
+
'UPDATE attributes SET node_id = 1234 WHERE (id = 4567)',
|
606
|
+
'COMMIT'
|
607
|
+
]
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
describe "association_multi_add_remove plugin - many_to_many" do
|
612
|
+
before do
|
613
|
+
@c1 = Class.new(Sequel::Model(:attributes)) do
|
614
|
+
unrestrict_primary_key
|
615
|
+
attr_accessor :yyy
|
616
|
+
def self.name; 'Attribute'; end
|
617
|
+
def self.to_s; 'Attribute'; end
|
618
|
+
columns :id, :y, :z
|
619
|
+
end
|
620
|
+
|
621
|
+
@c2 = Class.new(Sequel::Model(:nodes)) do
|
622
|
+
unrestrict_primary_key
|
623
|
+
|
624
|
+
plugin :association_multi_add_remove
|
625
|
+
|
626
|
+
attr_accessor :xxx
|
627
|
+
|
628
|
+
def self.name; 'Node'; end
|
629
|
+
def self.to_s; 'Node'; end
|
630
|
+
columns :id, :x
|
631
|
+
end
|
632
|
+
@dataset = @c2.dataset
|
633
|
+
@c1.dataset = @c1.dataset.with_autoid(1)
|
634
|
+
|
635
|
+
[@c1, @c2].each{|c| c.dataset = c.dataset.with_fetch({})}
|
636
|
+
DB.reset
|
637
|
+
end
|
638
|
+
|
639
|
+
it "should define an add_*s method that works on existing records" do
|
640
|
+
@c2.many_to_many :attributes, :class => @c1
|
641
|
+
|
642
|
+
n = @c2.load(:id => 1234)
|
643
|
+
a1 = @c1.load(:id => 2345)
|
644
|
+
a2 = @c1.load(:id => 3456)
|
645
|
+
n.add_attributes([a1, a2]).must_equal [a1, a2]
|
646
|
+
DB.sqls.must_equal [
|
647
|
+
'BEGIN',
|
648
|
+
"INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (1234, 2345)",
|
649
|
+
"INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (1234, 3456)",
|
650
|
+
'COMMIT'
|
651
|
+
]
|
652
|
+
end
|
653
|
+
|
654
|
+
it "should define an add_*s method that works with a primary key" do
|
655
|
+
@c2.many_to_many :attributes, :class => @c1
|
656
|
+
|
657
|
+
n = @c2.load(:id => 1234)
|
658
|
+
a1 = @c1.load(:id => 2345)
|
659
|
+
a2 = @c1.load(:id => 3456)
|
660
|
+
@c1.dataset = @c1.dataset.with_fetch([[{ :id=>2345 }], [{ :id=>3456 }]])
|
661
|
+
n.add_attributes([2345, 3456]).must_equal [a1, a2]
|
662
|
+
DB.sqls.must_equal [
|
663
|
+
'BEGIN',
|
664
|
+
"SELECT * FROM attributes WHERE id = 2345",
|
665
|
+
"INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (1234, 2345)",
|
666
|
+
"SELECT * FROM attributes WHERE id = 3456",
|
667
|
+
"INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (1234, 3456)",
|
668
|
+
'COMMIT'
|
669
|
+
]
|
670
|
+
end
|
671
|
+
|
672
|
+
it "should allow passing hashes to the add_*s method which creates new records" do
|
673
|
+
@c2.many_to_many :attributes, :class => @c1
|
674
|
+
|
675
|
+
n = @c2.load(:id => 1234)
|
676
|
+
@c1.dataset = @c1.dataset.with_fetch([[{ :id=>1 }], [{ :id=>2 }]])
|
677
|
+
n.add_attributes([{ :id => 1 }, { :id => 2 }]).must_equal [
|
678
|
+
@c1.load(:id => 1), @c1.load(:id => 2)
|
679
|
+
]
|
680
|
+
DB.sqls.must_equal [
|
681
|
+
'BEGIN',
|
682
|
+
'INSERT INTO attributes (id) VALUES (1)',
|
683
|
+
"SELECT * FROM attributes WHERE id = 1",
|
684
|
+
"INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (1234, 1)",
|
685
|
+
'INSERT INTO attributes (id) VALUES (2)',
|
686
|
+
"SELECT * FROM attributes WHERE id = 2",
|
687
|
+
"INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (1234, 2)",
|
688
|
+
'COMMIT'
|
689
|
+
]
|
690
|
+
end
|
691
|
+
|
692
|
+
it "should define a remove_*s method that works on existing records" do
|
693
|
+
@c2.many_to_many :attributes, :class => @c1
|
694
|
+
|
695
|
+
n = @c2.new(:id => 1234)
|
696
|
+
a1 = @c1.new(:id => 2345)
|
697
|
+
a2 = @c1.new(:id => 3456)
|
698
|
+
n.remove_attributes([a1, a2]).must_equal [a1, a2]
|
699
|
+
DB.sqls.must_equal [
|
700
|
+
'BEGIN',
|
701
|
+
'DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 2345))',
|
702
|
+
'DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 3456))',
|
703
|
+
'COMMIT'
|
704
|
+
]
|
705
|
+
end
|
706
|
+
|
707
|
+
it "should accept primary keys for the remove_*s method and remove existing records" do
|
708
|
+
@c2.many_to_many :attributes, :class => @c1
|
709
|
+
n = @c2.new(:id => 1234)
|
710
|
+
@c1.dataset = @c1.dataset.with_fetch([[{ :id=>234 }], [{ :id=>345 }]])
|
711
|
+
n.remove_attributes([234, 345]).must_equal [
|
712
|
+
@c1.load(:id => 234), @c1.load(:id => 345)
|
713
|
+
]
|
714
|
+
DB.sqls.must_equal [
|
715
|
+
'BEGIN',
|
716
|
+
"SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON (attributes_nodes.attribute_id = attributes.id) WHERE ((attributes_nodes.node_id = 1234) AND (attributes.id = 234)) LIMIT 1",
|
717
|
+
"DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 234))",
|
718
|
+
"SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON (attributes_nodes.attribute_id = attributes.id) WHERE ((attributes_nodes.node_id = 1234) AND (attributes.id = 345)) LIMIT 1",
|
719
|
+
"DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 345))",
|
720
|
+
'COMMIT'
|
721
|
+
]
|
722
|
+
end
|
723
|
+
|
724
|
+
it "should have the add_*s method respect the :left_primary_key and :right_primary_key options" do
|
725
|
+
@c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx, :right_primary_key=>:yyy
|
726
|
+
|
727
|
+
n = @c2.load(:id => 1234).set(:xxx=>5)
|
728
|
+
a1 = @c1.load(:id => 2345).set(:yyy=>8)
|
729
|
+
a2 = @c1.load(:id => 3456).set(:yyy=>9)
|
730
|
+
n.add_attributes([a1, a2]).must_equal [a1, a2]
|
731
|
+
DB.sqls.must_equal [
|
732
|
+
'BEGIN',
|
733
|
+
"INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (5, 8)",
|
734
|
+
"INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (5, 9)",
|
735
|
+
'COMMIT'
|
736
|
+
]
|
737
|
+
end
|
738
|
+
|
739
|
+
it "should have the add_*s method respect composite keys" do
|
740
|
+
@c2.many_to_many :attributes, :class => @c1, :left_key=>[:l1, :l2], :right_key=>[:r1, :r2], :left_primary_key=>[:id, :x], :right_primary_key=>[:id, :z]
|
741
|
+
@c1.dataset = @c1.dataset.with_fetch([
|
742
|
+
[{ :id=>2345, :z=>8 }], [{ :id=>3456, :z=>9 }]
|
743
|
+
])
|
744
|
+
@c1.set_primary_key [:id, :z]
|
745
|
+
n = @c2.load(:id => 1234, :x=>5)
|
746
|
+
a1 = @c1.load(:id => 2345, :z=>8)
|
747
|
+
a2 = @c1.load(:id => 3456, :z=>9)
|
748
|
+
n.add_attributes([[2345, 8], [3456, 9]]).must_equal [a1, a2]
|
749
|
+
DB.sqls.must_equal [
|
750
|
+
'BEGIN',
|
751
|
+
"SELECT * FROM attributes WHERE ((id = 2345) AND (z = 8)) LIMIT 1",
|
752
|
+
"INSERT INTO attributes_nodes (l1, l2, r1, r2) VALUES (1234, 5, 2345, 8)",
|
753
|
+
"SELECT * FROM attributes WHERE ((id = 3456) AND (z = 9)) LIMIT 1",
|
754
|
+
"INSERT INTO attributes_nodes (l1, l2, r1, r2) VALUES (1234, 5, 3456, 9)",
|
755
|
+
'COMMIT'
|
756
|
+
]
|
757
|
+
end
|
758
|
+
|
759
|
+
it "should have the remove_*s method respect the :left_primary_key and :right_primary_key options" do
|
760
|
+
@c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx, :right_primary_key=>:yyy
|
761
|
+
|
762
|
+
n = @c2.new(:id => 1234, :xxx=>5)
|
763
|
+
a1 = @c1.new(:id => 2345, :yyy=>8)
|
764
|
+
a2 = @c1.new(:id => 3456, :yyy=>9)
|
765
|
+
n.remove_attributes([a1, a2]).must_equal [a1, a2]
|
766
|
+
DB.sqls.must_equal [
|
767
|
+
'BEGIN',
|
768
|
+
'DELETE FROM attributes_nodes WHERE ((node_id = 5) AND (attribute_id = 8))',
|
769
|
+
'DELETE FROM attributes_nodes WHERE ((node_id = 5) AND (attribute_id = 9))',
|
770
|
+
'COMMIT'
|
771
|
+
]
|
772
|
+
end
|
773
|
+
|
774
|
+
it "should have the remove_*s method respect composite keys" do
|
775
|
+
@c2.many_to_many :attributes, :class => @c1, :left_key=>[:l1, :l2], :right_key=>[:r1, :r2], :left_primary_key=>[:id, :x], :right_primary_key=>[:id, :z]
|
776
|
+
n = @c2.load(:id => 1234, :x=>5)
|
777
|
+
a1 = @c1.load(:id => 2345, :z=>8)
|
778
|
+
a2 = @c1.load(:id => 3456, :z=>9)
|
779
|
+
[a1, a2].must_equal n.remove_attributes([a1, a2])
|
780
|
+
DB.sqls.must_equal [
|
781
|
+
'BEGIN',
|
782
|
+
"DELETE FROM attributes_nodes WHERE ((l1 = 1234) AND (l2 = 5) AND (r1 = 2345) AND (r2 = 8))",
|
783
|
+
"DELETE FROM attributes_nodes WHERE ((l1 = 1234) AND (l2 = 5) AND (r1 = 3456) AND (r2 = 9))",
|
784
|
+
'COMMIT'
|
785
|
+
]
|
786
|
+
end
|
787
|
+
|
788
|
+
it "should accept an array of arrays of composite primary key values for the remove_*s method and remove existing records" do
|
789
|
+
@c1.dataset = @c1.dataset.with_fetch([
|
790
|
+
[{ :id=>234, :y=>8 }], [{ :id=>345, :y=>9 }]
|
791
|
+
])
|
792
|
+
@c1.set_primary_key [:id, :y]
|
793
|
+
@c2.many_to_many :attributes, :class => @c1
|
794
|
+
n = @c2.new(:id => 1234)
|
795
|
+
n.remove_attributes([[234, 8], [345, 9]]).must_equal [
|
796
|
+
@c1.load(:id => 234, :y=>8), @c1.load(:id => 345, :y=>9)
|
797
|
+
]
|
798
|
+
DB.sqls.must_equal [
|
799
|
+
'BEGIN',
|
800
|
+
"SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON (attributes_nodes.attribute_id = attributes.id) WHERE ((attributes_nodes.node_id = 1234) AND (attributes.id = 234) AND (attributes.y = 8)) LIMIT 1",
|
801
|
+
"DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 234))",
|
802
|
+
"SELECT attributes.* FROM attributes INNER JOIN attributes_nodes ON (attributes_nodes.attribute_id = attributes.id) WHERE ((attributes_nodes.node_id = 1234) AND (attributes.id = 345) AND (attributes.y = 9)) LIMIT 1",
|
803
|
+
"DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 345))",
|
804
|
+
'COMMIT'
|
805
|
+
]
|
806
|
+
end
|
807
|
+
|
808
|
+
it "should raise an error if trying to remove model objects that don't have valid primary keys" do
|
809
|
+
@c2.many_to_many :attributes, :class => @c1
|
810
|
+
n = @c2.new
|
811
|
+
a1 = @c1.load(:id=>123)
|
812
|
+
a2 = @c1.load(:id=>234)
|
813
|
+
proc { n.remove_attributes([a1, a2]) }.must_raise(Sequel::Error)
|
814
|
+
end
|
815
|
+
|
816
|
+
it "should remove items from cache if they exist when calling remove_*s" do
|
817
|
+
@c2.many_to_many :attributes, :class => @c1
|
818
|
+
|
819
|
+
n = @c2.new(:id => 1234)
|
820
|
+
a1 = @c1.load(:id => 345)
|
821
|
+
a2 = @c1.load(:id => 456)
|
822
|
+
arr = [a1, a2]
|
823
|
+
n.associations[:attributes] = arr
|
824
|
+
n.remove_attributes([a1, a2])
|
825
|
+
arr.must_equal []
|
826
|
+
end
|
827
|
+
|
828
|
+
it "should remove items from reciprocal's if they exist when calling remove_*s" do
|
829
|
+
@c2.many_to_many :attributes, :class => @c1
|
830
|
+
@c1.many_to_many :nodes, :class => @c2
|
831
|
+
|
832
|
+
n = @c2.new(:id => 1234)
|
833
|
+
a1 = @c1.new(:id => 345)
|
834
|
+
a2 = @c1.new(:id => 456)
|
835
|
+
a1.associations[:nodes] = [n]
|
836
|
+
a2.associations[:nodes] = [n]
|
837
|
+
n.remove_attributes([a1, a2])
|
838
|
+
a1.nodes.must_equal []
|
839
|
+
a2.nodes.must_equal []
|
840
|
+
end
|
841
|
+
|
842
|
+
it "should not create the add_*s or remove_*s methods if :read_only option is used" do
|
843
|
+
@c2.many_to_many :attributes, :class => @c1, :read_only=>true
|
844
|
+
im = @c2.instance_methods
|
845
|
+
im.wont_include(:add_attributes)
|
846
|
+
im.wont_include(:remove_attributes)
|
847
|
+
end
|
848
|
+
|
849
|
+
it "should not add associations methods directly to class" do
|
850
|
+
@c2.many_to_many :attributes, :class => @c1
|
851
|
+
im = @c2.instance_methods
|
852
|
+
im.must_include(:add_attributes)
|
853
|
+
im.must_include(:remove_attributes)
|
854
|
+
im2 = @c2.instance_methods(false)
|
855
|
+
im2.wont_include(:add_attributes)
|
856
|
+
im2.wont_include(:remove_attributes)
|
857
|
+
end
|
858
|
+
|
859
|
+
it "should call a _remove_*s method internally to remove attributes" do
|
860
|
+
@c2.many_to_many :attributes, :class => @c1
|
861
|
+
@c2.private_instance_methods.must_include(:_remove_attribute)
|
862
|
+
p = @c2.load(:id=>10)
|
863
|
+
c1 = @c1.load(:id=>123)
|
864
|
+
c2 = @c1.load(:id=>234)
|
865
|
+
def p._remove_attribute(x)
|
866
|
+
(@x ||= []) << x
|
867
|
+
end
|
868
|
+
p.remove_attributes([c1, c2])
|
869
|
+
p.instance_variable_get(:@x).must_equal [c1, c2]
|
870
|
+
DB.sqls.must_equal ['BEGIN', 'COMMIT']
|
871
|
+
end
|
872
|
+
|
873
|
+
it "should support a :remover option for defining the _remove_*s method" do
|
874
|
+
@c2.many_to_many :attributes, :class => @c1,
|
875
|
+
:remover=>proc { |x| (@x ||= []) << x }
|
876
|
+
p = @c2.load(:id=>10)
|
877
|
+
c1 = @c1.load(:id=>123)
|
878
|
+
c2 = @c1.load(:id=>234)
|
879
|
+
p.remove_attributes([c1, c2])
|
880
|
+
p.instance_variable_get(:@x).must_equal [c1, c2]
|
881
|
+
DB.sqls.must_equal ['BEGIN', 'COMMIT']
|
882
|
+
end
|
883
|
+
|
884
|
+
it "should allow additional arguments given to the remove_*s method and pass them onwards to the _remove_ method" do
|
885
|
+
@c2.many_to_many :attributes, :class => @c1
|
886
|
+
p = @c2.load(:id=>10)
|
887
|
+
c1 = @c1.load(:id=>123)
|
888
|
+
c2 = @c1.load(:id=>234)
|
889
|
+
def p._remove_attribute(x,*y)
|
890
|
+
(@x ||= []) << x
|
891
|
+
(@y ||= []) << y
|
892
|
+
end
|
893
|
+
p.remove_attributes([c1, c2], :foo, :bar=>:baz)
|
894
|
+
p.instance_variable_get(:@x).must_equal [c1, c2]
|
895
|
+
p.instance_variable_get(:@y).must_equal [
|
896
|
+
[:foo, { :bar=>:baz }], [:foo, { :bar=>:baz }]
|
897
|
+
]
|
898
|
+
end
|
899
|
+
|
900
|
+
it "should raise an error in the remove_*s method if the passed associated objects are not of the correct type" do
|
901
|
+
@c2.many_to_many :attributes, :class => @c1
|
902
|
+
proc do
|
903
|
+
@c2.new(:id => 1234).remove_attributes([@c2.new, @c2.new])
|
904
|
+
end
|
905
|
+
.must_raise(Sequel::Error)
|
906
|
+
end
|
907
|
+
|
908
|
+
it "should support (before|after)_(add|remove) callbacks for (add|remove)_* methods" do
|
909
|
+
h = []
|
910
|
+
@c2.many_to_many :attributes, :class => @c1, :before_add=>[proc{|x,y| h << x.pk; h << -y.pk}, :blah], :after_add=>proc{h << 3}, :before_remove=>:blah, :after_remove=>[:blahr]
|
911
|
+
@c2.class_eval do
|
912
|
+
self::Foo = h
|
913
|
+
def _add_attribute(v)
|
914
|
+
model::Foo << 4
|
915
|
+
end
|
916
|
+
def _remove_attribute(v)
|
917
|
+
model::Foo << 5
|
918
|
+
end
|
919
|
+
def blah(x)
|
920
|
+
model::Foo << x.pk
|
921
|
+
end
|
922
|
+
def blahr(x)
|
923
|
+
model::Foo << 6
|
924
|
+
end
|
925
|
+
end
|
926
|
+
p = @c2.load(:id=>10)
|
927
|
+
c1 = @c1.load(:id=>123)
|
928
|
+
c2 = @c1.load(:id=>234)
|
929
|
+
h.must_equal []
|
930
|
+
p.add_attributes([c1, c2])
|
931
|
+
h.must_equal [
|
932
|
+
10, -123, 123, 4, 3,
|
933
|
+
10, -234, 234, 4, 3
|
934
|
+
]
|
935
|
+
p.remove_attributes([c1, c2])
|
936
|
+
h.must_equal [
|
937
|
+
10, -123, 123, 4, 3,
|
938
|
+
10, -234, 234, 4, 3,
|
939
|
+
123, 5, 6,
|
940
|
+
234, 5, 6
|
941
|
+
]
|
942
|
+
end
|
943
|
+
|
944
|
+
it "should raise error and not call internal add_*s or remove_*s method if before callback calls cancel_action if raise_on_save_failure is true" do
|
945
|
+
p = @c2.load(:id=>10)
|
946
|
+
c1 = @c1.load(:id=>123)
|
947
|
+
c2 = @c1.load(:id=>234)
|
948
|
+
@c2.many_to_many :attributes, :class => @c1, :before_add=>:ba, :before_remove=>:br
|
949
|
+
def p.ba(o) cancel_action end
|
950
|
+
def p._add_attribute; raise; end
|
951
|
+
def p._remove_attribute; raise; end
|
952
|
+
p.associations[:attributes] = []
|
953
|
+
p.raise_on_save_failure = true
|
954
|
+
proc{p.add_attributes([c1, c2])}.must_raise(Sequel::HookFailed)
|
955
|
+
p.attributes.must_equal []
|
956
|
+
p.associations[:attributes] = [c1, c2]
|
957
|
+
def p.br(o) cancel_action end
|
958
|
+
proc { p.remove_attributes([c1, c2]) }.must_raise(Sequel::HookFailed)
|
959
|
+
p.attributes.must_equal [c1, c2]
|
960
|
+
end
|
961
|
+
|
962
|
+
it "should return nil and not call internal add_*s or remove_*s method if before callback calls cancel_action if raise_on_save_failure is false" do
|
963
|
+
p = @c2.load(:id=>10)
|
964
|
+
c1 = @c1.load(:id=>123)
|
965
|
+
c2 = @c1.load(:id=>234)
|
966
|
+
p.raise_on_save_failure = false
|
967
|
+
@c2.many_to_many :attributes, :class => @c1, :before_add=>:ba, :before_remove=>:br
|
968
|
+
def p.ba(o) cancel_action end
|
969
|
+
def p._add_attribute; raise; end
|
970
|
+
def p._remove_attribute; raise; end
|
971
|
+
p.associations[:attributes] = []
|
972
|
+
p.add_attributes([c1, c2]).must_equal []
|
973
|
+
p.attributes.must_equal []
|
974
|
+
p.associations[:attributes] = [c1, c2]
|
975
|
+
def p.br(o) cancel_action end
|
976
|
+
p.remove_attributes([c1, c2]).must_equal []
|
977
|
+
p.attributes.must_equal [c1, c2]
|
978
|
+
end
|
979
|
+
|
980
|
+
it "should define a setter that works on existing records" do
|
981
|
+
@c2.many_to_many :attributes, class: @c1
|
982
|
+
|
983
|
+
n = @c2.load(id: 1234)
|
984
|
+
a1 = @c1.load(id: 2345)
|
985
|
+
a2 = @c1.load(id: 3456)
|
986
|
+
a3 = @c1.load(id: 4567)
|
987
|
+
|
988
|
+
n.associations[:attributes] = [a1, a2]
|
989
|
+
|
990
|
+
[a2, a3].must_equal(n.attributes = [a2, a3])
|
991
|
+
DB.sqls.must_equal [
|
992
|
+
'BEGIN',
|
993
|
+
'DELETE FROM attributes_nodes WHERE ((node_id = 1234) AND (attribute_id = 2345))',
|
994
|
+
'INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (1234, 4567)',
|
995
|
+
'COMMIT'
|
996
|
+
]
|
997
|
+
end
|
998
|
+
end
|
999
|
+
|
1000
|
+
describe "association_multi_add_remove plugin - sharding" do
|
1001
|
+
before do
|
1002
|
+
@db = Sequel.mock(:servers=>{:a=>{}}, :numrows=>1)
|
1003
|
+
@c1 = Class.new(Sequel::Model(@db[:attributes])) do
|
1004
|
+
unrestrict_primary_key
|
1005
|
+
columns :id, :node_id, :y, :z
|
1006
|
+
end
|
1007
|
+
|
1008
|
+
@c2 = Class.new(Sequel::Model(@db[:nodes])) do
|
1009
|
+
plugin :association_multi_add_remove
|
1010
|
+
|
1011
|
+
def _refresh(ds); end
|
1012
|
+
unrestrict_primary_key
|
1013
|
+
attr_accessor :xxx
|
1014
|
+
|
1015
|
+
def self.name; 'Node'; end
|
1016
|
+
def self.to_s; 'Node'; end
|
1017
|
+
|
1018
|
+
columns :id, :x
|
1019
|
+
end
|
1020
|
+
@dataset = @c2.dataset = @c2.dataset.with_fetch({})
|
1021
|
+
@c1.dataset = @c1.dataset.with_fetch(proc { |sql| sql =~ /SELECT 1/ ? { a: 1 } : {} })
|
1022
|
+
@db.sqls
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
it "should handle servers correctly" do
|
1026
|
+
@c2.one_to_many :attributes, class: @c1
|
1027
|
+
|
1028
|
+
n = @c2.load(id: 1234).set_server(:a)
|
1029
|
+
a1 = @c1.load(id: 2345).set_server(:a)
|
1030
|
+
a2 = @c1.load(id: 3456).set_server(:a)
|
1031
|
+
[a1, a2].must_equal n.add_attributes([a1, a2])
|
1032
|
+
a1.values.must_equal(:node_id => 1234, id: 2345)
|
1033
|
+
a2.values.must_equal(:node_id => 1234, id: 3456)
|
1034
|
+
@db.sqls.must_equal [
|
1035
|
+
'BEGIN -- a',
|
1036
|
+
'UPDATE attributes SET node_id = 1234 WHERE (id = 2345) -- a',
|
1037
|
+
'UPDATE attributes SET node_id = 1234 WHERE (id = 3456) -- a',
|
1038
|
+
'COMMIT -- a'
|
1039
|
+
]
|
1040
|
+
end
|
1041
|
+
end
|