activerecord 5.2.0 → 5.2.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +214 -0
  3. data/lib/active_record/association_relation.rb +3 -3
  4. data/lib/active_record/associations.rb +9 -9
  5. data/lib/active_record/associations/alias_tracker.rb +1 -1
  6. data/lib/active_record/associations/association.rb +25 -10
  7. data/lib/active_record/associations/belongs_to_association.rb +14 -5
  8. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +4 -1
  9. data/lib/active_record/associations/builder/belongs_to.rb +11 -2
  10. data/lib/active_record/associations/collection_association.rb +19 -15
  11. data/lib/active_record/associations/collection_proxy.rb +8 -34
  12. data/lib/active_record/associations/has_many_association.rb +9 -0
  13. data/lib/active_record/associations/has_many_through_association.rb +25 -1
  14. data/lib/active_record/associations/has_one_association.rb +8 -0
  15. data/lib/active_record/associations/has_one_through_association.rb +5 -1
  16. data/lib/active_record/associations/join_dependency.rb +39 -64
  17. data/lib/active_record/associations/join_dependency/join_association.rb +12 -18
  18. data/lib/active_record/associations/join_dependency/join_part.rb +7 -0
  19. data/lib/active_record/associations/singular_association.rb +4 -10
  20. data/lib/active_record/associations/through_association.rb +1 -1
  21. data/lib/active_record/attribute_methods/dirty.rb +15 -10
  22. data/lib/active_record/attribute_methods/read.rb +1 -1
  23. data/lib/active_record/autosave_association.rb +7 -2
  24. data/lib/active_record/callbacks.rb +4 -0
  25. data/lib/active_record/collection_cache_key.rb +2 -2
  26. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +14 -8
  27. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +19 -6
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +5 -0
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -0
  31. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -4
  32. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +5 -14
  33. data/lib/active_record/connection_adapters/abstract/transaction.rb +23 -14
  34. data/lib/active_record/connection_adapters/abstract_adapter.rb +3 -1
  35. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +18 -19
  36. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  37. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +11 -2
  38. data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -0
  39. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +2 -2
  40. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +11 -1
  41. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +4 -0
  42. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  43. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +12 -26
  44. data/lib/active_record/connection_adapters/postgresql/utils.rb +1 -1
  45. data/lib/active_record/connection_adapters/postgresql_adapter.rb +9 -1
  46. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +5 -0
  47. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +5 -1
  48. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -4
  49. data/lib/active_record/core.rb +2 -1
  50. data/lib/active_record/counter_cache.rb +17 -13
  51. data/lib/active_record/enum.rb +1 -0
  52. data/lib/active_record/errors.rb +18 -12
  53. data/lib/active_record/gem_version.rb +1 -1
  54. data/lib/active_record/log_subscriber.rb +1 -1
  55. data/lib/active_record/migration.rb +1 -1
  56. data/lib/active_record/migration/compatibility.rb +15 -15
  57. data/lib/active_record/model_schema.rb +1 -1
  58. data/lib/active_record/persistence.rb +6 -5
  59. data/lib/active_record/query_cache.rb +4 -11
  60. data/lib/active_record/querying.rb +1 -1
  61. data/lib/active_record/railtie.rb +1 -3
  62. data/lib/active_record/relation.rb +39 -20
  63. data/lib/active_record/relation/calculations.rb +11 -8
  64. data/lib/active_record/relation/delegation.rb +30 -0
  65. data/lib/active_record/relation/finder_methods.rb +10 -8
  66. data/lib/active_record/relation/merger.rb +10 -11
  67. data/lib/active_record/relation/predicate_builder.rb +20 -14
  68. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  69. data/lib/active_record/relation/query_attribute.rb +5 -3
  70. data/lib/active_record/relation/query_methods.rb +45 -19
  71. data/lib/active_record/relation/spawn_methods.rb +1 -1
  72. data/lib/active_record/scoping/named.rb +2 -0
  73. data/lib/active_record/tasks/database_tasks.rb +1 -1
  74. data/lib/active_record/timestamp.rb +8 -1
  75. data/lib/active_record/transactions.rb +23 -20
  76. data/lib/active_record/type/serialized.rb +4 -0
  77. metadata +9 -10
@@ -201,6 +201,7 @@ module ActiveRecord
201
201
  klass.scope value_method_name, -> { where(attr => value) }
202
202
  end
203
203
  end
204
+ enum_values.freeze
204
205
  end
205
206
  end
206
207
 
@@ -117,16 +117,27 @@ module ActiveRecord
117
117
 
118
118
  # Raised when a foreign key constraint cannot be added because the column type does not match the referenced column type.
119
119
  class MismatchedForeignKey < StatementInvalid
120
- def initialize(adapter = nil, message: nil, table: nil, foreign_key: nil, target_table: nil, primary_key: nil)
121
- @adapter = adapter
120
+ def initialize(
121
+ adapter = nil,
122
+ message: nil,
123
+ sql: nil,
124
+ binds: nil,
125
+ table: nil,
126
+ foreign_key: nil,
127
+ target_table: nil,
128
+ primary_key: nil,
129
+ primary_key_column: nil
130
+ )
122
131
  if table
123
- msg = <<-EOM.strip_heredoc
124
- Column `#{foreign_key}` on table `#{table}` has a type of `#{column_type(table, foreign_key)}`.
125
- This does not match column `#{primary_key}` on `#{target_table}`, which has type `#{column_type(target_table, primary_key)}`.
126
- To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :integer. (For example `t.integer #{foreign_key}`).
132
+ type = primary_key_column.bigint? ? :bigint : primary_key_column.type
133
+ msg = <<-EOM.squish
134
+ Column `#{foreign_key}` on table `#{table}` does not match column `#{primary_key}` on `#{target_table}`,
135
+ which has type `#{primary_key_column.sql_type}`.
136
+ To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :#{type}.
137
+ (For example `t.#{type} :#{foreign_key}`).
127
138
  EOM
128
139
  else
129
- msg = <<-EOM
140
+ msg = <<-EOM.squish
130
141
  There is a mismatch between the foreign key and primary key column types.
131
142
  Verify that the foreign key column type and the primary key of the associated table match types.
132
143
  EOM
@@ -136,11 +147,6 @@ module ActiveRecord
136
147
  end
137
148
  super(msg)
138
149
  end
139
-
140
- private
141
- def column_type(table, column)
142
- @adapter.columns(table).detect { |c| c.name == column }.sql_type
143
- end
144
150
  end
145
151
 
146
152
  # Raised when a record cannot be inserted or updated because it would violate a not null constraint.
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 5
11
11
  MINOR = 2
12
- TINY = 0
12
+ TINY = 3
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -125,7 +125,7 @@ module ActiveRecord
125
125
  ]
126
126
  end
127
127
 
128
- RAILS_GEM_ROOT = File.expand_path("../../../..", __FILE__) + "/"
128
+ RAILS_GEM_ROOT = File.expand_path("../../..", __dir__) + "/"
129
129
 
130
130
  def ignored_callstack(path)
131
131
  path.start_with?(RAILS_GEM_ROOT) ||
@@ -1163,7 +1163,7 @@ module ActiveRecord
1163
1163
 
1164
1164
  def migrations_path=(path)
1165
1165
  ActiveSupport::Deprecation.warn \
1166
- "ActiveRecord::Migrator.migrations_paths= is now deprecated and will be removed in Rails 6.0." \
1166
+ "`ActiveRecord::Migrator.migrations_path=` is now deprecated and will be removed in Rails 6.0. " \
1167
1167
  "You can set the `migrations_paths` on the `connection` instead through the `database.yml`."
1168
1168
  self.migrations_paths = [path]
1169
1169
  end
@@ -17,20 +17,18 @@ module ActiveRecord
17
17
 
18
18
  class V5_1 < V5_2
19
19
  def change_column(table_name, column_name, type, options = {})
20
- if adapter_name == "PostgreSQL"
21
- clear_cache!
22
- sql = connection.send(:change_column_sql, table_name, column_name, type, options)
23
- execute "ALTER TABLE #{quote_table_name(table_name)} #{sql}"
24
- change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
25
- change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
26
- change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
20
+ if connection.adapter_name == "PostgreSQL"
21
+ super(table_name, column_name, type, options.except(:default, :null, :comment))
22
+ connection.change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
23
+ connection.change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
24
+ connection.change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
27
25
  else
28
26
  super
29
27
  end
30
28
  end
31
29
 
32
30
  def create_table(table_name, options = {})
33
- if adapter_name == "Mysql2"
31
+ if connection.adapter_name == "Mysql2"
34
32
  super(table_name, options: "ENGINE=InnoDB", **options)
35
33
  else
36
34
  super
@@ -52,13 +50,13 @@ module ActiveRecord
52
50
  end
53
51
 
54
52
  def create_table(table_name, options = {})
55
- if adapter_name == "PostgreSQL"
53
+ if connection.adapter_name == "PostgreSQL"
56
54
  if options[:id] == :uuid && !options.key?(:default)
57
55
  options[:default] = "uuid_generate_v4()"
58
56
  end
59
57
  end
60
58
 
61
- unless adapter_name == "Mysql2" && options[:id] == :bigint
59
+ unless connection.adapter_name == "Mysql2" && options[:id] == :bigint
62
60
  if [:integer, :bigint].include?(options[:id]) && !options.key?(:default)
63
61
  options[:default] = nil
64
62
  end
@@ -175,7 +173,7 @@ module ActiveRecord
175
173
  if options[:name].present?
176
174
  options[:name].to_s
177
175
  else
178
- index_name(table_name, column: column_names)
176
+ connection.index_name(table_name, column: column_names)
179
177
  end
180
178
  super
181
179
  end
@@ -195,15 +193,17 @@ module ActiveRecord
195
193
  end
196
194
 
197
195
  def index_name_for_remove(table_name, options = {})
198
- index_name = index_name(table_name, options)
196
+ index_name = connection.index_name(table_name, options)
199
197
 
200
- unless index_name_exists?(table_name, index_name)
198
+ unless connection.index_name_exists?(table_name, index_name)
201
199
  if options.is_a?(Hash) && options.has_key?(:name)
202
200
  options_without_column = options.dup
203
201
  options_without_column.delete :column
204
- index_name_without_column = index_name(table_name, options_without_column)
202
+ index_name_without_column = connection.index_name(table_name, options_without_column)
205
203
 
206
- return index_name_without_column if index_name_exists?(table_name, index_name_without_column)
204
+ if connection.index_name_exists?(table_name, index_name_without_column)
205
+ return index_name_without_column
206
+ end
207
207
  end
208
208
 
209
209
  raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
@@ -375,7 +375,7 @@ module ActiveRecord
375
375
  # default values when instantiating the Active Record object for this table.
376
376
  def column_defaults
377
377
  load_schema
378
- @column_defaults ||= _default_attributes.to_hash
378
+ @column_defaults ||= _default_attributes.deep_dup.to_hash
379
379
  end
380
380
 
381
381
  def _default_attributes # :nodoc:
@@ -373,7 +373,7 @@ module ActiveRecord
373
373
  became = klass.allocate
374
374
  became.send(:initialize)
375
375
  became.instance_variable_set("@attributes", @attributes)
376
- became.instance_variable_set("@mutations_from_database", @mutations_from_database) if defined?(@mutations_from_database)
376
+ became.instance_variable_set("@mutations_from_database", @mutations_from_database ||= nil)
377
377
  became.instance_variable_set("@changed_attributes", attributes_changed_by_setter)
378
378
  became.instance_variable_set("@new_record", new_record?)
379
379
  became.instance_variable_set("@destroyed", destroyed?)
@@ -473,15 +473,16 @@ module ActiveRecord
473
473
  verify_readonly_attribute(key.to_s)
474
474
  end
475
475
 
476
+ id_in_database = self.id_in_database
477
+ attributes.each do |k, v|
478
+ write_attribute_without_type_cast(k, v)
479
+ end
480
+
476
481
  affected_rows = self.class._update_record(
477
482
  attributes,
478
483
  self.class.primary_key => id_in_database
479
484
  )
480
485
 
481
- attributes.each do |k, v|
482
- write_attribute_without_type_cast(k, v)
483
- end
484
-
485
486
  affected_rows == 1
486
487
  end
487
488
 
@@ -26,19 +26,12 @@ module ActiveRecord
26
26
  end
27
27
 
28
28
  def self.run
29
- ActiveRecord::Base.connection_handler.connection_pool_list.map do |pool|
30
- caching_was_enabled = pool.query_cache_enabled
31
-
32
- pool.enable_query_cache!
33
-
34
- [pool, caching_was_enabled]
35
- end
29
+ ActiveRecord::Base.connection_handler.connection_pool_list.
30
+ reject { |p| p.query_cache_enabled }.each { |p| p.enable_query_cache! }
36
31
  end
37
32
 
38
- def self.complete(caching_pools)
39
- caching_pools.each do |pool, caching_was_enabled|
40
- pool.disable_query_cache! unless caching_was_enabled
41
- end
33
+ def self.complete(pools)
34
+ pools.each { |pool| pool.disable_query_cache! }
42
35
 
43
36
  ActiveRecord::Base.connection_handler.connection_pool_list.each do |pool|
44
37
  pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
@@ -40,7 +40,7 @@ module ActiveRecord
40
40
  def find_by_sql(sql, binds = [], preparable: nil, &block)
41
41
  result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable)
42
42
  column_types = result_set.column_types.dup
43
- columns_hash.each_key { |k| column_types.delete k }
43
+ attribute_types.each_key { |k| column_types.delete k }
44
44
  message_bus = ActiveSupport::Notifications.instrumenter
45
45
 
46
46
  payload = {
@@ -169,9 +169,7 @@ end_warning
169
169
  end
170
170
 
171
171
  initializer "active_record.set_executor_hooks" do
172
- ActiveSupport.on_load(:active_record) do
173
- ActiveRecord::QueryCache.install_executor_hooks
174
- end
172
+ ActiveRecord::QueryCache.install_executor_hooks
175
173
  end
176
174
 
177
175
  initializer "active_record.add_watchable_files" do |app|
@@ -54,7 +54,7 @@ module ActiveRecord
54
54
  # user = users.new { |user| user.name = 'Oscar' }
55
55
  # user.name # => Oscar
56
56
  def new(attributes = nil, &block)
57
- scoping { klass.new(scope_for_create(attributes), &block) }
57
+ scoping { klass.new(values_for_create(attributes), &block) }
58
58
  end
59
59
 
60
60
  alias build new
@@ -82,7 +82,7 @@ module ActiveRecord
82
82
  if attributes.is_a?(Array)
83
83
  attributes.collect { |attr| create(attr, &block) }
84
84
  else
85
- scoping { klass.create(scope_for_create(attributes), &block) }
85
+ scoping { klass.create(values_for_create(attributes), &block) }
86
86
  end
87
87
  end
88
88
 
@@ -96,7 +96,7 @@ module ActiveRecord
96
96
  if attributes.is_a?(Array)
97
97
  attributes.collect { |attr| create!(attr, &block) }
98
98
  else
99
- scoping { klass.create!(scope_for_create(attributes), &block) }
99
+ scoping { klass.create!(values_for_create(attributes), &block) }
100
100
  end
101
101
  end
102
102
 
@@ -277,10 +277,10 @@ module ActiveRecord
277
277
  # Please check unscoped if you want to remove all previous scopes (including
278
278
  # the default_scope) during the execution of a block.
279
279
  def scoping
280
- previous, klass.current_scope = klass.current_scope(true), self
280
+ previous, klass.current_scope = klass.current_scope(true), self unless @delegate_to_klass
281
281
  yield
282
282
  ensure
283
- klass.current_scope = previous
283
+ klass.current_scope = previous unless @delegate_to_klass
284
284
  end
285
285
 
286
286
  def _exec_scope(*args, &block) # :nodoc:
@@ -337,6 +337,14 @@ module ActiveRecord
337
337
  @klass.connection.update stmt, "#{@klass} Update All"
338
338
  end
339
339
 
340
+ def update(id = :all, attributes) # :nodoc:
341
+ if id == :all
342
+ each { |record| record.update(attributes) }
343
+ else
344
+ klass.update(id, attributes)
345
+ end
346
+ end
347
+
340
348
  # Destroys the records by instantiating each
341
349
  # record and calling its {#destroy}[rdoc-ref:Persistence#destroy] method.
342
350
  # Each object's callbacks are executed (including <tt>:dependent</tt> association options).
@@ -436,17 +444,16 @@ module ActiveRecord
436
444
  # # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
437
445
  def to_sql
438
446
  @to_sql ||= begin
439
- relation = self
440
-
441
- if eager_loading?
442
- apply_join_dependency { |rel, _| relation = rel }
443
- end
444
-
445
- conn = klass.connection
446
- conn.unprepared_statement {
447
- conn.to_sql(relation.arel)
448
- }
449
- end
447
+ if eager_loading?
448
+ apply_join_dependency do |relation, join_dependency|
449
+ relation = join_dependency.apply_column_aliases(relation)
450
+ relation.to_sql
451
+ end
452
+ else
453
+ conn = klass.connection
454
+ conn.unprepared_statement { conn.to_sql(arel) }
455
+ end
456
+ end
450
457
  end
451
458
 
452
459
  # Returns a hash of where conditions.
@@ -457,10 +464,8 @@ module ActiveRecord
457
464
  where_clause.to_h(relation_table_name)
458
465
  end
459
466
 
460
- def scope_for_create(attributes = nil)
461
- scope = where_values_hash.merge!(create_with_value.stringify_keys)
462
- scope.merge!(attributes) if attributes
463
- scope
467
+ def scope_for_create
468
+ where_values_hash.merge!(create_with_value.stringify_keys)
464
469
  end
465
470
 
466
471
  # Returns true if relation needs eager loading.
@@ -546,6 +551,7 @@ module ActiveRecord
546
551
  if ActiveRecord::NullRelation === relation
547
552
  []
548
553
  else
554
+ relation = join_dependency.apply_column_aliases(relation)
549
555
  rows = connection.select_all(relation.arel, "SQL")
550
556
  join_dependency.instantiate(rows, &block)
551
557
  end.freeze
@@ -606,5 +612,18 @@ module ActiveRecord
606
612
  # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
607
613
  string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map(&:downcase).uniq - ["raw_sql_"]
608
614
  end
615
+
616
+ def values_for_create(attributes = nil)
617
+ result = attributes ? where_values_hash.merge!(attributes) : where_values_hash
618
+
619
+ # NOTE: if there are same keys in both create_with and result, create_with should be used.
620
+ # This is to make sure nested attributes don't get passed to the klass.new,
621
+ # while keeping the precedence of the duplicate keys in create_with.
622
+ create_with_value.stringify_keys.each do |k, v|
623
+ result[k] = v if result.key?(k)
624
+ end
625
+
626
+ result
627
+ end
609
628
  end
610
629
  end
@@ -190,11 +190,9 @@ module ActiveRecord
190
190
  relation = apply_join_dependency
191
191
  relation.pluck(*column_names)
192
192
  else
193
- enforce_raw_sql_whitelist(column_names)
193
+ klass.enforce_raw_sql_whitelist(column_names)
194
194
  relation = spawn
195
- relation.select_values = column_names.map { |cn|
196
- @klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn
197
- }
195
+ relation.select_values = column_names
198
196
  result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) }
199
197
  result.cast_values(klass.attribute_types)
200
198
  end
@@ -209,7 +207,6 @@ module ActiveRecord
209
207
  end
210
208
 
211
209
  private
212
-
213
210
  def has_include?(column_name)
214
211
  eager_loading? || (includes_values.present? && column_name && column_name != :all)
215
212
  end
@@ -224,10 +221,12 @@ module ActiveRecord
224
221
  if operation == "count"
225
222
  column_name ||= select_for_count
226
223
  if column_name == :all
227
- if distinct && (group_values.any? || select_values.empty? && order_values.empty?)
224
+ if !distinct
225
+ distinct = distinct_select?(select_for_count) if group_values.empty?
226
+ elsif group_values.any? || select_values.empty? && order_values.empty?
228
227
  column_name = primary_key
229
228
  end
230
- elsif column_name =~ /\s*DISTINCT[\s(]+/i
229
+ elsif distinct_select?(column_name)
231
230
  distinct = nil
232
231
  end
233
232
  end
@@ -239,6 +238,10 @@ module ActiveRecord
239
238
  end
240
239
  end
241
240
 
241
+ def distinct_select?(column_name)
242
+ column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
243
+ end
244
+
242
245
  def aggregate_column(column_name)
243
246
  return column_name if Arel::Expressions === column_name
244
247
 
@@ -383,7 +386,7 @@ module ActiveRecord
383
386
  case operation
384
387
  when "count" then value.to_i
385
388
  when "sum" then type.deserialize(value || 0)
386
- when "average" then value.respond_to?(:to_d) ? value.to_d : value
389
+ when "average" then value && value.respond_to?(:to_d) ? value.to_d : value
387
390
  else type.deserialize(value)
388
391
  end
389
392
  end
@@ -17,6 +17,7 @@ module ActiveRecord
17
17
  delegate = Class.new(klass) {
18
18
  include ClassSpecificRelation
19
19
  }
20
+ include_relation_methods(delegate)
20
21
  mangled_name = klass.name.gsub("::".freeze, "_".freeze)
21
22
  const_set mangled_name, delegate
22
23
  private_constant mangled_name
@@ -29,6 +30,35 @@ module ActiveRecord
29
30
  child_class.initialize_relation_delegate_cache
30
31
  super
31
32
  end
33
+
34
+ protected
35
+ def include_relation_methods(delegate)
36
+ superclass.include_relation_methods(delegate) unless base_class == self
37
+ delegate.include generated_relation_methods
38
+ end
39
+
40
+ private
41
+ def generated_relation_methods
42
+ @generated_relation_methods ||= Module.new.tap do |mod|
43
+ mod_name = "GeneratedRelationMethods"
44
+ const_set mod_name, mod
45
+ private_constant mod_name
46
+ end
47
+ end
48
+
49
+ def generate_relation_method(method)
50
+ if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method)
51
+ generated_relation_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
52
+ def #{method}(*args, &block)
53
+ scoping { klass.#{method}(*args, &block) }
54
+ end
55
+ RUBY
56
+ else
57
+ generated_relation_methods.send(:define_method, method) do |*args, &block|
58
+ scoping { klass.public_send(method, *args, &block) }
59
+ end
60
+ end
61
+ end
32
62
  end
33
63
 
34
64
  extend ActiveSupport::Concern