This ::
that you see prefixing a class name from time to time
::Account.find_by("123abc")
is called the Scope Resolution Operator.
You can think about it like this. The thing on the right-hand side of the ::
operator resolves to the scope of the thing on the left-hand side.
In the case of something like Net::HTTP
, the HTTP
resolves to being scoped to the Net
namespace.
As for our above example, ::Account
, there is nothing on the left-hand side which means Account
will be scoped to the global namespace.
How to use it
Referencing a class on the global namespace
Consider this paired down banking software example.
class Account
attr_accessor :first_name, :last_name, :email
def self.find_by(account_number)
# ...
end
end
module Bank
class Account
def withdraw(amount)
if would_overdraw(amount)
user_account = Account.find_by(account_number)
# send alert or email to user_account
end
# ...
end
end
end
In this example, I have the user's Account
within the web app as well as a Bank::Account
of which one or more could be associated with a user Account
.
In #withdraw
, I try to look up the user's Account
in the event of an overdraft so that they can be alerted.
Here's what happens when that overdraft code path is executed:
`withdraw': undefined method `find_by' for Bank::Account:Class (NoMethodError)
Take notice that it is looking for find_by
in Bank::Account
, not Account
.
Ruby applies the current namespace context when referencing Account
in #withdraw
. Because the call is within the Bank::Account
namespace, there is a NoMethodError
.
The find_by
method it is looking for is instead on Account
. And that's where the scope resolution operator comes into play.
def withdraw(amount)
if would_overdraw(amount)
user_account = ::Account.find_by(account_number)
# send alert or email to user_account
end
# ...
end
Prepending Account
with ::
tells Ruby that I want to reference that class from the global namespace. And now Ruby knows that I am talking about the other account, the one that defines find_by
.
We can use this same operator when defining a class if we'd like.
Defining a class on the global namespace
class SomethingDoer < BaseService
class SomethingIsAfootError < StandardError; end
def self.run(thing)
# do stuff
end
end
I've defined an error class within a service object. Because of that, the error class is now namespaced under that service object.
If I were to inspect that error class from inside the #run
method, I'd see this namespacing:
SomethingIsAfootError.ancestors
=> [SomethingDoer::SomethingIsAfootError, StandardError, Exception, Object, ...]
Notice how SomethingIsAfootError
is prefixed with SomethingDoer::
.
If however I want the error class defined at the global scope, I could apply the scope resolution operator when defining it.
class SomethingDoer < BaseService
class ::SomethingIsAfootError < StandardError; end
def self.run(thing)
# do stuff
end
end
Inspecting it this time, I see the error class is globally namespaced.
SomethingIsAfootError.ancestors
=> [SomethingIsAfootError, StandardError, Exception, Object, ...]
Note: in this second example, I could also define the error class outside of the service object. The example is meant to be instructive so as to convey how this operator can be used as part of a class definition.
If you enjoyed this post, join my newsletter to get a direct line on more stuff like this.
References
Acknowledgements
Thanks to Jack Christensen for providing feedback on an early draft of this post.
Top comments (0)