sequel 4.31.0 → 4.32.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +24 -0
- data/Rakefile +17 -15
- data/doc/association_basics.rdoc +7 -3
- data/doc/opening_databases.rdoc +7 -0
- data/doc/release_notes/4.32.0.txt +132 -0
- data/doc/schema_modification.rdoc +1 -1
- data/doc/security.rdoc +70 -26
- data/doc/testing.rdoc +1 -0
- data/lib/sequel/adapters/jdbc.rb +2 -1
- data/lib/sequel/adapters/postgres.rb +3 -4
- data/lib/sequel/adapters/shared/mysql.rb +14 -1
- data/lib/sequel/adapters/shared/sqlite.rb +2 -2
- data/lib/sequel/extensions/_pretty_table.rb +2 -0
- data/lib/sequel/extensions/arbitrary_servers.rb +2 -0
- data/lib/sequel/extensions/columns_introspection.rb +2 -0
- data/lib/sequel/extensions/connection_validator.rb +2 -0
- data/lib/sequel/extensions/constraint_validations.rb +2 -0
- data/lib/sequel/extensions/core_extensions.rb +1 -5
- data/lib/sequel/extensions/current_datetime_timestamp.rb +2 -0
- data/lib/sequel/extensions/dataset_source_alias.rb +2 -0
- data/lib/sequel/extensions/date_arithmetic.rb +2 -0
- data/lib/sequel/extensions/empty_array_consider_nulls.rb +3 -1
- data/lib/sequel/extensions/error_sql.rb +2 -0
- data/lib/sequel/extensions/eval_inspect.rb +2 -0
- data/lib/sequel/extensions/filter_having.rb +2 -0
- data/lib/sequel/extensions/from_block.rb +2 -0
- data/lib/sequel/extensions/graph_each.rb +2 -0
- data/lib/sequel/extensions/hash_aliases.rb +2 -0
- data/lib/sequel/extensions/inflector.rb +2 -0
- data/lib/sequel/extensions/looser_typecasting.rb +3 -1
- data/lib/sequel/extensions/meta_def.rb +2 -0
- data/lib/sequel/extensions/migration.rb +4 -0
- data/lib/sequel/extensions/mssql_emulate_lateral_with_apply.rb +2 -0
- data/lib/sequel/extensions/named_timezones.rb +2 -0
- data/lib/sequel/extensions/no_auto_literal_strings.rb +84 -0
- data/lib/sequel/extensions/null_dataset.rb +2 -0
- data/lib/sequel/extensions/pagination.rb +2 -0
- data/lib/sequel/extensions/pg_array.rb +2 -4
- data/lib/sequel/extensions/pg_array_ops.rb +2 -0
- data/lib/sequel/extensions/pg_enum.rb +2 -0
- data/lib/sequel/extensions/pg_hstore.rb +2 -4
- data/lib/sequel/extensions/pg_hstore_ops.rb +2 -0
- data/lib/sequel/extensions/pg_inet.rb +2 -4
- data/lib/sequel/extensions/pg_inet_ops.rb +2 -0
- data/lib/sequel/extensions/pg_interval.rb +2 -4
- data/lib/sequel/extensions/pg_json.rb +4 -4
- data/lib/sequel/extensions/pg_json_ops.rb +3 -0
- data/lib/sequel/extensions/pg_loose_count.rb +2 -0
- data/lib/sequel/extensions/pg_range.rb +2 -4
- data/lib/sequel/extensions/pg_range_ops.rb +2 -0
- data/lib/sequel/extensions/pg_row.rb +2 -4
- data/lib/sequel/extensions/pg_row_ops.rb +2 -0
- data/lib/sequel/extensions/pg_static_cache_updater.rb +2 -0
- data/lib/sequel/extensions/pretty_table.rb +2 -0
- data/lib/sequel/extensions/query.rb +3 -0
- data/lib/sequel/extensions/query_literals.rb +7 -5
- data/lib/sequel/extensions/round_timestamps.rb +4 -3
- data/lib/sequel/extensions/schema_caching.rb +2 -0
- data/lib/sequel/extensions/schema_dumper.rb +2 -0
- data/lib/sequel/extensions/select_remove.rb +2 -0
- data/lib/sequel/extensions/sequel_3_dataset_methods.rb +2 -0
- data/lib/sequel/extensions/server_block.rb +3 -0
- data/lib/sequel/extensions/set_overrides.rb +2 -0
- data/lib/sequel/extensions/split_array_nil.rb +2 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +2 -0
- data/lib/sequel/extensions/to_dot.rb +2 -0
- data/lib/sequel/model/associations.rb +95 -55
- data/lib/sequel/plugins/association_pks.rb +58 -33
- data/lib/sequel/plugins/eager_each.rb +22 -0
- data/lib/sequel/plugins/pg_typecast_on_load.rb +3 -2
- data/lib/sequel/plugins/tactical_eager_loading.rb +44 -3
- data/lib/sequel/version.rb +2 -2
- data/spec/adapters/mysql_spec.rb +34 -6
- data/spec/adapters/oracle_spec.rb +1 -1
- data/spec/bin_spec.rb +2 -2
- data/spec/core/dataset_spec.rb +7 -0
- data/spec/extensions/association_pks_spec.rb +38 -0
- data/spec/extensions/class_table_inheritance_spec.rb +24 -0
- data/spec/extensions/eager_each_spec.rb +25 -1
- data/spec/extensions/no_auto_literal_strings_spec.rb +65 -0
- data/spec/extensions/pg_range_spec.rb +1 -0
- data/spec/extensions/spec_helper.rb +5 -5
- data/spec/extensions/tactical_eager_loading_spec.rb +71 -17
- data/spec/integration/associations_test.rb +77 -62
- data/spec/integration/dataset_test.rb +3 -3
- data/spec/integration/plugin_test.rb +22 -0
- data/spec/integration/prepared_statement_test.rb +8 -8
- data/spec/integration/spec_helper.rb +4 -0
- data/spec/model/association_reflection_spec.rb +30 -0
- data/spec/model/associations_spec.rb +177 -16
- metadata +6 -2
@@ -29,6 +29,12 @@ module Sequel
|
|
29
29
|
# objects, and the setting will not be persisted until after the object has
|
30
30
|
# been saved.
|
31
31
|
#
|
32
|
+
# By default, if you pass a nil value to the setter, an exception will be raised.
|
33
|
+
# You can change this behavior by using the :association_pks_nil association option.
|
34
|
+
# If set to :ignore, the setter will take no action if nil is given.
|
35
|
+
# If set to :remove, the setter will treat the nil as an empty array, removing
|
36
|
+
# the association all currently associated values.
|
37
|
+
#
|
32
38
|
# Usage:
|
33
39
|
#
|
34
40
|
# # Make all model subclass *_to_many associations have association_pks
|
@@ -74,21 +80,25 @@ module Sequel
|
|
74
80
|
end
|
75
81
|
|
76
82
|
opts[:pks_setter] = lambda do |pks|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
83
|
+
if pks.empty?
|
84
|
+
send(opts.remove_all_method)
|
85
|
+
else
|
86
|
+
checked_transaction do
|
87
|
+
if clpk
|
88
|
+
lpkv = lpk.map{|k| get_column_value(k)}
|
89
|
+
cond = lk.zip(lpkv)
|
90
|
+
else
|
91
|
+
lpkv = get_column_value(lpk)
|
92
|
+
cond = {lk=>lpkv}
|
93
|
+
end
|
94
|
+
ds = _join_table_dataset(opts).filter(cond)
|
95
|
+
ds.exclude(rk=>pks).delete
|
96
|
+
pks -= ds.select_map(rk)
|
97
|
+
lpkv = Array(lpkv)
|
98
|
+
key_array = crk ? pks.map{|pk| lpkv + pk} : pks.map{|pk| lpkv + [pk]}
|
99
|
+
key_columns = Array(lk) + Array(rk)
|
100
|
+
ds.import(key_columns, key_array)
|
84
101
|
end
|
85
|
-
ds = _join_table_dataset(opts).filter(cond)
|
86
|
-
ds.exclude(rk=>pks).delete
|
87
|
-
pks -= ds.select_map(rk)
|
88
|
-
lpkv = Array(lpkv)
|
89
|
-
key_array = crk ? pks.map{|pk| lpkv + pk} : pks.map{|pk| lpkv + [pk]}
|
90
|
-
key_columns = Array(lk) + Array(rk)
|
91
|
-
ds.import(key_columns, key_array)
|
92
102
|
end
|
93
103
|
end
|
94
104
|
|
@@ -109,25 +119,29 @@ module Sequel
|
|
109
119
|
end
|
110
120
|
|
111
121
|
opts[:pks_setter] = lambda do |pks|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
if key.is_a?(Array)
|
116
|
-
h = {}
|
117
|
-
nh = {}
|
118
|
-
key.zip(pk).each do|k, v|
|
119
|
-
h[k] = v
|
120
|
-
nh[k] = nil
|
121
|
-
end
|
122
|
+
if pks.empty?
|
123
|
+
send(opts.remove_all_method)
|
122
124
|
else
|
123
|
-
|
124
|
-
|
125
|
-
|
125
|
+
primary_key = opts.associated_class.primary_key
|
126
|
+
pkh = {primary_key=>pks}
|
127
|
+
|
128
|
+
if key.is_a?(Array)
|
129
|
+
h = {}
|
130
|
+
nh = {}
|
131
|
+
key.zip(pk).each do|k, v|
|
132
|
+
h[k] = v
|
133
|
+
nh[k] = nil
|
134
|
+
end
|
135
|
+
else
|
136
|
+
h = {key=>pk}
|
137
|
+
nh = {key=>nil}
|
138
|
+
end
|
126
139
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
140
|
+
checked_transaction do
|
141
|
+
ds = send(opts.dataset_method)
|
142
|
+
ds.unfiltered.filter(pkh).update(h)
|
143
|
+
ds.exclude(pkh).update(nh)
|
144
|
+
end
|
131
145
|
end
|
132
146
|
end
|
133
147
|
|
@@ -141,7 +155,7 @@ module Sequel
|
|
141
155
|
def after_save
|
142
156
|
if assoc_pks = @_association_pks
|
143
157
|
assoc_pks.each do |name, pks|
|
144
|
-
instance_exec(pks, &model.association_reflection(name)[:pks_setter])
|
158
|
+
instance_exec(pks, &model.association_reflection(name)[:pks_setter])
|
145
159
|
end
|
146
160
|
@_association_pks = nil
|
147
161
|
end
|
@@ -174,7 +188,19 @@ module Sequel
|
|
174
188
|
# If the receiver is a new object, save the pks
|
175
189
|
# so the update can happen after the received has been saved.
|
176
190
|
def _association_pks_setter(opts, pks)
|
191
|
+
if pks.nil?
|
192
|
+
case opts[:association_pks_nil]
|
193
|
+
when :remove
|
194
|
+
pks = []
|
195
|
+
when :ignore
|
196
|
+
return
|
197
|
+
else
|
198
|
+
raise Error, "nil value given to association_pks setter"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
177
202
|
pks = convert_pk_array(opts, pks)
|
203
|
+
|
178
204
|
delay = opts[:delay_pks]
|
179
205
|
if (new? && delay) || (delay == :always)
|
180
206
|
modified!
|
@@ -190,7 +216,6 @@ module Sequel
|
|
190
216
|
klass = opts.associated_class
|
191
217
|
primary_key = klass.primary_key
|
192
218
|
sch = klass.db_schema
|
193
|
-
pks = Array(pks)
|
194
219
|
|
195
220
|
if primary_key.is_a?(Array)
|
196
221
|
if (cols = sch.values_at(*klass.primary_key)).all? && (convs = cols.map{|c| c[:type] == :integer}).all?
|
@@ -15,6 +15,11 @@ module Sequel
|
|
15
15
|
# and setting a new flag in the cloned dataset, so that each can check with the flag to
|
16
16
|
# determine whether it should call all.
|
17
17
|
#
|
18
|
+
# This plugin also makes #first and related methods that load single records work with
|
19
|
+
# eager loading. Note that when using eager_graph, calling #first or a similar method
|
20
|
+
# will result in two queries, one to load the main object, and one to eagerly load all associated
|
21
|
+
# objects to that main object.
|
22
|
+
#
|
18
23
|
# Usage:
|
19
24
|
#
|
20
25
|
# # Make all model subclass instances eagerly load for each (called before loading subclasses)
|
@@ -53,6 +58,23 @@ module Sequel
|
|
53
58
|
end
|
54
59
|
end
|
55
60
|
|
61
|
+
# Handle eager loading when calling first and related methods. For eager_graph,
|
62
|
+
# this does an additional query after retrieving a single record, because otherwise
|
63
|
+
# the associated records won't get eager loaded correctly.
|
64
|
+
def single_record!
|
65
|
+
if use_eager_all?
|
66
|
+
obj = clone(:all_called=>true).all.first
|
67
|
+
|
68
|
+
if opts[:eager_graph]
|
69
|
+
obj = clone(:all_called=>true).where(obj.qualified_pk_hash).unlimited.all.first
|
70
|
+
end
|
71
|
+
|
72
|
+
obj
|
73
|
+
else
|
74
|
+
super
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
56
78
|
private
|
57
79
|
|
58
80
|
# Wether to use all when each is called, true when eager loading
|
@@ -3,11 +3,12 @@
|
|
3
3
|
module Sequel
|
4
4
|
module Plugins
|
5
5
|
# The PgTypecastOnLoad plugin exists because when you connect to PostgreSQL
|
6
|
-
# using the do
|
6
|
+
# using the do and swift adapters, Sequel doesn't have complete
|
7
7
|
# control over typecasting, and may return columns as strings instead of how
|
8
8
|
# the native postgres adapter would typecast them. This is mostly needed for
|
9
9
|
# the additional support that the pg_* extensions add for advanced PostgreSQL
|
10
|
-
# types such as arrays.
|
10
|
+
# types such as arrays. This plugin is not needed if using the postgres or
|
11
|
+
# jdbc/postgresql adapters.
|
11
12
|
#
|
12
13
|
# This plugin makes model loading to do the same conversion that the
|
13
14
|
# native postgres adapter would do for all columns given. You can either
|
@@ -19,7 +19,47 @@ module Sequel
|
|
19
19
|
# Album.filter{id<100}.all do |a|
|
20
20
|
# a.artists
|
21
21
|
# end
|
22
|
+
# # SELECT * FROM albums WHERE (id < 100)
|
23
|
+
# # SELECT * FROM artists WHERE id IN (...)
|
24
|
+
#
|
25
|
+
# Note that if you are passing a callback to the association method via
|
26
|
+
# a block or :callback option, or using the :reload option to reload
|
27
|
+
# the association, eager loading will not be done.
|
28
|
+
#
|
29
|
+
# You can use the :eager_reload option to reload the association for all
|
30
|
+
# objects that the current object was retrieved with:
|
31
|
+
#
|
32
|
+
# # SELECT * FROM albums WHERE (id < 100)
|
33
|
+
# albums = Album.filter{id<100}.all
|
34
|
+
#
|
35
|
+
# # Eagerly load all artists for these albums
|
36
|
+
# # SELECT * FROM artists WHERE id IN (...)
|
37
|
+
# albums.first.artists
|
38
|
+
#
|
39
|
+
# # Do something that may affect which artists are associated to the albums
|
40
|
+
#
|
41
|
+
# # Eagerly reload all artists for these albums
|
42
|
+
# # SELECT * FROM artists WHERE id IN (...)
|
43
|
+
# albums.first.artists(:eager_reload=>true)
|
22
44
|
#
|
45
|
+
# You can also use the :eager option to specify dependent associations
|
46
|
+
# to eager load:
|
47
|
+
#
|
48
|
+
# albums = Album.filter{id<100}.all
|
49
|
+
#
|
50
|
+
# # Eager load all artists for these albums, and all albums for those artists
|
51
|
+
# # SELECT * FROM artists WHERE id IN (...)
|
52
|
+
# # SELECT * FROM albums WHERE artist_id IN (...)
|
53
|
+
# albums.first.artists(:eager=>:albums)
|
54
|
+
#
|
55
|
+
# You can also use :eager to specify an eager callback. For example:
|
56
|
+
#
|
57
|
+
# albums = Album.filter{id<100}.all
|
58
|
+
#
|
59
|
+
# # Eagerly load all artists whose name starts with A-M for these albums
|
60
|
+
# # SELECT * FROM artists WHERE name > 'N' AND id IN (...)
|
61
|
+
# albums.first.artists(:eager=>proc{|ds| ds.where(:name > 'N')})
|
62
|
+
#
|
23
63
|
# Usage:
|
24
64
|
#
|
25
65
|
# # Make all model subclass instances use tactical eager loading (called before loading subclasses)
|
@@ -51,11 +91,12 @@ module Sequel
|
|
51
91
|
# If there the association is not in the associations cache and the object
|
52
92
|
# was reteived via Dataset#all, eagerly load the association for all model
|
53
93
|
# objects retrieved with the current object.
|
54
|
-
def load_associated_objects(opts,
|
94
|
+
def load_associated_objects(opts, dynamic_opts=nil, &block)
|
95
|
+
dynamic_opts = load_association_objects_options(dynamic_opts, &block)
|
55
96
|
name = opts[:name]
|
56
|
-
if !associations.include?(name) && retrieved_by && !frozen?
|
97
|
+
if (!associations.include?(name) || dynamic_opts[:eager_reload]) && retrieved_by && !frozen? && !dynamic_opts[:callback] && !dynamic_opts[:reload]
|
57
98
|
begin
|
58
|
-
retrieved_by.send(:eager_load, retrieved_with.reject(&:frozen?), name=>{})
|
99
|
+
retrieved_by.send(:eager_load, retrieved_with.reject(&:frozen?), name=>dynamic_opts[:eager] || {})
|
59
100
|
rescue Sequel::UndefinedAssociation
|
60
101
|
# This can happen if class table inheritance is used and the association
|
61
102
|
# is only defined in a subclass. This particular instance can use the
|
data/lib/sequel/version.rb
CHANGED
@@ -5,13 +5,13 @@ module Sequel
|
|
5
5
|
MAJOR = 4
|
6
6
|
# The minor version of Sequel. Bumped for every non-patch level
|
7
7
|
# release, generally around once a month.
|
8
|
-
MINOR =
|
8
|
+
MINOR = 32
|
9
9
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
10
10
|
# releases that fix regressions from previous versions.
|
11
11
|
TINY = 0
|
12
12
|
|
13
13
|
# The version of Sequel you are using, as a string (e.g. "2.11.0")
|
14
|
-
VERSION = [MAJOR, MINOR, TINY].join('.')
|
14
|
+
VERSION = [MAJOR, MINOR, TINY].join('.').freeze
|
15
15
|
|
16
16
|
# The version of Sequel you are using, as a string (e.g. "2.11.0")
|
17
17
|
def self.version
|
data/spec/adapters/mysql_spec.rb
CHANGED
@@ -325,12 +325,6 @@ describe "Joined MySQL dataset" do
|
|
325
325
|
@ds.join(:attributes, :node_id => :id).sql.must_equal "SELECT * FROM `nodes` INNER JOIN `attributes` ON (`attributes`.`node_id` = `nodes`.`id`)"
|
326
326
|
end
|
327
327
|
|
328
|
-
it "should allow a having clause on ungrouped datasets" do
|
329
|
-
@ds.having('blah')
|
330
|
-
|
331
|
-
@ds.having('blah').sql.must_equal "SELECT * FROM `nodes` HAVING (blah)"
|
332
|
-
end
|
333
|
-
|
334
328
|
it "should put a having clause before an order by clause" do
|
335
329
|
@ds.order(:aaa).having(:bbb => :ccc).sql.must_equal "SELECT * FROM `nodes` HAVING (`bbb` = `ccc`) ORDER BY `aaa`"
|
336
330
|
end
|
@@ -1289,3 +1283,37 @@ if DB.adapter_scheme == :mysql2
|
|
1289
1283
|
end
|
1290
1284
|
end
|
1291
1285
|
end
|
1286
|
+
|
1287
|
+
describe "MySQL joined datasets" do
|
1288
|
+
before do
|
1289
|
+
@db = DB
|
1290
|
+
@db.create_table!(:a) do
|
1291
|
+
Integer :id
|
1292
|
+
end
|
1293
|
+
@db.create_table!(:b) do
|
1294
|
+
Integer :id
|
1295
|
+
Integer :a_id
|
1296
|
+
end
|
1297
|
+
@db[:a].insert(1)
|
1298
|
+
@db[:a].insert(2)
|
1299
|
+
@db[:b].insert(3, 1)
|
1300
|
+
@db[:b].insert(4, 1)
|
1301
|
+
@db[:b].insert(5, 2)
|
1302
|
+
@ds = @db[:a].join(:b, :a_id=>:id)
|
1303
|
+
end
|
1304
|
+
after do
|
1305
|
+
@db.drop_table?(:a, :b)
|
1306
|
+
end
|
1307
|
+
|
1308
|
+
it "should support deletions from a single table" do
|
1309
|
+
@ds.where(:a__id=>1).delete
|
1310
|
+
@db[:a].select_order_map(:id).must_equal [2]
|
1311
|
+
@db[:b].select_order_map(:id).must_equal [3, 4, 5]
|
1312
|
+
end
|
1313
|
+
|
1314
|
+
it "should support deletions from multiple tables" do
|
1315
|
+
@ds.delete_from(:a, :b).where(:a__id=>1).delete
|
1316
|
+
@db[:a].select_order_map(:id).must_equal [2]
|
1317
|
+
@db[:b].select_order_map(:id).must_equal [5]
|
1318
|
+
end
|
1319
|
+
end
|
@@ -222,7 +222,7 @@ describe "An Oracle database" do
|
|
222
222
|
it "should translate values correctly" do
|
223
223
|
@d << {:name => 'abc', :value => 456}
|
224
224
|
@d << {:name => 'def', :value => 789}
|
225
|
-
@d.filter
|
225
|
+
@d.filter{value > 500}.update(:date_created => Sequel.lit("to_timestamp('2009-09-09', 'YYYY-MM-DD')"))
|
226
226
|
|
227
227
|
@d[:name => 'def'][:date_created].strftime('%F').must_equal '2009-09-09'
|
228
228
|
end
|
data/spec/bin_spec.rb
CHANGED
@@ -139,7 +139,7 @@ END
|
|
139
139
|
end
|
140
140
|
|
141
141
|
it "-E should echo SQL statements to stdout" do
|
142
|
-
bin(:args=>'-E -c DB.tables').
|
142
|
+
bin(:args=>'-E -c DB.tables').must_include "SELECT * FROM `sqlite_master` WHERE ((`name` != 'sqlite_sequence') AND (`type` = 'table'))"
|
143
143
|
end
|
144
144
|
|
145
145
|
it "-I should include directory in load path" do
|
@@ -148,7 +148,7 @@ END
|
|
148
148
|
|
149
149
|
it "-l should log SQL statements to file" do
|
150
150
|
bin(:args=>"-l #{TMP_FILE} -c DB.tables").must_equal ''
|
151
|
-
File.read(TMP_FILE).
|
151
|
+
File.read(TMP_FILE).must_include "SELECT * FROM `sqlite_master` WHERE ((`name` != 'sqlite_sequence') AND (`type` = 'table'))"
|
152
152
|
end
|
153
153
|
|
154
154
|
it "-L should load all *.rb files in given directory" do
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -3642,6 +3642,13 @@ describe "Dataset prepared statements and bound variables " do
|
|
3642
3642
|
proc{ps.prepare(:select, :select_n2)}.must_raise Sequel::Error
|
3643
3643
|
end
|
3644
3644
|
|
3645
|
+
it "PreparedStatement#prepare should not raise an error if preparing prepared statements is allowed" do
|
3646
|
+
ps = @ds.prepare(:select, :select_n)
|
3647
|
+
def ps.allow_preparing_prepared_statements?; true end
|
3648
|
+
ps.prepare(:select, :select_n2).call
|
3649
|
+
@db.sqls.must_equal ["SELECT * FROM items"]
|
3650
|
+
end
|
3651
|
+
|
3645
3652
|
it "#call should default to using :all if an invalid type is given" do
|
3646
3653
|
@ds.filter(:num=>:$n).call(:select_all, :n=>1)
|
3647
3654
|
@db.sqls.must_equal ['SELECT * FROM items WHERE (num = 1)']
|
@@ -336,6 +336,14 @@ describe "Sequel::Plugins::AssociationPks" do
|
|
336
336
|
"UPDATE albums SET artist_id = NULL WHERE ((albums.artist_id = 1) AND (id NOT IN (2, 4)))"
|
337
337
|
]
|
338
338
|
|
339
|
+
ar.album_pks = []
|
340
|
+
@db.sqls.must_equal []
|
341
|
+
|
342
|
+
ar.save_changes
|
343
|
+
@db.sqls.must_equal [
|
344
|
+
"UPDATE albums SET artist_id = NULL WHERE (artist_id = 1)"
|
345
|
+
]
|
346
|
+
|
339
347
|
al = @Album.load(:id=>1)
|
340
348
|
al.tag_pks.must_equal [1,2]
|
341
349
|
@db.sqls
|
@@ -351,6 +359,14 @@ describe "Sequel::Plugins::AssociationPks" do
|
|
351
359
|
"INSERT INTO albums_tags (album_id, tag_id) VALUES (1, 3)",
|
352
360
|
"COMMIT",
|
353
361
|
]
|
362
|
+
|
363
|
+
al.tag_pks = []
|
364
|
+
@db.sqls.must_equal []
|
365
|
+
|
366
|
+
al.save_changes
|
367
|
+
@db.sqls.must_equal [
|
368
|
+
"DELETE FROM albums_tags WHERE (album_id = 1)",
|
369
|
+
]
|
354
370
|
end
|
355
371
|
|
356
372
|
it "should clear delayed associated pks if refreshing, if :delay plugin option is used" do
|
@@ -364,4 +380,26 @@ describe "Sequel::Plugins::AssociationPks" do
|
|
364
380
|
ar.refresh
|
365
381
|
ar.album_pks.must_equal [1,2,3]
|
366
382
|
end
|
383
|
+
|
384
|
+
it "should remove all values if nil given to setter and :association_pks_nil=>:remove" do
|
385
|
+
@Artist.one_to_many :albums, :clone=>:albums, :association_pks_nil=>:remove
|
386
|
+
|
387
|
+
ar = @Artist.load(:id=>1)
|
388
|
+
ar.album_pks = nil
|
389
|
+
@db.sqls.must_equal ["UPDATE albums SET artist_id = NULL WHERE (artist_id = 1)"]
|
390
|
+
end
|
391
|
+
|
392
|
+
it "should take no action if nil given to setter and :association_pks_nil=>:ignore" do
|
393
|
+
@Artist.one_to_many :albums, :clone=>:albums, :association_pks_nil=>:ignore
|
394
|
+
|
395
|
+
ar = @Artist.load(:id=>1)
|
396
|
+
ar = @Artist.new
|
397
|
+
ar.album_pks = nil
|
398
|
+
@db.sqls.must_equal []
|
399
|
+
end
|
400
|
+
|
401
|
+
it "should raise error if nil given to setter by default" do
|
402
|
+
ar = @Artist.load(:id=>1)
|
403
|
+
proc{ar.album_pks = nil}.must_raise Sequel::Error
|
404
|
+
end
|
367
405
|
end
|
@@ -51,6 +51,14 @@ describe "class_table_inheritance plugin" do
|
|
51
51
|
Object.send(:remove_const, :Employee)
|
52
52
|
end
|
53
53
|
|
54
|
+
it "#cti_base_model should be the model that loaded the plugin" do
|
55
|
+
Executive.cti_base_model.must_equal Employee
|
56
|
+
end
|
57
|
+
|
58
|
+
it "#cti_columns should be a mapping of table names to columns" do
|
59
|
+
Executive.cti_columns.must_equal(:employees=>[:id, :name, :kind], :managers=>[:id, :num_staff], :executives=>[:id, :num_managers])
|
60
|
+
end
|
61
|
+
|
54
62
|
it "should have simple_table = nil for all subclasses" do
|
55
63
|
Manager.simple_table.must_equal nil
|
56
64
|
Executive.simple_table.must_equal nil
|
@@ -246,7 +254,23 @@ describe "class_table_inheritance plugin" do
|
|
246
254
|
sqls[0].must_match(/INSERT INTO employees \((name|kind), (name|kind)\) VALUES \('(E|Ceo)', '(E|Ceo)'\)/)
|
247
255
|
sqls[1].must_match(/INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \([12], [12]\)/)
|
248
256
|
sqls[2].must_match(/INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([13], [13]\)/)
|
257
|
+
end
|
258
|
+
|
259
|
+
it "should insert the correct rows into all tables when inserting when insert select is important" do
|
260
|
+
[Ceo, Manager, Employee].each do |klass|
|
261
|
+
def (klass.cti_instance_dataset).supports_insert_select?; true; end
|
262
|
+
def (klass.cti_instance_dataset).insert_select(v)
|
263
|
+
db.run(insert_sql(v) + " RETURNING *")
|
264
|
+
v.merge(:id=>1)
|
265
|
+
end
|
249
266
|
end
|
267
|
+
Ceo.create(:num_managers=>3, :num_staff=>2, :name=>'E')
|
268
|
+
sqls = @db.sqls
|
269
|
+
sqls.length.must_equal 3
|
270
|
+
sqls[0].must_match(/INSERT INTO employees \((name|kind), (name|kind)\) VALUES \('(E|Ceo)', '(E|Ceo)'\) RETURNING \*/)
|
271
|
+
sqls[1].must_match(/INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \([12], [12]\) RETURNING \*/)
|
272
|
+
sqls[2].must_match(/INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([13], [13]\) RETURNING \*/)
|
273
|
+
end
|
250
274
|
|
251
275
|
it "should insert the correct rows into all tables with a given primary key" do
|
252
276
|
e = Ceo.new(:num_managers=>3, :num_staff=>2, :name=>'E')
|