2010-02-13 10 views
6

ich meine Website möchte URLs wie folgt aussehen haben:Recht (datiert) RESTful URLs in Rails

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

Ich habe meine Post Modell mit slug Feld ('my-first-post') und published_on Feld (von dem wir das Jahr und Monat Teile in der URL abziehen).

Ich möchte mein Post Modell RESTful sein, so Dinge wie url_for(@post) funktionieren wie sie sollten, dh: es sollte die zuvor erwähnte URL generieren.

Gibt es eine Möglichkeit, dies zu tun? Ich weiß, dass Sie to_param überschreiben müssen und map.resources :posts mit :requirements Option gesetzt haben, aber ich kann nicht alles zum Funktionieren bringen.


Ich habe es fast fertig, ich bin 90% da. Mit resource_hacks plugin kann ich das erreichen:

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"} 

und in der Ansicht:

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

erzeugt richtigen example.com/2010/02/my-first-post Link.

Ich würde dies gerne auch arbeiten:

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

Aber es braucht die to_param Methode im Modell überschreiben. Sollte ziemlich einfach sein, außer für die Tatsache, dass to_paramString, nicht Hash zurückgeben muss, wie ich es möchte.

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

Ergebnisse in can't convert Hash into String Fehler.

Dies scheint ignoriert werden:

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

wie es zum Fehler: post_url failed to generate from {:action=>"show", :year=>#<Post id: 1, title: (...) (es ordnet falsch @post Objekt der: Jahr-Taste). Ich bin irgendwie ahnungslos, wie man es hackt.

Antwort

4

Es ist immer noch ein Hack, aber die folgenden Werke:

in 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 

und die folgenden arbeiten (beide in der Bahn s 2.3.x und 3.0.0):

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

Dies ist die Antwort von einigen nice soul für eine ähnliche Frage von mir, url_for of a custom RESTful resource (composite key; not just id).

1

Ryan Bates sprach darüber in seinem Screencast "wie man benutzerdefinierte Routen hinzufügt, einige Parameter optional macht und Anforderungen für andere Parameter hinzufügt." http://railscasts.com/episodes/70-custom-routes

+0

Generische Routen (map.connect, worüber Ryan Bates spricht), named_routes und RESTful routes sind drei verschiedene Dinge. Ich kann leicht irgendeine URL haben, die ich sowohl mit generischen als auch mit benannten Routen verwenden möchte. Ich möchte hübsche URLs mit RESTful-Routen haben. –

1

Dies könnte hilfreich sein. Sie können eine default_url_options-Methode in Ihrem ApplicationController definieren, die einen Hash der Optionen empfängt, die an den URL-Helper übergeben wurden, und einen Hash mit zusätzlichen Optionen zurückgibt, die Sie für diese URLs verwenden möchten.

Wenn ein Post als Parameter an post_path übergeben wird, wird er dem ersten (nicht zugewiesenen) Parameter der Route zugewiesen. Nicht getestet, aber es könnte funktionieren:

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 

ich in einer ähnlichen Situation bin, wo ein Post einen Sprachparameter und Slug-Parameter hat.Schreiben post_path(@post) sendet diese Hash mit der default_url_options Methode:

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

UPDATE: Es gibt ein Problem, dass Sie nicht URL-Parameter von dieser Methode außer Kraft setzen können. Die an den URL-Helper übergebenen Parameter haben Vorrang. So könnte man so etwas wie:

post_path(:slug => @post) 

und:

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 

Dies würde funktionieren, wenn Post.to_param die Schnecke zurück. Sie müssten nur das Jahr und den Monat zum Hash hinzufügen.

+1

Funktioniert! Sehr schlau, aber auch sehr hackisch. Gibt es jetzt einen saubereren Weg, dies zu lösen? –

+0

Das ist interessant, weil es für mich nicht funktioniert hat. Ich konnte nur sehen, dass die Post-Instanz übergeben wurde. Vielleicht liegt es am verwendeten Plugin. Ich kann es nur schaffen, wenn ich den Post als ': slug => @ post' übergebe. –

+0

Jetzt verstehe ich: wenn Sie einen Wert in der 'options' Hash direkt wie ich versehentlich in meiner ersten Revision, z. 'options [: year] = 2009' dann ist es korrekt gesetzt (da der Hash als Referenz übergeben wird). Sie können also bestimmte Schlüssel überschreiben. –

4

Schöne URLs für Rails 3.x und Rails 2.x ohne die Notwendigkeit für ein externes Plugin, aber mit ein wenig Hack, leider.

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 

Ansicht

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

Hinweis, für Rails 3.x können Sie diese Routendefinition verwenden:

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

Gibt es eine Plakette Ihre eigene Frage zu beantworten? ;)

Btw: die routing_test Datei ist ein guter Ort, um zu sehen, was Sie mit Rails Routing tun können.

Update:default_url_options zu verwenden ist eine Sackgasse. Die veröffentlichte Lösung funktioniert nur, wenn die @post Variable im Controller definiert ist. Wenn es zum Beispiel @posts Variable mit Array von Stellen, sind wir kein Glück (becase default_url_options keinen Zugriff Variablen zu sehen, wie p in @posts.each do |p|.

Also das ist immer noch ein offenes Problem. Jemand Hilfe ?

+0

Badge? Es gibt Selbstlerner (wenn Sie Ihre eigene Frage beantwortet haben und es 3 Upvotes gibt) und Sie können Ihre Antwort akzeptieren. – klew

+0

Das war nur ein Scherz;) Es gibt ein größeres Problem. Meine Lösung bricht, wenn es sich um Posts in anderen Controllern handelt. Insbesondere, wenn kein @post vorhanden ist (während sich dort möglicherweise @posts-Variable befindet). –

1

Sie könnten sich einfach den Stress ersparen und friendly_id verwenden. Es ist genial, macht den Job und du könntest a screencast von Ryan Bates ansehen, um loszulegen.

+0

Ich denke, Sie haben den Punkt dieser Frage verpasst. Es geht nicht um die Slug-Generation (die selbst trivial ist), sondern um freundlich datierte URLs (Jahr-/Monatsteile sind hier das Schwierige). –