When we move from model IDs to slugs, we have to pay attention to controller actions’ names collision.
A while back, I wrote about the simplest method to implement slugs in Rails. While the implementation is easy, it’s not everything we have to pay attention to. Once we move from IDs to slugs, we need to make sure our users don’t choose slugs that would break the application.
The /teams/:slug
path could override or be overriden by /teams/controller_action_name
.
The following snipped would be a good start as alphanumeric restrictions are good for a URL identifier:
class Team < ApplicationRecord
...
validates :slug,
presence: true,
uniqueness: true,
length: {minimum: 2, maximum: 30},
format: {with: /\A[a-zA-Z0-9]+\Z/}
But it’s not enough. The entered slugs could still collide with regular application paths. What if someone choose the name of your controller action? Or something you want to reserve for the future?
To combat this, we could write a custom validator for this purpose:
# app/validators/restricted_paths_validator.rb
class RestrictedPathsValidator < ActiveModel::Validator
RESTRICTED_PATHS = TeamsController.action_methods + [
"admin",
"admins"
]
def validate(record)
if RESTRICTED_PATHS.include?(record.slug)
record.errors.add :slug, :restricted_path
end
end
end
Then include it in your model:
# Avoid slug conflicts with routes
validates_with ::RestrictedPathsValidator
The validator is restricting all current actions from the controller and more paths can be added on top.
If we want to provide a custom error message, we can add an I18n entry for activerecord.errors.messages.restricted_path
:
en:
activerecord:
errors:
messages:
restricted_path: not allowed
Get Test Driving Rails and make your tests faster and easier to maintain.