allenwoot
allenwoot

Reputation: 961

Accessing a static variable and method in a module

I have a module defined as follows in app/presenters/my_namespace/base_presenter.rb:

module MyNamespace
  class BasePresenter
  end

  MY_SET = Set.new(['a', 'b', 'c'])

  def self.my_method
    true
  end
end

When I run MyNamespace::MY_SET or MyNamespace::my_method in the console, I get an uninitialized constant error, but I can do MyNamespace::BasePresenter.new. What am I doing wrong?

Upvotes: 0

Views: 1006

Answers (3)

Aleksey Shein
Aleksey Shein

Reputation: 7482

I assume you're trying this out in Rails development mode. In this mode all constants (classes, modules and constants) are loaded by demand.

When ruby meets undefined constant, it throws an error, which autoloader intercepts and tries to load the constant by converting its name to a path.

In your case, autoloader tries to find MyNamespace::MY_SET in app/presenters/my_namespace.rb (which fails) and has no idea that you actually defined it in app/presenters/my_namespace/base_presenter.rb. But after you have loaded your MyNamespace::BasePresenter (which, btw, lies on the correct path), MyNamespace, MyNamespace::MY_SET, and MyNamespace.my_method got initialized and become available.

What you need to do is to

a) define MyNamespace correctly and move methods and constants to its definition:

app/presenters/my_namespace.rb

module MyNamespace
  MY_SET = Set.new(['a', 'b', 'c'])

  def self.my_method
    true
  end
end

app/presenters/my_namespace/base_presenter.rb

# note that I don't open module here, 
# but use a constant to enable autoloading of MyNamespace module
class MyNamespace::BasePresenter
end

or

b) just move all methods/constants to your BasePresenter class. Since it lies on a correct path, constant MyNamespace::BasePresenter::MY_SET will just work.

module MyNamespace
  class BasePresenter

    MY_SET = Set.new(['a', 'b', 'c'])

    def self.my_method
      true
    end
  end
end

Bonus part

The difference between

module MyNamespace
  class BasePresenter
  end
end

and

class MyNamespace::BasePresenter
end

is when MyNamespace is undefined in first case it will be defined (module MyNamespace either opens existing module or defines new one), but in second case the mechanism described above will try to load MyNamespace somewhere, and if it fails - you'll get uninitialized constant error MyNamespace.

What it also means to you is if you define all your namespaced classes as a first case (inside module MyNamespace)

app/presenters/my_namespace/base_presenter.rb

module MyNamespace
  class BasePresenter
  end
end

and also have MyNamespace in its proper place with some code inside

app/presenters/my_namespace.rb

module MyNamespace
  MY_SET = Set.new(['a', 'b', 'c'])
end

And if your MyNamespace::BasePresenter will gets loaded first, it'll actually define MyNamespace and your app/presenters/my_namespace.rb will not be loaded (since autoloading loads only missing constants), and you'll have to require it yourself.

presenter = MyNamespace::BasePresenter.new
MyNamespace::MY_SET # boom, uninitialized constant error

The solution here is to define modules in 1 proper place (that autoloading knows how to find) and use class MyNamespace::BasePresenter format for defining namespaced classes in their proper locations.

Upvotes: 2

sawa
sawa

Reputation: 168131

Probably you have not required the set gem. The class Set is defined in the set gem. It is part of the standard library, but not part of Ruby core. In that case, creation of BasePresenter succeeded because that was done prior to MY_SET = Set.new(['a', 'b', 'c']), which raised the error. MY_SET and my_method were not defined because those definitions appear on or after the offending line.

You need to do:

require "set"

Upvotes: 0

smathy
smathy

Reputation: 27961

There's nothing wrong with that code, which suggests that the problem is a load (or reload) problem. Stop and restart your console, and/or spring stop if you have the default spring gem installed.

Upvotes: 0

Related Questions