Довольно (датированные) спокойные 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 ответов:
Красивые 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 . Это потрясающе, делает работу, и вы можете посмотретьскринкаст Райана Бейтса, чтобы начать работу.