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
Make constantize look down the ancestor chain (excluding Object) #6165
Conversation
👍 /cc @wycats @tenderlove |
I'm 👎 on this because the ancestors chain can be quite large so could have a negative performance impact, e.g:
Also as implemented it wouldn't detect constants in the ancestors chain that appear after def constantize(camel_cased_word) #:nodoc:
names = camel_cased_word.split('::')
names.shift if names.empty? || names.first.empty?
names.inject(Object) do |constant, name|
constant = constant.ancestors.detect(constant) do |ancestor|
ancestor.const_defined?(name) unless ancestor == Object
end
constant.const_get(name, false)
end
end One possible performance optimisation would be to let ruby search the ancestor chain by passing |
@pixeltrix Thanks for your comment! I pushed a newer, shinier and better version that I dedicate to you :-) On performance: the patch has basically no impact in cases where the constant seeked is directly owned by class/module, which are the cases handled correctly currently. For cases that are currently not handled properly, then yes, there will be a small performance difference, but slower is better than buggy! In case the constant does not exist at all (even at the Object level or lower), the newer version will raise immediately, instead of looking down the chain for nothing. As for the after Object bit, you're absolutely right for the top level search, and there was actually another bug in On the other hand, when not looking up from So thanks to your comments, I've fixed the top level search and also improved the performance of my patch in cases (top level or when constant doesn't exist at all). |
@pixeltrix Oh, I've also included the optimization you suggested, so ancestors are looked up only if the name is globally accessible. If it isn't, then you're right, we know it's somewhere in the ancestry chain before |
# directly before we reach Object or the end of ancestors. | ||
constant.ancestors.each do |ancestor| | ||
break if ancestor == Object | ||
return candidate if ancestor.const_defined?(name, false) |
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.
The return
returns the candidate constant from the enclosing method which might not be the end of the chain, e.g:
class Alpha
class Beta
class Omega
end
end
end
class Delta < Alpha; end
class Beta; end
>> puts "Delta::Beta::Omega".constantize
=> Alpha::Beta
The following implementation handles that - it's basically the same as yours:
def constantize(camel_cased_word)
names = camel_cased_word.split('::')
names.shift if names.empty? || names.first.empty?
names.inject(Object) do |constant, name|
if constant == Object
constant.const_get(name)
else
candidate = constant.const_get(name)
next candidate if constant.const_defined?(name, false)
next candidate unless Object.const_defined?(name)
constant = constant.ancestors.each do |ancestor|
break constant if ancestor == Object
break ancestor if ancestor.const_defined?(name, false)
end
constant.const_get(name, false)
end
end
end
The two next candidate
lines could be written as:
next candidate if constant.const_defined?(name, false) || !Object.const_defined?(name)
I prefer the two lines as it makes the intention easier to see using the unless
but I'm not particularly fussed either way.
constantize
is meant to avoid looking up inObject
, e.g. so that"String::Fixnum".constantize
raises an error.Unfortunately, the ancestor chain is not looked up at all.
I ran into this when registering an observer (say "some_class/observer"), where
SomeClass::Observer
is defined in a base class ofSomeClass
. That currently fails, asObserver
has to be defined directly inSomeClass
.One solution is for me to write:
I hope you appreciate the irony :-)
I feel that there is no reason for
constantize
to be so strict, though. The attached patch fixes this.Thanks.