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

时间:2014-03-06 21:40:25

标签: ruby-on-rails ruby heroku routes

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

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

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

我试过这样的路线

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不允许路由中的数据库调用。有更简单的方法吗?我们需要能够以某种方式动态生成这些路线。

4 个答案:

答案 0 :(得分:7)

使用路径约束可以很好地解决这个问题。

使用路线约束

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

# 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模型并在那里使用验证。

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

并在模型中定义验证:

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之前通过永久加载来加载记录:< / p>

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

答案 1 :(得分:1)

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

创建一个名为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

示例页面为#<Page path: 'ruby', resource: #<Language name: "Ruby">>,可在/pages/ruby处获取。您可以将/ruby路由路由到PagesController,但是您严格限制了可以在应用中其他位置使用的路由数量。

答案 2 :(得分:1)

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

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

答案 3 :(得分:0)

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

在你的情况下,

所有/something格式的请求

将匹配

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