How to define a table name prefix for all Rails models in a given namespace

Posted . Visible to the public.

ActiveRecord computes table names of model classes, and results are usually just like you'd expect.
Adding a prefix for all classes in a namespace can feel a odd or broken, but does not have to be. It's actually very easy when done right.

Summary (tl;dr)

Here's the short version: Define a table_name_prefix method in the namespace module, and do not define any table_name_prefix in ActiveRecord classes inside of it. If this sounds familiar, we have a card about using it already.

This card explains why you want to use it. Read on for details.

Deep dive

Okay, so the example above has at least one obvious benefit: You do not have to put self.table_name_prefix = "universe_" into each of your classes.

But wait, there's more!

Consider a case where you define a class which is a module child (not a subclass) of an existing class in the namespace, like Universe::World::Earth.
Universe::World::Earth.table_name may now be very different, depending on how you defined the table_name_prefix.

Example outline

Below, we'll use the following classes as an example.

class World < ApplicationRecord; end
class World::Earth < ApplicationRecord; end

Without any further configuration, Rails will produce the following database table names.

>> World.table_name
=> "worlds"
>> World::Earth.table_name
=> "world_earths"

This makes sense and is expected. All is well.

Things get complicated when surrounding those classes with a module.

module Universe
  class World < ApplicationRecord; end
  class World::Earth < ApplicationRecord; end
end

By default, that module has no effect on table names. You'll still get "worlds" and "world_earths" respectively.

However, if you want to prefix classes in that module with "universe_", there are ways to do it right or wrong.

Example A: Setting a class' table_name_prefix

You might start doing the following.

class Universe::World < ApplicationRecord
  self.table_name_prefix = "universe_"
end
class Universe::World::Earth < ApplicationRecord
  self.table_name_prefix = "universe_"
end

Let's see the table names.

>> Universe::World.table_name
=> "universe_worlds"

Good. 👍

>> Universe::World::Earth.table_name
=> "universe_universe_world_earths"

Nobody wants that. 👎

In fact, it does not matter what you put into Universe::World::Earth.table_name_prefix or if you even define it. It's not read at all.
Here is why (follow links for a better understanding):

Explanation

This explains why we get "universe_universe_world_earths" for Universe::World::Earth from compute_table_name.
It takes "universe_worlds" from the module parent, adds its own "earths", but also prefixes that with "universe" (again, because of how full_table_name_prefix works).

Looking closely at how compute_table_name works, you'll see the actually intended way to do it. Here's how:

Example B: Defining table_name_prefix on the module

This might feel odd at first, but bear with me.

module Universe
  
  def self.table_name_prefix
    "universe_"
  end
  
end
class Universe::World < ApplicationRecord
end
class Universe::World::Earth < ApplicationRecord
end

This will actually produce really useful table names.

>> Universe::World.table_name
=> "universe_worlds"
>> Universe::World::Earth.table_name
=> "universe_world_earths"

IMO that's exactly what anyone would expect. And we don't even have any table_name_prefix setter calls in model classes of the Universe module.

Why is that?

Look again at ActiveRecord::ModelSchema's compute_table_name Show archive.org snapshot shows that the method itself would actually compute the correct name by taking the table_name from the class' module_parent, singularizing and then adding its own name, and prefixing with full_table_name_prefix (an internal method).

The key is how full_table_name_prefix Show archive.org snapshot works: it checks its module_parents and uses the table_name_prefix of the first that responds to it.

  • For Universe::World, the module_parents are [Universe, Object], i.e. Universe.table_name_prefix i.e. our "universe_".
  • For Universe::World::Earth, the module_parents are [Universe::World, Universe, Object] -- and Universe::World.table_name_prefix is pre-defined by ActiveRecord as "".

So compute_table_name does not try to add a prefix for Universe::World::Earth. Instead, it just takes the table_name as described above, adds "earths", and done. 🎉

Note

If you feel this is relying too much on internal ActiveRecord logic, I understand your point. You are obviously free to use self.table_name = "..." in all such classes.
However, the implementation has been stable like that for a long time and there are no indications anything would change. And even if, any automated tests would fail immediately when trying to read the wrong table name, and you could still adjust then, if ever necessary.

Arne Hartherz
Last edit
Arne Hartherz
License
Source code in this card is licensed under the MIT License.
Posted by Arne Hartherz to makandra dev (2025-08-14 10:46)