So I\'m building a Rails site that needs routes based on two different types
I have a Language model and a Category model
So I need to be able to go to a la
There is a nice solution to that problem 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.
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