4

I have set up two classes as shown below

class Parent

  def self.inherited(child)
    child.custom_class_method
  end

  def self.custom_class_method
    raise "You haven't implemented me yet!"
  end
end

class Child < Parent

  def self.custom_class_method
    "hello world"
  end
end

It seems that when the inheritence Child < Parent is evaluated, it calls self.inherited, which in turn raises the Parent's version of self.custom_class_method instead of the Child's. This is a problem because instead of getting an expected "hello world" I get an error raised saying "You haven't implemented me yet!"

Does Child's self.custom_class_method not get evaluated until after Parent's self.inherited is finished evaluating? And if so is there perhaps a work around this? Should I just not put a raise check on the Parent class?

2
  • This seems weird. The only way to get the Parent's custom_class_method should be to call on super. Otherwise, just called Child.custom_class_method should result in your "hello world" output. Could you please provide deeper logging? Commented Oct 13, 2015 at 1:01
  • Hmmm I agree it should ! But it seems as though I can replicate this by simply copy-pasting the code block into an irb console. It errors out when evaluating the Child class because of this very reason. Commented Oct 13, 2015 at 1:07

3 Answers 3

5

I think this should clarify:

class Parent
  def self.inherited(child)
    puts "Inherited"
  end
end

class Child < Parent
  puts "Starting to define methods"
  def self.stuff; end
end

Output makes it clear that .inherited gets called the moment you open the new class, not when you close it. Thus, as you guessed, Child.custom_class_method does not exist at the point when you are trying to call it - all .inherited sees is a blank slate.

(As to how to get around it... I can't say without more insight into what you are trying to do, unfortunately.)

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

7 Comments

Ah smart, I didn't think about debugging it like this! So what would be the best way to have abstract methods defined here? Do I just avoid raising errors (which is something I'm uncomfortable with to be honest).
Essentially what I'm trying to do is create a Parent class that is able to execute code in the self.inherited block that will differ slightly from child class to child class. However to ensure we don't get errors, I want to make it mandatory that each child inheriting will define custom_method (hence why I made the Parent class raise).
Don't ask "how do I get child code executed at inherit", because you can't. It seems to be an XY problem. Why do you want to have child code executed at inherit? .inherited is there to notify parents of new children, not to run code on children. Children can run code on children (see my puts).
Ah I see. It seems as though my thought process intrinsically doesn't follow design principles of inheritance (which I suppose is fine). What I was hoping to do was to abstract logic that various classes were doing (they were each establishing a connection to their own customized database oat the class level) into a Parent class. Once inherited, the Parent would ask the child what database they wanted to connect to and do it for them. This would have hopefully abstracted and kept this logic DRY instead of having it repeated throughout each model.
Yes sorry for the vagueness but your advice is spot on actually. Thank you ! So in each child I'll call DatabaseManager.connect_to(custom_database) . Thanks again!
|
3

The template pattern/lazy initialization may help solve your problem. The code assumes what is different between the children classes is the database connection information, perhaps just a table name or perhaps completely different databases. The parent class has all the code for creating and maintaining the connection, leaving the children only the responsibility of providing what is different.

class Parent
  def connection
    @connection ||= make_connection
  end

  def make_connection
    puts "code for making connection to database #{database_connection_info}"
    return :the_connection
  end

  def database_connection_info
    raise "subclass responsibility"
  end
end

class Child1 < Parent
  def database_connection_info
    {host: '1'}
  end
end

class Child2 < Parent
  def database_connection_info
    {host: '2'}
  end
end

child1 = Child1.new
child1.connection  # => makes the connection with host: 1
child1.connection  # => uses existing connection

Child2.new.connection  # => makes connection with host: 2

4 Comments

Awesome stuff, thanks! As a design question do you think this is any better/worse than abstracting this into a DatabaseManager model which maintains this connection logic and call DatabaseManager.make_connection in each class rather than inheriting it ?
Excellent question. Delegation vs inheritance is an age old question. Rails does persistence via inheritance (technically can include modules instead but typically done via inheritance). Many don't think persistence should be a responsibility of the class and go another direction, such as the data mapper pattern (en.wikipedia.org/wiki/Data_mapper_pattern). Even if you fall in the persistence-is-a-responsibility camp, you may still want to abstract database behavior outside the class -- maybe a connection pool. There are good libraries for this, such as the sequel gem.
I am not married to the DatabaseManager idea, so this is another excellent suggestion. As I said, my answer was necessarily vague, as you know about your project way more than I do. The main point I was trying to make is that you can't do this on inherit. You can do it with an external manager, or with inherited method as here, or even with an initialize on an ancestor; but inherited has another purpose.
How to make the opposite, so so that method, instead of raising the error "children responsibility" would be impossible for them to implement?
0

You have to wrap self.inherited in Thread.new

Example:

class Foo
  def self.inherited klass
    begin
      puts klass::BAZ
    rescue => error
      # Can't find and autoload module/class "BAZ".
      puts error.message
    end

    Thread.new do
      # 123
      puts klass::BAZ
    end
  end
end

class Bar < Foo
  BAZ = 123
end

Comments

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.