sequel 5.30.0 → 5.35.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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +86 -0
  3. data/README.rdoc +1 -1
  4. data/doc/advanced_associations.rdoc +4 -4
  5. data/doc/association_basics.rdoc +10 -5
  6. data/doc/code_order.rdoc +12 -2
  7. data/doc/dataset_filtering.rdoc +2 -2
  8. data/doc/model_dataset_method_design.rdoc +1 -1
  9. data/doc/postgresql.rdoc +71 -0
  10. data/doc/release_notes/5.31.0.txt +148 -0
  11. data/doc/release_notes/5.32.0.txt +46 -0
  12. data/doc/release_notes/5.33.0.txt +24 -0
  13. data/doc/release_notes/5.34.0.txt +40 -0
  14. data/doc/release_notes/5.35.0.txt +56 -0
  15. data/doc/testing.rdoc +1 -1
  16. data/lib/sequel/adapters/oracle.rb +2 -1
  17. data/lib/sequel/adapters/shared/access.rb +6 -6
  18. data/lib/sequel/adapters/shared/mssql.rb +5 -5
  19. data/lib/sequel/adapters/shared/mysql.rb +9 -9
  20. data/lib/sequel/adapters/shared/oracle.rb +16 -16
  21. data/lib/sequel/adapters/shared/postgres.rb +169 -14
  22. data/lib/sequel/adapters/shared/sqlanywhere.rb +9 -9
  23. data/lib/sequel/adapters/shared/sqlite.rb +33 -6
  24. data/lib/sequel/adapters/tinytds.rb +1 -0
  25. data/lib/sequel/connection_pool/sharded_single.rb +4 -1
  26. data/lib/sequel/connection_pool/sharded_threaded.rb +12 -12
  27. data/lib/sequel/connection_pool/single.rb +1 -1
  28. data/lib/sequel/connection_pool/threaded.rb +2 -2
  29. data/lib/sequel/core.rb +318 -314
  30. data/lib/sequel/database/connecting.rb +1 -1
  31. data/lib/sequel/database/misc.rb +16 -10
  32. data/lib/sequel/database/query.rb +3 -1
  33. data/lib/sequel/database/schema_generator.rb +0 -1
  34. data/lib/sequel/database/schema_methods.rb +15 -16
  35. data/lib/sequel/database/transactions.rb +7 -4
  36. data/lib/sequel/dataset/placeholder_literalizer.rb +3 -7
  37. data/lib/sequel/dataset/query.rb +5 -4
  38. data/lib/sequel/deprecated.rb +3 -1
  39. data/lib/sequel/exceptions.rb +2 -0
  40. data/lib/sequel/extensions/_pretty_table.rb +1 -2
  41. data/lib/sequel/extensions/columns_introspection.rb +1 -2
  42. data/lib/sequel/extensions/connection_expiration.rb +2 -2
  43. data/lib/sequel/extensions/connection_validator.rb +2 -2
  44. data/lib/sequel/extensions/core_refinements.rb +2 -0
  45. data/lib/sequel/extensions/duplicate_columns_handler.rb +2 -0
  46. data/lib/sequel/extensions/fiber_concurrency.rb +24 -0
  47. data/lib/sequel/extensions/index_caching.rb +9 -7
  48. data/lib/sequel/extensions/integer64.rb +2 -0
  49. data/lib/sequel/extensions/migration.rb +1 -2
  50. data/lib/sequel/extensions/pg_array_ops.rb +4 -0
  51. data/lib/sequel/extensions/pg_enum.rb +7 -2
  52. data/lib/sequel/extensions/pg_extended_date_support.rb +1 -1
  53. data/lib/sequel/extensions/pg_hstore.rb +6 -0
  54. data/lib/sequel/extensions/pg_hstore_ops.rb +2 -0
  55. data/lib/sequel/extensions/pg_inet.rb +15 -5
  56. data/lib/sequel/extensions/pg_interval.rb +2 -0
  57. data/lib/sequel/extensions/pg_json_ops.rb +2 -0
  58. data/lib/sequel/extensions/pg_range.rb +5 -7
  59. data/lib/sequel/extensions/pg_range_ops.rb +2 -0
  60. data/lib/sequel/extensions/pg_row.rb +0 -1
  61. data/lib/sequel/extensions/pg_timestamptz.rb +2 -0
  62. data/lib/sequel/extensions/run_transaction_hooks.rb +72 -0
  63. data/lib/sequel/extensions/s.rb +2 -0
  64. data/lib/sequel/extensions/schema_dumper.rb +10 -4
  65. data/lib/sequel/extensions/server_block.rb +3 -3
  66. data/lib/sequel/extensions/symbol_aref_refinement.rb +2 -0
  67. data/lib/sequel/extensions/symbol_as_refinement.rb +2 -0
  68. data/lib/sequel/extensions/to_dot.rb +9 -3
  69. data/lib/sequel/model.rb +2 -0
  70. data/lib/sequel/model/associations.rb +54 -25
  71. data/lib/sequel/model/base.rb +70 -57
  72. data/lib/sequel/model/plugins.rb +3 -3
  73. data/lib/sequel/plugins/association_lazy_eager_option.rb +66 -0
  74. data/lib/sequel/plugins/association_multi_add_remove.rb +2 -0
  75. data/lib/sequel/plugins/association_pks.rb +60 -18
  76. data/lib/sequel/plugins/association_proxies.rb +2 -0
  77. data/lib/sequel/plugins/blacklist_security.rb +1 -2
  78. data/lib/sequel/plugins/boolean_subsets.rb +4 -1
  79. data/lib/sequel/plugins/class_table_inheritance.rb +28 -28
  80. data/lib/sequel/plugins/csv_serializer.rb +2 -0
  81. data/lib/sequel/plugins/dirty.rb +13 -13
  82. data/lib/sequel/plugins/forbid_lazy_load.rb +216 -0
  83. data/lib/sequel/plugins/instance_specific_default.rb +113 -0
  84. data/lib/sequel/plugins/json_serializer.rb +3 -7
  85. data/lib/sequel/plugins/lazy_attributes.rb +1 -1
  86. data/lib/sequel/plugins/pg_array_associations.rb +2 -3
  87. data/lib/sequel/plugins/prepared_statements.rb +5 -11
  88. data/lib/sequel/plugins/prepared_statements_safe.rb +1 -3
  89. data/lib/sequel/plugins/rcte_tree.rb +10 -16
  90. data/lib/sequel/plugins/single_table_inheritance.rb +15 -15
  91. data/lib/sequel/plugins/skip_saving_columns.rb +108 -0
  92. data/lib/sequel/plugins/string_stripper.rb +1 -1
  93. data/lib/sequel/plugins/subclasses.rb +2 -0
  94. data/lib/sequel/plugins/validation_class_methods.rb +5 -1
  95. data/lib/sequel/timezones.rb +6 -4
  96. data/lib/sequel/version.rb +1 -1
  97. metadata +18 -2
@@ -460,47 +460,6 @@ module Sequel
460
460
  super
461
461
  end
462
462
 
463
- # If possible, set the dataset for the model subclass as soon as it
464
- # is created. Also, make sure the inherited class instance variables
465
- # are copied into the subclass.
466
- #
467
- # Sequel queries the database to get schema information as soon as
468
- # a model class is created:
469
- #
470
- # class Artist < Sequel::Model # Causes schema query
471
- # end
472
- def inherited(subclass)
473
- super
474
- ivs = subclass.instance_variables
475
- inherited_instance_variables.each do |iv, dup|
476
- next if ivs.include?(iv)
477
- if (sup_class_value = instance_variable_get(iv)) && dup
478
- sup_class_value = case dup
479
- when :dup
480
- sup_class_value.dup
481
- when :hash_dup
482
- h = {}
483
- sup_class_value.each{|k,v| h[k] = v.dup}
484
- h
485
- when Proc
486
- dup.call(sup_class_value)
487
- else
488
- raise Error, "bad inherited instance variable type: #{dup.inspect}"
489
- end
490
- end
491
- subclass.instance_variable_set(iv, sup_class_value)
492
- end
493
-
494
- unless ivs.include?(:@dataset)
495
- if @dataset && self != Model
496
- subclass.set_dataset(@dataset.clone, :inherited=>true)
497
- elsif (n = subclass.name) && !n.to_s.empty?
498
- db
499
- subclass.set_dataset(subclass.implicit_table_name)
500
- end
501
- end
502
- end
503
-
504
463
  # Returns the implicit table name for the model class, which is the demodulized,
505
464
  # underscored, pluralized name of the class.
506
465
  #
@@ -515,12 +474,6 @@ module Sequel
515
474
  call(values)
516
475
  end
517
476
 
518
- # Clear the setter_methods cache when a setter method is added.
519
- def method_added(meth)
520
- clear_setter_methods_cache if meth.to_s.end_with?('=')
521
- super
522
- end
523
-
524
477
  # Mark the model as not having a primary key. Not having a primary key
525
478
  # can cause issues, among which is that you won't be able to update records.
526
479
  #
@@ -538,6 +491,11 @@ module Sequel
538
491
  # the module using a the camelized plugin name under Sequel::Plugins.
539
492
  def plugin(plugin, *args, &block)
540
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
+
541
499
  unless @plugins.include?(m)
542
500
  @plugins << m
543
501
  m.apply(self, *args, &block) if m.respond_to?(:apply)
@@ -547,6 +505,7 @@ module Sequel
547
505
  dataset_extend(m::DatasetMethods, :create_class_methods=>false)
548
506
  end
549
507
  end
508
+
550
509
  m.configure(self, *args, &block) if m.respond_to?(:configure)
551
510
  end
552
511
 
@@ -640,7 +599,7 @@ module Sequel
640
599
  @columns = superclass.instance_variable_get(:@columns)
641
600
  @db_schema = superclass.instance_variable_get(:@db_schema)
642
601
  else
643
- @dataset = @dataset.with_extend(*@dataset_method_modules.reverse) if @dataset_method_modules
602
+ @dataset = @dataset.with_extend(*@dataset_method_modules.reverse)
644
603
  @db_schema = get_db_schema
645
604
  end
646
605
 
@@ -679,8 +638,7 @@ module Sequel
679
638
 
680
639
  # Cache of setter methods to allow by default, in order to speed up mass assignment.
681
640
  def setter_methods
682
- return @setter_methods if @setter_methods
683
- @setter_methods = get_setter_methods
641
+ @setter_methods || (@setter_methods = get_setter_methods)
684
642
  end
685
643
 
686
644
  # Returns name of primary table for the dataset. If the table for the dataset
@@ -854,6 +812,46 @@ module Sequel
854
812
  meths
855
813
  end
856
814
 
815
+ # If possible, set the dataset for the model subclass as soon as it
816
+ # is created. Also, make sure the inherited class instance variables
817
+ # are copied into the subclass.
818
+ #
819
+ # Sequel queries the database to get schema information as soon as
820
+ # a model class is created:
821
+ #
822
+ # class Artist < Sequel::Model # Causes schema query
823
+ # end
824
+ def inherited(subclass)
825
+ super
826
+ ivs = subclass.instance_variables
827
+ inherited_instance_variables.each do |iv, dup|
828
+ if (sup_class_value = instance_variable_get(iv)) && dup
829
+ sup_class_value = case dup
830
+ when :dup
831
+ sup_class_value.dup
832
+ when :hash_dup
833
+ h = {}
834
+ sup_class_value.each{|k,v| h[k] = v.dup}
835
+ h
836
+ when Proc
837
+ dup.call(sup_class_value)
838
+ else
839
+ raise Error, "bad inherited instance variable type: #{dup.inspect}"
840
+ end
841
+ end
842
+ subclass.instance_variable_set(iv, sup_class_value)
843
+ end
844
+
845
+ unless ivs.include?(:@dataset)
846
+ if @dataset && self != Model
847
+ subclass.set_dataset(@dataset.clone, :inherited=>true)
848
+ elsif (n = subclass.name) && !n.to_s.empty?
849
+ db
850
+ subclass.set_dataset(subclass.implicit_table_name)
851
+ end
852
+ end
853
+ end
854
+
857
855
  # A hash of instance variables to automatically set up in subclasses.
858
856
  # Keys are instance variable symbols, values should be:
859
857
  # nil :: Assign directly from superclass to subclass (frozen objects)
@@ -908,6 +906,12 @@ module Sequel
908
906
  opts[:class_name] ||= '::' + ((name || '').split("::")[0..-2] + [camelize(default)]).join('::')
909
907
  end
910
908
 
909
+ # Clear the setter_methods cache when a setter method is added.
910
+ def method_added(meth)
911
+ clear_setter_methods_cache if meth.to_s.end_with?('=')
912
+ super
913
+ end
914
+
911
915
  # Module that the class includes that holds methods the class adds for column accessors and
912
916
  # associations so that the methods can be overridden with +super+.
913
917
  def overridable_methods_module
@@ -1116,7 +1120,7 @@ module Sequel
1116
1120
  when nil
1117
1121
  return false
1118
1122
  when Array
1119
- return false if pk.any?(&:nil?)
1123
+ return false if pkv.any?(&:nil?)
1120
1124
  end
1121
1125
 
1122
1126
  (obj.class == model) && (obj.pk == pkv)
@@ -1718,6 +1722,7 @@ module Sequel
1718
1722
 
1719
1723
  # The values hash to use when inserting a new record.
1720
1724
  alias _insert_values values
1725
+ private :_insert_values
1721
1726
 
1722
1727
  # Refresh using a particular dataset, used inside save to make sure the same server
1723
1728
  # is used for reading newly inserted values from the database
@@ -1772,14 +1777,12 @@ module Sequel
1772
1777
  before_update
1773
1778
  columns = opts[:columns]
1774
1779
  if columns.nil?
1775
- if opts[:changed]
1776
- cc = changed_columns
1777
- columns_updated = @values.reject{|k,v| !cc.include?(k)}
1778
- cc.clear
1780
+ columns_updated = if opts[:changed]
1781
+ _save_update_changed_colums_hash
1779
1782
  else
1780
- columns_updated = _save_update_all_columns_hash
1781
- _clear_changed_columns(:update)
1783
+ _save_update_all_columns_hash
1782
1784
  end
1785
+ _clear_changed_columns(:update)
1783
1786
  else # update only the specified columns
1784
1787
  columns = Array(columns)
1785
1788
  columns_updated = @values.reject{|k, v| !columns.include?(k)}
@@ -1827,6 +1830,14 @@ module Sequel
1827
1830
  v
1828
1831
  end
1829
1832
 
1833
+ # Return a hash of values used when saving changed columns of an
1834
+ # existing object. Defaults to all of the objects current values
1835
+ # that are recorded as modified.
1836
+ def _save_update_changed_colums_hash
1837
+ cc = changed_columns
1838
+ @values.reject{|k,v| !cc.include?(k)}
1839
+ end
1840
+
1830
1841
  # Validate the object if validating on save. Skips validation
1831
1842
  # completely (including validation hooks) if
1832
1843
  # skip_validation_on_save! has been called on the object,
@@ -2225,7 +2236,9 @@ module Sequel
2225
2236
  plugin self
2226
2237
 
2227
2238
  singleton_class.send(:undef_method, :dup, :clone, :initialize_copy)
2239
+ # :nocov:
2228
2240
  if RUBY_VERSION >= '1.9.3'
2241
+ # :nocov:
2229
2242
  singleton_class.send(:undef_method, :initialize_clone, :initialize_dup)
2230
2243
  end
2231
2244
  end
@@ -40,6 +40,7 @@ module Sequel
40
40
  mod.send(:define_method, :inherited_instance_variables) do ||
41
41
  super().merge!(hash)
42
42
  end
43
+ mod.send(:private, :inherited_instance_variables)
43
44
  end
44
45
 
45
46
  # Add method to +mod+ that overrides set_dataset to call the method afterward.
@@ -148,9 +149,8 @@ module Sequel
148
149
  required_args = arity
149
150
  arity -= 1 if keyword == :required
150
151
 
151
- if callable.is_a?(Proc) && !callable.lambda?
152
- optional_args -= arity
153
- end
152
+ # callable currently is always a non-lambda Proc
153
+ optional_args -= arity
154
154
 
155
155
  [required_args, optional_args, rest, keyword]
156
156
  end
@@ -0,0 +1,66 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # The association_lazy_eager_option plugin supports passing
6
+ # an +:eager+ option to an association method. If the related
7
+ # association is already cached, the cached version will be
8
+ # returned. If the association is not already cached, it will
9
+ # be loaded, and the value of the +:eager+ option will be used
10
+ # to perform an eager load of the given associations.
11
+ # the plural versions.
12
+ #
13
+ # With Sequel's default behavior, you can already perform an
14
+ # eager load when lazy loading using a block:
15
+ #
16
+ # obj.association{|ds| ds.eager(:nested_association)}
17
+ #
18
+ # However, this will ignore any cached version. In more
19
+ # complex software, the association may already be cached
20
+ # and have the nested association cached inside of it, and
21
+ # using this callback approach then requires 2 unnecessary
22
+ # queries. This plugin will not perform any queries if the
23
+ # association is already cached, preventing duplicate work.
24
+ # However, you should make sure that an already loaded
25
+ # association has the nested association already eagerly
26
+ # loaded.
27
+ #
28
+ # Usage:
29
+ #
30
+ # # Make all model subclasses support the :eager association
31
+ # # method option (called before loading subclasses)
32
+ # Sequel::Model.plugin :association_lazy_eager_option
33
+ #
34
+ # # Make the Album class support the :eager association
35
+ # # method option
36
+ # Album.plugin :association_lazy_eager_option
37
+ module AssociationLazyEagerOption
38
+ module InstanceMethods
39
+ private
40
+
41
+ # Return a dataset for the association after applying any dynamic callback.
42
+ def _associated_dataset(opts, dynamic_opts)
43
+ ds = super
44
+
45
+ if eager = dynamic_opts[:eager]
46
+ ds = ds.eager(eager)
47
+ end
48
+
49
+ ds
50
+ end
51
+
52
+ # A placeholder literalizer that can be used to load the association, or nil to not use one.
53
+ def _associated_object_loader(opts, dynamic_opts)
54
+ return if dynamic_opts[:eager]
55
+ super
56
+ end
57
+
58
+ # Whether to use a simple primary key lookup on the associated class when loading.
59
+ def load_with_primary_key_lookup?(opts, dynamic_opts)
60
+ return false if dynamic_opts[:eager]
61
+ super
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -39,6 +39,8 @@ module Sequel
39
39
  # Album.plugin :association_multi_add_remove
40
40
  module AssociationMultiAddRemove
41
41
  module ClassMethods
42
+ private
43
+
42
44
  # Define the methods use to add/remove/set multiple associated objects
43
45
  # in a single method call.
44
46
  def def_association_instance_methods(opts)
@@ -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