sequel 5.23.0 → 5.28.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 +62 -0
- data/README.rdoc +1 -1
- data/doc/cheat_sheet.rdoc +1 -0
- data/doc/postgresql.rdoc +2 -2
- 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/release_notes/5.27.0.txt +21 -0
- data/doc/release_notes/5.28.0.txt +16 -0
- data/doc/testing.rdoc +11 -6
- data/lib/sequel/adapters/jdbc.rb +7 -1
- data/lib/sequel/adapters/jdbc/postgresql.rb +7 -13
- data/lib/sequel/adapters/mysql2.rb +0 -1
- data/lib/sequel/adapters/shared/mssql.rb +4 -2
- data/lib/sequel/adapters/shared/postgres.rb +30 -7
- data/lib/sequel/adapters/shared/sqlite.rb +7 -2
- 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/features.rb +6 -0
- 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/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 +4 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +145 -17
- data/spec/adapters/sqlite_spec.rb +1 -1
- data/spec/bin_spec.rb +1 -1
- data/spec/core/database_spec.rb +20 -1
- data/spec/core/expression_filters_spec.rb +26 -7
- 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/any_not_empty_spec.rb +23 -0
- data/spec/extensions/association_multi_add_remove_spec.rb +1041 -0
- data/spec/extensions/dirty_spec.rb +33 -0
- data/spec/extensions/exclude_or_null_spec.rb +15 -0
- data/spec/extensions/insert_conflict_spec.rb +26 -0
- data/spec/extensions/nested_attributes_spec.rb +48 -0
- data/spec/extensions/pg_array_ops_spec.rb +3 -3
- 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_range_spec.rb +35 -21
- data/spec/extensions/sharding_spec.rb +8 -0
- data/spec/extensions/spec_helper.rb +1 -1
- data/spec/extensions/static_cache_cache_spec.rb +35 -0
- data/spec/guards_helper.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/spec_helper.rb +1 -1
- metadata +35 -3
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,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
|