Rails: Override RESTful paths? - ruby-on-rails

Is it possible to override a RESTful path?
For instance, I have photo_path(photo) which would generate /photos/12345
But I'd like for all uses of photo_path to actually generate URL's like /photos/joeschmoe/12345 (which is the photo's user and the photo id).
Obviously I could just create a new route, but wanted to make sure there wasn't a more RESTful way of doing it.

You could make photos a sub-resource to user's, so you'd have users/joeschmoe/photos/12345 (of course here, your users controller would require the ability to accept a username instead of an id, which is another routing problem to solve but not difficult)
resources :users do
resources :photos
end
Then your controller could maybe call
#photos = Photo.find_by_username(params[:id])
Although I think there are less hacky ways of doing that.
You could also add joeschmoe as a query string parameter.
Or you could make username an optional parameter in the route, so it would be something like
match "/photos(/:username)/:id" => "photos#show"
Or if you want a new named route:
match "/photos/:username/:id" => "photos#show_by_user", :as => :user_photo

Related

Rails routes based on a value in a column

Rails 3.1. I have a table places. It has a column called type which has value such as cafe, restaurant, etc. Just one value in each row.
In my routes, I define the resources as:
resources :places
The URL is:
http://domain.com/places/123?type=cafe
I always have the type appended in my URLs because I use that to determine which navigation menu to be highlighted.
Now, I want to create a friendlier URL. How can I create a URL that reads either of these:
1. http://domain.com/places/cafe/123
2. http://domain.com/cafe/123
Many thanks!
There are two types of routes - resourceful and non-resourceful. It sounds like you're trying to do a mix between the two. Unfortunately, resourceful route URLs can't be customized like you're trying to do via resources, but you can add additional routes to match the URLs you're trying to make.
To accomplish this, you'll have to create non-resourceful routes such as these:
resources :places, :only => [:create, :update, :destroy]
match 'places/:type/:id' => 'places#show'
match 'places/:type' => 'places#index'
match 'places/:type/new' => 'places#new'
match 'places/:type/edit' => 'places#edit'
You don't need to create special routes for create, update, and destroy (though you could). The user never really sees those anyway. Just include the place type as a parameter in your forms and delete links, and it will be available in the controller the same way that it would as if it were coming from the URL.
In the controller, access the type via params[:type].
Why don't you use the value stored in #place.type, where #place = Place.find params[:id], to determine which navigation to use?

Rails "pretty URLs", using entries/23 or 2011/07/some-post-slug-here for creating URLs via helpers

I'm attempting to create "pretty URLs" for linking to posts in a blog. I want to maintain access to the blog entries via entries/23 and 2011/07/some-post-slug-here as I only generate a slug once an entry has been published (just in case the title of the posts changes, and, though not a strict requirement, I would prefer to be able to edit/delete posts via the entries/23 style URL. Currently, the appropriate part of what I have in my config/routes.rb:
root :to => 'entries#index'
resources :entries
match ':year/:month/:slug' => 'entries#show', :constraints => {
:year => /[0-9][0-9][0-9][0-9]/,
:month => /[0-9][0-9]/,
:slug => /[a-zA-Z0-9\-]+/
}, :as => :vanity_entry
and I use this (in my application helper) function for creating the links:
def appropriate_entry_path entry
if entry.published
vanity_entry_path entry.published_on.year.to_s, entry.published_on.month.to_s, entry.slug
else
entries_path entry
end
end
def appropriate_entry_url entry
if entry.published
vanity_entry_url entry.published_on.year.to_s, entry.published_on.month.to_s, entry.slug
else
entries_url entry
end
end
Basically, I check if the article is published (and therefore has a slug) and then use that URL/path helper, or otherwise use the default one.
However, when trying to use this, I get the following from Rails:
No route matches {:slug=>"try-this-one-on-for", :month=>"7", :controller=>"entries", :year=>"2011", :action=>"show"}
I have tried a few different solutions, including overriding to_param in my Entry model, however then I would have to create match routes for the edit/delete actions, and I would like to keep my routes.rb file as clean as possible. Ideally, I would also love to lose the appropriate_entry_path/appropriate_entry_url helper methods, but I'm not sure that this is possible?
Is there any thing I am missing regarding routing that might make this easier and/or is there any specific way of doing this that is the cleanest?
Thanks in advance.
You might want to take a look at friendly_id. It's a gem for creating seo friendly slugs :)
I found the issue with what I had been doing, the regex for :month in the route wanted two numbers, whereas I was only passing in one number. Anyways, I decided that the URLs look nicer (in my opinion) without the month padded to 2 digits, so I updated my route accordingly.

I want the following urls pattern, please help I'm using rails 3

I want the following urls for my UserController:
localhost/user/join
localhost/user/login
localhost/user/user_name (this can be any name in here, it should fire the 'profile' action)
Then within the /user/user_name_here/ folder I want:
/user/user_name/blah1/
/user/user_name/blah2/
/user/user_name/blah3/
It seems doing resources :user only creates things for index/show/get, so I'm confused as to how to do this other than creating so many match '/user/join' etc. lines in routes.
match "user/:user_name" => "users#show"
then /user/username will redirect to the User controller, call the show method, and pass the :user_name param
you could do the same to other actions that doesn't neet parameters,
match '/user/login' => "sessions#new"
match '/user/join' => "user#new"
Yup - 'resources :user' is just scaffolding for the usual CRUD methods. If you want paths additional to those, you're going to have to create routes (it's the only way your app knows how to route a given URL to a given controller and method).
The friendly_id gem does something similar to what you're suggesting (though I believe it's monkey-patching the .find method on ActiveRecords classes rather than handling routing specifically).

What is the best way to deal with Vanity URL helpers in Rails 3?

I have a web application I am working on with Rails 3 and I have just implemented some basic Vanity URL paths to existing resources in the application. What I am looking to do is to not have to explictly build the urls on the user's profile page for the resources that are available, e.g. I would like to be able to build a URL with link_to in the view in the format of:
typealoud.com/:user_id/:thread_id/:comment_id
And not what the standard nested resource helpers give me, something like:
typealoud.com/threads/:thread_id/comments/:comment_id
Should I do this myself as a URL helper, or is there an existing gem?
To do this, I would put this at the top of my routes:
match ':user_id/:thread_id/:id', :to => "comments#show"
I've changed comment_id in this example to id because it's "The Rails Way" that the last id parameter is simply called id. It also results in shorter code.
If you wish to have a routing helper for it use the :as option:
match ':user_id/:thread_id/:id', :to => "comments#show", :as => "comment"
Then you can use comment_path/comment_urlto access the route, but you must pass in three arguments to it, each of them being an object or an id of an object.

Validate no routing overlap when creating new resources in Ruby on Rails

I've got a RESTful setup for the routes in a Rails app using text permalinks as the ID for resources.
In addition, there are a few special named routes as well which overlap with the named resource e.g.:
# bunch of special URLs for one off views to be exposed, not RESTful
map.connect '/products/specials', :controller => 'products', :action => 'specials'
map.connect '/products/new-in-stock', :controller => 'products', :action => 'new_in_stock'
# the real resource where the products are exposed at
map.resources :products
The Product model is using permalink_fu to generate permalinks based on the name, and ProductsController does a lookup on the permalink field when accessing. That all works fine.
However when creating new Product records in the database, I want to validate that the generated permalink does not overlap with a special URL.
If a user tries to create a product named specials or new-in-stock or even a normal Rails RESTful resource method like new or edit, I want the controller to lookup the routing configuration, set errors on the model object, fail validation for the new record, and not save it.
I could hard code a list of known illegal permalink names, but it seems messy to do it that way. I'd prefer to hook into the routing to do it automatically.
(controller and model names changed to protect the innocent and make it easier to answer, the actual setup is more complicated than this example)
Well, this works, but I'm not sure how pretty it is. Main issue is mixing controller/routing logic into the model. Basically, you can add a custom validation on the model to check it. This is using undocumented routing methods, so I'm not sure how stable it'll be going forward. Anyone got better ideas?
class Product < ActiveRecord::Base
#... other logic and stuff here...
validate :generated_permalink_is_not_reserved
def generated_permalink_is_not_reserved
create_unique_permalink # permalink_fu method to set up permalink
#TODO feels really ugly having controller/routing logic in the model. Maybe extract this out and inject it somehow so the model doesn't depend on routing
unless ActionController::Routing::Routes.recognize_path("/products/#{permalink}", :method => :get) == {:controller => 'products', :id => permalink, :action => 'show'}
errors.add(:name, "is reserved")
end
end
end
You can use a route that would not otherwise exist. This way it won't make any difference if someone chooses a reserved word for a title or not.
map.product_view '/product_view/:permalink', :controller => 'products', :action => 'view'
And in your views:
product_view_path(:permalink => #product.permalink)
It's a better practice to manage URIs explicitly yourself for reasons like this, and to avoid accidentally exposing routes you don't want to.

Resources