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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +84 -0
  3. data/README.rdoc +1 -1
  4. data/doc/association_basics.rdoc +7 -2
  5. data/doc/dataset_filtering.rdoc +2 -2
  6. data/doc/model_plugins.rdoc +1 -1
  7. data/doc/release_notes/5.33.0.txt +24 -0
  8. data/doc/release_notes/5.34.0.txt +40 -0
  9. data/doc/release_notes/5.35.0.txt +56 -0
  10. data/doc/release_notes/5.36.0.txt +60 -0
  11. data/doc/release_notes/5.37.0.txt +30 -0
  12. data/doc/transactions.rdoc +0 -8
  13. data/doc/validations.rdoc +1 -1
  14. data/lib/sequel/adapters/odbc.rb +4 -6
  15. data/lib/sequel/adapters/oracle.rb +2 -1
  16. data/lib/sequel/adapters/shared/mssql.rb +14 -4
  17. data/lib/sequel/adapters/shared/oracle.rb +12 -6
  18. data/lib/sequel/adapters/shared/postgres.rb +39 -1
  19. data/lib/sequel/adapters/shared/sqlite.rb +13 -3
  20. data/lib/sequel/adapters/tinytds.rb +1 -0
  21. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -0
  22. data/lib/sequel/connection_pool/sharded_single.rb +4 -1
  23. data/lib/sequel/connection_pool/sharded_threaded.rb +10 -10
  24. data/lib/sequel/connection_pool/single.rb +1 -1
  25. data/lib/sequel/connection_pool/threaded.rb +1 -1
  26. data/lib/sequel/core.rb +5 -6
  27. data/lib/sequel/database/connecting.rb +1 -1
  28. data/lib/sequel/database/misc.rb +16 -10
  29. data/lib/sequel/database/query.rb +2 -0
  30. data/lib/sequel/database/schema_generator.rb +0 -1
  31. data/lib/sequel/database/schema_methods.rb +15 -16
  32. data/lib/sequel/database/transactions.rb +8 -5
  33. data/lib/sequel/dataset/actions.rb +10 -6
  34. data/lib/sequel/dataset/placeholder_literalizer.rb +3 -7
  35. data/lib/sequel/dataset/query.rb +5 -4
  36. data/lib/sequel/deprecated.rb +3 -1
  37. data/lib/sequel/exceptions.rb +2 -0
  38. data/lib/sequel/extensions/_pretty_table.rb +1 -2
  39. data/lib/sequel/extensions/columns_introspection.rb +1 -2
  40. data/lib/sequel/extensions/core_refinements.rb +2 -0
  41. data/lib/sequel/extensions/duplicate_columns_handler.rb +2 -0
  42. data/lib/sequel/extensions/migration.rb +8 -2
  43. data/lib/sequel/extensions/pg_array_ops.rb +4 -0
  44. data/lib/sequel/extensions/pg_enum.rb +2 -0
  45. data/lib/sequel/extensions/pg_extended_date_support.rb +1 -1
  46. data/lib/sequel/extensions/pg_hstore_ops.rb +2 -0
  47. data/lib/sequel/extensions/pg_inet.rb +2 -0
  48. data/lib/sequel/extensions/pg_json_ops.rb +46 -2
  49. data/lib/sequel/extensions/pg_range.rb +3 -7
  50. data/lib/sequel/extensions/pg_range_ops.rb +2 -0
  51. data/lib/sequel/extensions/pg_row.rb +0 -1
  52. data/lib/sequel/extensions/pg_row_ops.rb +24 -0
  53. data/lib/sequel/extensions/query.rb +1 -0
  54. data/lib/sequel/extensions/run_transaction_hooks.rb +1 -1
  55. data/lib/sequel/extensions/s.rb +2 -0
  56. data/lib/sequel/extensions/schema_dumper.rb +3 -3
  57. data/lib/sequel/extensions/symbol_aref_refinement.rb +2 -0
  58. data/lib/sequel/extensions/symbol_as_refinement.rb +2 -0
  59. data/lib/sequel/extensions/to_dot.rb +9 -3
  60. data/lib/sequel/model.rb +3 -1
  61. data/lib/sequel/model/associations.rb +54 -25
  62. data/lib/sequel/model/base.rb +13 -5
  63. data/lib/sequel/model/plugins.rb +3 -3
  64. data/lib/sequel/plugins/association_pks.rb +60 -18
  65. data/lib/sequel/plugins/association_proxies.rb +1 -0
  66. data/lib/sequel/plugins/blacklist_security.rb +1 -2
  67. data/lib/sequel/plugins/class_table_inheritance.rb +3 -3
  68. data/lib/sequel/plugins/csv_serializer.rb +2 -0
  69. data/lib/sequel/plugins/dirty.rb +45 -0
  70. data/lib/sequel/plugins/forbid_lazy_load.rb +2 -0
  71. data/lib/sequel/plugins/instance_specific_default.rb +113 -0
  72. data/lib/sequel/plugins/lazy_attributes.rb +1 -1
  73. data/lib/sequel/plugins/pg_array_associations.rb +2 -3
  74. data/lib/sequel/plugins/prepared_statements.rb +5 -11
  75. data/lib/sequel/plugins/prepared_statements_safe.rb +1 -3
  76. data/lib/sequel/plugins/rcte_tree.rb +10 -16
  77. data/lib/sequel/plugins/string_stripper.rb +1 -1
  78. data/lib/sequel/plugins/validation_class_methods.rb +5 -1
  79. data/lib/sequel/version.rb +1 -1
  80. 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(?: unsigned)?|double(?: precision)?|double\(\d+,\d+\)(?: unsigned)?)\z/
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
@@ -25,7 +25,9 @@
25
25
  #
26
26
  # Related module: Sequel::SymbolAref
27
27
 
28
+ # :nocov:
28
29
  raise(Sequel::Error, "Refinements require ruby 2.0.0 or greater") unless RUBY_VERSION >= '2.0.0'
30
+ # :nocov:
29
31
 
30
32
  module Sequel::SymbolAref
31
33
  refine Symbol do
@@ -23,7 +23,9 @@
23
23
  #
24
24
  # Related module: Sequel::SymbolAs
25
25
 
26
+ # :nocov:
26
27
  raise(Sequel::Error, "Refinements require ruby 2.0.0 or greater") unless RUBY_VERSION >= '2.0.0'
28
+ # :nocov:
27
29
 
28
30
  module Sequel::SymbolAs
29
31
  refine Symbol do
@@ -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
- @dot << "#{j||@i} [label=#{label.to_s.inspect}];"
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}") if l
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.to_s)
153
+ v(val, k)
148
154
  end
149
155
  end
150
156
  else
@@ -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
- elsif sr = slice_range(limit_and_offset)
171
- offset = sr.begin
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
- finalize_settings.each do |meth, key|
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
- !self[:instance_specific] && !self[:eager_graph]
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
- next unless objects = h[hash_key]
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
- if !opts.has_key?(:instance_specific) && (block || orig_opts[:block] || orig_opts[:dataset])
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] = true
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
- 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
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
- if objects = h[hash_key]
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
- next unless objects = h[hash_key]
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 by providing a Sequel::SQL::AliasedExpression object instead of
2970
- # an a Symbol for the assocation name.
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 and the alias base as the aliaz.
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
@@ -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) if @dataset_method_modules
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
- return @setter_methods if @setter_methods
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 pk.any?(&:nil?)
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
@@ -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
- if callable.is_a?(Proc) && !callable.lambda?
153
- optional_args -= arity
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 association_pks=
6
- # instance methods to the model class for each association added. These
7
- # methods allow for easily returning the primary keys of the associated
8
- # objects, and easily modifying which objects are associated:
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 the
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[:pks_getter] = if join_associated_table = opts[:association_pks_use_associated_table]
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
- select_map(Sequel.public_send(rpk.is_a?(Array) ? :deep_qualify : :qualify, opts.associated_class.table_name, rpk))
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).select_map(rk)
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
- _join_table_dataset(opts).where(lk=>get_column_value(lpk)).select_map(rk)
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(opts[:dataset_method]).select_map(opts.associated_class.primary_key)
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
- if new? && delay
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 delay && @_association_pks && (objs = @_association_pks[opts[:name]])
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.zip(convs).map do |pk, conv|
259
- conv ? model.db.typecast_value(:integer, pk) : pk
300
+ cpk.map do |pk|
301
+ db.typecast_value(:integer, pk)
260
302
  end
261
303
  end
262
304
  else