2010-02-13 3 views
6

Я хочу, чтобы мой сайт, чтобы иметь адреса вида:Довольно (датированный) RESTful URL-адрес в Rails

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

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

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

ответ

4

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

в 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 

И следующий будет работать (как в железнодорожном s 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).

1

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

+0

Общие маршруты (map.connect, о которых говорит Райан Бэйтс), маршруты named_routes и RESTful - это три разные вещи. Я могу легко получить любой URL-адрес, который я хочу использовать как с помощью родовых, так и с именованных маршрутов. Я хочу иметь довольно URL-адреса, используя маршруты RESTful. –

1

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

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

+1

Работает! Очень умный, хотя и очень хакерский. Теперь есть более чистый способ решить эту проблему? –

+0

Это интересно, потому что это не сработало для меня. Я мог только видеть, что почтовый экземпляр передан. Возможно, это из-за плагина, который вы используете. Я могу заставить его работать, когда я передаю сообщение как ': slug => @ post'. –

+0

Теперь я понимаю: если вы установили значение в хэш элемента 'options' непосредственно, как я сделал по ошибке в моей первой ревизии, например. 'options [: year] = 2009' то он установлен правильно (поскольку хеш передается по ссылке). Таким образом, вы можете перезаписать данные ключи. –

4

Довольно 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\-]+/ 

есть ли значок для ответа на свой вопрос? ;)

Btw: файл routing_test - хорошее место, чтобы увидеть, что вы можете сделать с маршрутизацией Rails.

Update: Использование default_url_options является тупик. Выложенное решение работает только при наличии переменной @post, определенной в контроллере. Если есть, например, @posts переменная с массив сообщений, мы не повезло (Becase default_url_options не имеют доступа для просмотра переменных, как p в @posts.each do |p|.

Так что это остается открытым. Кто-то помощь ?

+0

Значок? Существует самостоятельный ученик (если вы ответили на свой вопрос, и у него есть 3 upvotes), и вы можете принять свой ответ. – klew

+0

Это была просто шутка;) Есть большая проблема. Мое решение ломается, когда я занимаюсь сообщениями в других контроллерах. В частности, когда нет @post настоящего (в то время как там может быть переменная @posts). –

1

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

+0

Я думаю, вы пропустили суть этого вопроса. Речь идет не о генерации слизней (которая сама по себе является тривиальной), а о дружественных устаревших URL (часть года/месяца здесь сложная). –