sequel 5.32.0 → 5.37.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 +84 -0
- data/README.rdoc +1 -1
- data/doc/association_basics.rdoc +7 -2
- data/doc/dataset_filtering.rdoc +2 -2
- data/doc/model_plugins.rdoc +1 -1
- data/doc/release_notes/5.33.0.txt +24 -0
- data/doc/release_notes/5.34.0.txt +40 -0
- data/doc/release_notes/5.35.0.txt +56 -0
- data/doc/release_notes/5.36.0.txt +60 -0
- data/doc/release_notes/5.37.0.txt +30 -0
- data/doc/transactions.rdoc +0 -8
- data/doc/validations.rdoc +1 -1
- data/lib/sequel/adapters/odbc.rb +4 -6
- data/lib/sequel/adapters/oracle.rb +2 -1
- data/lib/sequel/adapters/shared/mssql.rb +14 -4
- data/lib/sequel/adapters/shared/oracle.rb +12 -6
- data/lib/sequel/adapters/shared/postgres.rb +39 -1
- data/lib/sequel/adapters/shared/sqlite.rb +13 -3
- data/lib/sequel/adapters/tinytds.rb +1 -0
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -0
- data/lib/sequel/connection_pool/sharded_single.rb +4 -1
- data/lib/sequel/connection_pool/sharded_threaded.rb +10 -10
- data/lib/sequel/connection_pool/single.rb +1 -1
- data/lib/sequel/connection_pool/threaded.rb +1 -1
- data/lib/sequel/core.rb +5 -6
- data/lib/sequel/database/connecting.rb +1 -1
- data/lib/sequel/database/misc.rb +16 -10
- data/lib/sequel/database/query.rb +2 -0
- data/lib/sequel/database/schema_generator.rb +0 -1
- data/lib/sequel/database/schema_methods.rb +15 -16
- data/lib/sequel/database/transactions.rb +8 -5
- data/lib/sequel/dataset/actions.rb +10 -6
- data/lib/sequel/dataset/placeholder_literalizer.rb +3 -7
- data/lib/sequel/dataset/query.rb +5 -4
- data/lib/sequel/deprecated.rb +3 -1
- data/lib/sequel/exceptions.rb +2 -0
- data/lib/sequel/extensions/_pretty_table.rb +1 -2
- data/lib/sequel/extensions/columns_introspection.rb +1 -2
- data/lib/sequel/extensions/core_refinements.rb +2 -0
- data/lib/sequel/extensions/duplicate_columns_handler.rb +2 -0
- data/lib/sequel/extensions/migration.rb +8 -2
- data/lib/sequel/extensions/pg_array_ops.rb +4 -0
- data/lib/sequel/extensions/pg_enum.rb +2 -0
- data/lib/sequel/extensions/pg_extended_date_support.rb +1 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +2 -0
- data/lib/sequel/extensions/pg_inet.rb +2 -0
- data/lib/sequel/extensions/pg_json_ops.rb +46 -2
- data/lib/sequel/extensions/pg_range.rb +3 -7
- data/lib/sequel/extensions/pg_range_ops.rb +2 -0
- data/lib/sequel/extensions/pg_row.rb +0 -1
- data/lib/sequel/extensions/pg_row_ops.rb +24 -0
- data/lib/sequel/extensions/query.rb +1 -0
- data/lib/sequel/extensions/run_transaction_hooks.rb +1 -1
- data/lib/sequel/extensions/s.rb +2 -0
- data/lib/sequel/extensions/schema_dumper.rb +3 -3
- data/lib/sequel/extensions/symbol_aref_refinement.rb +2 -0
- data/lib/sequel/extensions/symbol_as_refinement.rb +2 -0
- data/lib/sequel/extensions/to_dot.rb +9 -3
- data/lib/sequel/model.rb +3 -1
- data/lib/sequel/model/associations.rb +54 -25
- data/lib/sequel/model/base.rb +13 -5
- data/lib/sequel/model/plugins.rb +3 -3
- data/lib/sequel/plugins/association_pks.rb +60 -18
- data/lib/sequel/plugins/association_proxies.rb +1 -0
- data/lib/sequel/plugins/blacklist_security.rb +1 -2
- data/lib/sequel/plugins/class_table_inheritance.rb +3 -3
- data/lib/sequel/plugins/csv_serializer.rb +2 -0
- data/lib/sequel/plugins/dirty.rb +45 -0
- data/lib/sequel/plugins/forbid_lazy_load.rb +2 -0
- data/lib/sequel/plugins/instance_specific_default.rb +113 -0
- data/lib/sequel/plugins/lazy_attributes.rb +1 -1
- data/lib/sequel/plugins/pg_array_associations.rb +2 -3
- data/lib/sequel/plugins/prepared_statements.rb +5 -11
- data/lib/sequel/plugins/prepared_statements_safe.rb +1 -3
- data/lib/sequel/plugins/rcte_tree.rb +10 -16
- data/lib/sequel/plugins/string_stripper.rb +1 -1
- data/lib/sequel/plugins/validation_class_methods.rb +5 -1
- data/lib/sequel/version.rb +1 -1
- metadata +13 -2
@@ -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|double(?: precision)?|double\(\d+,\d+\))(?: unsigned)?\z/
|
41
41
|
{:type=>Float}
|
42
42
|
when 'boolean', 'bit', 'bool'
|
43
43
|
{:type=>TrueClass}
|
@@ -57,7 +57,7 @@ module Sequel
|
|
57
57
|
{:type=>String, :size=>($1.to_i if $1)}
|
58
58
|
when /\A(?:small)?money\z/
|
59
59
|
{:type=>BigDecimal, :size=>[19,2]}
|
60
|
-
when /\A(?:decimal|numeric|number)(?:\((\d+)(?:,\s*(\d+))?\))?\z/
|
60
|
+
when /\A(?:decimal|numeric|number)(?:\((\d+)(?:,\s*(\d+))?\))?(?: unsigned)?\z/
|
61
61
|
s = [($1.to_i if $1), ($2.to_i if $2)].compact
|
62
62
|
{:type=>BigDecimal, :size=>(s.empty? ? nil : s)}
|
63
63
|
when /\A(?:bytea|(?:tiny|medium|long)?blob|(?:var)?binary)(?:\((\d+)\))?\z/
|
@@ -218,7 +218,7 @@ END_MIG
|
|
218
218
|
gen.foreign_key(name, table, col_opts)
|
219
219
|
else
|
220
220
|
gen.column(name, type, col_opts)
|
221
|
-
if [Integer, :Bignum, Float].include?(type) && schema[:db_type] =~ / unsigned\z/io
|
221
|
+
if [Integer, :Bignum, Float, BigDecimal].include?(type) && schema[:db_type] =~ / unsigned\z/io
|
222
222
|
gen.check(Sequel::SQL::Identifier.new(name) >= 0)
|
223
223
|
end
|
224
224
|
end
|
@@ -53,7 +53,13 @@ module Sequel
|
|
53
53
|
# is given, it is used directly as the node or transition. Otherwise
|
54
54
|
# a node is created for the current object.
|
55
55
|
def dot(label, j=nil)
|
56
|
-
|
56
|
+
label = case label
|
57
|
+
when nil
|
58
|
+
"<nil>"
|
59
|
+
else
|
60
|
+
label.to_s
|
61
|
+
end
|
62
|
+
@dot << "#{j||@i} [label=#{label.inspect}];"
|
57
63
|
end
|
58
64
|
|
59
65
|
# Recursive method that parses all of Sequel's internal datastructures,
|
@@ -61,7 +67,7 @@ module Sequel
|
|
61
67
|
# structure.
|
62
68
|
def v(e, l)
|
63
69
|
@i += 1
|
64
|
-
dot(l, "#{@stack.last} -> #{@i}")
|
70
|
+
dot(l, "#{@stack.last} -> #{@i}")
|
65
71
|
@stack.push(@i)
|
66
72
|
case e
|
67
73
|
when LiteralString
|
@@ -144,7 +150,7 @@ module Sequel
|
|
144
150
|
dot "Dataset"
|
145
151
|
TO_DOT_OPTIONS.each do |k|
|
146
152
|
if val = e.opts[k]
|
147
|
-
v(val, k
|
153
|
+
v(val, k)
|
148
154
|
end
|
149
155
|
end
|
150
156
|
else
|
data/lib/sequel/model.rb
CHANGED
@@ -69,7 +69,9 @@ module Sequel
|
|
69
69
|
require_relative "model/base"
|
70
70
|
require_relative "model/exceptions"
|
71
71
|
require_relative "model/errors"
|
72
|
+
# :nocov:
|
72
73
|
if !defined?(::SEQUEL_NO_ASSOCIATIONS) && !ENV.has_key?('SEQUEL_NO_ASSOCIATIONS')
|
74
|
+
# :nocov:
|
73
75
|
require_relative 'model/associations'
|
74
76
|
plugin Model::Associations
|
75
77
|
end
|
@@ -77,7 +79,7 @@ module Sequel
|
|
77
79
|
def_Model(::Sequel)
|
78
80
|
|
79
81
|
# The setter methods (methods ending with =) that are never allowed
|
80
|
-
# to be called automatically via +set+/+update+/+new+/etc
|
82
|
+
# to be called automatically via +set+/+update+/+new+/etc.
|
81
83
|
RESTRICTED_SETTER_METHODS = instance_methods.map(&:to_s).select{|l| l.end_with?('=')}.freeze
|
82
84
|
end
|
83
85
|
end
|
@@ -164,11 +164,11 @@ module Sequel
|
|
164
164
|
# range to return the object(s) at the correct offset/limit.
|
165
165
|
def apply_ruby_eager_limit_strategy(rows, limit_and_offset = limit_and_offset())
|
166
166
|
name = self[:name]
|
167
|
+
return unless range = slice_range(limit_and_offset)
|
167
168
|
if returns_array?
|
168
|
-
range = slice_range(limit_and_offset)
|
169
169
|
rows.each{|o| o.associations[name] = o.associations[name][range] || []}
|
170
|
-
|
171
|
-
offset =
|
170
|
+
else
|
171
|
+
offset = range.begin
|
172
172
|
rows.each{|o| o.associations[name] = o.associations[name][offset]}
|
173
173
|
end
|
174
174
|
end
|
@@ -356,7 +356,7 @@ module Sequel
|
|
356
356
|
def finalize
|
357
357
|
return unless cache = self[:cache]
|
358
358
|
|
359
|
-
|
359
|
+
finalizer = proc do |meth, key|
|
360
360
|
next if has_key?(key)
|
361
361
|
|
362
362
|
# Allow calling private methods to make sure caching is done appropriately
|
@@ -364,6 +364,13 @@ module Sequel
|
|
364
364
|
self[key] = cache.delete(key) if cache.has_key?(key)
|
365
365
|
end
|
366
366
|
|
367
|
+
finalize_settings.each(&finalizer)
|
368
|
+
|
369
|
+
unless self[:instance_specific]
|
370
|
+
finalizer.call(:associated_eager_dataset, :associated_eager_dataset)
|
371
|
+
finalizer.call(:filter_by_associations_conditions_dataset, :filter_by_associations_conditions_dataset)
|
372
|
+
end
|
373
|
+
|
367
374
|
nil
|
368
375
|
end
|
369
376
|
|
@@ -371,9 +378,7 @@ module Sequel
|
|
371
378
|
FINALIZE_SETTINGS = {
|
372
379
|
:associated_class=>:class,
|
373
380
|
:associated_dataset=>:_dataset,
|
374
|
-
:associated_eager_dataset=>:associated_eager_dataset,
|
375
381
|
:eager_limit_strategy=>:_eager_limit_strategy,
|
376
|
-
:filter_by_associations_conditions_dataset=>:filter_by_associations_conditions_dataset,
|
377
382
|
:placeholder_loader=>:placeholder_loader,
|
378
383
|
:predicate_key=>:predicate_key,
|
379
384
|
:predicate_keys=>:predicate_keys,
|
@@ -432,7 +437,11 @@ module Sequel
|
|
432
437
|
if use_placeholder_loader?
|
433
438
|
cached_fetch(:placeholder_loader) do
|
434
439
|
Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds|
|
435
|
-
ds.where(Sequel.&(*predicate_keys.map{|k| SQL::BooleanExpression.new(:'=', k, pl.arg)}))
|
440
|
+
ds = ds.where(Sequel.&(*predicate_keys.map{|k| SQL::BooleanExpression.new(:'=', k, pl.arg)}))
|
441
|
+
if self[:block]
|
442
|
+
ds = self[:block].call(ds)
|
443
|
+
end
|
444
|
+
ds
|
436
445
|
end
|
437
446
|
end
|
438
447
|
end
|
@@ -796,7 +805,7 @@ module Sequel
|
|
796
805
|
|
797
806
|
# Whether the placeholder loader can be used to load the association.
|
798
807
|
def use_placeholder_loader?
|
799
|
-
|
808
|
+
self[:use_placeholder_loader]
|
800
809
|
end
|
801
810
|
end
|
802
811
|
|
@@ -1244,7 +1253,9 @@ module Sequel
|
|
1244
1253
|
else
|
1245
1254
|
assoc_record.values.delete(left_key_alias)
|
1246
1255
|
end
|
1247
|
-
|
1256
|
+
|
1257
|
+
objects = h[hash_key]
|
1258
|
+
|
1248
1259
|
if assign_singular
|
1249
1260
|
objects.each do |object|
|
1250
1261
|
object.associations[name] ||= assoc_record
|
@@ -1791,11 +1802,12 @@ module Sequel
|
|
1791
1802
|
opts.merge!(:type => type, :name => name, :cache=>({} if cache_associations), :model => self)
|
1792
1803
|
|
1793
1804
|
opts[:block] = block if block
|
1794
|
-
|
1805
|
+
opts[:instance_specific] = true if orig_opts[:dataset]
|
1806
|
+
if !opts.has_key?(:instance_specific) && (block || orig_opts[:block])
|
1795
1807
|
# It's possible the association is instance specific, in that it depends on
|
1796
1808
|
# values other than the foreign key value. This needs to be checked for
|
1797
1809
|
# in certain places to disable optimizations.
|
1798
|
-
opts[:instance_specific] =
|
1810
|
+
opts[:instance_specific] = _association_instance_specific_default(name)
|
1799
1811
|
end
|
1800
1812
|
opts = assoc_class.new.merge!(opts)
|
1801
1813
|
|
@@ -1803,6 +1815,7 @@ module Sequel
|
|
1803
1815
|
raise(Error, "cannot clone an association to an association of different type (association #{name} with type #{type} cloning #{opts[:clone]} with type #{cloned_assoc[:type]})")
|
1804
1816
|
end
|
1805
1817
|
|
1818
|
+
opts[:use_placeholder_loader] = !opts[:instance_specific] && !opts[:eager_graph]
|
1806
1819
|
opts[:eager_block] = opts[:block] unless opts.include?(:eager_block)
|
1807
1820
|
opts[:graph_join_type] ||= :left_outer
|
1808
1821
|
opts[:order_eager_graph] = true unless opts.include?(:order_eager_graph)
|
@@ -1899,6 +1912,12 @@ module Sequel
|
|
1899
1912
|
Plugins.def_dataset_methods(self, [:eager, :eager_graph, :eager_graph_with_options, :association_join, :association_full_join, :association_inner_join, :association_left_join, :association_right_join])
|
1900
1913
|
|
1901
1914
|
private
|
1915
|
+
|
1916
|
+
# The default value for the instance_specific option, if the association
|
1917
|
+
# could be instance specific and the :instance_specific option is not specified.
|
1918
|
+
def _association_instance_specific_default(_)
|
1919
|
+
true
|
1920
|
+
end
|
1902
1921
|
|
1903
1922
|
# The module to use for the association's methods. Defaults to
|
1904
1923
|
# the overridable_methods_module.
|
@@ -1948,10 +1967,8 @@ module Sequel
|
|
1948
1967
|
if opts[:block]
|
1949
1968
|
opts[:block_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_block", 1, &opts[:block])
|
1950
1969
|
end
|
1951
|
-
|
1952
|
-
|
1953
|
-
opts[:dataset_opt_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_dataset_opt", opts[:dataset_opt_arity], &opts[:dataset])
|
1954
|
-
end
|
1970
|
+
opts[:dataset_opt_arity] = opts[:dataset].arity == 0 ? 0 : 1
|
1971
|
+
opts[:dataset_opt_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_dataset_opt", opts[:dataset_opt_arity], &opts[:dataset])
|
1955
1972
|
def_association_method(opts)
|
1956
1973
|
|
1957
1974
|
return if opts[:read_only]
|
@@ -2122,9 +2139,7 @@ module Sequel
|
|
2122
2139
|
|
2123
2140
|
eager_load_results(opts, eo) do |assoc_record|
|
2124
2141
|
hash_key = uses_cks ? pk_meths.map{|k| assoc_record.get_column_value(k)} : assoc_record.get_column_value(opts.primary_key_method)
|
2125
|
-
|
2126
|
-
objects.each{|object| object.associations[name] = assoc_record}
|
2127
|
-
end
|
2142
|
+
h[hash_key].each{|object| object.associations[name] = assoc_record}
|
2128
2143
|
end
|
2129
2144
|
end
|
2130
2145
|
|
@@ -2171,7 +2186,7 @@ module Sequel
|
|
2171
2186
|
eager_load_results(opts, eo) do |assoc_record|
|
2172
2187
|
assoc_record.values.delete(delete_rn) if delete_rn
|
2173
2188
|
hash_key = uses_cks ? km.map{|k| assoc_record.get_column_value(k)} : assoc_record.get_column_value(km)
|
2174
|
-
|
2189
|
+
objects = h[hash_key]
|
2175
2190
|
if assign_singular
|
2176
2191
|
objects.each do |object|
|
2177
2192
|
unless object.associations[name]
|
@@ -2966,8 +2981,8 @@ module Sequel
|
|
2966
2981
|
# dataset. If that association also has dependent associations, instead of a callable object,
|
2967
2982
|
# use a hash with the callable object being the key, and the dependent association(s) as the value.
|
2968
2983
|
#
|
2969
|
-
# You can specify an alias
|
2970
|
-
# an a Symbol for the
|
2984
|
+
# You can specify an custom alias and/or join type on a per-association basis by providing an
|
2985
|
+
# Sequel::SQL::AliasedExpression object instead of an a Symbol for the association name.
|
2971
2986
|
#
|
2972
2987
|
# Examples:
|
2973
2988
|
#
|
@@ -2983,6 +2998,14 @@ module Sequel
|
|
2983
2998
|
# # FROM albums
|
2984
2999
|
# # LEFT OUTER JOIN artists AS a ON (a.id = albums.artist_id)
|
2985
3000
|
#
|
3001
|
+
# # For each album, eager_graph load the artist, using a specified alias
|
3002
|
+
# # and custom join type
|
3003
|
+
#
|
3004
|
+
# Album.eager_graph(Sequel[:artist].as(:a, join_type: :inner)).all
|
3005
|
+
# # SELECT ...
|
3006
|
+
# # FROM albums
|
3007
|
+
# # INNER JOIN artists AS a ON (a.id = albums.artist_id)
|
3008
|
+
#
|
2986
3009
|
# # For each album, eager_graph load the artist and genre
|
2987
3010
|
# Album.eager_graph(:artist, :genre).all
|
2988
3011
|
# Album.eager_graph(:artist).eager_graph(:genre).all
|
@@ -3056,6 +3079,8 @@ module Sequel
|
|
3056
3079
|
# significantly slower in some cases (perhaps even the majority of cases), so you should
|
3057
3080
|
# only use this if you have benchmarked that it is faster for your use cases.
|
3058
3081
|
def eager_graph_with_options(associations, opts=OPTS)
|
3082
|
+
return self if associations.empty?
|
3083
|
+
|
3059
3084
|
opts = opts.dup unless opts.frozen?
|
3060
3085
|
associations = [associations] unless associations.is_a?(Array)
|
3061
3086
|
ds = if eg = @opts[:eager_graph]
|
@@ -3125,11 +3150,16 @@ module Sequel
|
|
3125
3150
|
# ta :: table_alias used for the parent association
|
3126
3151
|
# requirements :: an array, used as a stack for requirements
|
3127
3152
|
# r :: association reflection for the current association, or an SQL::AliasedExpression
|
3128
|
-
# with the reflection as the expression
|
3153
|
+
# with the reflection as the expression, the alias base as the alias (or nil to
|
3154
|
+
# use the default alias), and an optional hash with a :join_type entry as the columns
|
3155
|
+
# to use a custom join type.
|
3129
3156
|
# *associations :: any associations dependent on this one
|
3130
3157
|
def eager_graph_association(ds, model, ta, requirements, r, *associations)
|
3131
3158
|
if r.is_a?(SQL::AliasedExpression)
|
3132
3159
|
alias_base = r.alias
|
3160
|
+
if r.columns.is_a?(Hash)
|
3161
|
+
join_type = r.columns[:join_type]
|
3162
|
+
end
|
3133
3163
|
r = r.expression
|
3134
3164
|
else
|
3135
3165
|
alias_base = r[:graph_alias_base]
|
@@ -3152,7 +3182,7 @@ module Sequel
|
|
3152
3182
|
raise Error, "Cannot eager_graph association when :conditions specified and not a hash or an array of pairs. Specify :graph_conditions, :graph_only_conditions, or :graph_block for the association. Model: #{r[:model]}, association: #{r[:name]}"
|
3153
3183
|
end
|
3154
3184
|
|
3155
|
-
ds = loader.call(:self=>ds, :table_alias=>assoc_table_alias, :implicit_qualifier=>(ta == ds.opts[:eager_graph][:master]) ? first_source : qualifier_from_alias_symbol(ta, first_source), :callback=>callback, :join_type=>local_opts[:join_type], :join_only=>local_opts[:join_only], :limit_strategy=>limit_strategy, :from_self_alias=>ds.opts[:eager_graph][:master])
|
3185
|
+
ds = loader.call(:self=>ds, :table_alias=>assoc_table_alias, :implicit_qualifier=>(ta == ds.opts[:eager_graph][:master]) ? first_source : qualifier_from_alias_symbol(ta, first_source), :callback=>callback, :join_type=>join_type || local_opts[:join_type], :join_only=>local_opts[:join_only], :limit_strategy=>limit_strategy, :from_self_alias=>ds.opts[:eager_graph][:master])
|
3156
3186
|
if r[:order_eager_graph] && (order = r.fetch(:graph_order, r[:order]))
|
3157
3187
|
ds = ds.order_append(*qualified_expression(order, assoc_table_alias))
|
3158
3188
|
end
|
@@ -3177,7 +3207,6 @@ module Sequel
|
|
3177
3207
|
# requirements :: an array, used as a stack for requirements
|
3178
3208
|
# *associations :: the associations to add to the graph
|
3179
3209
|
def eager_graph_associations(ds, model, ta, requirements, *associations)
|
3180
|
-
return ds if associations.empty?
|
3181
3210
|
associations.flatten.each do |association|
|
3182
3211
|
ds = case association
|
3183
3212
|
when Symbol, SQL::AliasedExpression
|
@@ -3307,7 +3336,7 @@ module Sequel
|
|
3307
3336
|
end
|
3308
3337
|
end
|
3309
3338
|
|
3310
|
-
SQL::AliasedExpression.new(check_association(model, expr), association.alias)
|
3339
|
+
SQL::AliasedExpression.new(check_association(model, expr), association.alias || expr, association.columns)
|
3311
3340
|
else
|
3312
3341
|
check_association(model, association)
|
3313
3342
|
end
|
data/lib/sequel/model/base.rb
CHANGED
@@ -491,6 +491,11 @@ module Sequel
|
|
491
491
|
# the module using a the camelized plugin name under Sequel::Plugins.
|
492
492
|
def plugin(plugin, *args, &block)
|
493
493
|
m = plugin.is_a?(Module) ? plugin : plugin_module(plugin)
|
494
|
+
|
495
|
+
if !m.respond_to?(:apply) && !m.respond_to?(:configure) && (!args.empty? || block)
|
496
|
+
Deprecation.deprecate("Plugin #{plugin} accepts no arguments or block, and passing arguments/block to it", "Remove arguments and block when loading the plugin")
|
497
|
+
end
|
498
|
+
|
494
499
|
unless @plugins.include?(m)
|
495
500
|
@plugins << m
|
496
501
|
m.apply(self, *args, &block) if m.respond_to?(:apply)
|
@@ -500,8 +505,10 @@ module Sequel
|
|
500
505
|
dataset_extend(m::DatasetMethods, :create_class_methods=>false)
|
501
506
|
end
|
502
507
|
end
|
508
|
+
|
503
509
|
m.configure(self, *args, &block) if m.respond_to?(:configure)
|
504
510
|
end
|
511
|
+
ruby2_keywords(:plugin) if respond_to?(:ruby2_keywords, true)
|
505
512
|
|
506
513
|
# Returns primary key attribute hash. If using a composite primary key
|
507
514
|
# value such be an array with values for each primary key in the correct
|
@@ -593,7 +600,7 @@ module Sequel
|
|
593
600
|
@columns = superclass.instance_variable_get(:@columns)
|
594
601
|
@db_schema = superclass.instance_variable_get(:@db_schema)
|
595
602
|
else
|
596
|
-
@dataset = @dataset.with_extend(*@dataset_method_modules.reverse)
|
603
|
+
@dataset = @dataset.with_extend(*@dataset_method_modules.reverse)
|
597
604
|
@db_schema = get_db_schema
|
598
605
|
end
|
599
606
|
|
@@ -632,8 +639,7 @@ module Sequel
|
|
632
639
|
|
633
640
|
# Cache of setter methods to allow by default, in order to speed up mass assignment.
|
634
641
|
def setter_methods
|
635
|
-
|
636
|
-
@setter_methods = get_setter_methods
|
642
|
+
@setter_methods || (@setter_methods = get_setter_methods)
|
637
643
|
end
|
638
644
|
|
639
645
|
# Returns name of primary table for the dataset. If the table for the dataset
|
@@ -751,6 +757,7 @@ module Sequel
|
|
751
757
|
else
|
752
758
|
define_singleton_method(meth){|*args, &block| dataset.public_send(meth, *args, &block)}
|
753
759
|
end
|
760
|
+
singleton_class.send(:ruby2_keywords, meth) if respond_to?(:ruby2_keywords, true)
|
754
761
|
end
|
755
762
|
|
756
763
|
# Get the schema from the database, fall back on checking the columns
|
@@ -820,7 +827,6 @@ module Sequel
|
|
820
827
|
super
|
821
828
|
ivs = subclass.instance_variables
|
822
829
|
inherited_instance_variables.each do |iv, dup|
|
823
|
-
next if ivs.include?(iv)
|
824
830
|
if (sup_class_value = instance_variable_get(iv)) && dup
|
825
831
|
sup_class_value = case dup
|
826
832
|
when :dup
|
@@ -1116,7 +1122,7 @@ module Sequel
|
|
1116
1122
|
when nil
|
1117
1123
|
return false
|
1118
1124
|
when Array
|
1119
|
-
return false if
|
1125
|
+
return false if pkv.any?(&:nil?)
|
1120
1126
|
end
|
1121
1127
|
|
1122
1128
|
(obj.class == model) && (obj.pk == pkv)
|
@@ -2232,7 +2238,9 @@ module Sequel
|
|
2232
2238
|
plugin self
|
2233
2239
|
|
2234
2240
|
singleton_class.send(:undef_method, :dup, :clone, :initialize_copy)
|
2241
|
+
# :nocov:
|
2235
2242
|
if RUBY_VERSION >= '1.9.3'
|
2243
|
+
# :nocov:
|
2236
2244
|
singleton_class.send(:undef_method, :initialize_clone, :initialize_dup)
|
2237
2245
|
end
|
2238
2246
|
end
|
data/lib/sequel/model/plugins.rb
CHANGED
@@ -31,6 +31,7 @@ module Sequel
|
|
31
31
|
def self.def_dataset_methods(mod, meths)
|
32
32
|
Array(meths).each do |meth|
|
33
33
|
mod.class_eval("def #{meth}(*args, &block); dataset.#{meth}(*args, &block) end", __FILE__, __LINE__)
|
34
|
+
mod.send(:ruby2_keywords, meth) if respond_to?(:ruby2_keywords, true)
|
34
35
|
end
|
35
36
|
end
|
36
37
|
|
@@ -149,9 +150,8 @@ module Sequel
|
|
149
150
|
required_args = arity
|
150
151
|
arity -= 1 if keyword == :required
|
151
152
|
|
152
|
-
|
153
|
-
|
154
|
-
end
|
153
|
+
# callable currently is always a non-lambda Proc
|
154
|
+
optional_args -= arity
|
155
155
|
|
156
156
|
[required_args, optional_args, rest, keyword]
|
157
157
|
end
|
@@ -2,13 +2,17 @@
|
|
2
2
|
|
3
3
|
module Sequel
|
4
4
|
module Plugins
|
5
|
-
# The association_pks plugin adds association_pks and
|
6
|
-
# instance methods to the model class for each
|
7
|
-
#
|
8
|
-
#
|
5
|
+
# The association_pks plugin adds association_pks, association_pks=, and
|
6
|
+
# association_pks_dataset instance methods to the model class for each
|
7
|
+
# one_to_many and many_to_many association added. These methods allow for
|
8
|
+
# easily returning the primary keys of the associated objects, and easily
|
9
|
+
# modifying which objects are associated:
|
9
10
|
#
|
10
11
|
# Artist.one_to_many :albums
|
11
12
|
# artist = Artist[1]
|
13
|
+
# artist.album_pks_dataset
|
14
|
+
# # SELECT id FROM albums WHERE (albums.artist_id = 1)
|
15
|
+
#
|
12
16
|
# artist.album_pks # [1, 2, 3]
|
13
17
|
# artist.album_pks = [2, 4]
|
14
18
|
# artist.album_pks # [2, 4]
|
@@ -22,11 +26,18 @@ module Sequel
|
|
22
26
|
# This plugin makes modifications directly to the underlying tables,
|
23
27
|
# it does not create or return any model objects, and therefore does
|
24
28
|
# not call any callbacks. If you have any association callbacks,
|
25
|
-
# you probably should not use the setter methods.
|
29
|
+
# you probably should not use the setter methods this plugin adds.
|
26
30
|
#
|
27
31
|
# By default, changes to the association will not happen until the object
|
28
|
-
# is saved. However, using the delay_pks: false option, you can have
|
29
|
-
# changes made immediately when the association_pks setter method is called.
|
32
|
+
# is saved. However, using the delay_pks: false association option, you can have
|
33
|
+
# the changes made immediately when the association_pks setter method is called.
|
34
|
+
#
|
35
|
+
# By default, repeated calls to the association_pks getter method will not be
|
36
|
+
# cached, unless the setter method has been used and the delay_pks: false
|
37
|
+
# association option is not used. You can set caching of repeated calls to the
|
38
|
+
# association_pks getter method using the :cache_pks association option. You can
|
39
|
+
# pass the :refresh option when calling the getter method to ignore any existing
|
40
|
+
# cached values, similar to how the :refresh option works with associations.
|
30
41
|
#
|
31
42
|
# By default, if you pass a nil value to the setter, an exception will be raised.
|
32
43
|
# You can change this behavior by using the :association_pks_nil association option.
|
@@ -60,9 +71,11 @@ module Sequel
|
|
60
71
|
|
61
72
|
# Define a association_pks method using the block for the association reflection
|
62
73
|
def def_association_pks_methods(opts)
|
74
|
+
association_module_def(opts[:pks_dataset_method], &opts[:pks_dataset])
|
75
|
+
|
63
76
|
opts[:pks_getter_method] = :"#{singularize(opts[:name])}_pks_getter"
|
64
77
|
association_module_def(opts[:pks_getter_method], &opts[:pks_getter])
|
65
|
-
association_module_def(:"#{singularize(opts[:name])}_pks", opts){_association_pks_getter(opts)}
|
78
|
+
association_module_def(:"#{singularize(opts[:name])}_pks", opts){|dynamic_opts=OPTS| _association_pks_getter(opts, dynamic_opts)}
|
66
79
|
|
67
80
|
if opts[:pks_setter]
|
68
81
|
opts[:pks_setter_method] = :"#{singularize(opts[:name])}_pks_setter"
|
@@ -84,7 +97,9 @@ module Sequel
|
|
84
97
|
clpk = lpk.is_a?(Array)
|
85
98
|
crk = rk.is_a?(Array)
|
86
99
|
|
87
|
-
opts[:
|
100
|
+
dataset_method = opts[:pks_dataset_method] = :"#{singularize(opts[:name])}_pks_dataset"
|
101
|
+
|
102
|
+
opts[:pks_dataset] = if join_associated_table = opts[:association_pks_use_associated_table]
|
88
103
|
tname = opts[:join_table]
|
89
104
|
lambda do
|
90
105
|
cond = if clpk
|
@@ -95,16 +110,26 @@ module Sequel
|
|
95
110
|
rpk = opts.associated_class.primary_key
|
96
111
|
opts.associated_dataset.
|
97
112
|
naked.where(cond).
|
98
|
-
|
113
|
+
select(*Sequel.public_send(rpk.is_a?(Array) ? :deep_qualify : :qualify, opts.associated_class.table_name, rpk))
|
99
114
|
end
|
100
115
|
elsif clpk
|
101
116
|
lambda do
|
102
117
|
cond = lk.zip(lpk).map{|k, pk| [k, get_column_value(pk)]}
|
103
|
-
_join_table_dataset(opts).where(cond).
|
118
|
+
_join_table_dataset(opts).where(cond).select(*rk)
|
119
|
+
end
|
120
|
+
else
|
121
|
+
lambda do
|
122
|
+
_join_table_dataset(opts).where(lk=>get_column_value(lpk)).select(*rk)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
opts[:pks_getter] = if join_associated_table = opts[:association_pks_use_associated_table]
|
127
|
+
lambda do
|
128
|
+
public_send(dataset_method).map(opts.associated_class.primary_key)
|
104
129
|
end
|
105
130
|
else
|
106
131
|
lambda do
|
107
|
-
|
132
|
+
public_send(dataset_method).map(rk)
|
108
133
|
end
|
109
134
|
end
|
110
135
|
|
@@ -145,8 +170,14 @@ module Sequel
|
|
145
170
|
|
146
171
|
key = opts[:key]
|
147
172
|
|
173
|
+
dataset_method = opts[:pks_dataset_method] = :"#{singularize(opts[:name])}_pks_dataset"
|
174
|
+
|
175
|
+
opts[:pks_dataset] = lambda do
|
176
|
+
public_send(opts[:dataset_method]).select(*opts.associated_class.primary_key)
|
177
|
+
end
|
178
|
+
|
148
179
|
opts[:pks_getter] = lambda do
|
149
|
-
public_send(
|
180
|
+
public_send(dataset_method).map(opts.associated_class.primary_key)
|
150
181
|
end
|
151
182
|
|
152
183
|
unless opts[:read_only]
|
@@ -207,12 +238,22 @@ module Sequel
|
|
207
238
|
# Return the primary keys of the associated objects.
|
208
239
|
# If the receiver is a new object, return any saved
|
209
240
|
# pks, or an empty array if no pks have been saved.
|
210
|
-
def _association_pks_getter(opts)
|
241
|
+
def _association_pks_getter(opts, dynamic_opts=OPTS)
|
242
|
+
do_cache = opts[:cache_pks]
|
211
243
|
delay = opts.fetch(:delay_pks, true)
|
212
|
-
|
244
|
+
cache_or_delay = do_cache || delay
|
245
|
+
|
246
|
+
if dynamic_opts[:refresh] && @_association_pks
|
247
|
+
@_association_pks.delete(opts[:name])
|
248
|
+
end
|
249
|
+
|
250
|
+
if new? && cache_or_delay
|
213
251
|
(@_association_pks ||= {})[opts[:name]] ||= []
|
214
|
-
elsif
|
252
|
+
elsif cache_or_delay && @_association_pks && (objs = @_association_pks[opts[:name]])
|
215
253
|
objs
|
254
|
+
elsif do_cache
|
255
|
+
# pks_getter_method is private
|
256
|
+
(@_association_pks ||= {})[opts[:name]] = send(opts[:pks_getter_method])
|
216
257
|
else
|
217
258
|
# pks_getter_method is private
|
218
259
|
send(opts[:pks_getter_method])
|
@@ -254,9 +295,10 @@ module Sequel
|
|
254
295
|
|
255
296
|
if primary_key.is_a?(Array)
|
256
297
|
if (cols = sch.values_at(*klass.primary_key)).all? && (convs = cols.map{|c| c[:type] == :integer}).all?
|
298
|
+
db = model.db
|
257
299
|
pks.map do |cpk|
|
258
|
-
cpk.
|
259
|
-
|
300
|
+
cpk.map do |pk|
|
301
|
+
db.typecast_value(:integer, pk)
|
260
302
|
end
|
261
303
|
end
|
262
304
|
else
|