【问题标题】:Pretty (dated) RESTful URLs in RailsRails 中漂亮(过时)的 RESTful URL
【发布时间】:2011-01-16 12:44:05
【问题描述】:

我希望我的网站具有如下所示的 URL:

example.com/2010/02/my-first-post

我的Post 模型带有slug 字段('my-first-post')和published_on 字段(我们将从中扣除网址中的年份和月份部分)。

我希望我的 Post 模型是 RESTful 的,所以像 url_for(@post) 这样的东西应该可以正常工作,即:它应该生成上述 url。

有没有办法做到这一点?我知道您需要覆盖 to_param 并设置 map.resources :posts 并设置 :requirements 选项,但我无法让它全部工作。


我几乎完成了,我已经完成了 90%。使用resource_hacks plugin 我可以做到这一点:

map.resources :posts, :member_path => '/:year/:month/:slug',
  :member_path_requirements => {:year => /[\d]{4}/, :month => /[\d]{2}/, :slug => /[a-z0-9\-]+/}

rake routes
(...)
post GET    /:year/:month/:slug(.:format)      {:controller=>"posts", :action=>"show"}

在视图中:

<%= link_to 'post', post_path(:slug => @post.slug, :year => '2010', :month => '02') %>

生成正确的example.com/2010/02/my-first-post 链接。

我也希望这样:

<%= link_to 'post', post_path(@post) %>

但它需要覆盖模型中的to_param 方法。应该相当容易,除了 to_param 必须返回 String,而不是 Hash,因为我想要它。

class Post < ActiveRecord::Base
  def to_param
   {:slug => 'my-first-post', :year => '2010', :month => '02'}
  end
end

导致can't convert Hash into String 错误。

这似乎被忽略了:

def to_param
  '2010/02/my-first-post'
end

因为它导致错误:post_url failed to generate from {:action=&gt;"show", :year=&gt;#&lt;Post id: 1, title: (...)(它错误地将 @post 对象分配给 :year 键)。我对如何破解它一无所知。

【问题讨论】:

    标签: ruby-on-rails url rest slug


    【解决方案1】:

    Rails 3.x 和 Rails 2.x 的漂亮 URL,不需要任何外部插件,但不幸的是,有点 hack。

    routes.rb

    map.resources :posts, :except => [:show]
    map.post '/:year/:month/:slug', :controller => :posts, :action => :show, :year => /\d{4}/, :month => /\d{2}/, :slug => /[a-z0-9\-]+/
    

    application_controller.rb

    def default_url_options(options = {})
      # resource hack so that url_for(@post) works like it should
      if options[:controller] == 'posts' && options[:action] == 'show'
        options[:year] = @post.year
        options[:month] = @post.month
      end
      options
    end
    

    post.rb

    def to_param # optional
      slug
    end
    
    def year
      published_on.year
    end
    
    def month
      published_on.strftime('%m')
    end
    

    查看

    <%= link_to 'post', @post %>
    

    注意,对于 Rails 3.x,您可能希望使用此路由定义:

    resources :posts
    match '/:year/:month/:slug', :to => "posts#show", :as => :post, :year => /\d{4}/, :month => /\d{2}/, :slug => /[a-z0-9\-]+/
    

    是否有任何徽章可以回答您自己的问题? ;)

    顺便说一句:routing_test 文件是了解 Rails 路由可以做什么的好地方。

    更新:使用default_url_options死胡同。仅当控制器中定义了@post 变量时,发布的解决方案才有效。例如,如果存在带有帖子数组的 @posts 变量,我们就不走运了(因为 default_url_options 无权访问视图变量,例如 @posts.each do |p| 中的 p

    所以这仍然是一个悬而未决的问题。有人帮忙吗?

    【讨论】:

    • 徽章?有自学者(如果您回答了自己的问题并且获得了 3 次赞成),您可以接受自己的答案。
    • 那只是个玩笑;)还有一个更大的问题。当我处理其他控制器中的帖子时,我的解决方案会中断。具体来说,当没有 @post 存在时(虽然那里可能有 @posts 变量)。
    【解决方案2】:

    它仍然是一个 hack,但以下工作:

    application_controller.rb:

    def url_for(options = {})
      if options[:year].class.to_s == 'Post'
        post = options[:year]
        options[:year] = post.year
        options[:month] = post.month
        options[:slug] = post.slug
      end
      super(options)
    end
    

    以下将起作用(在 Rails 2.3.x 和 3.0.0 中):

    url_for(@post)
    post_path(@post)
    link_to @post.title, @post
    etc.
    

    这是一些nice soul 对我的一个类似问题url_for of a custom RESTful resource (composite key; not just id) 的回答。

    【讨论】:

      【解决方案3】:

      Ryan Bates 在他的屏幕截图中谈到了“如何添加自定义路线、使某些参数成为可选参数以及为其他参数添加要求”。 http://railscasts.com/episodes/70-custom-routes

      【讨论】:

      • 通用路由(Ryan Bates 谈到的 map.connect)、named_routes 和 RESTful 路由是三种不同的东西。我可以使用通用路由和命名路由轻松获得任何我想要的 URL。我想拥有使用 RESTful 路由的漂亮 url。
      【解决方案4】:

      这可能会有所帮助。您可以在 ApplicationController 中定义一个 default_url_options 方法,该方法接收传递给 url 帮助程序的选项哈希,并返回您要用于这些 url 的其他选项的哈希。

      如果将帖子作为参数提供给post_path,它将被分配给路由的第一个(未分配的)参数。尚未测试,但它可能有效:

      def default_url_options(options = {})
        if options[:controller] == "posts" && options[:year].is_a?Post
          post = options[:year]
          {
            :year  => post.created_at.year,
            :month => post.created_at.month,
            :slug  => post.slug
          }
        else
          {}
        end
      end
      

      我处于类似情况,帖子具有语言参数和 slug 参数。写入post_path(@post) 会将此哈希发送到default_url_options 方法:

      {:language=>#<Post id: 1, ...>, :controller=>"posts", :action=>"show"}
      

      更新:存在一个问题,即您无法从该方法覆盖 url 参数。传递给 url 助手的参数优先。所以你可以这样做:

      post_path(:slug => @post)
      

      和:

      def default_url_options(options = {})
        if options[:controller] == "posts" && options[:slug].is_a?Post
          {
            :year  => options[:slug].created_at.year,
            :month => options[:slug].created_at.month
          }
        else
          {}
        end
      end
      

      如果Post.to_param 返回了 slug,这将起作用。您只需将年份和月份添加到哈希中。

      【讨论】:

      • 有效!非常聪明,虽然也很hackish。现在有没有更清洁的方法来解决这个问题?
      • 这很有趣,因为它对我不起作用。我只能看到 post 实例已通过。也许是因为你使用的插件。只有当我将帖子作为:slug =&gt; @post 传递时,我才能让它工作。
      • 现在我明白了:如果你直接在 options 哈希中设置一个值,就像我在第一次修订中错误地做的那样,例如options[:year] = 2009 然后设置正确(因为哈希是通过引用传递的)。所以你实际上可以覆盖给定的键。
      • 是的,它需要一个插件来确保路由正确(这是一个先决条件)。检查这个:gist.github.com/303710
      【解决方案5】:

      您可以减轻压力并使用friendly_id。太棒了,可以完成这项工作,您可以查看 Ryan Bates 的 a screencast 以开始使用。

      【讨论】:

      • 我认为你错过了这个问题的重点。这不是关于 slug 生成(这本身是微不足道的),而是关于友好的日期 url(年/月部分在这里是困难的)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多