Today I wanted to share with you a trivial pattern, that I call “technical key”. In rails apps, we typically use surrogate keys (autoincremented ids without any business meaning) as a primary key for database tables. However there are cases where our code depends on database data. In my previous article, I shared the case where the code assumed certain team exists and finds it in the database. There are many cases like that in Rails apps, where we give users the power to configure certain aspects of the app via UI and the configuration is stored in the database.
Whether we want to find a team, country, or organization, you need a way to
locate such a record in the database. That’s where the technical key becomes useful.
For simplicity, I usually call the field identifier
.
Let’s say we need to check if a country belongs to European Union.
class Organization < ApplicationRecord
has_many :members
has_many :countries, through: :members
validates_uniquness_of :identifier
def self.by_identifier(identifier)
find_by!(identifier: identifier.to_s)
end
end
With this setup, we can use Organization.by_identifier(:eu)
in the code that depends on it.
I believe this approach is much more elegant than finding by hardcoded integer ID, or assigning an integer ID to a constant and assuming the ID is the same in all environments.
For some concepts there are natural keys that can be used for this purpose. Countries, currencies, languages have standardized codes. Your domain-specific concepts will likely need to invent their own.
I quickly checked that in my current app, there are 20 tables with such technical keys. Examples include:
- Permissions
- Email templates
- Surveys
- AI Prompts
- Teams
When it comes to testing, I like to have factory traits named the same as the identifier.
factory :organization do
trait :eu do
name { 'European Union' }
identifier { 'eu' }
countries { [...] }
end
end
It’s worth noting that since these values rarely change, some teams manage such tables using classic seeds. In some cases, these seeds are even synchronized with production data. However, that’s a topic for a different article.