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):
- An ActiveRecord class will pick the
table_name_prefix
of any of its module parents, if available. Only when none provide one, its owntable_name_prefix
is used. (seefull_table_name_prefix
Show archive.org snapshot inActiveRecord::ModelSchema
). - The relevant call to
full_table_name_prefix
happens incompute_table_name
Show archive.org snapshot . - And
compute_table_name
is called only if notable_name
has been defined for a class (seereset_table_name
Show archive.org snapshot andtable_name
Show archive.org snapshot ). This is why you might be tempted to fix the odd behavior by just settingself.table_name = "universe_world_earths"
and move along. But don't do that just yet.
Explanation
This explains why we get
"universe_universe_world_earths"
forUniverse::World::Earth
fromcompute_table_name
.
It takes"universe_worlds"
from the module parent, adds its own"earths"
, but also prefixes that with"universe"
(again, because of howfull_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
, themodule_parents
are[Universe, Object]
, i.e.Universe.table_name_prefix
i.e. our"universe_"
. - For
Universe::World::Earth
, themodule_parents
are[Universe::World, Universe, Object]
-- andUniverse::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.