actor_class followed the constant it cached, not the one the app has - #8
Merged
Merged
Conversation
Reported from a Rails app in development: every signup after the first code change of the session failed with Clickwrap was asked to record User as the actor, but `config.actor_class_name` says the records that can act are User. Both halves name the same class, which reads as a contradiction and sends whoever hits it to an initializer that is correct. They were two different Ruby objects printing the same name. `actor_class` cached the constantized Class and re-resolved only when the class NAME changed — which never happens, because the name is set once in an initializer. Reloading replaces the class behind the name on every edit: Zeitwerk unloads the old User and defines a new one. From the second request of a dev session onwards the cache held a class no living record was an instance of, so `actor.is_a?(config.actor_class)` was false for an ordinary User and capture refused to record it. The cache is gone. `constantize` after the first load is a const_get, and this is called once per capture rather than in a loop, so it was never buying anything; `parent_controller_class` two lines below has always resolved this way. The test reloads a constant the way Rails does — remove_const, const_set — and fails against the old implementation. Found the hard way in a host app, where the tell was that a restart fixed it and the next edit broke it again.
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The report
From a Rails app in development: every signup after the first code change of the session failed with
Both halves of that sentence name the same class. It reads as a contradiction, and it sends whoever hits it to an initializer that is perfectly correct.
The cause
They were two different Ruby objects that print the same name.
Configuration#actor_classcached the constantizedClassand re-resolved only when the class name changed:The name never changes — it is set once in an initializer. But reloading replaces the class behind the name on every edit: Zeitwerk unloads the old
Userand defines a new one. From the second request of a dev session onwards, the cache held a class no living record was an instance of, soactor.is_a?(config.actor_class)was false for an ordinaryUserand capture refused to record it.The tell, from the host app's side: a restart fixed it, and the next edit broke it again.
The fix
Drop the cache.
constantizeafter the first load is aconst_get, andactor_classis called once per capture rather than in a loop, so the memo was never buying anything.parent_controller_class, two lines below, has always resolved this way.The test
Reloads a constant the way Rails does —
remove_constthenconst_set— and asserts the resolved class follows it. Verified to fail against the old implementation and pass against the new one.Verification
touch app/models/user.rb, both 303. Before the fix the second was a 500.