Красивые (устаревшие) RESTful URL в Rails - PullRequest
6 голосов
/ 13 февраля 2010

Я бы хотел, чтобы на моем сайте были URL-адреса, выглядящие так:

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

У меня есть Post модель с полями slug ('my-first-post') и полем published_on (из которых мы будем вычитать части года и месяца в URL).

Я хочу, чтобы моя Post модель была RESTful, чтобы такие вещи, как url_for(@post), работали так, как они должны, т.е. она должна генерировать вышеупомянутый URL.

Есть ли способ сделать это? Я знаю, что вам нужно переопределить to_param и иметь map.resources :posts с установленной опцией :requirements, но я не могу заставить все это работать.


Я почти сделал это, я на 90%. С помощью плагина resource_hacks я могу добиться этого:

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=>"show", :year=>#<Post id: 1, title: (...) (ошибочно назначает объект @post ключу: year). Я не знаю, как взломать его.

Ответы [ 5 ]

4 голосов
/ 24 февраля 2010

Это все еще хак, но работает следующее:

В 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.

Это ответ некой приятной души на мой похожий вопрос, url_for пользовательского ресурса RESTful (составной ключ; не просто идентификатор) .

4 голосов
/ 14 февраля 2010

Красивые URL-адреса для Rails 3.x и Rails 2.x без необходимости использования какого-либо внешнего плагина, но с небольшим взломом, к сожалению.

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 не имеет доступа к переменным просмотра, как p в @posts.each do |p|.

Так что это все еще открытая проблема. Кто-нибудь поможет?

1 голос
/ 12 января 2012

Вы можете просто избавить себя от стресса и использовать friendly_id .Это здорово, делает свою работу, и вы можете посмотреть скринкаст Райана Бейтса, чтобы начать.

1 голос
/ 14 февраля 2010

Это может быть полезно. Вы можете определить метод default_url_options в вашем ApplicationController, который получает хэш параметров, которые были переданы помощнику 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 вернет слизняк. Вам нужно только добавить год и месяц к хешу.

1 голос
/ 13 февраля 2010

Райан Бейтс говорил об этом в своем фильме «Как добавить пользовательские маршруты, сделать некоторые параметры необязательными и добавить требования для других параметров». http://railscasts.com/episodes/70-custom-routes

...