0

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?

3
  • Your MyNamespace::MY_SET and MyNamespace::my_method should work. What error do you have? Commented Sep 17, 2015 at 18:43
  • 1
    Does MyNamespace::MY_SET work after you do MyNamespace::BasePresenter.new? Commented Sep 17, 2015 at 19:01
  • @AlexeyShein it does!! What is going on here? Commented Sep 17, 2015 at 19:15

3 Answers 3

2

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks so much for the detailed answer! Could you elaborate on class MyNamespace::BasePresenter vs putting the class definition inside an open module?
0

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.

Comments

0

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"

1 Comment

He uses Rails end it's required by default. Also he would get an error in the initialization if it was not initialized Set.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.