sequel 5.14.0 → 5.15.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 +18 -0
- data/doc/opening_databases.rdoc +4 -0
- data/doc/release_notes/5.15.0.txt +39 -0
- data/doc/validations.rdoc +1 -1
- data/lib/sequel/adapters/postgres.rb +1 -1
- data/lib/sequel/adapters/shared/mssql.rb +6 -0
- data/lib/sequel/adapters/shared/sqlite.rb +6 -2
- data/lib/sequel/connection_pool/sharded_threaded.rb +6 -3
- data/lib/sequel/connection_pool/threaded.rb +6 -3
- data/lib/sequel/model/associations.rb +20 -3
- data/lib/sequel/model/base.rb +23 -1
- data/lib/sequel/plugins/class_table_inheritance.rb +23 -3
- data/lib/sequel/plugins/nested_attributes.rb +1 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/extensions/class_table_inheritance_spec.rb +116 -0
- data/spec/extensions/constant_sql_override_spec.rb +7 -1
- data/spec/extensions/nested_attributes_spec.rb +18 -0
- data/spec/model/associations_spec.rb +62 -0
- data/spec/model/validations_spec.rb +27 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 965fa50e6e91a5630a4e484651180b38b9d2eb2c785be8b91dd5b099e3ba5be6
|
4
|
+
data.tar.gz: 3c3c5bd93d51326fff0081272e41a0a23b0dbb47d393603f073cbc0f0486e6c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a633436d4a066e195e847cffc7dd68d0cd2902915ddca33d5df724dc020aee22dbd95a261f8b15ea1581a73517d5736423a8693f40a3ef59fb27c88b8c24ca01
|
7
|
+
data.tar.gz: bee56c79793138296ced8395deabf2652a68fe2de3bc4f144470d885b253883017e15ca0281e36e04c6064b590b505daed285b23699140e77cae119bddee3914
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
=== 5.15.0 (2018-12-01)
|
2
|
+
|
3
|
+
* Add :conn_str option in the postgres adapter for PostgreSQL connection strings, if the pg driver is used (graywolf) (#1572)
|
4
|
+
|
5
|
+
* Add :qualify_tables option to class_table_inheritance plugin to automatically qualify subclass tables with superclass qualifier (benalavi) (#1571)
|
6
|
+
|
7
|
+
* Access already allocated connections in a thread safe manner when checking out connections in the sharded threaded connection pool (jeremyevans)
|
8
|
+
|
9
|
+
* Automatically support datasets using qualified tables in the class_table_inheritance plugin without having to use the :alias option (benalavi) (#1565)
|
10
|
+
|
11
|
+
* Support rename_column without emulation on SQLite 3.25+ (jeremyevans)
|
12
|
+
|
13
|
+
* Do not remove currently cached many_to_one associated objects when changing the related foreign key value from nil to non-nil (jeremyevans)
|
14
|
+
|
15
|
+
* Do not validate new *_to_many associated objects twice when saving in the nested_attributes plugin (jeremyevans)
|
16
|
+
|
17
|
+
* Add Model#skip_validation_on_next_save! for skipping validation on next save call (jeremyevans)
|
18
|
+
|
1
19
|
=== 5.14.0 (2018-11-01)
|
2
20
|
|
3
21
|
* Drop defaulting the :port option to 5432 in the postgres adapter, so that setting the :service option in :driver_options works (jeremyevans) (#1558)
|
data/doc/opening_databases.rdoc
CHANGED
@@ -309,6 +309,10 @@ The following additional options are supported:
|
|
309
309
|
conversion is done, so an error is raised if you attempt to retrieve an infinite
|
310
310
|
timestamp/date. You can set this to :nil to convert to nil, :string to leave
|
311
311
|
as a string, or :float to convert to an infinite float.
|
312
|
+
:conn_str :: Use connection string (in form of `host=x port=y ...`). Ignores all other options, only supported with pg
|
313
|
+
library. See https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING and
|
314
|
+
https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS for format and list of supported
|
315
|
+
options.
|
312
316
|
:connect_timeout :: Set the number of seconds to wait for a connection (default 20, only respected
|
313
317
|
if using the pg library).
|
314
318
|
:driver_options :: A hash of options to pass to the underlying driver (only respected if using the pg library)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A :qualify_tables option has been added to the
|
4
|
+
class_table_inheritance plugin, which will automatically qualify
|
5
|
+
subclass tables with the same qualifier as the superclass table
|
6
|
+
if the superclass table is qualified.
|
7
|
+
|
8
|
+
* Model#save_validation_on_next_save! has been added, which skips all
|
9
|
+
validation on the next save to the object, including the running
|
10
|
+
of validation related hooks. This method is designed for use only
|
11
|
+
when Model#valid? is called on the object before saving, to avoid
|
12
|
+
running validations on the object twice. This method takes
|
13
|
+
precedence even over an explicit validate: true option passed to
|
14
|
+
Model#save, and as such should be used with care.
|
15
|
+
|
16
|
+
* The postgres adapter now supports a :conn_str Database option to
|
17
|
+
use a PostgreSQL connection string (e.g. "host=foo port=2442") when
|
18
|
+
connecting. This option has preference over other connection
|
19
|
+
related options if it is present.
|
20
|
+
|
21
|
+
= Other Improvements
|
22
|
+
|
23
|
+
* If a foreign key for a model object is changed from a nil value to
|
24
|
+
a non-nil value, any cached associated objects related to the
|
25
|
+
foreign key are no longer removed. Such associated objects could
|
26
|
+
only be set manually, and if they have been set manually, it is
|
27
|
+
probably not a good idea to remove them automatically.
|
28
|
+
|
29
|
+
* When using the nested_attributes plugin, new *_to_many associated
|
30
|
+
objects are not validated twice when saving.
|
31
|
+
|
32
|
+
* The default table alias when using the class_table_inheritance
|
33
|
+
plugin now correctly handles qualified tables.
|
34
|
+
|
35
|
+
* A theoretical thread safety issue when assigning connections
|
36
|
+
in the threaded connection pools has been fixed.
|
37
|
+
|
38
|
+
* Renaming columns is now supported without emulation when using
|
39
|
+
SQLite 3.25+.
|
data/doc/validations.rdoc
CHANGED
@@ -535,7 +535,7 @@ Note that the column names used in the errors are used verbatim in the error mes
|
|
535
535
|
errors.full_messages
|
536
536
|
# => ["Album name is not valid"]
|
537
537
|
|
538
|
-
Alternatively, feel free to override Sequel::Model::Errors#full_messages. As long as it returns an array of strings,
|
538
|
+
Alternatively, feel free to override Sequel::Model::Errors#full_messages. As long as it returns an array of strings, overriding it is completely safe.
|
539
539
|
|
540
540
|
=== +count+
|
541
541
|
|
@@ -203,7 +203,7 @@ module Sequel
|
|
203
203
|
:sslrootcert => opts[:sslrootcert]
|
204
204
|
}.delete_if { |key, value| blank_object?(value) }
|
205
205
|
connection_params.merge!(opts[:driver_options]) if opts[:driver_options]
|
206
|
-
conn = Adapter.connect(connection_params)
|
206
|
+
conn = Adapter.connect(opts[:conn_str] || connection_params)
|
207
207
|
|
208
208
|
conn.instance_variable_set(:@prepared_statements, {})
|
209
209
|
|
@@ -40,6 +40,12 @@ module Sequel
|
|
40
40
|
# :numrows :: The number of rows affected by the stored procedure
|
41
41
|
# output params :: Values for any output paramters, using the name given for the output parameter
|
42
42
|
#
|
43
|
+
# Because Sequel datasets only support a single result set per query, and retrieving
|
44
|
+
# the result code and number of rows requires a query, this does not support
|
45
|
+
# stored procedures which also return result sets. To handle such stored procedures,
|
46
|
+
# you should drop down to the connection/driver level by using Sequel::Database#synchronize
|
47
|
+
# to get access to the underlying connection object.
|
48
|
+
#
|
43
49
|
# Examples:
|
44
50
|
#
|
45
51
|
# DB.call_mssql_sproc(:SequelTest, {args: ['input arg', :output]})
|
@@ -217,8 +217,12 @@ module Sequel
|
|
217
217
|
ocp = lambda{|oc| oc.delete_if{|c| c.to_s == op[:name].to_s}}
|
218
218
|
duplicate_table(table, :old_columns_proc=>ocp){|columns| columns.delete_if{|s| s[:name].to_s == op[:name].to_s}}
|
219
219
|
when :rename_column
|
220
|
-
|
221
|
-
|
220
|
+
if sqlite_version >= 32500
|
221
|
+
super
|
222
|
+
else
|
223
|
+
ncp = lambda{|nc| nc.map!{|c| c.to_s == op[:name].to_s ? op[:new_name] : c}}
|
224
|
+
duplicate_table(table, :new_columns_proc=>ncp){|columns| columns.each{|s| s[:name] = op[:new_name] if s[:name].to_s == op[:name].to_s}}
|
225
|
+
end
|
222
226
|
when :set_column_default
|
223
227
|
duplicate_table(table){|columns| columns.each{|s| s[:default] = op[:default] if s[:name].to_s == op[:name].to_s}}
|
224
228
|
when :set_column_null
|
@@ -46,6 +46,7 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
|
|
46
46
|
# A hash of connections currently being used for the given server, key is the
|
47
47
|
# Thread, value is the connection. Nonexistent servers will return nil. Treat
|
48
48
|
# this as read only, do not modify the resulting object.
|
49
|
+
# The calling code should already have the mutex before calling this.
|
49
50
|
def allocated(server=:default)
|
50
51
|
@allocated[server]
|
51
52
|
end
|
@@ -70,13 +71,14 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
|
|
70
71
|
# An array of connections opened but not currently used, for the given
|
71
72
|
# server. Nonexistent servers will return nil. Treat this as read only, do
|
72
73
|
# not modify the resulting object.
|
74
|
+
# The calling code should already have the mutex before calling this.
|
73
75
|
def available_connections(server=:default)
|
74
76
|
@available_connections[server]
|
75
77
|
end
|
76
78
|
|
77
79
|
# The total number of connections opened for the given server.
|
78
80
|
# Nonexistent servers will return the created count of the default server.
|
79
|
-
# The calling code should
|
81
|
+
# The calling code should NOT have the mutex before calling this.
|
80
82
|
def size(server=:default)
|
81
83
|
@mutex.synchronize{_size(server)}
|
82
84
|
end
|
@@ -221,10 +223,11 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
|
|
221
223
|
# Assign a connection to the thread, or return nil if one cannot be assigned.
|
222
224
|
# The caller should NOT have the mutex before calling this.
|
223
225
|
def assign_connection(thread, server)
|
224
|
-
alloc =
|
226
|
+
alloc = nil
|
225
227
|
|
226
228
|
do_make_new = false
|
227
229
|
sync do
|
230
|
+
alloc = allocated(server)
|
228
231
|
if conn = next_available(server)
|
229
232
|
alloc[thread] = conn
|
230
233
|
return conn
|
@@ -286,7 +289,7 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
|
|
286
289
|
end
|
287
290
|
|
288
291
|
# Disconnect all available connections immediately, and schedule currently allocated connections for disconnection
|
289
|
-
# as soon as they are returned to the pool. The calling code should
|
292
|
+
# as soon as they are returned to the pool. The calling code should NOT
|
290
293
|
# have the mutex before calling this.
|
291
294
|
def disconnect_connections(conns)
|
292
295
|
conns.each{|conn| disconnect_connection(conn)}
|
@@ -11,10 +11,11 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
|
|
11
11
|
attr_reader :max_size
|
12
12
|
|
13
13
|
# An array of connections that are available for use by the pool.
|
14
|
+
# The calling code should already have the mutex before calling this.
|
14
15
|
attr_reader :available_connections
|
15
16
|
|
16
|
-
# A hash with thread keys and connection values for currently allocated
|
17
|
-
#
|
17
|
+
# A hash with thread keys and connection values for currently allocated connections.
|
18
|
+
# The calling code should already have the mutex before calling this.
|
18
19
|
attr_reader :allocated
|
19
20
|
|
20
21
|
# The following additional options are respected:
|
@@ -171,6 +172,8 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
|
|
171
172
|
# Assign a connection to the thread, or return nil if one cannot be assigned.
|
172
173
|
# The caller should NOT have the mutex before calling this.
|
173
174
|
def assign_connection(thread)
|
175
|
+
# Thread safe as instance variable is only assigned to local variable
|
176
|
+
# and not operated on outside mutex.
|
174
177
|
allocated = @allocated
|
175
178
|
do_make_new = false
|
176
179
|
to_disconnect = nil
|
@@ -183,7 +186,7 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
|
|
183
186
|
if (n = _size) >= (max = @max_size)
|
184
187
|
allocated.keys.each do |t|
|
185
188
|
unless t.alive?
|
186
|
-
(to_disconnect ||= []) <<
|
189
|
+
(to_disconnect ||= []) << allocated.delete(t)
|
187
190
|
end
|
188
191
|
end
|
189
192
|
n = nil
|
@@ -2406,15 +2406,32 @@ module Sequel
|
|
2406
2406
|
# cached associations.
|
2407
2407
|
def change_column_value(column, value)
|
2408
2408
|
if assocs = model.autoreloading_associations[column]
|
2409
|
+
vals = @values
|
2409
2410
|
if new?
|
2410
2411
|
# Do deeper checking for new objects, so that associations are
|
2411
2412
|
# not deleted when values do not change. This code is run at
|
2412
2413
|
# a higher level for existing objects.
|
2413
|
-
|
2414
|
-
|
2414
|
+
if value == (c = vals[column]) && value.class == c.class
|
2415
|
+
# If the value is the same, there is no reason to delete
|
2416
|
+
# the related associations, so exit early in that case.
|
2417
|
+
return super
|
2418
|
+
end
|
2419
|
+
|
2420
|
+
only_delete_nil = c.nil?
|
2421
|
+
elsif vals[column].nil?
|
2422
|
+
only_delete_nil = true
|
2415
2423
|
end
|
2416
2424
|
|
2417
|
-
|
2425
|
+
if only_delete_nil
|
2426
|
+
# If the current foreign key value is nil, but the association
|
2427
|
+
# is already present in the cache, it was probably added to the
|
2428
|
+
# cache for a reason, and we do not want to delete it in that case.
|
2429
|
+
# However, we still want to delete associations with nil values
|
2430
|
+
# to remove the cached false negative.
|
2431
|
+
assocs.each{|a| associations.delete(a) if associations[a].nil?}
|
2432
|
+
else
|
2433
|
+
assocs.each{|a| associations.delete(a)}
|
2434
|
+
end
|
2418
2435
|
end
|
2419
2436
|
super
|
2420
2437
|
end
|
data/lib/sequel/model/base.rb
CHANGED
@@ -1456,7 +1456,7 @@ module Sequel
|
|
1456
1456
|
def save(opts=OPTS)
|
1457
1457
|
raise Sequel::Error, "can't save frozen object" if frozen?
|
1458
1458
|
set_server(opts[:server]) if opts[:server]
|
1459
|
-
unless
|
1459
|
+
unless _save_valid?(opts)
|
1460
1460
|
raise(ValidationFailed.new(self)) if raise_on_failure?(opts)
|
1461
1461
|
return
|
1462
1462
|
end
|
@@ -1551,6 +1551,15 @@ module Sequel
|
|
1551
1551
|
super
|
1552
1552
|
end
|
1553
1553
|
|
1554
|
+
# Skip all validation of the object on the next call to #save,
|
1555
|
+
# including the running of validation hooks. This is designed for
|
1556
|
+
# and should only be used in cases where #valid? is called before
|
1557
|
+
# saving and the <tt>validate: false</tt> option cannot be passed to
|
1558
|
+
# #save.
|
1559
|
+
def skip_validation_on_next_save!
|
1560
|
+
@skip_validation_on_next_save = true
|
1561
|
+
end
|
1562
|
+
|
1554
1563
|
# Returns (naked) dataset that should return only this instance.
|
1555
1564
|
#
|
1556
1565
|
# Artist[1].this
|
@@ -1809,6 +1818,19 @@ module Sequel
|
|
1809
1818
|
v
|
1810
1819
|
end
|
1811
1820
|
|
1821
|
+
# Validate the object if validating on save. Skips validation
|
1822
|
+
# completely (including validation hooks) if
|
1823
|
+
# skip_validation_on_save! has been called on the object,
|
1824
|
+
# resetting the flag so that future saves will validate.
|
1825
|
+
def _save_valid?(opts)
|
1826
|
+
if @skip_validation_on_next_save
|
1827
|
+
@skip_validation_on_next_save = false
|
1828
|
+
return true
|
1829
|
+
end
|
1830
|
+
|
1831
|
+
checked_save_failure(opts){_valid?(opts)}
|
1832
|
+
end
|
1833
|
+
|
1812
1834
|
# Call _update with the given columns, if any are present.
|
1813
1835
|
# Plugins can override this method in order to update with
|
1814
1836
|
# additional columns, even when the column hash is initially empty.
|
@@ -198,6 +198,9 @@ module Sequel
|
|
198
198
|
# Overrides implicit table names.
|
199
199
|
# :ignore_subclass_columns :: Array with column names as symbols that are ignored
|
200
200
|
# on all sub-classes.
|
201
|
+
# :qualify_tables :: Boolean true to qualify automatically determined
|
202
|
+
# subclass tables with the same qualifier as their
|
203
|
+
# superclass.
|
201
204
|
def self.configure(model, opts = OPTS)
|
202
205
|
SingleTableInheritance.configure model, opts[:key], opts
|
203
206
|
|
@@ -207,8 +210,14 @@ module Sequel
|
|
207
210
|
@cti_instance_dataset = @instance_dataset
|
208
211
|
@cti_table_columns = columns
|
209
212
|
@cti_table_map = opts[:table_map] || {}
|
210
|
-
@cti_alias = opts[:alias] || @dataset.first_source
|
213
|
+
@cti_alias = opts[:alias] || case source = @dataset.first_source
|
214
|
+
when SQL::QualifiedIdentifier
|
215
|
+
@dataset.unqualified_column_for(source)
|
216
|
+
else
|
217
|
+
source
|
218
|
+
end
|
211
219
|
@cti_ignore_subclass_columns = opts[:ignore_subclass_columns] || []
|
220
|
+
@cti_qualify_tables = !!opts[:qualify_tables]
|
212
221
|
end
|
213
222
|
end
|
214
223
|
|
@@ -239,6 +248,13 @@ module Sequel
|
|
239
248
|
# primary key column is always allowed to be duplicated
|
240
249
|
attr_reader :cti_ignore_subclass_columns
|
241
250
|
|
251
|
+
# A boolean indicating whether or not to automatically qualify tables
|
252
|
+
# backing subclasses with the same qualifier as their superclass, if
|
253
|
+
# the superclass is qualified. Specified with the :qualify_tables
|
254
|
+
# option to the plugin and only applied to automatically determined
|
255
|
+
# table names (not to the :table_map option).
|
256
|
+
attr_reader :cti_qualify_tables
|
257
|
+
|
242
258
|
# Freeze CTI information when freezing model class.
|
243
259
|
def freeze
|
244
260
|
@cti_models.freeze
|
@@ -250,7 +266,7 @@ module Sequel
|
|
250
266
|
super
|
251
267
|
end
|
252
268
|
|
253
|
-
Plugins.inherited_instance_variables(self, :@cti_models=>nil, :@cti_tables=>nil, :@cti_table_columns=>nil, :@cti_instance_dataset=>nil, :@cti_table_map=>nil, :@cti_alias=>nil, :@cti_ignore_subclass_columns=>nil)
|
269
|
+
Plugins.inherited_instance_variables(self, :@cti_models=>nil, :@cti_tables=>nil, :@cti_table_columns=>nil, :@cti_instance_dataset=>nil, :@cti_table_map=>nil, :@cti_alias=>nil, :@cti_ignore_subclass_columns=>nil, :@cti_qualify_tables=>nil)
|
254
270
|
|
255
271
|
def inherited(subclass)
|
256
272
|
ds = sti_dataset
|
@@ -267,7 +283,11 @@ module Sequel
|
|
267
283
|
if table = cti_table_map[n.to_sym]
|
268
284
|
columns = db.from(table).columns
|
269
285
|
else
|
270
|
-
table =
|
286
|
+
table = if cti_qualify_tables && (schema = dataset.schema_and_table(table_name).first)
|
287
|
+
SQL::QualifiedIdentifier.new(schema, subclass.implicit_table_name)
|
288
|
+
else
|
289
|
+
subclass.implicit_table_name
|
290
|
+
end
|
271
291
|
columns = check_non_connection_error(false){db.from(table).columns}
|
272
292
|
table = nil if !columns || columns.empty?
|
273
293
|
end
|
@@ -176,6 +176,7 @@ module Sequel
|
|
176
176
|
delay_validate_associated_object(reflection, obj)
|
177
177
|
if reflection.returns_array?
|
178
178
|
public_send(reflection[:name]) << obj
|
179
|
+
obj.skip_validation_on_next_save!
|
179
180
|
after_save_hook{public_send(reflection[:add_method], obj)}
|
180
181
|
else
|
181
182
|
associations[reflection[:name]] = obj
|
data/lib/sequel/version.rb
CHANGED
@@ -6,7 +6,7 @@ module Sequel
|
|
6
6
|
|
7
7
|
# The minor version of Sequel. Bumped for every non-patch level
|
8
8
|
# release, generally around once a month.
|
9
|
-
MINOR =
|
9
|
+
MINOR = 15
|
10
10
|
|
11
11
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
12
12
|
# releases that fix regressions from previous versions.
|
@@ -566,3 +566,119 @@ describe "class_table_inheritance plugin with duplicate columns" do
|
|
566
566
|
end
|
567
567
|
end
|
568
568
|
|
569
|
+
describe "class_table_inheritance plugin with dataset defined with QualifiedIdentifier" do
|
570
|
+
before do
|
571
|
+
@db = Sequel.mock(:numrows=>1, :autoid=>proc{|sql| 1})
|
572
|
+
def @db.supports_schema_parsing?() true end
|
573
|
+
def @db.schema(table, opts={})
|
574
|
+
{Sequel[:hr][:employees]=>[[:id, {:primary_key=>true, :type=>:integer}], [:name, {:type=>:string}], [:kind, {:type=>:string}]],
|
575
|
+
Sequel[:hr][:managers]=>[[:id, {:type=>:integer}]],
|
576
|
+
Sequel[:hr][:staff]=>[[:id, {:type=>:integer}], [:manager_id, {:type=>:integer}]],
|
577
|
+
}[table.is_a?(Sequel::Dataset) ? table.first_source_table : table]
|
578
|
+
end
|
579
|
+
@db.extend_datasets do
|
580
|
+
def columns
|
581
|
+
{[Sequel[:hr][:employees]]=>[:id, :name, :kind],
|
582
|
+
[Sequel[:hr][:managers]]=>[:id],
|
583
|
+
[Sequel[:hr][:staff]]=>[:id, :manager_id],
|
584
|
+
[Sequel[:hr][:employees], Sequel[:hr][:managers]]=>[:id, :name, :kind],
|
585
|
+
[Sequel[:hr][:employees], Sequel[:hr][:staff]]=>[:id, :name, :kind, :manager_id],
|
586
|
+
}[opts[:from] + (opts[:join] || []).map{|x| x.table}]
|
587
|
+
end
|
588
|
+
end
|
589
|
+
end
|
590
|
+
after do
|
591
|
+
[:Manager, :Staff, :Employee].each{|s| Object.send(:remove_const, s) if Object.const_defined?(s)}
|
592
|
+
end
|
593
|
+
|
594
|
+
describe "with table_map used to qualify subclasses" do
|
595
|
+
before do
|
596
|
+
::Employee = Class.new(Sequel::Model)
|
597
|
+
::Employee.db = @db
|
598
|
+
::Employee.set_dataset(Sequel[:hr][:employees])
|
599
|
+
class ::Employee
|
600
|
+
def _save_refresh; @values[:id] = 1 end
|
601
|
+
def self.columns
|
602
|
+
dataset.columns || dataset.opts[:from].first.expression.columns
|
603
|
+
end
|
604
|
+
plugin :class_table_inheritance, :table_map=>{:Manager=>Sequel[:hr][:managers],:Staff=>Sequel[:hr][:staff]}
|
605
|
+
end
|
606
|
+
class ::Manager < Employee
|
607
|
+
one_to_many :staff_members, :class=>:Staff
|
608
|
+
end
|
609
|
+
class ::Staff < Employee
|
610
|
+
many_to_one :manager
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
it "should handle many_to_one relationships correctly" do
|
615
|
+
Manager.dataset = Manager.dataset.with_fetch(:id=>3, :name=>'E')
|
616
|
+
Staff.load(:manager_id=>3).manager.must_equal Manager.load(:id=>3, :name=>'E')
|
617
|
+
@db.sqls.must_equal ['SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind FROM hr.employees INNER JOIN hr.managers ON (hr.managers.id = hr.employees.id)) AS employees WHERE (id = 3) LIMIT 1']
|
618
|
+
end
|
619
|
+
|
620
|
+
it "should handle one_to_many relationships correctly" do
|
621
|
+
Staff.dataset = Staff.dataset.with_fetch(:id=>1, :name=>'S', :manager_id=>3)
|
622
|
+
Manager.load(:id=>3).staff_members.must_equal [Staff.load(:id=>1, :name=>'S', :manager_id=>3)]
|
623
|
+
@db.sqls.must_equal ['SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind, hr.staff.manager_id FROM hr.employees INNER JOIN hr.staff ON (hr.staff.id = hr.employees.id)) AS employees WHERE (employees.manager_id = 3)']
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
describe "without table_map or qualify_tables set" do
|
628
|
+
it "should use a non-qualified subquery in subclasses" do
|
629
|
+
def @db.schema(table, opts={})
|
630
|
+
{Sequel[:hr][:employees]=>[[:id, {:primary_key=>true, :type=>:integer}], [:name, {:type=>:string}], [:kind, {:type=>:string}]],
|
631
|
+
:managers=>[[:id, {:type=>:integer}]],
|
632
|
+
}[table.is_a?(Sequel::Dataset) ? table.first_source_table : table]
|
633
|
+
end
|
634
|
+
@db.extend_datasets do
|
635
|
+
def columns
|
636
|
+
{[Sequel[:hr][:employees]]=>[:id, :name, :kind],
|
637
|
+
[:managers]=>[:id],
|
638
|
+
[Sequel[:hr][:employees], :managers]=>[:id, :name, :kind]
|
639
|
+
}[opts[:from] + (opts[:join] || []).map{|x| x.table}]
|
640
|
+
end
|
641
|
+
end
|
642
|
+
::Employee = Class.new(Sequel::Model)
|
643
|
+
::Employee.db = @db
|
644
|
+
::Employee.set_dataset(Sequel[:hr][:employees])
|
645
|
+
class ::Employee
|
646
|
+
def _save_refresh; @values[:id] = 1 end
|
647
|
+
def self.columns
|
648
|
+
dataset.columns || dataset.opts[:from].first.expression.columns
|
649
|
+
end
|
650
|
+
plugin :class_table_inheritance
|
651
|
+
end
|
652
|
+
class ::Manager < ::Employee
|
653
|
+
end
|
654
|
+
|
655
|
+
Employee.dataset.sql.must_equal 'SELECT * FROM hr.employees'
|
656
|
+
Manager.dataset.sql.must_equal 'SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind FROM hr.employees INNER JOIN managers ON (managers.id = hr.employees.id)) AS employees'
|
657
|
+
end
|
658
|
+
end
|
659
|
+
|
660
|
+
describe "with qualify_tables option set" do
|
661
|
+
it "should use a subquery with the same qualifier in subclasses" do
|
662
|
+
::Employee = Class.new(Sequel::Model)
|
663
|
+
::Employee.db = @db
|
664
|
+
::Employee.set_dataset(Sequel[:hr][:employees])
|
665
|
+
class ::Employee
|
666
|
+
def _save_refresh; @values[:id] = 1 end
|
667
|
+
def self.columns
|
668
|
+
dataset.columns || dataset.opts[:from].first.expression.columns
|
669
|
+
end
|
670
|
+
plugin :class_table_inheritance, :table_map=>{:Staff=>Sequel[:hr][:staff]}, qualify_tables: true
|
671
|
+
end
|
672
|
+
class ::Manager < ::Employee
|
673
|
+
one_to_many :staff_members, :class=>:Staff
|
674
|
+
end
|
675
|
+
class ::Staff < ::Employee
|
676
|
+
many_to_one :manager
|
677
|
+
end
|
678
|
+
|
679
|
+
Employee.dataset.sql.must_equal 'SELECT * FROM hr.employees'
|
680
|
+
Manager.dataset.sql.must_equal 'SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind FROM hr.employees INNER JOIN hr.managers ON (hr.managers.id = hr.employees.id)) AS employees'
|
681
|
+
Staff.dataset.sql.must_equal 'SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind, hr.staff.manager_id FROM hr.employees INNER JOIN hr.staff ON (hr.staff.id = hr.employees.id)) AS employees'
|
682
|
+
end
|
683
|
+
end
|
684
|
+
end
|
@@ -11,8 +11,14 @@ describe "constant_sql_override extension" do
|
|
11
11
|
@db.sqls.must_equal ["SELECT * FROM tbl WHERE (foo = CURRENT TIMESTAMP AT TIME ZONE 'UTC') LIMIT 1"]
|
12
12
|
end
|
13
13
|
|
14
|
-
it '
|
14
|
+
it 'does not change behavior for unconfigured constants' do
|
15
|
+
@db[:tbl].where(foo: Sequel::CURRENT_TIMESTAMP).first
|
16
|
+
@db.sqls.must_equal ["SELECT * FROM tbl WHERE (foo = CURRENT_TIMESTAMP) LIMIT 1"]
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'freezes the constant_sqls hash when frozen' do
|
15
20
|
@db.freeze
|
16
21
|
@db.constant_sqls.frozen?.must_equal true
|
22
|
+
proc{@db.set_constant_sql(Sequel::CURRENT_TIMESTAMP, "CURRENT TIMESTAMP AT TIME ZONE 'UTC'")}.must_raise RuntimeError
|
17
23
|
end
|
18
24
|
end
|
@@ -492,6 +492,24 @@ describe "NestedAttributes plugin" do
|
|
492
492
|
@db.sqls.must_equal ["UPDATE artists SET name = 'Ar' WHERE (id = 10)"]
|
493
493
|
end
|
494
494
|
|
495
|
+
it "should not attempt to validate nested attributes twice for one_to_many associations when creating them" do
|
496
|
+
@Artist.nested_attributes :albums
|
497
|
+
validated = []
|
498
|
+
@Album.class_eval do
|
499
|
+
define_method(:validate) do
|
500
|
+
super()
|
501
|
+
validated << self
|
502
|
+
end
|
503
|
+
end
|
504
|
+
a = @Artist.new(:name=>'Ar', :albums_attributes=>[{:name=>'Al'}])
|
505
|
+
@db.sqls.must_equal []
|
506
|
+
validated.length.must_equal 0
|
507
|
+
a.save
|
508
|
+
validated.length.must_equal 1
|
509
|
+
check_sql_array("INSERT INTO artists (name) VALUES ('Ar')",
|
510
|
+
["INSERT INTO albums (artist_id, name) VALUES (1, 'Al')", "INSERT INTO albums (name, artist_id) VALUES ('Al', 1)"])
|
511
|
+
end
|
512
|
+
|
495
513
|
it "should not save if nested attribute is not valid and should include nested attribute validation errors in the main object's validation errors" do
|
496
514
|
@Artist.class_eval do
|
497
515
|
def validate
|
@@ -189,6 +189,68 @@ describe Sequel::Model, "associate" do
|
|
189
189
|
o.associations.must_equal(:c=>1)
|
190
190
|
end
|
191
191
|
|
192
|
+
it "should not autoreload associations when the current foreign key value is nil" do
|
193
|
+
c = Class.new(Sequel::Model(Sequel::Model.db[:c]))
|
194
|
+
c.many_to_one :c
|
195
|
+
o = c.new
|
196
|
+
o.associations[:c] = 1
|
197
|
+
o[:c_id] = 2
|
198
|
+
o.associations[:c].must_equal 1
|
199
|
+
|
200
|
+
o = c.load({})
|
201
|
+
o.associations[:c] = 1
|
202
|
+
o[:c_id] = 2
|
203
|
+
o.associations[:c].must_equal 1
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should autoreload associations when the current foreign key is nil and the current associated value is nil" do
|
207
|
+
c = Class.new(Sequel::Model(Sequel::Model.db[:c]))
|
208
|
+
c.many_to_one :c
|
209
|
+
o = c.new
|
210
|
+
o.associations[:c] = nil
|
211
|
+
o[:c_id] = 2
|
212
|
+
o.associations.must_be_empty
|
213
|
+
|
214
|
+
o = c.load({})
|
215
|
+
o.associations[:c] = nil
|
216
|
+
o[:c_id] = 2
|
217
|
+
o.associations.must_be_empty
|
218
|
+
end
|
219
|
+
|
220
|
+
it "should handle autoreloading for multiple associations when the current foreign key is nil" do
|
221
|
+
c = Class.new(Sequel::Model(Sequel::Model.db[:c]))
|
222
|
+
c.many_to_one :c
|
223
|
+
c.many_to_one :d, :key=>:c_id
|
224
|
+
o = c.new
|
225
|
+
o.associations[:c] = nil
|
226
|
+
o.associations[:d] = 1
|
227
|
+
o[:c_id] = nil
|
228
|
+
o.associations.must_equal(:c=>nil, :d=>1)
|
229
|
+
|
230
|
+
o[:c_id] = 2
|
231
|
+
o.associations.must_equal(:d=>1)
|
232
|
+
|
233
|
+
o[:c_id] = 2
|
234
|
+
o.associations.must_equal(:d=>1)
|
235
|
+
|
236
|
+
o[:c_id] = nil
|
237
|
+
o.associations.must_be_empty
|
238
|
+
|
239
|
+
o = c.load({:c_id=>nil})
|
240
|
+
o.associations[:c] = nil
|
241
|
+
o.associations[:d] = 1
|
242
|
+
o[:c_id] = nil
|
243
|
+
o.associations.must_equal(:c=>nil, :d=>1)
|
244
|
+
|
245
|
+
o[:c_id] = 2
|
246
|
+
o.associations.must_equal(:d=>1)
|
247
|
+
|
248
|
+
o[:c_id] = 2
|
249
|
+
o.associations.must_equal(:d=>1)
|
250
|
+
|
251
|
+
o[:c_id] = nil
|
252
|
+
o.associations.must_be_empty
|
253
|
+
end
|
192
254
|
end
|
193
255
|
|
194
256
|
describe Sequel::Model, "many_to_one" do
|
@@ -170,6 +170,33 @@ describe "Model#save" do
|
|
170
170
|
DB.sqls.must_equal ['UPDATE people SET x = 6 WHERE (id = 4)']
|
171
171
|
end
|
172
172
|
|
173
|
+
it "should skip validations if the skip_validation_on_save! method is used" do
|
174
|
+
@m.raise_on_save_failure = false
|
175
|
+
@m.wont_be :valid?
|
176
|
+
@m.skip_validation_on_next_save!
|
177
|
+
@m.save
|
178
|
+
DB.sqls.must_equal ['UPDATE people SET x = 6 WHERE (id = 4)']
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should not skip future validations if the skip_validation_on_save! method is used" do
|
182
|
+
@m.wont_be :valid?
|
183
|
+
@m.skip_validation_on_next_save!
|
184
|
+
@m.save
|
185
|
+
DB.sqls.must_equal ['UPDATE people SET x = 6 WHERE (id = 4)']
|
186
|
+
proc{@m.save}.must_raise Sequel::ValidationFailed
|
187
|
+
|
188
|
+
@m.skip_validation_on_next_save!
|
189
|
+
@m.save
|
190
|
+
DB.sqls.must_equal ['UPDATE people SET x = 6 WHERE (id = 4)']
|
191
|
+
end
|
192
|
+
|
193
|
+
it "should skip validations if the skip_validation_on_save! method is used and :validate=>true option is used" do
|
194
|
+
@m.wont_be :valid?
|
195
|
+
@m.skip_validation_on_next_save!
|
196
|
+
@m.save(:validate=>true)
|
197
|
+
DB.sqls.must_equal ['UPDATE people SET x = 6 WHERE (id = 4)']
|
198
|
+
end
|
199
|
+
|
173
200
|
it "should raise error if validations fail and raise_on_save_failure is true" do
|
174
201
|
begin
|
175
202
|
@m.save
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -131,8 +131,8 @@ extra_rdoc_files:
|
|
131
131
|
- doc/schema_modification.rdoc
|
132
132
|
- doc/sharding.rdoc
|
133
133
|
- doc/testing.rdoc
|
134
|
-
- doc/transactions.rdoc
|
135
134
|
- doc/validations.rdoc
|
135
|
+
- doc/transactions.rdoc
|
136
136
|
- doc/release_notes/4.0.0.txt
|
137
137
|
- doc/release_notes/4.1.0.txt
|
138
138
|
- doc/release_notes/4.10.0.txt
|
@@ -198,6 +198,7 @@ extra_rdoc_files:
|
|
198
198
|
- doc/release_notes/5.12.0.txt
|
199
199
|
- doc/release_notes/5.13.0.txt
|
200
200
|
- doc/release_notes/5.14.0.txt
|
201
|
+
- doc/release_notes/5.15.0.txt
|
201
202
|
files:
|
202
203
|
- CHANGELOG
|
203
204
|
- MIT-LICENSE
|
@@ -282,6 +283,7 @@ files:
|
|
282
283
|
- doc/release_notes/5.12.0.txt
|
283
284
|
- doc/release_notes/5.13.0.txt
|
284
285
|
- doc/release_notes/5.14.0.txt
|
286
|
+
- doc/release_notes/5.15.0.txt
|
285
287
|
- doc/release_notes/5.2.0.txt
|
286
288
|
- doc/release_notes/5.3.0.txt
|
287
289
|
- doc/release_notes/5.4.0.txt
|