sequel 5.14.0 → 5.15.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|