Manish Chauhan
Manish Chauhan

Reputation: 73

Scope resolution works differently in ruby when written in different ways

I faced this issue while developing a feature.Lets say there is following code:

case 1:
module Person
  module Employee 
    class Officer
      def self.print_class(obj)
        obj.is_a? Employee
      end
    end
  end
end

case 2:
class Person::Employee::Officer
  def self.print_class(obj)
       puts obj.is_a? Employee
  end
end

class Employee < ActiveRecord::Base
end

emp = Employee.last

and we have model Employee . Now

For case 1: Person::Employee::Officer.print_class(emp) gives "false"

For case 2: Person::Employee::Officer.print_class(emp) gives "true"

Why is this happening?

Upvotes: 7

Views: 476

Answers (2)

Giuseppe Schembri
Giuseppe Schembri

Reputation: 857

At first I will try to make clearer what is the context in which the class method Emplyee::print_class is defined:

# top-level context

class Employee
end

module Person
  module Employee 
    class Officer
      # context_a the context of case 1 

      ## here the constant Employee is a Module
      p (Employee.class) # -> Module
      # ## If you want a reference to a constant defined at the top-level(the Employee class)
      # # you may preceded it with ::
      p ::Employee.class # ->  class
      p ::Employee == Employee # -> false
    end
  end
end

class Person::Employee::Officer
  #context_b the context of case 2

  # here The constant Employee is the class Employee
  # There are not Employee Constant defined in this context
  # the constant look up will reach the top-level context
  # and Employee will reference to ::Employee
  p (Employee.class) # -> Class
  p ::Employee == Employee # -> true
end

Now we can take into consideration the method Emplyee::print_class definition and execution.

When you defined the method Emplyee::print_class you used the constant Employee:

When and in which context this constant is evaluated to be a class or a module or a string?

def self.print_class(obj)
  obj.is_a? Employee
end

The answer to when is: when the method is executed.

The answer to in which context is: the context where you defined the method, not the one you execute it. For the Constant is the outer scope of the method definition (If you try to create a constant inside the method you will get an error 'dynamic constant assignment').

In your example case 1 the context will be context_a.

In your example case 2 the context will be context_b, but the constant look-up will reach the top-level context.

Upvotes: 0

max
max

Reputation: 101811

:: is the scope resolution operator. Unlike the class and module keywords it does not reopen the module and properly set the module nesting.

For example:

TEST = "I'm in the global scope"

module Foo
  TEST = "I'm scoped to foo"
end


module Foo::Bar
  def self.test
    TEST 
  end

  def self.nesting
    Module.nesting
  end
end

puts Foo::Bar.test # "I'm in the global scope"
puts Foo::Bar.nesting.inspect [Foo::Bar]

This is because the module nesting is resolved lexically at the point of definition. When you do module Foo::Bar that module nesting is the global scope - when you reference TEST it is not resolved to Foo::TEST since Foo is not in the module nesting.

In your case 2 Employee is resolved to ::Employee not Person::Employee.

Therefore you should always explicitly nest classes and modules as it will set the correct module nesting and avoid these very unexpected module lookups.

TEST = "I'm in the global scope"

module Foo
  TEST = "I'm scoped to foo"
  module Bar
    def self.test
      TEST 
    end

    def self.nesting
      Module.nesting
    end
  end
end

puts Foo::Bar.test # "I'm scoped to foo"
puts Foo::Bar.nesting.inspect [Foo::Bar, Foo]

Upvotes: 1

Related Questions