-
Notifications
You must be signed in to change notification settings - Fork 21.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix thread_mattr_accessor
share variable superclass with subclass
#25681
Conversation
Thanks for the pull request, and welcome! The Rails team is excited to review your changes, and you should hear from @senny (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. Please see the contribution instructions for more information. |
thread_mattr_reader :shaq, instance_reader: false | ||
thread_mattr_accessor :camp, instance_accessor: false | ||
end | ||
class Foo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are anonymous classes not sufficient to reproduce the issue? Would:
@class = Class.new do
end
@subclass = Class.new(@class) do
end
not work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anonymous classes don't work with thread_mattr_accessor
.
thread_mattr_accessor
uses class name as part of thread local variable name.
Following code set Thread[:attr_Account_user]
.
class Account
thread_mattr_accessor :user
end
But anonymous class set Thread[:attr_user]
. Its subclass use same variable because
both of them don't have name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If anonymous class use thread_mattr_accessor
raise error by my pull request.
I will fix If it's needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it worked before it would be nice to have it working still.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case I think it'd be better for it to raise: the previous behaviour treated all anonymous classes as one, so that doesn't seem terribly useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@senny OK. I added a commit not to raise error if use thread_mattr_accessor
with anonymous class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, Bad timing. Which is do you think better? @senny
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What @matthewd pointed out sounds reasonable. If it didn't work properly before then we should be good to raise. Would be nice to have the inline classes in the test-case work though. Maybe using def name; "MyClass" end
does the trick.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reverted a previous commit and add a commit using the trick to use anonymous class in test case 😃.
@willnet good catch! Added a minor note about the test style. If possible I'd like to stick to the anonymous classes that we had before. |
@willnet can you squash your changes into a single commit? There's no need for us to keep track of that revert in your branch. Makes the history a lot cleaner if everything related to a change is in the same commit. Thanks. |
4eb6c98
to
345eaf5
Compare
@senny OK, I did it 😃. |
@willnet awesome! Sorry one last thing I forgot to mention, could you add an entry to the |
345eaf5
to
649cd96
Compare
@senny OK, I did it 😄. |
The current implementation of `thread_mattr_accessor` set variable sharing superclass with subclass. So the method doesn't work as documented. Precondition class Account thread_mattr_accessor :user end class Customer < Account end Account.user = "DHH" Account.user #=> "DHH" Customer.user = "Rafael" Customer.user # => "Rafael" Documented behavior Account.user # => "DHH" Actual behavior Account.user # => "Rafael" Current implementation set variable statically likes `Thread[:attr_Account_user]`, and customer also use it. Make variable name dynamic to use own thread-local variable.
649cd96
to
3529e58
Compare
Fix `thread_mattr_accessor` share variable superclass with subclass
@willnet thanks 💛 ! Merged with a slightly modified version of the |
Fix `thread_mattr_accessor` share variable superclass with subclass
Backported to |
Let me double-check this patch, just in case. Given: class A
thread_cattr_accessor :foo
self.foo = 1
end
class B < A
end before this change the attribute was shared/inherited: B.foo # => 1 and after this patch the attribute is no longer shared/inherited: B.foo # => nil This interpolation has been there since the initial implementation. Which documentation does the description of the PR refer to? @dhh what do you think? |
@fxn here's the docs this was referring to: http://api.rubyonrails.org/classes/Module.html#method-i-thread_mattr_accessor |
OK. And the sentence
also seems to match the new behaviour in my example. Looking good then. The only thing that bothers me in these macros is that the documentation leaks the implementation ( |
@fxn totally agree on leaking the implementation. I'm going to remove the |
@fxn on second thought, without the |
@senny totally agree. A reader without a writer is useless if the user is not supposed to use the private key directly. And viceversa, why write something you cannot read? I think the public interface here is the accessor, and the reader/writer would be private methods that help implement the accessor. If you want something to be private you can issue a manual |
This is a follow up to #25681, specifically this comment: #25681 (comment) The way the thread local variable is stored is an implementation detail and subject to change. It makes no sense to only generate a reader or writer as you'd have to know where to read from or where it writes to.
I pushed a commit that hides the |
This is a follow up to #25681, specifically this comment: #25681 (comment) The way the thread local variable is stored is an implementation detail and subject to change. It makes no sense to only generate a reader or writer as you'd have to know where to read from or where it writes to.
@@ -41,14 +41,14 @@ def thread_mattr_reader(*syms) | |||
raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym) | |||
class_eval(<<-EOS, __FILE__, __LINE__ + 1) | |||
def self.#{sym} | |||
Thread.current[:"attr_#{name}_#{sym}"] | |||
Thread.current["attr_"+ name + "_#{sym}"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why change the concat method? +
is slower
https://github.com/JuanitoFatas/fast-ruby#string-concatenation-code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#{name}
is different from + name +
in class_eval
.
#{name}
is evaluated immediately on class_eval
, but + name +
is evaluated on executing method defined in class_eval
Summary
The current implementation of
thread_mattr_accessor
set variablesharing superclass with subclass. So the method doesn't work as documented.
Precondition
Documented behavior
Actual behavior
Current implementation set variable statically likes
Thread[:attr_Account_user]
,and Customer also use it.
Make variable name dynamic to use own thread-local variable.