Home > Back-end >  Ruby/Rails - force subclasses to override specific methods?
Ruby/Rails - force subclasses to override specific methods?

Time:12-12

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.

  • Related