loading...

Referencing the global namespace with Ruby's Scope Resolution Operator

jbranchaud profile image Josh Branchaud Updated on ・3 min read

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.

References

Acknowledgements

Thanks to Jack Christensen for providing feedback on an early draft of this post.

Posted on by:

jbranchaud profile

Josh Branchaud

@jbranchaud

I'm a developer and consultant focused primarily on the web, specializing in React, Ruby on Rails, and PostgreSQL. Newsletter: https://tinyletter.com/jbranchaud

Discussion

markdown guide