Довольно (датированные) спокойные URL-адреса в Rails


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

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

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

Я хочу, чтобы моя модель Post была спокойной, поэтому такие вещи, как 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 6

5 ответов:

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

Маршруты.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

Пост.rb

def to_param # optional
  slug
end

def year
  published_on.year
end

def month
  published_on.strftime('%m')
end

Вид

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

Примечание, Для рельсов 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 - хорошее место, чтобы посмотреть, что вы можете сделайте с рельсами маршрутизацию.

Обновление: использование default_url_options - это тупик. Опубликованное решение работает только тогда, когда в контроллере определена переменная @post. Если есть, например, переменная @posts с массивом сообщений, нам не повезло (потому что default_url_options не имеет доступа к просмотру переменных, как p в @posts.each do |p|.

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

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

В 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 ресурса (составной ключ; а не просто id).

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

Это может быть полезно. Вы можете определить a 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"}

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

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