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 sub
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
You can do self.abstract_class = true
in the base class to tell ActiveRecord that it's an abstract class.
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
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.
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.
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.