0

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.

2
  • "However, with this code, I get the undefined method generate_query for class A." – Hint: what line do you call it on? What line do you define it on? Which of the two comes first? Commented Mar 24, 2018 at 14:08
  • @JörgWMittag Thanks! I did realize that myself that the error is due to the ordering. However, just changing the ordering would not give desired behavior in case of sub-classes. Commented Mar 26, 2018 at 7:33

1 Answer 1

1

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
Sign up to request clarification or add additional context in comments.

3 Comments

This defeats the purpose of making query a class attribute. What this means is that generate_query will be called and a new string constructed every time a new instance of the class is created.
No, generate_query will be called only if @query is nil. @query is a class attribute and will stay defined on the A/B object forever - try it yourself by adding a puts statement in generate_query
Ah..my mistake. Thanks for your answer, this works perfectly.

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.