Ashu Pachauri
Ashu Pachauri

Reputation: 1403

Initializing class attribute using a method

The goal is to initialize a class attribute using a class method that's overridden in sub-classes. Following is the definition of my ruby classes:

class A
  class_attribute :query
  self.query = self.generate_query

  def self.generate_query
     return "abcd"
  end
end

class B  < A
  def self.generate_query
     query_part_1 = "ab"
     return query_part_1 + generate_query_part_2
  end

  def self.generate_query_part_2
    return part_2
  end
end

The reason I want to do this is because query is a constant string per class and should not be created again on instantiation but it's a complex string which is generated in multiple independent parts. Separating this logic out in methods would make the code cleaner. However, with this code, I get the undefined method generate_query for class A.

I have tried lazy initialization of the class attribute while instantiating the class like the following:

def initialize
  query = self.class.get_query
end

def self.get_query
    self.query = self.generate_query if self.query.nil?
end

However, this initializes the query to same value for both class A and B if A is instantiated first because self.query.nil? would then return false for B also.

Upvotes: 0

Views: 705

Answers (1)

Nether
Nether

Reputation: 1160

The solution to your problem is simple:

You are calling self.query = self.generate_query before your generate_query method has been defined! Remember - Ruby is interpreted top to bottom and your class body is no different. You cannot call a method before it is defined. Simply changing the code around to

class A
  class_attribute :query

  def self.generate_query
    return "abcd"
  end

  self.query = self.generate_query
end

will make it work, but then you will have another problem, as the line self.query = self.generate_query will only get evaluated once in your class - B will reference the "abcd" query, not "ab2".

To achieve the behavior you want - you need to define a getter method yourself which acts as an attribute (class_attribute does the same thing under the hood btw)

Solution

class A
  def self.query
    @query ||= self.generate_query
  end

  def self.generate_query
    return "abcd"
  end
end

class B  < A
  def self.generate_query
    query_part_1 = "ab"
    return query_part_1 + generate_query_part_2
  end

  def self.generate_query_part_2
    return '2'
  end
end

Upvotes: 1

Related Questions