I'm wondering if there is a way to force a subclass to override a method from its parent method in either Ruby or Rails (in Java you would do this with an abstract class/method).
Let's say I have the following classes:
class Pet
end
class Dog < Pet
def collar_color
"red"
end
end
class Cat < Pet
end
dog = Dog.new
dog.collar_color
==> red
cat = Cat.new
cat.collar_color
==> NoMethodError
In this example I would never instantiate a Pet object, but it exists to serve as a way to collect common methods to common classes. But let's say I want to ensure that all subclasses of Pet override the collar_color
method. Is there a way to do that? Could I achieve it through testing in some way? Assume I don't want a default defined in the parent class.
My real-life use case is a collection of classes that all have polymorphic ownership of another class. If I have a display page of the owned class, then one of the owner classes not having a method could leave me with a NoMethodError
problem.
CodePudding user response:
Just define the default method implementation in the abstract class by raising Not implemented error or something. By doing that you also clarifies in your class design that when others / you want to inherit the Pet
class they need to override collar_color
method. Clarity is a good think and there is no benefit in not defining a default method in the abstract class.
Or if you want to achieve that by testing you can create a test case for Pet
class that check if its descendants is defining their own collar_color
method or not. I think Rails / Ruby 3.1 have .descendants
methods defined or you can just google them.
# Pet_spec.rb
describe "descendants must implement collar_color" do
it "should not throw error" do
descendants = Pet.descendants
descendants.each do |descendant|
expect { descendant.new.collar_color }.to.not raise_error
end
end
end
CodePudding user response:
in Ruby you can use the abstract keyword to define an abstract method in the parent class that must be implemented by any subclasses. For example:
class Pet
abstract :collar_color
end
class Dog < Pet
def collar_color
"red"
end
end
class Cat < Pet
def collar_color
"blue"
end
end
Now, if you try to instantiate a Pet object, you will get an error:
pet = Pet.new
=> TypeError: Cannot instantiate abstract class Pet
However, the Dog and Cat classes can be instantiated and will return the correct result for the collar_color method:
dog = Dog.new
dog.collar_color
=> "red"
cat = Cat.new
cat.collar_color
=> "blue"
You can also use the prepend method to ensure that any subclasses implement the required method:
class Pet
def collar_color
raise "This method must be implemented by a subclass"
end
end
class Dog < Pet
prepend collar_color
def collar_color
"red"
end
end
class Cat < Pet
prepend collar_color
def collar_color
"blue"
end
end
If any subclass does not implement the collar_color method, it will raise an error when the method is called:
dog = Dog.new
dog.collar_color
=> "red"
cat = Cat.new
cat.collar_color
=> "blue"
pet = Pet.new
pet.collar_color
=> RuntimeError: This method must be implemented by a subclass
You can also use testing to ensure that the required method is implemented in the subclasses. For example, using RSpec:
RSpec.describe Pet do
describe "collar_color" do
it "must be implemented by a subclass" do
expect { subject.collar_color }.to raise_error(RuntimeError, "This method must be implemented by a subclass")
end
end
end
You can then include this test in the specs for each subclass to verify that they implement the required method.