Undefined method `attribute_before_type_cast`
For the last 3 months, we got 13 occurrences of similar errors originating from typed store, one example:
NameError: undefined method `can_perform_before_type_cast' for class `Activity' (NameError)
undef_method(method) if method_defined?(method)
^^^^^^^^^^^^
from activerecord-typedstore (7d6dce4b7029) lib/active_record/typed_store/behavior.rb:30:in `undef_method'
from activerecord-typedstore (7d6dce4b7029) lib/active_record/typed_store/behavior.rb:30:in `undefine_before_type_cast_method'
from activerecord-typedstore (7d6dce4b7029) lib/active_record/typed_store/behavior.rb:22:in `block in define_typed_store_attribute_methods'
from set.rb:511:in `each_key'
from set.rb:511:in `each'
from activerecord-typedstore (7d6dce4b7029) lib/active_record/typed_store/behavior.rb:20:in `define_typed_store_attribute_methods'
from activerecord-typedstore (7d6dce4b7029) lib/active_record/typed_store/behavior.rb:10:in `define_attribute_methods'
from activerecord (7.0.8) lib/active_record/core.rb:765:in `init_internals'
from activerecord (7.0.8) lib/active_record/associations.rb:323:in `init_internals'
from activerecord (7.0.8) lib/active_record/core.rb:502:in `init_with_attributes'
from activerecord (7.0.8) lib/active_record/persistence.rb:537:in `instantiate_instance_of'
from activerecord (7.0.8) lib/active_record/querying.rb:76:in `block (2 levels) in _load_from_sql'
from activerecord (7.0.8) lib/active_record/result.rb:69:in `each'
from activerecord (7.0.8) lib/active_record/result.rb:69:in `each'
from activerecord (7.0.8) lib/active_record/querying.rb:76:in `map'
from activerecord (7.0.8) lib/active_record/querying.rb:76:in `block in _load_from_sql'
from activerecord (7.0.8) lib/active_record/querying.rb:71:in `_load_from_sql'
from activerecord (7.0.8) lib/active_record/relation.rb:954:in `instantiate_records'
from activerecord (7.0.8) lib/active_record/relation.rb:917:in `block in exec_queries'
from activerecord (7.0.8) lib/active_record/relation.rb:962:in `skip_query_cache_if_necessary'
from activerecord (7.0.8) lib/active_record/relation.rb:908:in `exec_queries'
from activerecord (7.0.8) lib/active_record/association_relation.rb:44:in `exec_queries'
from activerecord (7.0.8) lib/active_record/relation.rb:695:in `load'
from activerecord (7.0.8) lib/active_record/relation.rb:250:in `records'
from activerecord (7.0.8) lib/active_record/relation.rb:245:in `to_ary'
from activerecord (7.0.8) lib/active_record/associations/association.rb:224:in `find_target'
from activerecord (7.0.8) lib/active_record/associations/singular_association.rb:44:in `find_target'
from activerecord (7.0.8) lib/active_record/associations/association.rb:173:in `load_target'
from activerecord (7.0.8) lib/active_record/associations/association.rb:67:in `reload'
from activerecord (7.0.8) lib/active_record/associations/singular_association.rb:11:in `reader'
from activerecord (7.0.8) lib/active_record/associations/builder/association.rb:104:in `activity'
from app/models/parent_activity.rb:301:in `check_activity'
Our Activity model has 3 typed stores, and the error seems to be triggered by random attributes inside the typed stores, not a particular one. We also have some other models with a single typed store, and we haven't seen any issues arising from those.
The crash comes from:
def undefine_before_type_cast_method(attribute)
# because it mess with ActionView forms, see #14.
method = "#{attribute}_before_type_cast"
undef_method(method) if method_defined?(method)
end
It was added as a response to this bug report in ActionView forms: https://github.com/byroot/activerecord-typedstore/issues/14
It seems like method_defined? is returning true, but when we run undef_method, the method might already be removed...
We use Puma and Sidekiq, and can observe the issue on both processes. I suspect this is a thread-safety issue caused by ActiveRecord.
Sadly, I don't know enough about the active record internals to figure out if a race condition can happen.
Has someone else come across the same issue or has some pointers?
Sharing here in case someone has the same issue:
Since we don't use ActionView forms and to avoid the issue in the app, we opted to monkey-patch the gem in the initializers:
require "active_record/typed_store/version"
raise "Check if still valid" if ActiveRecord::TypedStore::VERSION != "1.6.0"
module ActiveRecord::TypedStore::Behavior
extend ActiveSupport::Concern
module ClassMethods
def undefine_before_type_cast_method(attribute)
# This seems to have been added to fix a bug with ActionView form validations but we don't need those
# https://github.com/byroot/activerecord-typedstore/issues/14
# https://github.com/byroot/activerecord-typedstore/issues/105
end
end
end