ruby-on-railsactiverecordsingle-table-inheritance

How to get an instance of the base class in a Ruby on Rails class with inherited classes (STI)


I've got user and they've got pets. All kinds of pets. Reptiles, fish, birds, and mammals. The Mammal model has two sub-classes, inherited through single table inheritance (STI): cats and dogs. My user has a method to get their last pet.

In a static pages controller, I have a method to visit the user's last pet:

  def go_to_pet
    redirect_to current_user.last_pet || root_path
  end

Now the issue I'm running into is that when a cat or a dog is returned as the user's last pet, the app is looking for the cats or dogs controller, but I only have and need a mammals controller. The mammals controller is handling both cats and dogs pretty fine when it is actually invoked.

My question is such: how can I retrieve an instance of the Mammal class? I've tried Mammal.find(cat.id), but it will return a cat automatically because that's the whole point of STI.

The only ugly workaround I can think of is checking the class in the static pages controller (in the code block above) and then have redirect_to use the mammals controller, but I'd like to rather avoid this.

I tried to explicitly load the base class through Mammal.find(cat.id), but it returned an instance of the inherited class Cat based on the type column automatically. I had hoped to get an instance of the Mammal class.


Solution

  • I think you're trying to shoot yourself in the foot. If you have a Dog and Cat class instead of just a Mammal class then they probably deserve their own controllers. And if they behave the same then just make them trivial subclasses of a parent with shared behavior.

    But if you really wanna do what you describe, I'd probably do it something like:

      def go_to_pet
        last_pet = current_user.last_pet
        if last_pet.class.superclass.abstract_class?
          redirect_to url_for(controller: last_pet.class.base_class.name.tableize, id; last_pet.id)
        else
          redirect_to current_user.last_pet || root_path
        end
      end
    

    But mostly I'd probably just head it off in the routes.

      resources :dogs, controller: 'mammals'