sequel 5.36.0 → 5.41.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +56 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/doc/cheat_sheet.rdoc +5 -5
  6. data/doc/code_order.rdoc +0 -12
  7. data/doc/fork_safety.rdoc +84 -0
  8. data/doc/opening_databases.rdoc +5 -1
  9. data/doc/postgresql.rdoc +1 -1
  10. data/doc/querying.rdoc +3 -3
  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/release_notes/5.39.0.txt +19 -0
  14. data/doc/release_notes/5.40.0.txt +40 -0
  15. data/doc/release_notes/5.41.0.txt +25 -0
  16. data/doc/sql.rdoc +1 -1
  17. data/doc/transactions.rdoc +0 -8
  18. data/lib/sequel/adapters/jdbc.rb +15 -3
  19. data/lib/sequel/adapters/jdbc/mysql.rb +4 -4
  20. data/lib/sequel/adapters/shared/mssql.rb +21 -1
  21. data/lib/sequel/adapters/shared/oracle.rb +1 -1
  22. data/lib/sequel/adapters/shared/postgres.rb +6 -4
  23. data/lib/sequel/adapters/shared/sqlite.rb +35 -1
  24. data/lib/sequel/core.rb +5 -6
  25. data/lib/sequel/database/connecting.rb +0 -1
  26. data/lib/sequel/database/misc.rb +14 -0
  27. data/lib/sequel/database/schema_generator.rb +6 -0
  28. data/lib/sequel/database/schema_methods.rb +16 -6
  29. data/lib/sequel/database/transactions.rb +1 -1
  30. data/lib/sequel/dataset/actions.rb +10 -6
  31. data/lib/sequel/dataset/features.rb +10 -0
  32. data/lib/sequel/dataset/prepared_statements.rb +2 -0
  33. data/lib/sequel/dataset/sql.rb +32 -10
  34. data/lib/sequel/extensions/blank.rb +8 -0
  35. data/lib/sequel/extensions/date_arithmetic.rb +8 -9
  36. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  37. data/lib/sequel/extensions/inflector.rb +8 -0
  38. data/lib/sequel/extensions/migration.rb +9 -1
  39. data/lib/sequel/extensions/named_timezones.rb +5 -1
  40. data/lib/sequel/extensions/pg_array.rb +1 -0
  41. data/lib/sequel/extensions/pg_interval.rb +34 -8
  42. data/lib/sequel/extensions/pg_row.rb +1 -0
  43. data/lib/sequel/extensions/pg_row_ops.rb +24 -0
  44. data/lib/sequel/extensions/query.rb +2 -0
  45. data/lib/sequel/extensions/schema_dumper.rb +3 -3
  46. data/lib/sequel/model/associations.rb +28 -4
  47. data/lib/sequel/model/base.rb +21 -4
  48. data/lib/sequel/model/plugins.rb +5 -0
  49. data/lib/sequel/plugins/association_proxies.rb +2 -0
  50. data/lib/sequel/plugins/auto_validations.rb +15 -1
  51. data/lib/sequel/plugins/class_table_inheritance.rb +0 -5
  52. data/lib/sequel/plugins/composition.rb +5 -1
  53. data/lib/sequel/plugins/constraint_validations.rb +2 -1
  54. data/lib/sequel/plugins/dataset_associations.rb +4 -1
  55. data/lib/sequel/plugins/dirty.rb +44 -0
  56. data/lib/sequel/plugins/nested_attributes.rb +3 -1
  57. data/lib/sequel/plugins/pg_array_associations.rb +4 -0
  58. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +2 -0
  59. data/lib/sequel/plugins/single_table_inheritance.rb +7 -0
  60. data/lib/sequel/plugins/tree.rb +9 -4
  61. data/lib/sequel/plugins/validation_helpers.rb +6 -2
  62. data/lib/sequel/timezones.rb +8 -3
  63. data/lib/sequel/version.rb +1 -1
  64. metadata +33 -21
@@ -22,7 +22,7 @@ module Sequel
22
22
  def insert_sql(*values)
23
23
  return static_sql(@opts[:sql]) if @opts[:sql]
24
24
 
25
- check_modification_allowed!
25
+ check_insert_allowed!
26
26
 
27
27
  columns = []
28
28
 
@@ -172,7 +172,7 @@ module Sequel
172
172
  # than one table.
173
173
  def update_sql(values = OPTS)
174
174
  return static_sql(opts[:sql]) if opts[:sql]
175
- check_modification_allowed!
175
+ check_update_allowed!
176
176
  check_not_limited!(:update)
177
177
 
178
178
  case values
@@ -215,7 +215,7 @@ module Sequel
215
215
  lines << "def #{'_' if priv}#{type}_sql"
216
216
  lines << 'if sql = opts[:sql]; return static_sql(sql) end' unless priv
217
217
  lines << "if sql = cache_get(:_#{type}_sql); return sql end" if cacheable
218
- lines << 'check_modification_allowed!' << 'check_not_limited!(:delete)' if type == :delete
218
+ lines << 'check_delete_allowed!' << 'check_not_limited!(:delete)' if type == :delete
219
219
  lines << 'sql = @opts[:append_sql] || sql_string_origin'
220
220
 
221
221
  if clauses.all?{|c| c.is_a?(Array)}
@@ -918,10 +918,35 @@ module Sequel
918
918
  !@opts[:no_cache_sql] && !cache_get(:_no_cache_sql)
919
919
  end
920
920
 
921
- # Raise an InvalidOperation exception if deletion is not allowed for this dataset.
921
+ # Raise an InvalidOperation exception if modification is not allowed for this dataset.
922
+ # Check whether it is allowed to insert into this dataset.
923
+ # Only for backwards compatibility with older external adapters.
922
924
  def check_modification_allowed!
925
+ # SEQUEL6: Remove
926
+ Sequel::Deprecation.deprecate("Dataset#check_modification_allowed!", "Use check_{insert,delete,update,truncation}_allowed! instead")
927
+ _check_modification_allowed!(supports_modifying_joins?)
928
+ end
929
+
930
+ # Check whether it is allowed to insert into this dataset.
931
+ def check_insert_allowed!
932
+ _check_modification_allowed!(false)
933
+ end
934
+ alias check_truncation_allowed! check_insert_allowed!
935
+
936
+ # Check whether it is allowed to delete from this dataset.
937
+ def check_delete_allowed!
938
+ _check_modification_allowed!(supports_deleting_joins?)
939
+ end
940
+
941
+ # Check whether it is allowed to update this dataset.
942
+ def check_update_allowed!
943
+ _check_modification_allowed!(supports_updating_joins?)
944
+ end
945
+
946
+ # Internals of the check_*_allowed! methods
947
+ def _check_modification_allowed!(modifying_joins_supported)
923
948
  raise(InvalidOperation, "Grouped datasets cannot be modified") if opts[:group]
924
- raise(InvalidOperation, "Joined datasets cannot be modified") if !supports_modifying_joins? && joined_dataset?
949
+ raise(InvalidOperation, "Joined datasets cannot be modified") if !modifying_joins_supported && joined_dataset?
925
950
  end
926
951
 
927
952
  # Raise error if the dataset uses limits or offsets.
@@ -930,11 +955,6 @@ module Sequel
930
955
  raise InvalidOperation, "Dataset##{type} not supported on datasets with limits or offsets" if opts[:limit] || opts[:offset]
931
956
  end
932
957
 
933
- # Alias of check_modification_allowed!
934
- def check_truncation_allowed!
935
- check_modification_allowed!
936
- end
937
-
938
958
  # Append column list to SQL string.
939
959
  # If the column list is empty, a wildcard (*) is appended.
940
960
  def column_list_append(sql, columns)
@@ -971,7 +991,9 @@ module Sequel
971
991
  # operators unsupported by some databases. Used by adapters for databases
972
992
  # that don't support the operators natively.
973
993
  def complex_expression_emulate_append(sql, op, args)
994
+ # :nocov:
974
995
  case op
996
+ # :nocov:
975
997
  when :%
976
998
  complex_expression_arg_pairs_append(sql, args){|a, b| Sequel.function(:MOD, a, b)}
977
999
  when :>>
@@ -6,6 +6,14 @@
6
6
  #
7
7
  # Sequel.extension :blank
8
8
 
9
+ [FalseClass, Object, NilClass, Numeric, String, TrueClass].each do |klass|
10
+ # :nocov:
11
+ if klass.method_defined?(:blank?)
12
+ klass.send(:alias_method, :blank?, :blank?)
13
+ end
14
+ # :nocov:
15
+ end
16
+
9
17
  class FalseClass
10
18
  # false is always blank
11
19
  def blank?
@@ -49,14 +49,13 @@ module Sequel
49
49
  # Options:
50
50
  # :cast :: Cast to the specified type instead of the default if casting
51
51
  def date_sub(expr, interval, opts=OPTS)
52
- interval = if interval.is_a?(Hash)
53
- h = {}
54
- interval.each{|k,v| h[k] = -v unless v.nil?}
55
- h
56
- else
57
- -interval
52
+ if defined?(ActiveSupport::Duration) && interval.is_a?(ActiveSupport::Duration)
53
+ interval = interval.parts
58
54
  end
59
- DateAdd.new(expr, interval, opts)
55
+ parts = {}
56
+ interval.each{|k,v| parts[k] = -v unless v.nil?}
57
+ parts
58
+ DateAdd.new(expr, parts, opts)
60
59
  end
61
60
  end
62
61
 
@@ -113,12 +112,12 @@ module Sequel
113
112
  end
114
113
  when :mssql, :h2, :access, :sqlanywhere
115
114
  units = case db_type
116
- when :mssql, :sqlanywhere
117
- MSSQL_DURATION_UNITS
118
115
  when :h2
119
116
  H2_DURATION_UNITS
120
117
  when :access
121
118
  ACCESS_DURATION_UNITS
119
+ else
120
+ MSSQL_DURATION_UNITS
122
121
  end
123
122
  each_valid_interval_unit(h, units) do |value, sql_unit|
124
123
  expr = Sequel.function(:DATEADD, sql_unit, value, expr)
@@ -55,6 +55,8 @@ module Sequel
55
55
 
56
56
  module SQL
57
57
  class Expression
58
+ alias inspect inspect
59
+
58
60
  # Attempt to produce a string suitable for eval, such that:
59
61
  #
60
62
  # eval(obj.inspect) == obj
@@ -105,6 +105,14 @@ class String
105
105
  yield Inflections if block_given?
106
106
  Inflections
107
107
  end
108
+
109
+ %w'classify constantize dasherize demodulize foreign_key humanize pluralize singularize tableize underscore'.each do |m|
110
+ # :nocov:
111
+ if method_defined?(m)
112
+ alias_method(m, m)
113
+ end
114
+ # :nocov:
115
+ end
108
116
 
109
117
  # By default, camelize converts the string to UpperCamelCase. If the argument to camelize
110
118
  # is set to :lower then camelize produces lowerCamelCase.
@@ -68,7 +68,9 @@ module Sequel
68
68
  # Allow calling private methods for backwards compatibility
69
69
  @db.send(method_sym, *args, &block)
70
70
  end
71
+ # :nocov:
71
72
  ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
73
+ # :nocov:
72
74
 
73
75
  # This object responds to all methods the database responds to.
74
76
  def respond_to_missing?(meth, include_private)
@@ -330,7 +332,8 @@ module Sequel
330
332
  # schema_migrations for timestamped migrations). in the database to keep track
331
333
  # of the current migration version. If no migration version is stored in the
332
334
  # database, the version is considered to be 0. If no target version is
333
- # specified, the database is migrated to the latest version available in the
335
+ # specified, or the target version specified is greater than the latest
336
+ # version available, the database is migrated to the latest version available in the
334
337
  # migration directory.
335
338
  #
336
339
  # For example, to migrate the database to the latest version:
@@ -538,6 +541,11 @@ module Sequel
538
541
  end
539
542
 
540
543
  @direction = current < target ? :up : :down
544
+
545
+ if @direction == :down && @current >= @files.length
546
+ raise Migrator::Error, "Missing migration version(s) needed to migrate down to target version (current: #{current}, target: #{target})"
547
+ end
548
+
541
549
  @migrations = get_migrations
542
550
  end
543
551
 
@@ -84,9 +84,9 @@ module Sequel
84
84
  def convert_output_time_other(v, output_timezone)
85
85
  Time.at(v.to_i, :in => output_timezone)
86
86
  end
87
- else
88
87
  # :nodoc:
89
88
  # :nocov:
89
+ else
90
90
  def convert_input_time_other(v, input_timezone)
91
91
  local_offset = input_timezone.period_for_local(v, &tzinfo_disambiguator_for(v)).utc_total_offset
92
92
  Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i
@@ -105,6 +105,8 @@ module Sequel
105
105
  Time.new(1970, 1, 1, 0, 0, 0, local_offset) + v.to_i
106
106
  end
107
107
  end
108
+ # :nodoc:
109
+ # :nocov:
108
110
  end
109
111
 
110
112
  # Handle both TZInfo 1 and TZInfo 2
@@ -142,6 +144,8 @@ module Sequel
142
144
  # Convert timezone offset from UTC to the offset for the output_timezone
143
145
  (v - local_offset).new_offset(local_offset)
144
146
  end
147
+ # :nodoc:
148
+ # :nocov:
145
149
  end
146
150
 
147
151
  # Returns TZInfo::Timezone instance if given a String.
@@ -213,6 +213,7 @@ module Sequel
213
213
  scalar_typecast_method = :"typecast_value_#{opts.fetch(:scalar_typecast, type)}"
214
214
  define_method(meth){|v| typecast_value_pg_array(v, creator, scalar_typecast_method)}
215
215
  private meth
216
+ alias_method(meth, meth)
216
217
  end
217
218
 
218
219
  @schema_type_classes[:"#{type}_array"] = PGArray
@@ -34,6 +34,13 @@
34
34
 
35
35
  require 'active_support/duration'
36
36
 
37
+ # :nocov:
38
+ begin
39
+ require 'active_support/version'
40
+ rescue LoadError
41
+ end
42
+ # :nocov:
43
+
37
44
  module Sequel
38
45
  module Postgres
39
46
  module IntervalDatabaseMethods
@@ -61,34 +68,47 @@ module Sequel
61
68
 
62
69
  # Creates callable objects that convert strings into ActiveSupport::Duration instances.
63
70
  class Parser
71
+ # Whether ActiveSupport::Duration.new takes parts as array instead of hash
72
+ USE_PARTS_ARRAY = !defined?(ActiveSupport::VERSION::STRING) || ActiveSupport::VERSION::STRING < '5.1'
73
+
74
+ if defined?(ActiveSupport::Duration::SECONDS_PER_MONTH)
75
+ SECONDS_PER_MONTH = ActiveSupport::Duration::SECONDS_PER_MONTH
76
+ SECONDS_PER_YEAR = ActiveSupport::Duration::SECONDS_PER_YEAR
77
+ # :nocov:
78
+ else
79
+ SECONDS_PER_MONTH = 2592000
80
+ SECONDS_PER_YEAR = 31557600
81
+ # :nocov:
82
+ end
83
+
64
84
  # Parse the interval input string into an ActiveSupport::Duration instance.
65
85
  def call(string)
66
86
  raise(InvalidValue, "invalid or unhandled interval format: #{string.inspect}") unless matches = /\A([+-]?\d+ years?\s?)?([+-]?\d+ mons?\s?)?([+-]?\d+ days?\s?)?(?:(?:([+-])?(\d{2,10}):(\d\d):(\d\d(\.\d+)?))|([+-]?\d+ hours?\s?)?([+-]?\d+ mins?\s?)?([+-]?\d+(\.\d+)? secs?\s?)?)?\z/.match(string)
67
87
 
68
88
  value = 0
69
- parts = []
89
+ parts = {}
70
90
 
71
91
  if v = matches[1]
72
92
  v = v.to_i
73
- value += 31557600 * v
74
- parts << [:years, v]
93
+ value += SECONDS_PER_YEAR * v
94
+ parts[:years] = v
75
95
  end
76
96
  if v = matches[2]
77
97
  v = v.to_i
78
- value += 2592000 * v
79
- parts << [:months, v]
98
+ value += SECONDS_PER_MONTH * v
99
+ parts[:months] = v
80
100
  end
81
101
  if v = matches[3]
82
102
  v = v.to_i
83
103
  value += 86400 * v
84
- parts << [:days, v]
104
+ parts[:days] = v
85
105
  end
86
106
  if matches[5]
87
107
  seconds = matches[5].to_i * 3600 + matches[6].to_i * 60
88
108
  seconds += matches[8] ? matches[7].to_f : matches[7].to_i
89
109
  seconds *= -1 if matches[4] == '-'
90
110
  value += seconds
91
- parts << [:seconds, seconds]
111
+ parts[:seconds] = seconds
92
112
  elsif matches[9] || matches[10] || matches[11]
93
113
  seconds = 0
94
114
  if v = matches[9]
@@ -101,8 +121,14 @@ module Sequel
101
121
  seconds += matches[12] ? v.to_f : v.to_i
102
122
  end
103
123
  value += seconds
104
- parts << [:seconds, seconds]
124
+ parts[:seconds] = seconds
125
+ end
126
+
127
+ # :nocov:
128
+ if USE_PARTS_ARRAY
129
+ parts = parts.to_a
105
130
  end
131
+ # :nocov:
106
132
 
107
133
  ActiveSupport::Duration.new(value, parts)
108
134
  end
@@ -482,6 +482,7 @@ module Sequel
482
482
  row_type(db_type, v)
483
483
  end
484
484
  private meth
485
+ alias_method(meth, meth)
485
486
  end
486
487
 
487
488
  nil
@@ -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,7 +74,9 @@ 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
+ # :nocov:
77
78
  ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
79
+ # :nocov:
78
80
  end
79
81
  end
80
82
 
@@ -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
@@ -1929,7 +1929,22 @@ module Sequel
1929
1929
  # can be easily overridden in the class itself while allowing for
1930
1930
  # super to be called.
1931
1931
  def association_module_def(name, opts=OPTS, &block)
1932
- association_module(opts).send(:define_method, name, &block)
1932
+ mod = association_module(opts)
1933
+ mod.send(:define_method, name, &block)
1934
+ mod.send(:alias_method, name, name)
1935
+ end
1936
+
1937
+ # Add a method to the module included in the class, so the method
1938
+ # can be easily overridden in the class itself while allowing for
1939
+ # super to be called. This method allows passing keywords through
1940
+ # the defined methods.
1941
+ def association_module_delegate_def(name, opts, &block)
1942
+ mod = association_module(opts)
1943
+ mod.send(:define_method, name, &block)
1944
+ # :nocov:
1945
+ mod.send(:ruby2_keywords, name) if mod.respond_to?(:ruby2_keywords, true)
1946
+ # :nocov:
1947
+ mod.send(:alias_method, name, name)
1933
1948
  end
1934
1949
 
1935
1950
  # Add a private method to the module included in the class.
@@ -1981,17 +1996,17 @@ module Sequel
1981
1996
 
1982
1997
  if adder = opts[:adder]
1983
1998
  association_module_private_def(opts[:_add_method], opts, &adder)
1984
- association_module_def(opts[:add_method], opts){|o,*args| add_associated_object(opts, o, *args)}
1999
+ association_module_delegate_def(opts[:add_method], opts){|o,*args| add_associated_object(opts, o, *args)}
1985
2000
  end
1986
2001
 
1987
2002
  if remover = opts[:remover]
1988
2003
  association_module_private_def(opts[:_remove_method], opts, &remover)
1989
- association_module_def(opts[:remove_method], opts){|o,*args| remove_associated_object(opts, o, *args)}
2004
+ association_module_delegate_def(opts[:remove_method], opts){|o,*args| remove_associated_object(opts, o, *args)}
1990
2005
  end
1991
2006
 
1992
2007
  if clearer = opts[:clearer]
1993
2008
  association_module_private_def(opts[:_remove_all_method], opts, &clearer)
1994
- association_module_def(opts[:remove_all_method], opts){|*args| remove_all_associated_objects(opts, *args)}
2009
+ association_module_delegate_def(opts[:remove_all_method], opts){|*args| remove_all_associated_objects(opts, *args)}
1995
2010
  end
1996
2011
  end
1997
2012
 
@@ -2423,6 +2438,9 @@ module Sequel
2423
2438
  run_association_callbacks(opts, :after_add, o)
2424
2439
  o
2425
2440
  end
2441
+ # :nocov:
2442
+ ruby2_keywords(:add_associated_object) if respond_to?(:ruby2_keywords, true)
2443
+ # :nocov:
2426
2444
 
2427
2445
  # Add/Set the current object to/as the given object's reciprocal association.
2428
2446
  def add_reciprocal_object(opts, o)
@@ -2565,6 +2583,9 @@ module Sequel
2565
2583
  associations[opts[:name]] = []
2566
2584
  ret
2567
2585
  end
2586
+ # :nocov:
2587
+ ruby2_keywords(:remove_all_associated_objects) if respond_to?(:ruby2_keywords, true)
2588
+ # :nocov:
2568
2589
 
2569
2590
  # Remove the given associated object from the given association
2570
2591
  def remove_associated_object(opts, o, *args)
@@ -2586,6 +2607,9 @@ module Sequel
2586
2607
  run_association_callbacks(opts, :after_remove, o)
2587
2608
  o
2588
2609
  end
2610
+ # :nocov:
2611
+ ruby2_keywords(:remove_associated_object) if respond_to?(:ruby2_keywords, true)
2612
+ # :nocov:
2589
2613
 
2590
2614
  # Check that the object from the associated table specified by the primary key
2591
2615
  # is currently associated to the receiver. If it is associated, return the object, otherwise