【问题标题】:Dynamic Rails routes based on database models基于数据库模型的动态 Rails 路由
【发布时间】:2014-03-06 21:40:25
【问题描述】:

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

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

所以我需要能够去一个语言路径 /ruby 来查看顶级 ruby​​ 资源,还可以去 /books 来查看所有语言的顶级书籍

我试过这样的路线

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 无法区分 /books/ruby,它总是会从 routes.rb 中选择第一个匹配的路线,在你的情况下是 get '/:language'

标签: ruby-on-rails ruby heroku routes


【解决方案1】:

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

使用路由约束

正如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'

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

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

并且,作为在控制器中使用 can 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

【讨论】:

    【解决方案2】:

    这听起来像是架构问题。如果干净的 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
    

    示例页面为#&lt;Page path: 'ruby', resource: #&lt;Language name: "Ruby"&gt;&gt;,可在/pages/ruby 获得。您可能可以将它路由到 /ruby 路由到 PagesController,但是这样您就严重限制了您可以在应用程序的其他地方使用的路由数量。

    【讨论】:

      【解决方案3】:

      由于我迟到了几个月,您可能已经想出了一些办法,但对于未来的人来说,约束可能就是您要寻找的东西。您可以设置一个根据请求对象决定的 lambda,也可以设置一个实现 matches? 方法的类,以供路由器调用。

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

      【讨论】:

        【解决方案4】:

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

        在你的情况下,

        所有/something格式的请求

        将匹配到

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-11-01
          • 2013-11-23
          • 1970-01-01
          • 2016-01-23
          • 1970-01-01
          • 1970-01-01
          • 2020-10-28
          相关资源
          最近更新 更多