sequel 5.19.0 → 5.24.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 +102 -0
- data/doc/dataset_filtering.rdoc +15 -0
- data/doc/opening_databases.rdoc +5 -1
- data/doc/release_notes/5.20.0.txt +89 -0
- data/doc/release_notes/5.21.0.txt +87 -0
- data/doc/release_notes/5.22.0.txt +48 -0
- data/doc/release_notes/5.23.0.txt +56 -0
- data/doc/release_notes/5.24.0.txt +56 -0
- data/doc/sharding.rdoc +2 -0
- data/doc/testing.rdoc +1 -0
- data/doc/transactions.rdoc +38 -0
- data/lib/sequel/adapters/ado.rb +27 -19
- data/lib/sequel/adapters/jdbc.rb +7 -1
- data/lib/sequel/adapters/jdbc/mysql.rb +2 -2
- data/lib/sequel/adapters/jdbc/postgresql.rb +1 -13
- data/lib/sequel/adapters/jdbc/sqlite.rb +29 -0
- data/lib/sequel/adapters/mysql2.rb +2 -3
- data/lib/sequel/adapters/shared/mssql.rb +7 -7
- data/lib/sequel/adapters/shared/postgres.rb +37 -19
- data/lib/sequel/adapters/shared/sqlite.rb +27 -3
- data/lib/sequel/adapters/sqlite.rb +1 -1
- data/lib/sequel/adapters/tinytds.rb +12 -0
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +2 -0
- data/lib/sequel/database/logging.rb +7 -1
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +12 -3
- data/lib/sequel/database/schema_methods.rb +2 -0
- data/lib/sequel/database/transactions.rb +57 -5
- data/lib/sequel/dataset.rb +4 -2
- data/lib/sequel/dataset/actions.rb +3 -2
- data/lib/sequel/dataset/placeholder_literalizer.rb +4 -1
- data/lib/sequel/dataset/query.rb +5 -1
- data/lib/sequel/dataset/sql.rb +11 -7
- data/lib/sequel/extensions/named_timezones.rb +52 -8
- data/lib/sequel/extensions/pg_array.rb +4 -0
- data/lib/sequel/extensions/pg_json.rb +387 -123
- data/lib/sequel/extensions/pg_range.rb +3 -2
- data/lib/sequel/extensions/pg_row.rb +3 -1
- data/lib/sequel/extensions/schema_dumper.rb +1 -1
- data/lib/sequel/extensions/server_block.rb +15 -4
- data/lib/sequel/model/associations.rb +35 -9
- data/lib/sequel/model/plugins.rb +104 -0
- data/lib/sequel/plugins/association_dependencies.rb +3 -3
- data/lib/sequel/plugins/association_pks.rb +14 -4
- data/lib/sequel/plugins/association_proxies.rb +3 -2
- data/lib/sequel/plugins/class_table_inheritance.rb +11 -0
- data/lib/sequel/plugins/composition.rb +13 -9
- data/lib/sequel/plugins/finder.rb +2 -2
- data/lib/sequel/plugins/hook_class_methods.rb +17 -5
- data/lib/sequel/plugins/insert_conflict.rb +72 -0
- data/lib/sequel/plugins/inverted_subsets.rb +2 -2
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +147 -59
- data/lib/sequel/plugins/rcte_tree.rb +6 -0
- data/lib/sequel/plugins/static_cache.rb +8 -3
- data/lib/sequel/plugins/static_cache_cache.rb +53 -0
- data/lib/sequel/plugins/subset_conditions.rb +2 -2
- data/lib/sequel/plugins/validation_class_methods.rb +5 -3
- data/lib/sequel/sql.rb +15 -3
- data/lib/sequel/timezones.rb +50 -11
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +24 -0
- data/spec/adapters/mysql_spec.rb +0 -5
- data/spec/adapters/postgres_spec.rb +319 -1
- data/spec/bin_spec.rb +1 -1
- data/spec/core/database_spec.rb +123 -2
- data/spec/core/dataset_spec.rb +33 -1
- data/spec/core/expression_filters_spec.rb +25 -1
- data/spec/core/schema_spec.rb +24 -0
- data/spec/extensions/class_table_inheritance_spec.rb +30 -8
- data/spec/extensions/core_refinements_spec.rb +1 -1
- data/spec/extensions/hook_class_methods_spec.rb +22 -0
- data/spec/extensions/insert_conflict_spec.rb +103 -0
- data/spec/extensions/migration_spec.rb +13 -0
- data/spec/extensions/named_timezones_spec.rb +109 -2
- data/spec/extensions/pg_auto_constraint_validations_spec.rb +45 -0
- data/spec/extensions/pg_json_spec.rb +218 -29
- data/spec/extensions/pg_range_spec.rb +76 -9
- data/spec/extensions/rcte_tree_spec.rb +6 -0
- data/spec/extensions/s_spec.rb +1 -1
- data/spec/extensions/schema_dumper_spec.rb +4 -2
- data/spec/extensions/server_block_spec.rb +38 -0
- data/spec/extensions/spec_helper.rb +8 -1
- data/spec/extensions/static_cache_cache_spec.rb +35 -0
- data/spec/integration/dataset_test.rb +25 -9
- data/spec/integration/plugin_test.rb +42 -0
- data/spec/integration/schema_test.rb +7 -2
- data/spec/integration/transaction_test.rb +50 -0
- data/spec/model/associations_spec.rb +84 -4
- data/spec/model/plugins_spec.rb +111 -0
- metadata +16 -2
|
@@ -453,13 +453,14 @@ module Sequel
|
|
|
453
453
|
end
|
|
454
454
|
|
|
455
455
|
ENDLESS_RANGE_NOT_SUPPORTED = RUBY_VERSION < '2.6'
|
|
456
|
+
STARTLESS_RANGE_NOT_SUPPORTED = RUBY_VERSION < '2.7'
|
|
456
457
|
|
|
457
458
|
# Return a ruby Range object for this instance, if one can be created.
|
|
458
459
|
def to_range
|
|
459
460
|
return @range if @range
|
|
460
461
|
raise(Error, "cannot create ruby range for an empty PostgreSQL range") if empty?
|
|
461
462
|
raise(Error, "cannot create ruby range when PostgreSQL range excludes beginning element") if exclude_begin?
|
|
462
|
-
raise(Error, "cannot create ruby range when PostgreSQL range has unbounded beginning")
|
|
463
|
+
raise(Error, "cannot create ruby range when PostgreSQL range has unbounded beginning") if STARTLESS_RANGE_NOT_SUPPORTED && !self.begin
|
|
463
464
|
raise(Error, "cannot create ruby range when PostgreSQL range has unbounded ending") if ENDLESS_RANGE_NOT_SUPPORTED && !self.end
|
|
464
465
|
@range = Range.new(self.begin, self.end, exclude_end?)
|
|
465
466
|
end
|
|
@@ -468,7 +469,7 @@ module Sequel
|
|
|
468
469
|
# it must have a beginning and an ending (no unbounded ranges), and it cannot exclude
|
|
469
470
|
# the beginning element.
|
|
470
471
|
def valid_ruby_range?
|
|
471
|
-
!(empty? || exclude_begin? || !self.begin || (ENDLESS_RANGE_NOT_SUPPORTED && !self.end))
|
|
472
|
+
!(empty? || exclude_begin? || (STARTLESS_RANGE_NOT_SUPPORTED && !self.begin) || (ENDLESS_RANGE_NOT_SUPPORTED && !self.end))
|
|
472
473
|
end
|
|
473
474
|
|
|
474
475
|
# Whether the beginning of the range is unbounded.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# that when composite fields are retrieved, they are parsed and returned
|
|
8
8
|
# as instances of Sequel::Postgres::PGRow::(HashRow|ArrayRow), or
|
|
9
9
|
# optionally a custom type. HashRow and ArrayRow are DelegateClasses of
|
|
10
|
-
#
|
|
10
|
+
# Hash and Array, so they mostly act like a hash or array, but not
|
|
11
11
|
# completely (is_a?(Hash) and is_a?(Array) are false). If you want the
|
|
12
12
|
# actual hash for a HashRow, call HashRow#to_hash, and if you want the
|
|
13
13
|
# actual array for an ArrayRow, call ArrayRow#to_a. This is done so
|
|
@@ -228,7 +228,9 @@ module Sequel
|
|
|
228
228
|
if skip(/\)/)
|
|
229
229
|
values << nil
|
|
230
230
|
else
|
|
231
|
+
# :nocov:
|
|
231
232
|
until eos?
|
|
233
|
+
# :nocov:
|
|
232
234
|
if skip(/"/)
|
|
233
235
|
values << scan(/(\\.|""|[^"])*/).gsub(/\\(.)|"(")/, '\1\2')
|
|
234
236
|
skip(/"[,)]/)
|
|
@@ -37,7 +37,7 @@ module Sequel
|
|
|
37
37
|
{:type =>schema[:type] == :boolean ? TrueClass : Integer}
|
|
38
38
|
when /\Abigint(?:\((?:\d+)\))?(?: unsigned)?\z/
|
|
39
39
|
{:type=>:Bignum}
|
|
40
|
-
when /\A(?:real|float
|
|
40
|
+
when /\A(?:real|float(?: unsigned)?|double(?: precision)?|double\(\d+,\d+\)(?: unsigned)?)\z/
|
|
41
41
|
{:type=>Float}
|
|
42
42
|
when 'boolean', 'bit', 'bool'
|
|
43
43
|
{:type=>TrueClass}
|
|
@@ -52,6 +52,12 @@
|
|
|
52
52
|
# DB[:a].server(:read_only).delete # Uses shard2
|
|
53
53
|
# end
|
|
54
54
|
#
|
|
55
|
+
# If you use an invalid server when calling with_server, it will be
|
|
56
|
+
# treated the same way as if you called Dataset#server with an invalid
|
|
57
|
+
# server. By default, the default server will be used in such cases.
|
|
58
|
+
# If you would like a different server to be used, or an exception to
|
|
59
|
+
# be raised, then use the :servers_hash Database option.
|
|
60
|
+
#
|
|
55
61
|
# Related modules: Sequel::ServerBlock, Sequel::UnthreadedServerBlock,
|
|
56
62
|
# Sequel::ThreadedServerBlock
|
|
57
63
|
|
|
@@ -110,9 +116,9 @@ module Sequel
|
|
|
110
116
|
else
|
|
111
117
|
case server
|
|
112
118
|
when :default, nil
|
|
113
|
-
@default_servers[-1][0]
|
|
119
|
+
@servers[@default_servers[-1][0]]
|
|
114
120
|
when :read_only
|
|
115
|
-
@default_servers[-1][1]
|
|
121
|
+
@servers[@default_servers[-1][1]]
|
|
116
122
|
else
|
|
117
123
|
super
|
|
118
124
|
end
|
|
@@ -155,11 +161,16 @@ module Sequel
|
|
|
155
161
|
if !a || a.empty?
|
|
156
162
|
super
|
|
157
163
|
else
|
|
164
|
+
# Hash handling required to work when loaded after arbitrary servers plugin.
|
|
158
165
|
case server
|
|
159
166
|
when :default, nil
|
|
160
|
-
a[-1][0]
|
|
167
|
+
v = a[-1][0]
|
|
168
|
+
v = @servers[v] unless v.is_a?(Hash)
|
|
169
|
+
v
|
|
161
170
|
when :read_only
|
|
162
|
-
a[-1][1]
|
|
171
|
+
v = a[-1][1]
|
|
172
|
+
v = @servers[v] unless v.is_a?(Hash)
|
|
173
|
+
v
|
|
163
174
|
else
|
|
164
175
|
super
|
|
165
176
|
end
|
|
@@ -1617,9 +1617,10 @@ module Sequel
|
|
|
1617
1617
|
# is hash or array of two element arrays. Consider also specifying the :graph_block
|
|
1618
1618
|
# option if the value for this option is not a hash or array of two element arrays
|
|
1619
1619
|
# and you plan to use this association in eager_graph or association_join.
|
|
1620
|
-
# :dataset :: A proc that is
|
|
1620
|
+
# :dataset :: A proc that is used to define the method to get the base dataset to use (before the other
|
|
1621
1621
|
# options are applied). If the proc accepts an argument, it is passed the related
|
|
1622
|
-
# association reflection.
|
|
1622
|
+
# association reflection. It is a best practice to always have the dataset accept an argument
|
|
1623
|
+
# and use the argument to return the appropriate dataset.
|
|
1623
1624
|
# :distinct :: Use the DISTINCT clause when selecting associating object, both when
|
|
1624
1625
|
# lazy loading and eager loading via .eager (but not when using .eager_graph).
|
|
1625
1626
|
# :eager :: The associations to eagerly load via +eager+ when loading the associated object(s).
|
|
@@ -1909,7 +1910,7 @@ module Sequel
|
|
|
1909
1910
|
# can be easily overridden in the class itself while allowing for
|
|
1910
1911
|
# super to be called.
|
|
1911
1912
|
def association_module_def(name, opts=OPTS, &block)
|
|
1912
|
-
association_module(opts).
|
|
1913
|
+
association_module(opts).send(:define_method, name, &block)
|
|
1913
1914
|
end
|
|
1914
1915
|
|
|
1915
1916
|
# Add a private method to the module included in the class.
|
|
@@ -1944,6 +1945,13 @@ module Sequel
|
|
|
1944
1945
|
end
|
|
1945
1946
|
|
|
1946
1947
|
association_module_def(opts.dataset_method, opts){_dataset(opts)}
|
|
1948
|
+
if opts[:block]
|
|
1949
|
+
opts[:block_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_block", 1, &opts[:block])
|
|
1950
|
+
end
|
|
1951
|
+
if opts[:dataset]
|
|
1952
|
+
opts[:dataset_opt_arity] = opts[:dataset].arity == 0 ? 0 : 1
|
|
1953
|
+
opts[:dataset_opt_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_dataset_opt", opts[:dataset_opt_arity], &opts[:dataset])
|
|
1954
|
+
end
|
|
1947
1955
|
def_association_method(opts)
|
|
1948
1956
|
|
|
1949
1957
|
return if opts[:read_only]
|
|
@@ -2204,12 +2212,28 @@ module Sequel
|
|
|
2204
2212
|
if one_to_one
|
|
2205
2213
|
opts[:setter] ||= proc do |o|
|
|
2206
2214
|
up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
|
|
2215
|
+
|
|
2216
|
+
if (froms = up_ds.opts[:from]) && (from = froms[0]) && (from.is_a?(Sequel::Dataset) || (from.is_a?(Sequel::SQL::AliasedExpression) && from.expression.is_a?(Sequel::Dataset)))
|
|
2217
|
+
if old = up_ds.first
|
|
2218
|
+
cks.each{|k| old.set_column_value(:"#{k}=", nil)}
|
|
2219
|
+
end
|
|
2220
|
+
save_old = true
|
|
2221
|
+
end
|
|
2222
|
+
|
|
2207
2223
|
if o
|
|
2208
|
-
|
|
2224
|
+
if !o.new? && !save_old
|
|
2225
|
+
up_ds = up_ds.exclude(o.pk_hash)
|
|
2226
|
+
end
|
|
2209
2227
|
cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
|
|
2210
2228
|
end
|
|
2229
|
+
|
|
2211
2230
|
checked_transaction do
|
|
2212
|
-
|
|
2231
|
+
if save_old
|
|
2232
|
+
old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
|
|
2233
|
+
else
|
|
2234
|
+
up_ds.skip_limit_check.update(ck_nil_hash)
|
|
2235
|
+
end
|
|
2236
|
+
|
|
2213
2237
|
o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
|
|
2214
2238
|
end
|
|
2215
2239
|
end
|
|
@@ -2287,7 +2311,8 @@ module Sequel
|
|
|
2287
2311
|
end
|
|
2288
2312
|
ds = ds.clone(:model_object => self)
|
|
2289
2313
|
ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph] && opts.eager_graph_lazy_dataset?
|
|
2290
|
-
|
|
2314
|
+
# block method is private
|
|
2315
|
+
ds = send(opts[:block_method], ds) if opts[:block_method]
|
|
2291
2316
|
ds
|
|
2292
2317
|
end
|
|
2293
2318
|
|
|
@@ -2310,10 +2335,11 @@ module Sequel
|
|
|
2310
2335
|
# Return an association dataset for the given association reflection
|
|
2311
2336
|
def _dataset(opts)
|
|
2312
2337
|
raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
|
|
2313
|
-
ds = if opts[:
|
|
2314
|
-
|
|
2338
|
+
ds = if opts[:dataset_opt_arity] == 1
|
|
2339
|
+
# dataset_opt_method is private
|
|
2340
|
+
send(opts[:dataset_opt_method], opts)
|
|
2315
2341
|
else
|
|
2316
|
-
|
|
2342
|
+
send(opts[:dataset_opt_method])
|
|
2317
2343
|
end
|
|
2318
2344
|
_apply_association_options(opts, ds)
|
|
2319
2345
|
end
|
data/lib/sequel/model/plugins.rb
CHANGED
|
@@ -51,5 +51,109 @@ module Sequel
|
|
|
51
51
|
r
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
|
+
|
|
55
|
+
method_num = 0
|
|
56
|
+
method_num_mutex = Mutex.new
|
|
57
|
+
# Return a unique method name symbol for the given suffix.
|
|
58
|
+
SEQUEL_METHOD_NAME = lambda do |suffix|
|
|
59
|
+
:"_sequel_#{suffix}_#{method_num_mutex.synchronize{method_num += 1}}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Define a private instance method using the block with the provided name and
|
|
63
|
+
# expected arity. If the name is given as a Symbol, it is used directly.
|
|
64
|
+
# If the name is given as a String, a unique name will be generated using
|
|
65
|
+
# that string. The expected_arity should be either 0 (no arguments) or
|
|
66
|
+
# 1 (single argument).
|
|
67
|
+
#
|
|
68
|
+
# If a block with an arity that does not match the expected arity is used,
|
|
69
|
+
# a deprecation warning will be issued. The method defined should still
|
|
70
|
+
# work, though it will be slower than a method with the expected arity.
|
|
71
|
+
#
|
|
72
|
+
# Sequel only checks arity for regular blocks, not lambdas. Lambdas were
|
|
73
|
+
# already strict in regards to arity, so there is no need to try to fix
|
|
74
|
+
# arity to keep backwards compatibility for lambdas.
|
|
75
|
+
#
|
|
76
|
+
# Blocks with required keyword arguments are not supported by this method.
|
|
77
|
+
def self.def_sequel_method(model, meth, expected_arity, &block)
|
|
78
|
+
if meth.is_a?(String)
|
|
79
|
+
meth = SEQUEL_METHOD_NAME.call(meth)
|
|
80
|
+
end
|
|
81
|
+
call_meth = meth
|
|
82
|
+
|
|
83
|
+
unless block.lambda?
|
|
84
|
+
required_args, optional_args, rest, keyword = _define_sequel_method_arg_numbers(block)
|
|
85
|
+
|
|
86
|
+
if keyword == :required
|
|
87
|
+
raise Error, "cannot use block with required keyword arguments when calling define_sequel_method with expected arity #{expected_arity}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
case expected_arity
|
|
91
|
+
when 0
|
|
92
|
+
unless required_args == 0
|
|
93
|
+
# SEQUEL6: remove
|
|
94
|
+
Sequel::Deprecation.deprecate("Arity mismatch in block passed to define_sequel_method. Expected Arity 0, but arguments required for #{block.inspect}. Support for this will be removed in Sequel 6.")
|
|
95
|
+
b = block
|
|
96
|
+
block = lambda{instance_exec(&b)} # Fallback
|
|
97
|
+
end
|
|
98
|
+
when 1
|
|
99
|
+
if required_args == 0 && optional_args == 0 && !rest
|
|
100
|
+
# SEQUEL6: remove
|
|
101
|
+
Sequel::Deprecation.deprecate("Arity mismatch in block passed to define_sequel_method. Expected Arity 1, but no arguments accepted for #{block.inspect}. Support for this will be removed in Sequel 6.")
|
|
102
|
+
temp_method = SEQUEL_METHOD_NAME.call("temp")
|
|
103
|
+
model.class_eval("def #{temp_method}(_) #{meth =~ /\A\w+\z/ ? "#{meth}_arity" : "send(:\"#{meth}_arity\")"} end", __FILE__, __LINE__)
|
|
104
|
+
model.send(:alias_method, meth, temp_method)
|
|
105
|
+
model.send(:undef_method, temp_method)
|
|
106
|
+
model.send(:private, meth)
|
|
107
|
+
meth = :"#{meth}_arity"
|
|
108
|
+
elsif required_args > 1
|
|
109
|
+
# SEQUEL6: remove
|
|
110
|
+
Sequel::Deprecation.deprecate("Arity mismatch in block passed to define_sequel_method. Expected Arity 1, but more arguments required for #{block.inspect}. Support for this will be removed in Sequel 6.")
|
|
111
|
+
b = block
|
|
112
|
+
block = lambda{|r| instance_exec(r, &b)} # Fallback
|
|
113
|
+
end
|
|
114
|
+
else
|
|
115
|
+
raise Error, "unexpected arity passed to define_sequel_method: #{expected_arity.inspect}"
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
model.send(:define_method, meth, &block)
|
|
120
|
+
model.send(:private, meth)
|
|
121
|
+
call_meth
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Return the number of required argument, optional arguments,
|
|
125
|
+
# whether the callable accepts any additional arguments,
|
|
126
|
+
# and whether the callable accepts keyword arguments (true, false
|
|
127
|
+
# or :required).
|
|
128
|
+
def self._define_sequel_method_arg_numbers(callable)
|
|
129
|
+
optional_args = 0
|
|
130
|
+
rest = false
|
|
131
|
+
keyword = false
|
|
132
|
+
callable.parameters.map(&:first).each do |arg_type, _|
|
|
133
|
+
case arg_type
|
|
134
|
+
when :opt
|
|
135
|
+
optional_args += 1
|
|
136
|
+
when :rest
|
|
137
|
+
rest = true
|
|
138
|
+
when :keyreq
|
|
139
|
+
keyword = :required
|
|
140
|
+
when :key, :keyrest
|
|
141
|
+
keyword ||= true
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
arity = callable.arity
|
|
145
|
+
if arity < 0
|
|
146
|
+
arity = arity.abs - 1
|
|
147
|
+
end
|
|
148
|
+
required_args = arity
|
|
149
|
+
arity -= 1 if keyword == :required
|
|
150
|
+
|
|
151
|
+
if callable.is_a?(Proc) && !callable.lambda?
|
|
152
|
+
optional_args -= arity
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
[required_args, optional_args, rest, keyword]
|
|
156
|
+
end
|
|
157
|
+
private_class_method :_define_sequel_method_arg_numbers
|
|
54
158
|
end
|
|
55
159
|
end
|
|
@@ -60,9 +60,9 @@ module Sequel
|
|
|
60
60
|
association_dependencies[:"#{time}_#{action}"] << if action == :nullify
|
|
61
61
|
case type
|
|
62
62
|
when :one_to_many , :many_to_many
|
|
63
|
-
|
|
63
|
+
[r[:remove_all_method]]
|
|
64
64
|
when :one_to_one
|
|
65
|
-
|
|
65
|
+
[r[:setter_method], nil]
|
|
66
66
|
else
|
|
67
67
|
raise(Error, "Can't nullify many_to_one associated objects: association: #{association}")
|
|
68
68
|
end
|
|
@@ -97,7 +97,7 @@ module Sequel
|
|
|
97
97
|
def before_destroy
|
|
98
98
|
model.association_dependencies[:before_delete].each{|m| public_send(m).delete}
|
|
99
99
|
model.association_dependencies[:before_destroy].each{|m| public_send(m).destroy}
|
|
100
|
-
model.association_dependencies[:before_nullify].each{|
|
|
100
|
+
model.association_dependencies[:before_nullify].each{|args| public_send(*args)}
|
|
101
101
|
super
|
|
102
102
|
end
|
|
103
103
|
end
|
|
@@ -60,8 +60,15 @@ module Sequel
|
|
|
60
60
|
|
|
61
61
|
# Define a association_pks method using the block for the association reflection
|
|
62
62
|
def def_association_pks_methods(opts)
|
|
63
|
+
opts[:pks_getter_method] = :"#{singularize(opts[:name])}_pks_getter"
|
|
64
|
+
association_module_def(opts[:pks_getter_method], &opts[:pks_getter])
|
|
63
65
|
association_module_def(:"#{singularize(opts[:name])}_pks", opts){_association_pks_getter(opts)}
|
|
64
|
-
|
|
66
|
+
|
|
67
|
+
if opts[:pks_setter]
|
|
68
|
+
opts[:pks_setter_method] = :"#{singularize(opts[:name])}_pks_setter"
|
|
69
|
+
association_module_def(opts[:pks_setter_method], &opts[:pks_setter])
|
|
70
|
+
association_module_def(:"#{singularize(opts[:name])}_pks=", opts){|pks| _association_pks_setter(opts, pks)}
|
|
71
|
+
end
|
|
65
72
|
end
|
|
66
73
|
|
|
67
74
|
# Add a getter that checks the join table for matching records and
|
|
@@ -181,7 +188,8 @@ module Sequel
|
|
|
181
188
|
def after_save
|
|
182
189
|
if assoc_pks = @_association_pks
|
|
183
190
|
assoc_pks.each do |name, pks|
|
|
184
|
-
|
|
191
|
+
# pks_setter_method is private
|
|
192
|
+
send(model.association_reflection(name)[:pks_setter_method], pks)
|
|
185
193
|
end
|
|
186
194
|
@_association_pks = nil
|
|
187
195
|
end
|
|
@@ -206,7 +214,8 @@ module Sequel
|
|
|
206
214
|
elsif delay && @_association_pks && (objs = @_association_pks[opts[:name]])
|
|
207
215
|
objs
|
|
208
216
|
else
|
|
209
|
-
|
|
217
|
+
# pks_getter_method is private
|
|
218
|
+
send(opts[:pks_getter_method])
|
|
210
219
|
end
|
|
211
220
|
end
|
|
212
221
|
|
|
@@ -231,7 +240,8 @@ module Sequel
|
|
|
231
240
|
modified!
|
|
232
241
|
(@_association_pks ||= {})[opts[:name]] = pks
|
|
233
242
|
else
|
|
234
|
-
|
|
243
|
+
# pks_setter_method is private
|
|
244
|
+
send(opts[:pks_setter_method], pks)
|
|
235
245
|
end
|
|
236
246
|
end
|
|
237
247
|
|
|
@@ -64,6 +64,8 @@ module Sequel
|
|
|
64
64
|
array = [].freeze
|
|
65
65
|
|
|
66
66
|
if RUBY_VERSION < '2.6'
|
|
67
|
+
# :nocov:
|
|
68
|
+
|
|
67
69
|
# Default proc used to determine whether to send the method to the dataset.
|
|
68
70
|
# If the array would respond to it, sends it to the array instead of the dataset.
|
|
69
71
|
DEFAULT_PROXY_TO_DATASET = proc do |opts|
|
|
@@ -73,10 +75,9 @@ module Sequel
|
|
|
73
75
|
end
|
|
74
76
|
!array_method
|
|
75
77
|
end
|
|
76
|
-
else
|
|
77
78
|
# :nocov:
|
|
79
|
+
else
|
|
78
80
|
DEFAULT_PROXY_TO_DATASET = proc{|opts| !array.respond_to?(opts[:method])}
|
|
79
|
-
# :nocov:
|
|
80
81
|
end
|
|
81
82
|
|
|
82
83
|
# Set the association reflection to use, and whether the association should be
|
|
@@ -108,6 +108,16 @@ module Sequel
|
|
|
108
108
|
# a = Executive.where{{employees[:id]=>1}}.first # works
|
|
109
109
|
# a = Executive.where{{executives[:id]=>1}}.first # doesn't work
|
|
110
110
|
#
|
|
111
|
+
# Note that because subclass datasets select from a subquery, you cannot update,
|
|
112
|
+
# delete, or insert into them directly. To delete related rows, you need to go
|
|
113
|
+
# through the related tables and remove the related rows. Code that does this would
|
|
114
|
+
# be similar to:
|
|
115
|
+
#
|
|
116
|
+
# pks = Executive.where{num_staff < 10}.select_map(:id)
|
|
117
|
+
# Executive.cti_tables.reverse_each do |table|
|
|
118
|
+
# DB.from(table).where(:id=>pks).delete
|
|
119
|
+
# end
|
|
120
|
+
#
|
|
111
121
|
# = Usage
|
|
112
122
|
#
|
|
113
123
|
# # Use the default of storing the class name in the sti_key
|
|
@@ -326,6 +336,7 @@ module Sequel
|
|
|
326
336
|
cti_tables.reverse_each do |ct|
|
|
327
337
|
db.schema(ct).each{|sk,v| db_schema[sk] = v}
|
|
328
338
|
end
|
|
339
|
+
setup_auto_validations if respond_to?(:setup_auto_validations, true)
|
|
329
340
|
end
|
|
330
341
|
end
|
|
331
342
|
|
|
@@ -32,9 +32,10 @@ module Sequel
|
|
|
32
32
|
#
|
|
33
33
|
# The :mapping option is just a shortcut that works in particular
|
|
34
34
|
# cases. To handle any case, you can define a custom :composer
|
|
35
|
-
# and :decomposer procs. The :composer
|
|
36
|
-
#
|
|
37
|
-
#
|
|
35
|
+
# and :decomposer procs. The :composer and :decomposer procs will
|
|
36
|
+
# be used to define instance methods. The :composer will be called
|
|
37
|
+
# the first time the getter is called, and the :decomposer
|
|
38
|
+
# will be called before saving. The above example could
|
|
38
39
|
# also be implemented as:
|
|
39
40
|
#
|
|
40
41
|
# Album.composition :date,
|
|
@@ -74,9 +75,9 @@ module Sequel
|
|
|
74
75
|
#
|
|
75
76
|
# Options:
|
|
76
77
|
# :class :: if using the :mapping option, the class to use, as a Class, String or Symbol.
|
|
77
|
-
# :composer :: A proc
|
|
78
|
+
# :composer :: A proc used to define the method that the composition getter method will call
|
|
78
79
|
# to create the composition.
|
|
79
|
-
# :decomposer :: A proc
|
|
80
|
+
# :decomposer :: A proc used to define the method called before saving the model object,
|
|
80
81
|
# if the composition object exists, which sets the columns in the model object
|
|
81
82
|
# based on the value of the composition object.
|
|
82
83
|
# :mapping :: An array where each element is either a symbol or an array of two symbols.
|
|
@@ -129,15 +130,17 @@ module Sequel
|
|
|
129
130
|
|
|
130
131
|
# Define getter and setter methods for the composition object.
|
|
131
132
|
def define_composition_accessor(name, opts=OPTS)
|
|
132
|
-
|
|
133
|
+
composer_meth = opts[:composer_method] = Plugins.def_sequel_method(@composition_module, "#{name}_composer", 0, &opts[:composer])
|
|
134
|
+
opts[:decomposer_method] = Plugins.def_sequel_method(@composition_module, "#{name}_decomposer", 0, &opts[:decomposer])
|
|
133
135
|
@composition_module.class_eval do
|
|
134
136
|
define_method(name) do
|
|
135
137
|
if compositions.has_key?(name)
|
|
136
138
|
compositions[name]
|
|
137
139
|
elsif frozen?
|
|
138
|
-
|
|
140
|
+
# composer_meth is private
|
|
141
|
+
send(composer_meth)
|
|
139
142
|
else
|
|
140
|
-
compositions[name] =
|
|
143
|
+
compositions[name] = send(composer_meth)
|
|
141
144
|
end
|
|
142
145
|
end
|
|
143
146
|
define_method("#{name}=") do |v|
|
|
@@ -171,7 +174,8 @@ module Sequel
|
|
|
171
174
|
# For each composition, set the columns in the model class based
|
|
172
175
|
# on the composition object.
|
|
173
176
|
def before_validation
|
|
174
|
-
|
|
177
|
+
# decomposer_method is private
|
|
178
|
+
@compositions.keys.each{|n| send(model.compositions[n][:decomposer_method])} if @compositions
|
|
175
179
|
super
|
|
176
180
|
end
|
|
177
181
|
|