Dynamic Rails Routes Based on Database Models

So, I am creating a Rails site that needs routes based on two different types

I have a language model and a category model

Therefore, I need to be able to go to the language route / ruby ​​to see the top ruby ​​resources, and also go to / books to see the best books in all languages

I tried routes like this

get '/:language', to: "top_voted#language" get '/:category', to: "top_voted#category" 

the problem with this was logic that could not understand the difference between the two and caused some conflicts at the back end.

I also tried this

 Language.all.each do |language| get "#{language.name}", to: "top_voted#language", :language => language.name end Category.all.each do |category| get "#{category.name}", to: "top_voted#category", :category => category.name end 

However, the problem in Heroku, where we deploy it, does not allow the use of database calls in routes. Is there an easier way to do this? We need to somehow dynamically generate these routes.

+6
source share
4 answers

There is a good solution to this problem using route restrictions.

Using route restrictions

As shown in the rail routing guide , you can define route restrictions so that they check if the path belongs to a language or 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? } 

Order determines priority. In the above example, if the language and category have the same name, the language wins, since its route is defined above the route of the category.

Using the Permalink Model

If you want to make sure that all paths are uniqe, an easy way is to define the Permalink model and use validation there.

Create a 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 map it to other types of objects:

 class Language < ApplicationRecord has_many :permalinks, as: :reference, dependent: :destroy end 

It also allows you to define several permalink paths for writing.

 rails_category.permalinks.create path: 'rails' rails_category.permalinks.create path: 'ruby-on-rails' 

With this solution, the route file should 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 note for other users using the cancan stone and load_and_authorize_resource in the controller: you must load the entry at the permalink link 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 
+4
source

It sounds like an architecture issue. If clean URLs are important to you, here's how I would do it:

Create a new model called Page , which will belong to a specific resource (either a category or a language).

 class Page < ActiveRecord::Base belongs_to :resource, polymorphic: true end 

The database columns will be id , resource_type , resource_id , path and whatever you want to hang there.

Your other models will have a mutual relationship:

 has_many :pages, as: :resource 

Now you can route along the same path, but still have access to resources from different classes.

Router:

 resources :pages, id: /[0-9a-z]/ 

Controller:

 class PagesController def show @page = Page.find_by_path(params[:id]) end end 

In the view, configure partial resources for your resource models and then render them in pages/show :

 =render @page.resource 

An example page will be #<Page path: 'ruby', resource: #<Language name: "Ruby">> , which will be available in /pages/ruby . You could route the route so that /ruby routed in the PagesController , but then you severely limit the number of routes that you can use elsewhere in the application.

+1
source

Since I’m several months late, you probably already understood something, but for future people, the restrictions may be what you are looking for. Can you customize a lambda that solves based on the request object, or can you customize a class that implements the matches? method matches? for the called router.

http://guides.rubyonrails.org/routing.html#advanced-constraints

+1
source

Two routes get '/: language' and get '/: category' in the same way for rails. The Rails Router cannot distinguish between /books and /ruby . In both cases, the rails just looked for a route in routes.rb that looks something like /something , it will select the first match and send the route to the specified controller action.

In your case

all requests with the format /something

will match

get '/:language', to: "top_voted#language"

0
source

All Articles