基于数据库模型的动态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
数据库列将是id
, resource_type
, resource_id
, path
以及您想要挂起的任何其他内容。
你的其他模特会有互惠的关系:
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"