Rails NoMethodError when calling a method from a constant

Viewed 206

I'm trying to call a method inside a class constant. It returns a NoMethodError. Here's a sample code:

class TestingA
  CONSTANT_HERE = { details: get_details('example.json') }

  def get_details(file)
    # retrieve details here
  end
end

The error that appears is:

NoMethodError (undefined method `get_details' for TestingA:Class)

Any ideas on why?

1 Answers

Generally, dynamic constant assignment is discouraged in Ruby. Why would you want to define a constant that can possibly change within the life-cycle of an object? We don't know exactly what get_details is doing, but what is the use case of creating an instance method that is called from a constant as well as exposing the method? We can only assume return value is dynamic at this stage. Rubocop is also going arrest you for not freezing the constants, which is bad as linters are a good tool to abide by.

Constants can be changed and there is no way to avoid this as variables in Ruby are not containers: they point towards an object. However, it is your duty to make your code readable. If you see a constant that you cannot easily discern the value of, would you think that is readable?

We should talk about how Ruby loads constants and, more generally, files. Every Ruby application has its entry point. The interpreter will need the file to load and execute the commands of the application. The Ruby interpreter will increment over each statement inside your file and execute them following a specific set of rules, until it parses the entire file. What happens when the interpreter iterates to a constant? What other types of constants are there? Hint: CONSTANT_HERE is not the only constant you are defining.

The class keyword is processed first and the interpreter creates a constant 'TestingA' and stores in that constant a class object. The name of the class instance is "TestingA", a string, named after the constant. Yes, classes and modules are constants. Each class has a constant table, which is where "TestingA" will be stored. After this, the body of the class is interpreted and a new entry is created in the constant table for "CONSTANT_HERE" and the value is set and stored. This is happening before your definition of get_details has been defined: the interpreter wants to set the value, to store it, before "get_details" has been interpreted, which is why you are experiencing the error you are.

Now we know this, and wanting to provide an example of how constants are evaluated in code, you would need to mimic the below in order to have a method defined in a constant:

def get_details(file)
  "stub_return"
end

class TestingA
  CONSTANT_HERE = { details: get_details('example.json') }
end

In my opinion, the above is not good practise as it is an example mis-use of constant assignment. If you want to get details from a file, define a class/method and call the API/method instead. It's neater, assigns role of responsibility and is less ambiguous.

Related