Back to Blog
Tips6/8/2025

Rails STI: Keeping all subclasses in the same file

I recently received a comment on my STI tutorial asking how to keep all the subclasses in one file. Indeed, creating one file for one line of code is a bit overkill but that's the way Rails works: Conventions over configuration.

However, I thought it was interesting to talk about 2 ways to avoid creating new files.

Initial Code

If you remember correctly, in the first part of the STI tutorial, we had the following:

# app/models/animal.rb
class Animal < ActiveRecord::Base
  belongs_to :tribe
  self.inheritance_column = :race

  # We will need a way to know which animals
  # will subclass the Animal model
  def self.races
    %w(Lion WildBoar Meerkat)
  end

end

--

# app/models/lion.rb
class Lion < Animal; end

--

# app/models/meerkat.rb
class Meerkat < Animal; end

--

# app/models/wild_boar.rb
class WildBoar < Animal; end

We had to split the subclasses in different files to use Rails autoloading. Without it, the classes Lion, Meerkat and WidlBoar were not available until we made at least one call to Animal. Let's see how we can keep everything in the same file.

1. Autoload

Here's the code we want to fix. If you use the following, start a rails console and try to access Lion, you will get an undefined error.

# app/models/animal.rb
class Animal < ActiveRecord::Base
  belongs_to :tribe
  self.inheritance_column = :race

  # We will need a way to know which animals
  # will subclass the Animal model
  def self.races
    %w(Lion WildBoar Meerkat)
  end

end

class Lion < Animal; end
class Meerkat < Animal; end
class WildBoar < Animal; end

That's simply because Rails is not aware of this class being declared here. We just have to tell him! To do that, we can use the autoload method.

Create an initializer in config/initializers named animal_loading.rb and put the following inside:

# config/initializers/animal_loading.rb
autoload :Lion, 'animal'
autoload :WildBoar, 'animal'
autoload :Meerkat, 'animal'

Thanks to this code, Rails will know where these classes are defined and they will be available anytime you need them!

2. Use a module

Another way to fix the subclasses loading is to use a module. This approach is far from the Rails way and I wouldn't recommend using it. Anyway, let's see how to do it.

What we're going to do, is replace the animal.rb file that loads a class with a file that loads a module containing our classes.

First, let's rename animal.rb to savanna.rb. Then, we have to change savanna.rb to look like this:

module Savanna
  class Animal < ActiveRecord::Base
    belongs_to :tribe
    self.inheritance_column = :race

    validates :race, presence: true

    scope :lions, -> { where(race: 'Lion') }
    scope :meerkats, -> { where(race: 'Meerkat') }
    scope :wild_boars, -> { where(race: 'WildBoar') }

    def talk
        raise 'Abstract Method'
    end

    class << self
      def races
        %w(lion wild_boar meerkat)
      end
    end
  end

  class Lion < Animal; end
  class Meerkat < Animal; end
  class WildBoar < Animal; end
end

See what we did there? Now Rails will autoload the module Savanna which contains our parent class and its subclasses. Main inconvenient: you will have to add the module name before your subclasses when you use them:

Savanna:Animal
Savanna::Lion
Savanna::Meerkat
Savanna::WildBoar

If you don't like to have a lot of files when using Single Table Inheritance, you can use these methods. However, I would still recommend splitting your subclasses in different files, especially if you plan to add some code inside them.

Comments

Loading comments...

Level Up Your Dev Skills & Income 💰💻

Learn how to sharpen your programming skills, monetize your expertise, and build a future-proof career — through freelancing, SaaS, digital products, or high-paying jobs.

Join 3,000+ developers learning how to earn more, improve their skills, and future-proof their careers.

Rails STI: Keeping all subclasses in the same file | Devmystify