Feature #19884
openMake Safe Navigation Operator work on classes
Description
If a constant isn't defined it will raise a NameError:
DoesNotExist.some_method # raises: uninitialized constant DoesNotExist (NameError)
In libraries that have optional dependencies, we can check if the constant is defined before calling a method on it:
defined?(OptionalDependency) && OptionalDependency.some_method
Currently in Ruby, the Safe Navigation Operator is used to avoid NoMethodError exceptions when calling methods on objects that may be nil.
It would be nice if we could use the Safe Navigation Operator to avoid NameError on undefined constants as well.
ClassThatMightNotExist&.some_method
Updated by sawa (Tsuyoshi Sawada) over 1 year ago
Currently, you can do ActiveRecord::Base.some_method rescue nil
Using &.
for rescuing an error in addition to the current behavior would make it confusing.
Furthermore, the specification you are asking for is not clear. What Exception classes do you want &.
to rescue?
You stated that you want &.
to work on classes, but that does not make sense. If ActiveRecord::Base
is not defined, it is not a class. How would Ruby know that it is (supposed to be) a class?
Updated by rubyFeedback (robert heiler) over 1 year ago
I am not sure that pattern is that common to warrant using &., aside from the syntax not being very elegant, but that may be my own bias against it (syntax-wise that is). I don't seem to need to do "defined?(Foo::Bar) && Foo::Bar.some_method".
Is it that common to use "defined?()"? For behaviour change I tend to use .respond_to? or even .is_a? more often.
Updated by AMomchilov (Alexander Momchilov) about 1 year ago
It's common in library code, that want's to be flexible to different configurations of the target application. E.g. https://github.com/rubocop/rubocop/blob/a455e9d55771f1e3dfea0cc4183e66f9632b431c/lib/rubocop/lockfile.rb#L31
Updated by p8 (Petrik de Heus) about 1 year ago
There's multiple examples in the Rails code base:
https://github.com/rails/rails/blob/7f7f9df8641e35a076fe26bd097f6a1b22cb4e2d/railties/lib/rails/generators/named_base.rb#L139
https://github.com/rails/rails/blob/7f7f9df8641e35a076fe26bd097f6a1b22cb4e2d/railties/lib/rails/generators/generated_attribute.rb#L67C1-L76
https://github.com/rails/rails/blob/7f7f9df8641e35a076fe26bd097f6a1b22cb4e2d/guides/rails_guides/markdown.rb#L184-L190
Considering the following:
def valid_type?(type)
DEFAULT_TYPES.include?(type.to_s) ||
!defined?(ActiveRecord::Base) ||
ActiveRecord::Base.connection.valid_type?(type)
end
It would be nice to rewrite this as:
def valid_type?(type)
DEFAULT_TYPES.include?(type.to_s) ||
ActiveRecord&::Base&.connection.valid_type?(type)
end
Updated by p8 (Petrik de Heus) about 1 year ago
sawa (Tsuyoshi Sawada) wrote in #note-1:
Currently, you can do
ActiveRecord::Base.some_method rescue nil
That would also rescue UndefinedMethodError, ArgumentError, and any error occuring in some_method
.
Furthermore, the specification you are asking for is not clear. What Exception classes do you want
&.
to rescue?
NameError
You stated that you want
&.
to work on classes, but that does not make sense. IfActiveRecord::Base
is not defined, it is not a class. How would Ruby know that it is (supposed to be) a class?
I have no idea how &.
is currently implemented, but defined?
also handles undefined classes.
Updated by sawa (Tsuyoshi Sawada) about 1 year ago
p8 (Petrik de Heus) wrote in #note-5:
Furthermore, the specification you are asking for is not clear. What Exception classes do you want
&.
to rescue?
NameError
Then you should edit and write that in the description.
defined?
also handles undefined classes.
Can you show an example of that?
Updated by p8 (Petrik de Heus) about 1 year ago
sawa (Tsuyoshi Sawada) wrote in #note-6:
Then you should edit and write that in the description.
I've updated the description. Hopefully it's more clear now.
defined?
also handles undefined classes.Can you show an example of that?
defined?(UndefinedConstant) # returns nil instead of raising an error
Updated by sawa (Tsuyoshi Sawada) about 1 year ago
sawa (Tsuyoshi Sawada) wrote:
You stated that you want &. to work on classes, but that does not make sense. If ActiveRecord::Base is not defined, it is not a class. How would Ruby know that it is (supposed to be) a class?
p8 (Petrik de Heus) wrote:
defined?
also handles undefined classes.
sawa (Tsuyoshi Sawada) wrote:
Can you show an example of that?
p8 (Petrik de Heus) wrote:
defined?(UndefinedConstant) # returns nil instead of raising an error
Where does it say that UndefinedConstant
is an "undefined class " rather than an undefined constant?
Updated by nobu (Nobuyoshi Nakada) about 1 year ago
What you want doesn't seem the extension of the safe navigation operator.
p8 (Petrik de Heus) wrote:
If a constant isn't defined it will raise a NameError:
DoesNotExist.some_method # raises: uninitialized constant DoesNotExist (NameError)
In libraries that have optional dependencies, we can check if the constant is defined before calling a method on it:
defined?(OptionalDependency) && OptionalDependency.some_method
Rather the extension of defined?
operator which returns the value (instead of the expression type string) or nil.
Let's call it defined⁉️
for now.
Isn't this what you want?
defined⁉️(OptionalDependency)&.some_method
Currently in Ruby, the Safe Navigation Operator is used to avoid NoMethodError exceptions when calling methods on objects that may be nil.
It would be nice if we could use the Safe Navigation Operator to avoid NameError on undefined constants as well.ClassThatMightNotExist&.some_method
This can't work because &.
will works on the result of the LHS, which might have raised already.
Updated by p8 (Petrik de Heus) about 1 year ago
nobu (Nobuyoshi Nakada) wrote in #note-10:
Rather the extension of
defined?
operator which returns the value (instead of the expression type string) or nil.Let's call it
defined⁉️
for now.
Isn't this what you want?defined⁉️(OptionalDependency)&.some_method
Yes, that would be nice.
Updated by kddnewton (Kevin Newton) about 1 year ago
@nobu (Nobuyoshi Nakada) has said this already, but just to reiterate, if we changed Foo&.bar
to do nothing in the case that it was undefined, it would break things like zeitwerk and other libraries that handle const_missing.