【问题标题】:Moving the :format attribute in routing from the end to beginning of route将路由中的 :format 属性从路由的末尾移动到路由的开头
【发布时间】:2009-11-23 02:40:19
【问题描述】:

在我的应用程序中,我试图让我的 API 模仿 GitHub 在路由开头的 (.:format),而不是在末尾可选地附加它。

这是我的“工作”但可以忽略的代码:

map.namespace :api do |api|
  api.namespace :v1 do |v1|
    v1.resource :company, :path_prefix => "api/v1/:format"
  end
end

我可以转到/api/v1/xml/company.json,Rails 将提供json 作为params[:format] 而不是xml

当我运行 rake routes 时,我得到了

/api/v1/:format/company(.:format)

有没有办法让它返回:

/api/v1/:format/company

提前致谢!

【问题讨论】:

    标签: ruby-on-rails api routing


    【解决方案1】:

    这将需要一些严重的猴子补丁。路由也是 Rails 代码库中最复杂的领域之一,既将传入的 HTTP 请求连接到您的代码并生成 URL。

    请原谅我冒昧,但据我所知,您违反 Rails 惯例的原因是模仿另一家公司。换句话说,您愿意无视 Rails 贡献者的集体智慧,而倾向于遵循少数开发人员做出的决定。

    我认为您应该问自己,您是否有理由希望这种吸引力足以与所需的努力相称?

    除了 Rails 方式之外,做某事越难,人们应该越严格地质疑他们的决定。存在重大障碍通常表明有更好的方法来做某事。

    【讨论】:

      【解决方案2】:

      EmFi 是对的。我没有回答这个问题,只是表达了我的看法。

      将以下代码放入 Rails 应用程序内的 config initializers 目录中的初始化文件中。您命名文件的名称与框架无关,因为此目录中的所有文件都在加载路径中。我建议您将其称为actioncontroller_resource_monkeypatch.rb,以明确意图。

      ActionController::Resources.module_eval do
        def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} )
          if resource.has_action?(action)
            action_options = action_options_for(action, resource, method, resource_options)
      
            formatted_route_path = route_path.match(/\/:format\//) ? route_path : "#{route_path}.:format" 
      
            if route_name && @set.named_routes[route_name.to_sym].nil?
              map.named_route(route_name, formatted_route_path, action_options)
            else
              map.connect(formatted_route_path, action_options)
            end
          end
        end
      end
      

      我的答案使用与 EmFi 相同的方法,即通过猴子补丁 ActionController::Resources#map_resource_routes。我决定把我的帽子扔进戒指,因为它没有提供完整的实现,这是留给你自己的练习。我也觉得formatted_route_path 的三元赋值比 if-else/unless-else 块更干净、更简洁。多行代码而不是五行!这是我为 200 赏金所能做的最低限度!

      现在运行rake routes

      new_api_v1_company GET    /api/v1/:format/company/new  {:action=>"new", :controller=>"api/v1/companies"}
      edit_api_v1_company GET    /api/v1/:format/company/edit {:action=>"edit", :controller=>"api/v1/companies"}
           api_v1_company GET    /api/v1/:format/company      {:action=>"show", :controller=>"api/v1/companies"}
                          PUT    /api/v1/:format/company      {:action=>"update", :controller=>"api/v1/companies"}
                          DELETE /api/v1/:format/company      {:action=>"destroy", :controller=>"api/v1/companies"}
                          POST   /api/v1/:format/company      {:action=>"create", :controller=>"api/v1/companies"}
      

      多田!

      【讨论】:

        【解决方案3】:

        我相信 (.format) 是可选的(这就是括号的意思),所以/api/v1/:format/company(.:format) == /api/v1/:format/company

        如果您想更改更多内容,则需要修改/修改铁轨。

        【讨论】:

        • 我明白,但如果我在 URI 末尾添加了不同的扩展名,我可以覆盖自定义放置的 :format,需要提供格式,这就是为什么我认为我应该放置在这里。
        • 直截了当,我不希望用户能够附加格式。
        【解决方案4】:

        我只是在这里试一试(我会在明天可以使用代码时检查并更新),但你不能避免使用 :format 并做这样的事情吗?

        路线

        map.connect ':controller/:return_type/:action/:id'
        

        控制器

        @results = MyObject.all
        
        case :return_type
          when 'xml' render :text => @results.to_xml
          when 'json' render :text => @results.to_json
        end
        

        如果案例太混乱/丑陋,您可以轻松创建一个模仿您正在寻找的行为的辅助方法。

        【讨论】:

        • 我仍然想使用标准的 respond_to,我相信当您开始离开并创建自己的时,您将不会获得相同的功能。
        【解决方案5】:

        Steve Graham says everything 需要说一下,但是一个有 200 代表赏金的问题值得一个正确的答案。无论多么糟糕的建议。除此之外,它看起来确实很有用。

        猴子补丁非常简单。你只需要重写 ActionController::Resource#map_resource_routes 如下。

        def map_resource_routes(map, resource, action, route_path, 
              route_name = nil, method = nil, resource_options = {} )
          if resource.has_action?(action)
            action_options = action_options_for(action, resource, method, resource_options)
        
            unless route_path.match(/\/:format\//)       # new line of code
              formatted_route_path = "#{route_path}.:format"
            else                                         # new line of code
              formatted_route_path = route_path          # new line of code
            end                                          # new line of code   
        
            if route_name && @set.named_routes[route_name.to_sym].nil?
              map.named_route(route_name, formatted_route_path, action_options)
            else
              map.connect(formatted_route_path, action_options)
            end
          end
        end
        

        将此补丁合并到您的代码中涉及修补您的 rails gem 或制作插件,这两者都非常简单,留给读者作为练习。

        如果路径已经包含一个名为 format 的参数,则代码会跳过将选项格式添加到所有路由的行。

        编辑:链接到此解决方案所指的 Steve Graham 答案。最初发帖时只有一个。

        【讨论】:

          【解决方案6】:

          我使用了这个代码:

          ActionController::Routing::Routes.draw do |map|
            map.namespace(:v1, :path_prefix => "api/v1/:format") do |v1|
              v1.resources :repositories
            end
          end
          

          URL 变成 api/v1/[json/xml/whatever]/

          我也喜欢命名版本的想法(就像你的问题一样):

          class V1::RepositoriesController < V1::ApplicationController
          
          end
          

          虽然此方法允许您将格式放入 URL 中两次:您不能确保用户不会将格式放入 URL 中两次,而是由您的用户确保他们不要在 URL 中输入格式两次。

          不要花时间解决 PEBKAC。

          【讨论】:

          • 为什么投反对票?我相信这是一个合法的解决方案。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-10-22
          • 1970-01-01
          • 2021-09-04
          • 1970-01-01
          • 2023-03-26
          • 1970-01-01
          • 2016-09-09
          相关资源
          最近更新 更多