基于数据库模型的动态Rails路由

所以我正在构建一个需要基于两种不同类型的路由的Rails站点

我有一个语言模型和一个分类模型

因此,我需要能够访问语言路线/ruby以查看顶级ruby资源,还可以访问/书籍以查看所有语言的热门书籍

我试过这样的路线

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

问题是逻辑无法弄清楚两者之间的差异,并在后端引起了一些冲突

我也尝试过这个

 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 

但问题是我们正在部署它的Heroku不允许在路由中进行数据库调用。 有更简单的方法吗? 我们需要能够以某种方式动态生成这些路由。

使用路由约束可以很好地解决该问题。

使用路线约束

正如rails路由指南所建议的那样,您可以通过检查路径是否属于某种语言或类别来定义路径约束。

 # 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? } 

订单定义优先级。 在上面的示例中,如果语言和类别具有相同的名称,则语言将获胜,因为其路由定义在类别路由之上。

使用永久链接模型

如果你想确保所有路径都是独一无二的,一个简单的方法就是定义一个Permalink模型并在那里使用validation。

生成数据库表: rails generate model Permalink path:string reference_type:string reference_id:integer && rails db:migrate

并在模型中定义validation:

 class Permalink < ApplicationRecord belongs_to :reference, polymorphic: true validates :path, presence: true, uniqueness: true end 

并将其与其他对象类型相关联:

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

这也允许您为记录定义几个永久链接路径。

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

使用此解决方案,routes文件必须如下所示:

 # 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? } 

并且,作为在控制器中使用cancan gem和load_and_authorize_resource其他用户的旁注 :您必须在调用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 

这听起来像是一个架构问题。 如果干净的url对您很重要,请按以下步骤进行设置:

创建一个名为Page的新模型,该模型属于特定资源(类别或语言)。

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

数据库列将是idresource_typeresource_idpath以及您想要挂起的任何其他内容。

你的其他模特会有互惠的关系:

 has_many :pages, as: :resource 

现在,您可以使用单个路径进行路由,但仍可以访问来自不同类的资源。

路由器:

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

控制器:

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

在视图中,为资源模型设置部分,然后在pages/show呈现它们:

 =render @page.resource 

一个示例页面是#> ,可在/pages/ruby 。 你可以将它路由到/ruby路由到PagesController ,但是你严重限制了你可以在应用程序的其他地方使用的路由数量。

由于我已经晚了几个月,你可能已经想出了一些东西,但是对于未来的人来说,限制可能就是你正在寻找的东西。 您可以设置基于请求对象决定的lambda,也可以设置实现matches?的类matches? 路由器调用的方法。

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

两个路由得到’/:language’和得到’/:category’对于rails来说是完全相同的。 Rails路由器无法区分/books/ruby 。 在这两种情况下,rails只会在routes.rb中寻找类似于/something的路由,它将选择第一个匹配并将路由分派到指定的控制器的操作。

在你的情况下,

带有/something格式的所有请求

将匹配

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