A custom route along with resource routes - ruby-on-rails

I have setup a custom route, and it seems to work. However, I also have a resources routes as well for the same controller. I think I am just doing something wrong, but I can't tell what it is. I am honestly hacking together routes since I am still a bit confused on how to set them up and when to use what method.
Here are my routes I am dealing with right now.
resources :shows
match "shows/:country" => "shows#index"
The routes like the are the resources :shows works just fine, but not the match. If I flip them the match route works fine, but the resources :shows doesn't.
Should I do this as a namespaced route? I am not exactly sure what I should do. What I am trying to accomplish is something like this.
http://site.com/shows/canada
That will return all Candian shows.
Any help is appreciated.

What you probably want to do is use constraints, or maybe even a custom constraints class. Here's a rough start that I haven't tested and am unsure if it would work:
resources :shows, :constraints => { :id => /[0-9]+/ }
match "shows/:country" => "shows#index", :constraints => { :country => /[a-z]+/ }
Note that typically this would be done via a get query parameter, e.g. http://example.com/shows?country=canada, which would already go to your shows#index action and have params[:country] set to "canada".

You may be getting bitten by the default route which expects /{controller}/{action} and routes accordingly. Try removing the default route. You will have to make sure to declare all of your routes, but the result is a more predictable set of routes for your app.

Related

Making Rails Resource and Custom Routes Conflict Work

I am new to rails and was wondering how I can make this work. I want a URL to look like this:
http://localhost:3000/businesses/coldfire-gundam
using this route:
match "/businesses/:permalink", :to => "businesses#show", :as => :business_permalink
however when I place this route before this:
resources :businesses
any call to /businesses/1 (1 as param[:id]) does not work anymore, obviously because it is caught by the permalink declaration
how can I make it work then?
You need a way to differentiate /businesses/:id and /businesses/:permalink. The :id should always be numeric (unless of course you're using MongoDB) so if you can force your :permalink to always contain something non-numeric then a simple :constraints should do the trick:
match '/businesses/:permalink', :to => 'businesses#show`, :constraints => { :permalink => /.*\D/ }, :as => :business_permalink
The /.*\D/ forces the route to only match if :permalink contains at least one non-numeric character. You need the .* because route regexes are implicitly anchored at the beginning.
If you happen to be using MongoDB then your :id will probably be a hex BSON ID so you'd want to use /.*\H/ as your constraint and you'd want some way to ensure that your :permalink always contains at least one non-hex character.
Once all that's in place you can put your match "/businesses/:permalink" before your resources :businesses in routes.rb and everything should work fine. And routes are checked in the same order that they appear in routes.rb so you will want your match before your resources.
I would suggest using the friendly_id gem for creating permalink routes. This will handle most of the 'magic' for you in an easily reusable way.
Resources for the gem and railscast:
https://github.com/norman/friendly_id
http://railscasts.com/episodes/314-pretty-urls-with-friendlyid

Shallow routing in Rails 3

Having a rough time with my routes in my rails 3 app, I want to have shallow routes like this:
/san-francisco/union-square
But my router insists on having them like so:
/cities/san-francisco/neighborhoods/union-square
I've used this for my routes.rb
shallow do
resources :cities do
resources :neighborhoods do
resources :locations
end
end
end
But still I have this:
city_neighborhood_locations GET /cities/:city_id/neighborhoods/:neighborhood_id/locations(.:format)
Shouldn't it look like:
city_neighborhood_locations GET /:city_id/:neighborhood_id/:id(.:format)
I'm not sure how to fix this, additionally I'm not sure what I'm doing wrong with my links, I want to be able to use the syntax:
link_to neighborhood.name, [:city, neighborhood]
but that seems to invert the :id, and :neighborhood_id when the request comes to the controller, any help on this would be really really helpful!
What you're looking for is not typically known as "shallow routing".
Shallow routing, as Rails defines it, would look like this:
city_neighborhoods GET /cities/:city_id/neighborhoods
new_city_neighborhood GET /cities/:city_id/neighborhoods/new
(create_city_neighborhood) POST /cities/:city_id/neighborhoods
neighborhood GET /neighborhoods/:neighborhood_id
Without shallow routing, that last route would be:
city_neighborhood GET /cities/:city_id/neighborhoods/:neighborhood_id
Shallow routing lets you nest a resource underneath another resource (neighborhood under city in this case), but gives you absolute/un-nested routes for the nested resource when the nesting isn't necessary.
This makes sense when you're referencing the nested resource with unique identifiers that are not dependent on the ID of the parent resource. In your example, that's not true (there's potentially a "Union Square" outside of San Francisco; there's definitely going to be duplicates like "Chinatown"), so you probably do not want shallow routes for this case.
What you're wanting is positionally-dependent routing, where the type of resource is assumed/fixed depending on where it appears in the URL. (For instance, you couldn't have anything other than a "neighborhood" follow a "city" under the scheme you outlined.)
I don't think the Rails resource(s) commands will support that by default, but you could probably do it with manual match commands. This is off the top of my head:
match ":city_id", :controller => "cities", :action => "show"
match ":city_id/:neighborhood_id", :controller => "neighborhoods", :action => "show"
This is still RESTful/resource-based, it's just not using the standard Rails way of naming routes.

Implicit creation of helpers - routes.rb and 'match' statements

I am reading Obie Fernandez' "The Rails 3 Way", and there is a bit of it that I am not sure I understand correctly. I am new to rails, and want to make sure I understand it correctly. I have some experience with vanilla Ruby. Not much, but some.
The text in question is as follows: (regarding routing and the config/routes.rb file)
"...
By creating a route like
match 'auctions/:id' => "auction#show", :as => 'auction'
you gain the ability to use nice helper methods in situations like
link_to item.description, auction_path(item.auction)
..."
My question is, specifically what part of match 'auctions/:id' => "auction#show", :as => 'auction' creates the helper functions? (such as link_to auction and auction_path() ) Is it the :as => 'auction' part? Would any helpers be created without appending :as => 'auction'?
My confusion stems from other guides I have seen where this is omitted, and yet helpers seem to be created regardless. What specifically does rails use in match statements in the routes.rb file to create helpers? If it isn't the :as => 'auction' part, then what is the specific purpose of appending this to the match statement?
I know this seems like a super basic question, but this detail seems to get glossed over in the texts I have read thus far. Thanks in advance for any light you can shed on this.
I just tried this:
match "alfa/beta", to: 'users#new'
In this case, even without an :as => 'named_route', I got for free the following helper
alfa_beta_path
which, as expected, points to users#new.
So, it seems that helpers are also automagically generated by parsing the route's string, in case there is no :as specification.
Yes, it is the :as => 'named_route' part that creates the named route (which in turn creates the helpers). As for leaving it off, are you referring to instances of resources :something in routes.rb? The resources method generates a set of URL helpers based on the name of the resource automagically.

Path helpers generate paths with dots instead of slashes

In my routes.rb I have the following:
resources :message_threads
When I call:
message_threads_path(1)
I get:
/message_threads.1
Why is this? My other resources work fine. Am I not pluralizing this correctly or something?
Yes, this is a pluralization error.
By passing the ID 1, I assume that you wish to display a single record.
So you need to use the singular 'message_thread':
message_thread_path(1)
Which will yield:
http://localhost:3000/message_threads/1
Sometimes this also is when you don't provide an :as parameter in your route:
delete "delete/:id" => "home#delete"
Changed to:
delete "delete/:id" => "home#delete", as: :delete
(ignore the odd example, just happened to be something we just ran into for an internal app we're building)
Other folks that land here might be in this situation:
If you have a singular resource declared in your routes.rb:
resource :map
You don't need to pass an object to map_path. Attempting to call map_path(map) will result in similar behavior (i.e. a URL like map.12).

url_for and route defaults in Rails 3

I have a rails route set up like:
match ':controller/:id/:action'
# match 'teams/:id' => "teams#show" # doesn't have any additional effect, which makes sense to me
resources :teams, :only => [:index, :show]
That way I can say /teams/cleveland-indians and it will call teams#show with :id => 'cleveland-indians'. Works great. My issue is that url_for doesn't quite do what I want. In my views/teams/index view, I get this behavior:
url_for(:id => "cleveland-indians") # => /teams/cleveland-indians/index
url_for(:id => "cleveland-indians", :action => :show) # => /teams/cleveland-indians/show
Of course that second one behaves the way I want, but I'd like to get rid of the unnecessary /show at the end. I don't know much about how these helpers work, but I'd have guessed it would know that show was the default action for a GET with a specified id, same as the routing engine does. Anyway, what's the best way for me to take care of this? Or am I just doing it all wrong?
'resources' line should already provide you with the routes you probably want so you can just remove first 'match' line.
Note that you can also use 'teams_path', 'team_path("cleveland-indians")' instead of 'url_for'.

Resources