sequel 5.108.0 → 5.110.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sequel/adapters/ado.rb +1 -0
  3. data/lib/sequel/adapters/amalgalite.rb +34 -8
  4. data/lib/sequel/adapters/jdbc/mssql.rb +28 -5
  5. data/lib/sequel/adapters/jdbc/postgresql.rb +14 -3
  6. data/lib/sequel/adapters/jdbc.rb +2 -1
  7. data/lib/sequel/adapters/postgres.rb +5 -2
  8. data/lib/sequel/adapters/shared/access.rb +5 -1
  9. data/lib/sequel/adapters/shared/db2.rb +3 -0
  10. data/lib/sequel/adapters/shared/mssql.rb +7 -1
  11. data/lib/sequel/adapters/shared/oracle.rb +7 -0
  12. data/lib/sequel/adapters/shared/postgres.rb +97 -1001
  13. data/lib/sequel/adapters/shared/sqlanywhere.rb +0 -32
  14. data/lib/sequel/adapters/sqlite.rb +13 -9
  15. data/lib/sequel/adapters/trilogy.rb +2 -1
  16. data/lib/sequel/core.rb +71 -23
  17. data/lib/sequel/database/misc.rb +1 -6
  18. data/lib/sequel/database/schema_methods.rb +3 -3
  19. data/lib/sequel/database/transactions.rb +1 -3
  20. data/lib/sequel/dataset/actions.rb +1 -1
  21. data/lib/sequel/dataset/sql.rb +14 -3
  22. data/lib/sequel/extensions/core_refinements.rb +1 -4
  23. data/lib/sequel/extensions/current_datetime_timestamp.rb +2 -2
  24. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -3
  25. data/lib/sequel/extensions/lit_require_frozen.rb +17 -1
  26. data/lib/sequel/extensions/named_timezones.rb +9 -7
  27. data/lib/sequel/extensions/pg_array.rb +1 -0
  28. data/lib/sequel/extensions/pg_json_ops.rb +4 -2
  29. data/lib/sequel/extensions/pg_loose_count.rb +37 -2
  30. data/lib/sequel/extensions/s.rb +4 -8
  31. data/lib/sequel/extensions/schema_dumper.rb +6 -2
  32. data/lib/sequel/extensions/sql_comments.rb +0 -4
  33. data/lib/sequel/extensions/string_date_time.rb +7 -4
  34. data/lib/sequel/extensions/symbol_aref.rb +11 -31
  35. data/lib/sequel/extensions/symbol_aref_refinement.rb +1 -4
  36. data/lib/sequel/extensions/symbol_as.rb +1 -2
  37. data/lib/sequel/extensions/symbol_as_refinement.rb +1 -5
  38. data/lib/sequel/model/base.rb +4 -9
  39. data/lib/sequel/plugins/caching.rb +27 -2
  40. data/lib/sequel/plugins/csv_serializer.rb +9 -20
  41. data/lib/sequel/plugins/instance_filters.rb +8 -0
  42. data/lib/sequel/plugins/instance_hooks.rb +19 -0
  43. data/lib/sequel/plugins/mssql_optimistic_locking.rb +9 -0
  44. data/lib/sequel/plugins/optimistic_locking.rb +12 -2
  45. data/lib/sequel/plugins/optimistic_locking_base.rb +14 -0
  46. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +11 -2
  47. data/lib/sequel/plugins/select_on_skipped_update.rb +63 -0
  48. data/lib/sequel/plugins/single_table_inheritance.rb +20 -2
  49. data/lib/sequel/plugins/subset_conditions.rb +5 -16
  50. data/lib/sequel/plugins/throw_failures.rb +1 -1
  51. data/lib/sequel/plugins/validate_associated.rb +7 -1
  52. data/lib/sequel/plugins/validate_associated_context.rb +52 -0
  53. data/lib/sequel/sql.rb +109 -105
  54. data/lib/sequel/version.rb +1 -1
  55. metadata +5 -3
@@ -22,14 +22,17 @@ class String
22
22
 
23
23
  # Converts a string into a DateTime object.
24
24
  def to_datetime
25
- DateTime.parse(self, Sequel.convert_two_digit_years)
26
- rescue => e
27
- raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
25
+ Sequel::Deprecation.deprecate("String#to_datetime is deprecated and will be removed in Sequel 6.")
26
+ begin
27
+ DateTime.parse(self, Sequel.convert_two_digit_years)
28
+ rescue => e
29
+ raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
30
+ end
28
31
  end
29
32
 
30
33
  # Converts a string into a Time or DateTime object, depending on the
31
34
  # value of Sequel.datetime_class
32
- def to_sequel_time
35
+ def to_sequel_time # SEQUEL6: Simplify making an alias to to_time
33
36
  if Sequel.datetime_class == DateTime
34
37
  DateTime.parse(self, Sequel.convert_two_digit_years)
35
38
  else
@@ -15,41 +15,21 @@
15
15
  #
16
16
  # Sequel.extension :symbol_aref
17
17
  #
18
- # If you are using Ruby 2+, and you would like to use refinements, there
19
- # is a refinement version of this in the symbol_aref_refinement extension.
18
+ # There is a refinement version of this in the symbol_aref_refinement extension.
20
19
  #
21
20
  # Related module: Sequel::SymbolAref
22
21
 
23
- if RUBY_VERSION >= '2.0'
24
- module Sequel::SymbolAref
25
- def [](v)
26
- case v
27
- when Symbol, Sequel::SQL::Identifier, Sequel::SQL::QualifiedIdentifier
28
- Sequel::SQL::QualifiedIdentifier.new(self, v)
29
- else
30
- super
31
- end
22
+ module Sequel::SymbolAref
23
+ def [](v)
24
+ case v
25
+ when Symbol, Sequel::SQL::Identifier, Sequel::SQL::QualifiedIdentifier
26
+ Sequel::SQL::QualifiedIdentifier.new(self, v)
27
+ else
28
+ super
32
29
  end
33
30
  end
31
+ end
34
32
 
35
- class Symbol
36
- prepend Sequel::SymbolAref
37
- end
38
- # simplecov:disable
39
- else
40
- class Symbol
41
- if method_defined?(:[])
42
- alias_method :aref_before_sequel, :[]
43
- end
44
-
45
- def [](v)
46
- case v
47
- when Symbol, Sequel::SQL::Identifier, Sequel::SQL::QualifiedIdentifier
48
- Sequel::SQL::QualifiedIdentifier.new(self, v)
49
- else
50
- aref_before_sequel(v)
51
- end
52
- end
53
- end
33
+ class Symbol
34
+ prepend Sequel::SymbolAref
54
35
  end
55
- # simplecov:enable
@@ -25,10 +25,7 @@
25
25
  #
26
26
  # Related module: Sequel::SymbolAref
27
27
 
28
- # simplecov:disable
29
- raise(Sequel::Error, "Refinements require ruby 2.0.0 or greater") unless RUBY_VERSION >= '2.0.0'
30
- # simplecov:enable
31
-
28
+ #
32
29
  module Sequel::SymbolAref
33
30
  refine Symbol do
34
31
  def [](v)
@@ -14,8 +14,7 @@
14
14
  #
15
15
  # Sequel.extension :symbol_as
16
16
  #
17
- # If you are using Ruby 2+, and you would like to use refinements, there
18
- # is a refinement version of this in the symbol_as_refinement extension.
17
+ # There is a refinement version of this in the symbol_as_refinement extension.
19
18
 
20
19
  #
21
20
  class Symbol
@@ -23,10 +23,7 @@
23
23
  #
24
24
  # Related module: Sequel::SymbolAs
25
25
 
26
- # simplecov:disable
27
- raise(Sequel::Error, "Refinements require ruby 2.0.0 or greater") unless RUBY_VERSION >= '2.0.0'
28
- # simplecov:enable
29
-
26
+ #
30
27
  module Sequel::SymbolAs
31
28
  refine Symbol do
32
29
  def as(aliaz, columns=nil)
@@ -34,4 +31,3 @@ module Sequel::SymbolAs
34
31
  end
35
32
  end
36
33
  end
37
-
@@ -1188,7 +1188,7 @@ END
1188
1188
  # By default it calls send with the argument to get the value. This can
1189
1189
  # be overridden if you have columns that conflict with existing
1190
1190
  # method names.
1191
- alias get_column_value send
1191
+ define_method(:get_column_value, Kernel.instance_method(:send))
1192
1192
 
1193
1193
  # Set the value of the column. Takes two arguments. The first is a
1194
1194
  # symbol or string argument for the column name, suffixed with =. The
@@ -1196,7 +1196,7 @@ END
1196
1196
  # with the argument to set the value. This can be overridden if you have
1197
1197
  # columns that conflict with existing method names (unlikely for setter
1198
1198
  # methods, but possible).
1199
- alias set_column_value send
1199
+ define_method(:set_column_value, Kernel.instance_method(:send))
1200
1200
 
1201
1201
  # Creates new instance and passes the given values to set.
1202
1202
  # If a block is given, yield the instance to the block.
@@ -1293,7 +1293,7 @@ END
1293
1293
  # self.class.
1294
1294
  #
1295
1295
  # Artist.new.model # => Artist
1296
- alias_method :model, :class
1296
+ define_method(:model, Kernel.instance_method(:class))
1297
1297
 
1298
1298
  # The autoincrementing primary key for this model object. Should be
1299
1299
  # overridden if you have a composite primary key with one part of it
@@ -2433,12 +2433,7 @@ END
2433
2433
  plugin self
2434
2434
  def_initialize_nil_instance_variables
2435
2435
 
2436
- singleton_class.send(:undef_method, :dup, :clone, :initialize_copy)
2437
- # simplecov:disable
2438
- if RUBY_VERSION >= '1.9.3'
2439
- # simplecov:enable
2440
- singleton_class.send(:undef_method, :initialize_clone, :initialize_dup)
2441
- end
2436
+ singleton_class.send(:undef_method, :dup, :clone, :initialize_copy, :initialize_clone, :initialize_dup)
2442
2437
 
2443
2438
  # simplecov:disable
2444
2439
  if defined?(Sequel::Postgres::SEQUEL_PG_VERSION_INTEGER) && Sequel::Postgres::SEQUEL_PG_VERSION_INTEGER >= 11800
@@ -29,6 +29,13 @@ module Sequel
29
29
  #
30
30
  # You should not use this plugin if you are using sharding and there are different
31
31
  # rows for the same primary key on different shards.
32
+ #
33
+ # Note that if you are using a composite key, and multiple columns in the composite
34
+ # key could potentially contain the character ",", you should override the
35
+ # +composite_cache_key+ class method to return an an unambiguous string identifying
36
+ # the specific primary key argument given. For consistency, if you would like to
37
+ # modify the cache key string used for scalar primary keys, you can override the
38
+ # +scalar_cache_key+ class method.
32
39
  #
33
40
  # Usage:
34
41
  #
@@ -81,8 +88,26 @@ module Sequel
81
88
 
82
89
  # Return a key string for the given primary key.
83
90
  def cache_key(pk)
84
- raise(Error, 'no primary key for this record') unless pk.is_a?(Array) ? pk.all? : pk
85
- "#{cache_key_prefix}:#{Array(pk).join(',')}"
91
+ case pk
92
+ when Array
93
+ raise(Error, 'no primary key for this record') unless pk.all?
94
+ "#{cache_key_prefix}:#{composite_cache_key(pk)}"
95
+ else
96
+ raise(Error, 'no primary key for this record') unless pk
97
+ "#{cache_key_prefix}:#{scalar_cache_key(pk)}"
98
+ end
99
+ end
100
+
101
+ # Return an appropriate cache key string for a valid scalar primary key.
102
+ # By default, converts the primary key to a string.
103
+ def scalar_cache_key(pk)
104
+ pk.to_s
105
+ end
106
+
107
+ # Return an appropriate cache key string for a valid composite primary key.
108
+ # By default, joins with the parts of the compsite key with ",".
109
+ def composite_cache_key(pk)
110
+ pk.join(',')
86
111
  end
87
112
 
88
113
  Plugins.inherited_instance_variables(self, :@cache_store=>nil, :@cache_ttl=>nil, :@cache_ignore_exceptions=>nil)
@@ -73,23 +73,12 @@ module Sequel
73
73
  end
74
74
  end
75
75
 
76
- # Avoid keyword argument separation warnings on Ruby 2.7, while still
77
- # being compatible with 1.9.
78
- if RUBY_VERSION >= "2.0"
79
- instance_eval(<<-END, __FILE__, __LINE__+1)
80
- def self.csv_call(*args, opts, &block)
81
- CSV.send(*args, **opts, &block)
82
- end
83
- END
84
- else
85
- # simplecov:disable
86
- # :nodoc:
87
- def self.csv_call(*args, opts, &block)
88
- CSV.send(*args, opts, &block)
89
- end
90
- # :nodoc:
91
- # simplecov:enable
76
+ # simplecov:disable
77
+ def self.csv_call(*args, opts, &block)
78
+ Sequel::Deprecation.deprecate("Sequel::Plugins::CsvSerializer.csv_call will be removed in Sequel 6.")
79
+ CSV.send(*args, **opts, &block)
92
80
  end
81
+ # simplecov:enable
93
82
 
94
83
  module ClassMethods
95
84
  # The default opts to use when serializing model objects to CSV
@@ -97,7 +86,7 @@ module Sequel
97
86
 
98
87
  # Attempt to parse an array of instances from the given CSV string
99
88
  def array_from_csv(csv, opts = OPTS)
100
- CsvSerializer.csv_call(:parse, csv, process_csv_serializer_opts(opts)).map do |row|
89
+ CSV.parse(csv, **process_csv_serializer_opts(opts)).map do |row|
101
90
  row = row.to_hash
102
91
  row.delete(nil)
103
92
  new(row)
@@ -147,7 +136,7 @@ module Sequel
147
136
  # :headers :: The headers to use for the CSV line. Use nil for a header
148
137
  # to specify the column should be ignored.
149
138
  def from_csv(csv, opts = OPTS)
150
- row = CsvSerializer.csv_call(:parse_line, csv, model.process_csv_serializer_opts(opts)).to_hash
139
+ row = CSV.parse_line(csv, **model.process_csv_serializer_opts(opts)).to_hash
151
140
  row.delete(nil)
152
141
  set(row)
153
142
  end
@@ -165,7 +154,7 @@ module Sequel
165
154
  opts = model.process_csv_serializer_opts(opts)
166
155
  headers = opts[:headers]
167
156
 
168
- CsvSerializer.csv_call(:generate, model.process_csv_serializer_opts(opts)) do |csv|
157
+ CSV.generate(**model.process_csv_serializer_opts(opts)) do |csv|
169
158
  csv << headers.map{|k| public_send(k)}
170
159
  end
171
160
  end
@@ -184,7 +173,7 @@ module Sequel
184
173
  items = opts.delete(:array) || self
185
174
  headers = opts[:headers]
186
175
 
187
- CsvSerializer.csv_call(:generate, opts) do |csv|
176
+ CSV.generate(**opts) do |csv|
188
177
  items.each do |object|
189
178
  csv << headers.map{|header| object.public_send(header)}
190
179
  end
@@ -80,6 +80,14 @@ module Sequel
80
80
  instance_filters << [args, block]
81
81
  end
82
82
 
83
+ # Clear instance filters when saving changes even if the
84
+ # object wasn't modified.
85
+ def save_changes(opts=OPTS)
86
+ ret = super
87
+ clear_instance_filters if ret.nil?
88
+ ret
89
+ end
90
+
83
91
  private
84
92
 
85
93
  # If there are any instance filters, make sure not to use the
@@ -93,6 +93,25 @@ module Sequel
93
93
  super
94
94
  end
95
95
 
96
+ # Run and clear save/update/validation hooks when saving changes even if the
97
+ # object wasn't modified.
98
+ def save_changes(opts=OPTS)
99
+ ret = super
100
+ if ret.nil? && @instance_hooks
101
+ run_before_instance_hooks(:before_validation)
102
+ run_before_instance_hooks(:after_validation)
103
+ run_before_instance_hooks(:before_save)
104
+ run_before_instance_hooks(:before_update)
105
+ ret = super
106
+ run_before_instance_hooks(:after_update)
107
+ run_before_instance_hooks(:after_save)
108
+ [:before_validation, :after_validation, :before_save, :before_update, :after_update, :after_save].each do |type|
109
+ @instance_hooks.delete(type)
110
+ end
111
+ end
112
+ ret
113
+ end
114
+
96
115
  private
97
116
 
98
117
  # Add the block as an instance level hook. For before hooks, add it to
@@ -22,6 +22,13 @@ module Sequel
22
22
  #
23
23
  # plugin :mssql_optimistic_locking, lock_column: :column_name
24
24
  #
25
+ # By default, the plugin allows using the lock column setter method to
26
+ # either increase or decrease the lock column value. To prevent increases,
27
+ # which could potentially be used to skip an update when it update should
28
+ # be made, you can use the +:prevent_increase+ plugin option:
29
+ #
30
+ # plugin :mssql_optimistic_locking, prevent_increase: true
31
+ #
25
32
  # This plugin relies on the instance_filters plugin.
26
33
  module MssqlOptimisticLocking
27
34
  # Load the instance_filters plugin into the model.
@@ -32,6 +39,8 @@ module Sequel
32
39
  # Set the lock column
33
40
  def self.configure(model, opts=OPTS)
34
41
  model.lock_column = opts[:lock_column] || model.lock_column || :timestamp
42
+ # SEQUEL6: Make prevent_increase true by default
43
+ model.send(:prevent_lock_column_increase!) if opts[:prevent_increase]
35
44
  end
36
45
 
37
46
  module InstanceMethods
@@ -20,6 +20,13 @@ module Sequel
20
20
  #
21
21
  # plugin :optimistic_locking, lock_column: :version
22
22
  #
23
+ # By default, the plugin allows using the lock column setter method to
24
+ # either increase or decrease the lock column value. To prevent increases,
25
+ # which could potentially be used to skip an update when it update should
26
+ # be made, you can use the +:prevent_increase+ plugin option:
27
+ #
28
+ # plugin :optimistic_locking, prevent_increase: true
29
+ #
23
30
  # This plugin relies on the instance_filters plugin.
24
31
  module OptimisticLocking
25
32
  # Exception class raised when trying to update or destroy a stale object.
@@ -32,6 +39,8 @@ module Sequel
32
39
  # Set the lock column
33
40
  def self.configure(model, opts=OPTS)
34
41
  model.lock_column = opts[:lock_column] || model.lock_column || :lock_version
42
+ # SEQUEL6: Make prevent_increase true by default
43
+ model.send(:prevent_lock_column_increase!) if opts[:prevent_increase]
35
44
  end
36
45
 
37
46
  module InstanceMethods
@@ -42,9 +51,10 @@ module Sequel
42
51
  def _update_columns(columns)
43
52
  lc = model.lock_column
44
53
  lcv = get_column_value(lc)
45
- columns[lc] = lcv + 1
54
+ new_lcv = columns[lc] = lcv + 1
46
55
  super
47
- set_column_value("#{lc}=", lcv + 1)
56
+ set_column_value("#{lc}=", nil)
57
+ set_column_value("#{lc}=", new_lcv)
48
58
  changed_columns.delete(lc)
49
59
  nil
50
60
  end
@@ -12,6 +12,20 @@ module Sequel
12
12
  module ClassMethods
13
13
  # The column holding the version of the lock
14
14
  attr_accessor :lock_column
15
+
16
+ private
17
+
18
+ # Prevent increasing the value of the lock column. If the lock column
19
+ # has an existing value, do not allow setting a value that is higher
20
+ # than it.
21
+ def prevent_lock_column_increase!
22
+ lc = lock_column
23
+ define_method(:"#{lc}=") do |v|
24
+ unless v && (lv = send(lc)) && v > lv
25
+ super(v)
26
+ end
27
+ end
28
+ end
15
29
 
16
30
  Plugins.inherited_instance_variables(self, :@lock_column=>nil)
17
31
  end
@@ -21,6 +21,13 @@ module Sequel
21
21
  # optimistic locking. The disadvantage is that testing can be
22
22
  # more difficult if you are modifying the underlying row between
23
23
  # when a model is retrieved and when it is saved.
24
+
25
+ # By default, the plugin allows using the +xmin=+ setter method to
26
+ # either increase or decrease the lock value. To prevent increases,
27
+ # which could potentially be used to skip an update when it update should
28
+ # be made, you can use the +:prevent_increase+ plugin argument:
29
+ #
30
+ # plugin :pg_xmin_optimistic_locking, prevent_increase: true
24
31
  #
25
32
  # This plugin may not work with the class_table_inheritance plugin.
26
33
  #
@@ -29,7 +36,7 @@ module Sequel
29
36
  WILDCARD = LiteralString.new('*').freeze
30
37
 
31
38
  # Define the xmin column accessor
32
- def self.apply(model)
39
+ def self.apply(model, prevent_increase: false)
33
40
  model.instance_exec do
34
41
  plugin(:optimistic_locking_base)
35
42
  @lock_column = :xmin
@@ -39,9 +46,11 @@ module Sequel
39
46
 
40
47
  # Update the dataset to append the xmin column if it is usable
41
48
  # and there is a dataset for the model.
42
- def self.configure(model)
49
+ def self.configure(model, prevent_increase: false)
43
50
  model.instance_exec do
44
51
  set_dataset(@dataset) if @dataset
52
+ # SEQUEL6: Make prevent_increase true by default
53
+ prevent_lock_column_increase! if prevent_increase
45
54
  end
46
55
  end
47
56
 
@@ -0,0 +1,63 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # This plugin changes the default model behavior so that for existing
6
+ # objects, if Model#save_changes skips the save as the object is not
7
+ # modified, or if the UPDATE query is skipped as there were no columns
8
+ # to update, Sequel will issue a SELECT query to make sure the object
9
+ # still exists. This is only done if require_modification is set for
10
+ # the object, so that these cases will raise the same error that
11
+ # Sequel would raise if the UPDATE require was run but did not modify
12
+ # a row.
13
+ #
14
+ # class Item < Sequel::Model
15
+ # plugin :select_on_skipped_update
16
+ # end
17
+ #
18
+ # item = Item[1]
19
+ #
20
+ # # Delete the item from the database
21
+ # Item.where(id: 1).delete
22
+ #
23
+ # # By default, this would return nil, but with the plugin,
24
+ # # this raises Sequel::NoExistingRow.
25
+ # item.save_changes
26
+ #
27
+ # # By default, this would return the object without issuing the
28
+ # # UPDATE query (as the columns to update is empty. With the plugin,
29
+ # # this raises Sequel::NoExistingRow.
30
+ # item.save(columns: [])
31
+ module SelectOnSkippedUpdate
32
+ COUNT = Sequel.function(:count).*.as(:count)
33
+ private_constant :COUNT
34
+
35
+ module InstanceMethods
36
+ # If an UPDATE statement was skipped as the object wasn't modified, run a SELECT query.
37
+ def save_changes(opts=OPTS)
38
+ ret = super
39
+ run_select_on_skipped_update if ret.nil? && require_modification
40
+ ret
41
+ end
42
+
43
+ private
44
+
45
+ # If an UPDATE statement was skipped as there were no columns to update, run a SELECT query.
46
+ def _update_columns(columns)
47
+ ret = super
48
+ run_select_on_skipped_update if ret.nil? && columns.empty? && require_modification
49
+ ret
50
+ end
51
+
52
+ # Run a SELECT query on the dataset to determine whether the object still exists in the database.
53
+ # This will result in an exception if a single row is not modified.
54
+ def run_select_on_skipped_update
55
+ ds = _update_dataset
56
+ unless ds.select(COUNT).single_value! == 1
57
+ raise(NoMatchingRow, "Attempt to select object did not result in a single row modification")
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -12,6 +12,11 @@ module Sequel
12
12
  # argument to the plugin) holds the class name as a string. However,
13
13
  # you can override this by using the <tt>:model_map</tt> option and/or
14
14
  # the <tt>:key_map</tt> option.
15
+ #
16
+ # The +sti_key+ column can be set via mass assignment, just like any
17
+ # non-primary key column. Make sure to follow the recommendations in the
18
+ # {mass assignment guide}[rdoc-ref:doc/mass_assignment.rdoc]
19
+ # to ensure this column is not set with untrusted data.
15
20
  #
16
21
  # You should only load this plugin in the parent class, not in the subclasses.
17
22
  #
@@ -84,6 +89,7 @@ module Sequel
84
89
  @sti_key_array = nil
85
90
  @sti_key = key
86
91
  @sti_dataset = dataset
92
+ @sti_root_class = self
87
93
  @sti_model_map = opts[:model_map] || lambda{|v| v if v && v != ''}
88
94
  @sti_key_map = if km = opts[:key_map]
89
95
  if km.is_a?(Hash)
@@ -150,7 +156,7 @@ module Sequel
150
156
  # This defaults to a lookup in the key map.
151
157
  attr_reader :sti_key_chooser
152
158
 
153
- Plugins.inherited_instance_variables(self, :@sti_dataset=>nil, :@sti_key=>nil, :@sti_key_map=>nil, :@sti_model_map=>nil, :@sti_key_chooser=>nil)
159
+ Plugins.inherited_instance_variables(self, :@sti_dataset=>nil, :@sti_key=>nil, :@sti_key_map=>nil, :@sti_model_map=>nil, :@sti_key_chooser=>nil, :@sti_root_class=>nil)
154
160
 
155
161
  # Freeze STI information when freezing model class. Note that
156
162
  # because of how STI works, you should not freeze an STI subclass
@@ -221,11 +227,23 @@ module Sequel
221
227
  # Return a class object. If a class is given, return it directly.
222
228
  # Treat strings and symbols as class names. If nil is given or
223
229
  # an invalid class name string or symbol is used, return self.
230
+ # If the class is valid but is not a subclass of the class that
231
+ # loaded the single_table_inheritance plugin, use the class that
232
+ # loaded the plugin.
224
233
  # Raise an error for other types.
225
234
  def sti_class(v)
226
235
  case v
227
236
  when String, Symbol
228
- constantize(v) rescue self
237
+ begin
238
+ klass = constantize(v)
239
+ if Class === klass && @sti_root_class >= klass
240
+ klass
241
+ else
242
+ @sti_root_class
243
+ end
244
+ rescue
245
+ self
246
+ end
229
247
  when nil
230
248
  self
231
249
  when Class
@@ -105,22 +105,11 @@ module Sequel
105
105
 
106
106
  private
107
107
 
108
- if RUBY_VERSION >= '2'
109
- # Backbone of #where_any and #where_all
110
- def _where_any_all(meth, name, args)
111
- ds = model.dataset
112
- # #bind used here because the dataset module may not yet be included in the model's dataset
113
- where(name, Sequel.send(meth, *args.map{|a| self.instance_method(:"#{a}_conditions").bind(ds).call}))
114
- end
115
- else
116
- # Cannot bind module method to arbitrary objects in Ruby 1.9.
117
- # simplecov:disable
118
- def _where_any_all(meth, name, args)
119
- ds = model.dataset.clone
120
- ds.extend(self)
121
- where(name, Sequel.send(meth, *args.map{|a| ds.send(:"#{a}_conditions")}))
122
- end
123
- # simplecov:enable
108
+ # Backbone of #where_any and #where_all
109
+ def _where_any_all(meth, name, args)
110
+ ds = model.dataset
111
+ # #bind used here because the dataset module may not yet be included in the model's dataset
112
+ where(name, Sequel.send(meth, *args.map{|a| self.instance_method(:"#{a}_conditions").bind(ds).call}))
124
113
  end
125
114
  end
126
115
  end
@@ -64,7 +64,7 @@ module Sequel
64
64
  end
65
65
  end
66
66
 
67
- if RUBY_VERSION >= '2.2' && (!defined?(JRUBY_VERSION) || JRUBY_VERSION > '9.1')
67
+ if RUBY_VERSION >= '2.2' && (!defined?(JRUBY_VERSION) || JRUBY_VERSION.to_r > Rational('9.1'))
68
68
  # Throw HookFailed with the generated error. If the throw is not
69
69
  # caught, just return the originally generated error.
70
70
  def hook_failed_error(msg)
@@ -43,6 +43,12 @@ module Sequel
43
43
  module InstanceMethods
44
44
  private
45
45
 
46
+ # Whether the associated object is valid. By default, the same as calling
47
+ # +valid?+ on the object, but exists so it can be overridden.
48
+ def associated_object_valid?(reflection, obj)
49
+ obj.valid?
50
+ end
51
+
46
52
  # Delay validating the associated object until validating the current object.
47
53
  def delay_validate_associated_object(reflection, obj)
48
54
  after_validation_hook{validate_associated_object(reflection, obj)}
@@ -62,7 +68,7 @@ module Sequel
62
68
  end
63
69
  end
64
70
 
65
- unless obj.valid?
71
+ unless associated_object_valid?(reflection, obj)
66
72
  if ignore_key_errors
67
73
  # Ignore errors on the key column in the associated object. This column
68
74
  # will be set when saving to a presumably valid value using a column
@@ -0,0 +1,52 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # The validate_associated_context plugin extends the validate_associated
6
+ # plugin to work with the validation_contexts plugin. It ensures associated
7
+ # objects are validated using the validation context of the current object.
8
+ #
9
+ # This plugin loads the validate_associated and validation_contexts plugins.
10
+ # The associated model must also use the validation_contexts plugin in order
11
+ # to access the validation context.
12
+ #
13
+ # class Album < Sequel::Model
14
+ # plugin :validate_associated_context
15
+ # many_to_one :artist
16
+ #
17
+ # def validate
18
+ # super
19
+ # validate_associated_object(model.association_reflection(:artist), artist) if artist
20
+ # end
21
+ # end
22
+ #
23
+ # class Artist < Sequel::Model
24
+ # plugin :validation_contexts
25
+ # def validate
26
+ # super
27
+ # errors.add(:name, 'not present') if name.nil? && validation_context == :publish
28
+ # end
29
+ # end
30
+ #
31
+ # album = Album.new(artist: Artist.new)
32
+ # album.valid? # => true
33
+ # album.valid?(validation_context: :publish) # => false
34
+ module ValidateAssociatedContext
35
+ # Depend on the validate_associated and validation_contexts plugins.
36
+ def self.apply(model)
37
+ model.plugin :validate_associated
38
+ model.plugin :validation_contexts
39
+ end
40
+
41
+ module InstanceMethods
42
+ private
43
+
44
+ # Whether the associated object is valid, using the validation context
45
+ # of the current object.
46
+ def associated_object_valid?(reflection, obj)
47
+ obj.valid?(:validation_context=>validation_context)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end