sequel 5.24.0 → 5.29.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 +58 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/doc/cheat_sheet.rdoc +1 -0
- data/doc/postgresql.rdoc +2 -2
- data/doc/release_notes/5.25.0.txt +32 -0
- data/doc/release_notes/5.26.0.txt +35 -0
- data/doc/release_notes/5.27.0.txt +21 -0
- data/doc/release_notes/5.28.0.txt +16 -0
- data/doc/release_notes/5.29.0.txt +22 -0
- data/doc/testing.rdoc +11 -6
- data/lib/sequel/adapters/jdbc/postgresql.rb +6 -0
- data/lib/sequel/adapters/postgres.rb +5 -1
- data/lib/sequel/adapters/shared/mssql.rb +4 -2
- data/lib/sequel/adapters/shared/mysql.rb +1 -1
- data/lib/sequel/adapters/shared/postgres.rb +15 -0
- data/lib/sequel/adapters/shared/sqlite.rb +7 -2
- data/lib/sequel/adapters/tinytds.rb +1 -1
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +1 -1
- data/lib/sequel/database/transactions.rb +3 -3
- data/lib/sequel/dataset/features.rb +6 -0
- data/lib/sequel/dataset/misc.rb +2 -2
- data/lib/sequel/dataset/query.rb +15 -2
- data/lib/sequel/dataset/sql.rb +17 -4
- data/lib/sequel/extensions/any_not_empty.rb +45 -0
- data/lib/sequel/extensions/exclude_or_null.rb +68 -0
- data/lib/sequel/extensions/pg_array_ops.rb +10 -6
- data/lib/sequel/extensions/pg_enum.rb +4 -1
- data/lib/sequel/extensions/pg_json.rb +1 -1
- data/lib/sequel/extensions/pg_json_ops.rb +124 -0
- data/lib/sequel/extensions/pg_range.rb +9 -0
- 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/caching.rb +3 -0
- data/lib/sequel/plugins/csv_serializer.rb +26 -9
- data/lib/sequel/plugins/dirty.rb +3 -9
- data/lib/sequel/plugins/empty_failure_backtraces.rb +38 -0
- data/lib/sequel/plugins/json_serializer.rb +15 -4
- data/lib/sequel/plugins/nested_attributes.rb +7 -0
- data/lib/sequel/plugins/sharding.rb +11 -5
- data/lib/sequel/plugins/throw_failures.rb +1 -1
- data/lib/sequel/plugins/typecast_on_load.rb +3 -2
- data/lib/sequel/sql.rb +4 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +82 -17
- data/spec/adapters/sqlite_spec.rb +1 -1
- data/spec/bin_spec.rb +1 -1
- data/spec/core/database_spec.rb +1 -1
- data/spec/core/dataset_spec.rb +0 -3
- data/spec/core/expression_filters_spec.rb +26 -7
- data/spec/core/spec_helper.rb +1 -1
- data/spec/core_extensions_spec.rb +1 -1
- data/spec/extensions/any_not_empty_spec.rb +23 -0
- data/spec/extensions/association_multi_add_remove_spec.rb +1041 -0
- data/spec/extensions/caller_logging_spec.rb +1 -1
- data/spec/extensions/dirty_spec.rb +33 -0
- data/spec/extensions/empty_failure_backtraces_spec.rb +60 -0
- data/spec/extensions/exclude_or_null_spec.rb +15 -0
- data/spec/extensions/json_serializer_spec.rb +10 -0
- data/spec/extensions/named_timezones_spec.rb +5 -5
- data/spec/extensions/nested_attributes_spec.rb +48 -0
- data/spec/extensions/pg_array_ops_spec.rb +3 -3
- data/spec/extensions/pg_json_ops_spec.rb +67 -0
- data/spec/extensions/pg_range_spec.rb +35 -21
- data/spec/extensions/sharding_spec.rb +8 -0
- data/spec/extensions/spec_helper.rb +1 -1
- data/spec/guards_helper.rb +1 -1
- data/spec/integration/associations_test.rb +1 -1
- data/spec/integration/dataset_test.rb +57 -17
- data/spec/integration/plugin_test.rb +1 -1
- data/spec/integration/schema_test.rb +9 -0
- data/spec/integration/spec_helper.rb +7 -1
- data/spec/model/plugins_spec.rb +2 -2
- data/spec/model/spec_helper.rb +1 -1
- data/spec/sequel_warning.rb +1 -0
- metadata +35 -3
@@ -623,7 +623,7 @@ describe "SQLite", 'INSERT ON CONFLICT' do
|
|
623
623
|
@ds.insert_conflict(:target=>:a).insert(1, 3, 4, false)
|
624
624
|
@ds.insert_conflict(:target=>:c, :conflict_where=>:c_is_unique).insert(11, 12, 3, true)
|
625
625
|
@ds.all.must_equal [{:a=>1, :b=>2, :c=>3, :c_is_unique=>false}, {:a=>10, :b=>11, :c=>3, :c_is_unique=>true}]
|
626
|
-
end
|
626
|
+
end unless DB.adapter_scheme == :amalgalite
|
627
627
|
|
628
628
|
it "Dataset#insert_ignore and insert_conflict should work with multi_insert/import" do
|
629
629
|
@ds.insert(1, 2, 3, false)
|
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={})
|
data/spec/core/database_spec.rb
CHANGED
@@ -2467,7 +2467,7 @@ describe "Database#typecast_value" do
|
|
2467
2467
|
@db.typecast_value(:date, 'a')
|
2468
2468
|
true.must_equal false
|
2469
2469
|
rescue Sequel::InvalidValue => e
|
2470
|
-
e.inspect.
|
2470
|
+
e.inspect.must_match(/\A#\<Sequel::InvalidValue: (Argument|Date::)Error: invalid date\>\z/)
|
2471
2471
|
end
|
2472
2472
|
end
|
2473
2473
|
end
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -3065,7 +3065,6 @@ describe "Dataset#get" do
|
|
3065
3065
|
end
|
3066
3066
|
|
3067
3067
|
it "should select the specified column and fetch its value" do
|
3068
|
-
@d
|
3069
3068
|
5.times do
|
3070
3069
|
@d.get(:name).must_equal "SELECT name FROM test LIMIT 1"
|
3071
3070
|
@d.get(:abc).must_equal "SELECT abc FROM test LIMIT 1"
|
@@ -3077,14 +3076,12 @@ describe "Dataset#get" do
|
|
3077
3076
|
end
|
3078
3077
|
|
3079
3078
|
it "should work with aliased fields" do
|
3080
|
-
@d
|
3081
3079
|
5.times do
|
3082
3080
|
@d.get(Sequel.expr(Sequel[:x][:b]).as(:name)).must_equal "SELECT x.b AS name FROM test LIMIT 1"
|
3083
3081
|
end
|
3084
3082
|
end
|
3085
3083
|
|
3086
3084
|
it "should work with plain strings" do
|
3087
|
-
@d
|
3088
3085
|
5.times do
|
3089
3086
|
@d.get('a').must_equal "SELECT 'a' AS v FROM test LIMIT 1"
|
3090
3087
|
end
|
@@ -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
|
@@ -348,6 +353,11 @@ describe "Blockless Ruby Filters" do
|
|
348
353
|
@d.l({:x => Sequel::FALSE}).must_equal '(x IS FALSE)'
|
349
354
|
@d.l({:x => Sequel::SQLTRUE}).must_equal '(x IS TRUE)'
|
350
355
|
@d.l({:x => Sequel::SQLFALSE}).must_equal '(x IS FALSE)'
|
356
|
+
|
357
|
+
@d.l({:x => Sequel::CURRENT_DATE}).must_equal '(x = CURRENT_DATE)'
|
358
|
+
@d.l({:x => Sequel::CURRENT_TIME}).must_equal '(x = CURRENT_TIME)'
|
359
|
+
@d.l({:x => Sequel::CURRENT_TIMESTAMP}).must_equal '(x = CURRENT_TIMESTAMP)'
|
360
|
+
@d.l({:x => Sequel::DEFAULT}).must_equal '(x = DEFAULT)'
|
351
361
|
end
|
352
362
|
|
353
363
|
it "should support negation of SQL::Constants" do
|
@@ -527,21 +537,21 @@ describe "Blockless Ruby Filters" do
|
|
527
537
|
end
|
528
538
|
|
529
539
|
it "should handle endless ranges" do
|
530
|
-
endless = eval('1..')
|
540
|
+
endless = eval('(1..)')
|
531
541
|
@d.l{x =~ endless}.must_equal '(x >= 1)'
|
532
542
|
@d.l(:x => endless).must_equal '(x >= 1)'
|
533
543
|
|
534
|
-
endless = eval('1...')
|
544
|
+
endless = eval('(1...)')
|
535
545
|
@d.l{x =~ endless}.must_equal '(x >= 1)'
|
536
546
|
@d.l(:x => endless).must_equal '(x >= 1)'
|
537
547
|
end if RUBY_VERSION >= '2.6'
|
538
548
|
|
539
549
|
it "should handle startless ranges" do
|
540
|
-
endless = eval('..1')
|
550
|
+
endless = eval('(..1)')
|
541
551
|
@d.l{x =~ endless}.must_equal '(x <= 1)'
|
542
552
|
@d.l(:x => endless).must_equal '(x <= 1)'
|
543
553
|
|
544
|
-
endless = eval('...1')
|
554
|
+
endless = eval('(...1)')
|
545
555
|
@d.l{x =~ endless}.must_equal '(x < 1)'
|
546
556
|
@d.l(:x => endless).must_equal '(x < 1)'
|
547
557
|
end if RUBY_VERSION >= '2.7'
|
@@ -736,14 +746,23 @@ describe Sequel::SQL::VirtualRow do
|
|
736
746
|
@d.l{mode.function.within_group(:a, :b)}.must_equal 'mode() WITHIN GROUP (ORDER BY "a", "b")'
|
737
747
|
end
|
738
748
|
|
749
|
+
it "should handle emualted filtered aggregate function calls" do
|
750
|
+
@d.l{count.function.*.filter(Sequel.&(:a, :b))}.must_equal 'count((CASE WHEN ("a" AND "b") THEN 1 ELSE NULL END))'
|
751
|
+
@d.l{count.function.*.filter(:a=>1)}.must_equal 'count((CASE WHEN ("a" = 1) THEN 1 ELSE NULL END))'
|
752
|
+
@d.l{count(:a).filter{b > 1}}.must_equal 'count((CASE WHEN ("b" > 1) THEN "a" ELSE NULL END))'
|
753
|
+
@d.l{count(:a).filter(:a=>1){b > 1}}.must_equal 'count((CASE WHEN (("a" = 1) AND ("b" > 1)) THEN "a" ELSE NULL END))'
|
754
|
+
end
|
755
|
+
|
739
756
|
it "should handle filtered aggregate function calls" do
|
757
|
+
@d = @d.with_extend{def supports_filtered_aggregates?; true end}
|
740
758
|
@d.l{count.function.*.filter(Sequel.&(:a, :b))}.must_equal 'count(*) FILTER (WHERE ("a" AND "b"))'
|
741
759
|
@d.l{count.function.*.filter(:a=>1)}.must_equal 'count(*) FILTER (WHERE ("a" = 1))'
|
742
760
|
@d.l{count.function.*.filter{b > 1}}.must_equal 'count(*) FILTER (WHERE ("b" > 1))'
|
743
761
|
@d.l{count.function.*.filter(:a=>1){b > 1}}.must_equal 'count(*) FILTER (WHERE (("a" = 1) AND ("b" > 1)))'
|
744
762
|
end
|
745
763
|
|
746
|
-
it "should handle
|
764
|
+
it "should handle filtered ordered-set and hypothetical-set function calls" do
|
765
|
+
@d = @d.with_extend{def supports_filtered_aggregates?; true end}
|
747
766
|
@d.l{mode.function.within_group(:a).filter(:a=>1)}.must_equal 'mode() WITHIN GROUP (ORDER BY "a") FILTER (WHERE ("a" = 1))'
|
748
767
|
end
|
749
768
|
|
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,23 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
|
3
|
+
describe "any_not_empty extension" do
|
4
|
+
before do
|
5
|
+
@ds = Sequel.mock[:t].extension(:any_not_empty)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should use a limited query if no block is given" do
|
9
|
+
@ds.with_fetch(:one=>1).any?.must_equal true
|
10
|
+
@ds.db.sqls.must_equal ["SELECT 1 AS one FROM t LIMIT 1"]
|
11
|
+
@ds.with_fetch([]).any?.must_equal false
|
12
|
+
@ds.db.sqls.must_equal ["SELECT 1 AS one FROM t LIMIT 1"]
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should use default behavior if block is given" do
|
16
|
+
@ds.with_fetch(:one=>1).any?{|x| x[:one] == 1}.must_equal true
|
17
|
+
@ds.db.sqls.must_equal ["SELECT * FROM t"]
|
18
|
+
@ds.with_fetch(:one=>1).any?{|x| x[:one] != 1}.must_equal false
|
19
|
+
@ds.db.sqls.must_equal ["SELECT * FROM t"]
|
20
|
+
@ds.with_fetch([]).any?{|x| x[:one] == 1}.must_equal false
|
21
|
+
@ds.db.sqls.must_equal ["SELECT * FROM t"]
|
22
|
+
end
|
23
|
+
end
|
@@ -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
|