sequel 5.33.0 → 5.38.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +88 -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/opening_databases.rdoc +5 -1
  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/release_notes/5.38.0.txt +28 -0
  13. data/doc/transactions.rdoc +0 -8
  14. data/doc/validations.rdoc +1 -1
  15. data/lib/sequel/adapters/jdbc.rb +13 -1
  16. data/lib/sequel/adapters/jdbc/mysql.rb +4 -4
  17. data/lib/sequel/adapters/odbc.rb +4 -6
  18. data/lib/sequel/adapters/oracle.rb +2 -1
  19. data/lib/sequel/adapters/shared/mssql.rb +14 -4
  20. data/lib/sequel/adapters/shared/oracle.rb +13 -7
  21. data/lib/sequel/adapters/shared/postgres.rb +39 -1
  22. data/lib/sequel/adapters/shared/sqlite.rb +8 -2
  23. data/lib/sequel/adapters/tinytds.rb +1 -0
  24. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -0
  25. data/lib/sequel/connection_pool/sharded_single.rb +4 -1
  26. data/lib/sequel/connection_pool/sharded_threaded.rb +10 -10
  27. data/lib/sequel/connection_pool/single.rb +1 -1
  28. data/lib/sequel/connection_pool/threaded.rb +1 -1
  29. data/lib/sequel/core.rb +5 -6
  30. data/lib/sequel/database/connecting.rb +1 -2
  31. data/lib/sequel/database/misc.rb +30 -10
  32. data/lib/sequel/database/query.rb +2 -0
  33. data/lib/sequel/database/schema_generator.rb +0 -1
  34. data/lib/sequel/database/schema_methods.rb +16 -17
  35. data/lib/sequel/database/transactions.rb +8 -5
  36. data/lib/sequel/dataset/actions.rb +10 -6
  37. data/lib/sequel/dataset/placeholder_literalizer.rb +3 -7
  38. data/lib/sequel/dataset/query.rb +5 -4
  39. data/lib/sequel/deprecated.rb +3 -1
  40. data/lib/sequel/exceptions.rb +2 -0
  41. data/lib/sequel/extensions/_pretty_table.rb +1 -2
  42. data/lib/sequel/extensions/columns_introspection.rb +1 -2
  43. data/lib/sequel/extensions/core_refinements.rb +2 -0
  44. data/lib/sequel/extensions/duplicate_columns_handler.rb +2 -0
  45. data/lib/sequel/extensions/migration.rb +8 -2
  46. data/lib/sequel/extensions/pg_array_ops.rb +4 -0
  47. data/lib/sequel/extensions/pg_enum.rb +2 -0
  48. data/lib/sequel/extensions/pg_extended_date_support.rb +1 -1
  49. data/lib/sequel/extensions/pg_hstore_ops.rb +2 -0
  50. data/lib/sequel/extensions/pg_inet.rb +2 -0
  51. data/lib/sequel/extensions/pg_json_ops.rb +46 -2
  52. data/lib/sequel/extensions/pg_range.rb +3 -7
  53. data/lib/sequel/extensions/pg_range_ops.rb +2 -0
  54. data/lib/sequel/extensions/pg_row.rb +0 -1
  55. data/lib/sequel/extensions/pg_row_ops.rb +24 -0
  56. data/lib/sequel/extensions/query.rb +1 -0
  57. data/lib/sequel/extensions/run_transaction_hooks.rb +1 -1
  58. data/lib/sequel/extensions/s.rb +2 -0
  59. data/lib/sequel/extensions/schema_dumper.rb +3 -3
  60. data/lib/sequel/extensions/symbol_aref_refinement.rb +2 -0
  61. data/lib/sequel/extensions/symbol_as_refinement.rb +2 -0
  62. data/lib/sequel/extensions/to_dot.rb +9 -3
  63. data/lib/sequel/model.rb +3 -1
  64. data/lib/sequel/model/associations.rb +36 -20
  65. data/lib/sequel/model/base.rb +13 -5
  66. data/lib/sequel/model/plugins.rb +3 -3
  67. data/lib/sequel/plugins/association_pks.rb +60 -18
  68. data/lib/sequel/plugins/association_proxies.rb +1 -0
  69. data/lib/sequel/plugins/blacklist_security.rb +1 -2
  70. data/lib/sequel/plugins/class_table_inheritance.rb +3 -8
  71. data/lib/sequel/plugins/csv_serializer.rb +2 -0
  72. data/lib/sequel/plugins/dirty.rb +44 -0
  73. data/lib/sequel/plugins/forbid_lazy_load.rb +2 -0
  74. data/lib/sequel/plugins/instance_specific_default.rb +113 -0
  75. data/lib/sequel/plugins/lazy_attributes.rb +1 -1
  76. data/lib/sequel/plugins/pg_array_associations.rb +2 -3
  77. data/lib/sequel/plugins/prepared_statements.rb +5 -11
  78. data/lib/sequel/plugins/prepared_statements_safe.rb +1 -3
  79. data/lib/sequel/plugins/rcte_tree.rb +10 -16
  80. data/lib/sequel/plugins/single_table_inheritance.rb +5 -0
  81. data/lib/sequel/plugins/string_stripper.rb +1 -1
  82. data/lib/sequel/plugins/validation_class_methods.rb +5 -1
  83. data/lib/sequel/version.rb +1 -1
  84. metadata +14 -3
@@ -222,7 +222,6 @@ module Sequel
222
222
  # Split the stored string into an array of strings, handling
223
223
  # the different types of quoting.
224
224
  def parse
225
- return @result if @result
226
225
  values = []
227
226
  skip(/\(/)
228
227
  if skip(/\)/)
@@ -158,6 +158,30 @@ module Sequel
158
158
  end
159
159
  end
160
160
  end
161
+
162
+ # :nocov:
163
+ if defined?(PGRow::ArrayRow)
164
+ # :nocov:
165
+ class PGRow::ArrayRow
166
+ # Wrap the PGRow::ArrayRow instance in an PGRowOp, allowing you to easily use
167
+ # the PostgreSQL row functions and operators with literal rows.
168
+ def op
169
+ Sequel.pg_row_op(self)
170
+ end
171
+ end
172
+ end
173
+
174
+ # :nocov:
175
+ if defined?(PGRow::HashRow)
176
+ # :nocov:
177
+ class PGRow::HashRow
178
+ # Wrap the PGRow::ArrayRow instance in an PGRowOp, allowing you to easily use
179
+ # the PostgreSQL row functions and operators with literal rows.
180
+ def op
181
+ Sequel.pg_row_op(self)
182
+ end
183
+ end
184
+ end
161
185
  end
162
186
 
163
187
  module SQL::Builders
@@ -74,6 +74,7 @@ module Sequel
74
74
  raise(Sequel::Error, "method #{method.inspect} did not return a dataset") unless @dataset.is_a?(Dataset)
75
75
  self
76
76
  end
77
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
77
78
  end
78
79
  end
79
80
 
@@ -48,7 +48,7 @@ class Sequel::Database
48
48
  def _run_transaction_hooks(type, opts)
49
49
  synchronize(opts[:server]) do |conn|
50
50
  unless h = _trans(conn)
51
- raise Error, "Cannot call run_#{type}_hooks outside of a transaction"
51
+ raise Sequel::Error, "Cannot call run_#{type}_hooks outside of a transaction"
52
52
  end
53
53
 
54
54
  if hooks = h[type]
@@ -49,7 +49,9 @@ module Sequel::S
49
49
  Sequel.expr(*a, &block)
50
50
  end
51
51
 
52
+ # :nocov:
52
53
  if RUBY_VERSION >= '2.0.0'
54
+ # :nocov:
53
55
  refine Object do
54
56
  include Sequel::S
55
57
  end
@@ -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]
@@ -3064,6 +3079,8 @@ module Sequel
3064
3079
  # significantly slower in some cases (perhaps even the majority of cases), so you should
3065
3080
  # only use this if you have benchmarked that it is faster for your use cases.
3066
3081
  def eager_graph_with_options(associations, opts=OPTS)
3082
+ return self if associations.empty?
3083
+
3067
3084
  opts = opts.dup unless opts.frozen?
3068
3085
  associations = [associations] unless associations.is_a?(Array)
3069
3086
  ds = if eg = @opts[:eager_graph]
@@ -3190,7 +3207,6 @@ module Sequel
3190
3207
  # requirements :: an array, used as a stack for requirements
3191
3208
  # *associations :: the associations to add to the graph
3192
3209
  def eager_graph_associations(ds, model, ta, requirements, *associations)
3193
- return ds if associations.empty?
3194
3210
  associations.flatten.each do |association|
3195
3211
  ds = case association
3196
3212
  when Symbol, SQL::AliasedExpression
@@ -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