28

Is there any way in a Rails STI situation to throw an error when the base class is Instantiated? Overriding initialize will do it but then that gets trickled down to the subclasses.

Thanks

John Topley
  • 113,588
  • 46
  • 195
  • 237
Jake
  • 353
  • 4
  • 6

6 Answers6

70

The answer by John Topley is actually wrong. Setting abstract_class = true in the base class will actually cause the child classes to stop setting their type automatically. Plus, unless you set_table_name in the base class, the child classes will complain their table does not exist.

This is because the purpose of abstract_class=true is to set up inheritance when you are NOT using STI, and want to have an abstract class (class not backed by a db table) in your class hierarchy between ActiveRecord::Base and one or more model classes.

Having initialize raise is one solution, also adding validates_presence_of :type to the base class is a solution.

Note if you DO override initialize, you need to call super:

def initialize(*args)
  raise "Cannot directly instantiate an AbstractUser" if self.class == AbstractUser
  super
end
Michael Johnston
  • 5,298
  • 1
  • 29
  • 37
4

You can try this:

class BaseClass
  def initialize
    raise "BaseClass cannot be initialized" if self.class == BaseClass
  end
end

class ChildClass
end

the result will be:

a = BaseClass.new  # Runtime Error
b = ChildClass.new # Ok

Hope that helps

Samuel Chandra
  • 1,185
  • 7
  • 13
  • 3
    Since BaseClass will probably descend from ActiveRecord::Base, initialize should probably call super. – Douglas Jan 18 '14 at 17:33
1

I often prefer to simply make the new class method private with:

class Base
  private_class_method :new 
end

This way accidental instantiation of the Base class triggers an error, but it's still possible to instantiate it with Base.send(:new) to write tests for the Base class.

svoop
  • 3,318
  • 1
  • 23
  • 41
1

even better than validating the presence is validating against the known list of accepted non abstract class types

  validates :type, :inclusion=> { :in => ["A", "B", "C"] }

because if you validate just for presence an "evil developer" can still pass in the abstract class name as the type parameter.

-1

In the initialize function check that the class is the STI base class.

Though the question is why would you exactly want to do this? It seems more likely that trying out a different design might help you more.

Jakub Hampl
  • 39,863
  • 10
  • 77
  • 106
-7

You can do self.abstract_class = true in the base class to tell ActiveRecord that it's an abstract class.

John Topley
  • 113,588
  • 46
  • 195
  • 237