-
Notifications
You must be signed in to change notification settings - Fork 21.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #50594 from Shopify/ar-marshal-define-attribute-me…
…thods Define missing attribute methods from `method_missing`
- Loading branch information
Showing
6 changed files
with
74 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,24 +63,6 @@ def eagerly_generate_alias_attribute_methods(_new_name, _old_name) # :nodoc: | |
# alias attributes in Active Record are lazily generated | ||
end | ||
|
||
def generate_alias_attributes # :nodoc: | ||
superclass.generate_alias_attributes unless superclass == Base | ||
return if @alias_attributes_mass_generated | ||
|
||
GeneratedAttributeMethods::LOCK.synchronize do | ||
return if @alias_attributes_mass_generated | ||
ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator| | ||
aliases_by_attribute_name.each do |old_name, new_names| | ||
new_names.each do |new_name| | ||
generate_alias_attribute_methods(code_generator, new_name, old_name) | ||
end | ||
end | ||
end | ||
|
||
@alias_attributes_mass_generated = true | ||
end | ||
end | ||
|
||
def alias_attribute_method_definition(code_generator, pattern, new_name, old_name) | ||
method_name = pattern.method_name(new_name).to_s | ||
target_name = pattern.method_name(old_name).to_s | ||
|
@@ -120,6 +102,10 @@ def alias_attribute_method_definition(code_generator, pattern, new_name, old_nam | |
end | ||
end | ||
|
||
def attribute_methods_generated? # :nodoc: | ||
@attribute_methods_generated | ||
end | ||
|
||
# Generates all the attribute related methods for columns in the database | ||
# accessors, mutators and query methods. | ||
def define_attribute_methods # :nodoc: | ||
|
@@ -128,11 +114,29 @@ def define_attribute_methods # :nodoc: | |
# attribute methods. | ||
GeneratedAttributeMethods::LOCK.synchronize do | ||
return false if @attribute_methods_generated | ||
superclass.define_attribute_methods unless base_class? | ||
super(attribute_names) | ||
alias_attribute(:id_value, :id) if attribute_names.include?("id") | ||
|
||
superclass.define_attribute_methods unless superclass == Base | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
ghiculescu
Member
|
||
|
||
unless abstract_class? | ||
load_schema | ||
super(attribute_names) | ||
if _has_attribute?("id") | ||
alias_attribute(:id_value, :id) | ||
end | ||
end | ||
|
||
ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator| | ||
aliases_by_attribute_name.each do |old_name, new_names| | ||
new_names.each do |new_name| | ||
generate_alias_attribute_methods(code_generator, new_name, old_name) | ||
end | ||
end | ||
end | ||
|
||
@attribute_methods_generated = true | ||
@alias_attributes_mass_generated = true | ||
end | ||
true | ||
end | ||
|
||
def undefine_attribute_methods # :nodoc: | ||
|
@@ -457,6 +461,36 @@ def accessed_fields | |
end | ||
|
||
private | ||
def respond_to_missing?(name, include_private = false) | ||
if self.class.define_attribute_methods | ||
# Some methods weren't defined yet. | ||
return true if self.class.method_defined?(name) | ||
return true if include_private && self.class.private_method_defined?(name) | ||
end | ||
|
||
super | ||
end | ||
|
||
def method_missing(name, ...) | ||
unless self.class.attribute_methods_generated? | ||
if self.class.method_defined?(name) | ||
# The method is explicitly defined in the model, but calls a generated | ||
# method with super. So we must resume the call chain at the right setp. | ||
last_method = method(name) | ||
last_method = last_method.super_method while last_method.super_method | ||
self.class.define_attribute_methods | ||
if last_method.super_method | ||
return last_method.super_method.call(...) | ||
end | ||
elsif self.class.define_attribute_methods | ||
# Some attribute methods weren't generated yet, we retry the call | ||
return public_send(name, ...) | ||
end | ||
end | ||
|
||
super | ||
end | ||
|
||
def attribute_method?(attr_name) | ||
@attributes&.key?(attr_name) | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+1.94 KB
activerecord/test/support/marshal_compatibility_fixtures/SQLite/rails_6_1_aliased_topic.dump
Binary file not shown.
This is causing problems in a multi-db application. The specific example we have is using delayed job connected to a different DB to the primary DB.
Here's a simplified setup: https://gist.github.com/ghiculescu/4beb81cae0f99660fda992811a3aebf6
To break it:
config.eager_load = true
andconfig.cache_classes = true
inenvironments/test.rb
(to match a CI config)The issue is the recursive calls to
define_attribute_methods
. When running in CI, we end up trying to callload_schema
for theDelayed::Backend::ActiveRecord::Job
model (since we goDelayedJobImplementation
->DelayedJobAbstractParentClass
->Delayed::Backend::ActiveRecord::Job
).Since we have not set
connects_to
on that model (we can't, because it's not abstract), it tries to connect to the primary database, but thedelayed_jobs
table does not exist in the primary database, so it crashes.Note that if I make this change, then things work as expected:
I can work on a more comprehensive replication app but just wanted to dump what I had so far in case the issue is obvious.