:: that you see prefixing a class name from time to time
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
HTTP resolves to being scoped to the
As for our above example,
::Account, there is nothing on the left-hand side which means
Account will be scoped to 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
#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
Ruby applies the current namespace context when referencing
#withdraw. Because the call is within the
Bank::Account namespace, there is a
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
:: 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
We can use this same operator when defining a class if we'd like.
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, ...]
SomethingIsAfootError is prefixed with
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.
Thanks to Jack Christensen for providing feedback on an early draft of this post.