Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
645 views
in Technique[技术] by (71.8m points)

ruby - Dynamic Rails routes based on database models

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 language route /ruby to see top ruby resources and also go to /books to see top books in all languages

I tried routes like this

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

the problem with that was the logic could not figure out the difference between the two and caused some conflicts on 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 is Heroku where we are deploying this does not allow database calls in the routes. Is there an easier way to do this? We need to be able to dynamically generate these routes somehow.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...