There is a nice solution to that problem using routes constraints.
Using routes constraints
As the rails routing guide suggests, you could define routes constraints in a way that they check if a path belongs to a language or a category.
# config/routes.rb
# ...
get ':language', to: 'top_voted#language', constraints: lambda { |request| Language.where(name: request[:language]).any? }
get ':category', to: 'top_voted#category', constraints: lambda { |request| Category.where(name: request[:category]).any? }
The order defines the priority. In the above example, if a language and a category have the same name, the language wins as its route is defined above the category route.
Using a Permalink model
If you want to make sure, all paths are uniqe, an easy way would be to define a Permalink
model and using a validation there.
Generate the database table: rails generate model Permalink path:string reference_type:string reference_id:integer && rails db:migrate
And define the validation in the model:
class Permalink < ApplicationRecord
belongs_to :reference, polymorphic: true
validates :path, presence: true, uniqueness: true
end
And associate it with the other object types:
class Language < ApplicationRecord
has_many :permalinks, as: :reference, dependent: :destroy
end
This also allows you to define several permalink paths for a record.
rails_category.permalinks.create path: 'rails'
rails_category.permalinks.create path: 'ruby-on-rails'
With this solution, the routes file has to look like this:
# config/routes.rb
# ...
get ':language', to: 'top_voted#language', constraints: lambda { |request| Permalink.where(reference_type: 'Language', path: request[:language]).any? }
get ':category', to: 'top_voted#category', constraints: lambda { |request| Permalink.where(reference_type: 'Category', path: request[:category]).any? }
And, as a side note for other users using the cancan gem and load_and_authorize_resource
in the controller: You have to load the record by permalink before calling load_and_authorize_resource
:
class Category < ApplicationRecord
before_action :find_resource_by_permalink, only: :show
load_and_authorize_resource
private
def find_resource_by_permalink
@category ||= Permalink.find_by(path: params[:category]).try(:reference)
end
end