Generate RESTful routes with dynamic prefix - ruby-on-rails

I have an e-commerce web site written in Rails 3.2.8 which sells tickets for music events. So far I've been using simple RESTful routes in the application:
/ => default route: /events
/events
/events/1
/events/1/new
/events/2
...
All events used to happen in the same place, but from now on there's going to be two places, let's say "Morumbi" and "MaracanĂ£". Place is a model in the application, and it's a very important distinction between the events. So I'd like to make the place name a part of the path, and have routes like this:
/ => default route: page to choose place
/morumbi => same as /morumbi/events
/morumbi/events/1
/morumbi/events/1/new
/maracana => same as /maracana/events
/maracana/events/2
...
Although I know how to do that using the #match method, I have already a good number of routes created with the much more maintainable #resources method, and I'd like to keep them.
Do you know a solution avoiding the use of #match?

You need to use the #scope method:
scope path: ':place_name', as: 'place' do
resources :events
...
end
So GET /morumbi/events/1 will call EventsController#show with parameters id: '1' and place_name: 'morumbi'.

Related

Nested routes in rails

I am someone who has always liked sinatra better than rails, and has never had to do a large enough scale project that rails was required (all the sources I have read say that rails is better for larger scale projects) and now I do have to do a large scale project. I have gotten very confused with the url structure of rails. What I am trying to do is the rails equivalent of this:
get "/" do
erb :index
end
get "/home" do
erb :dashboard
end
get "/home/profile" do
erb :profile
end
get "/home/friends" do
erb :friends
end
In the first one I understand that I should put in app/routes.rb I should put root home#index and in the home controller I should put def index end.
In the second one, I understand that I should do the same except replacing index with home.
But for the third and forth ones I have no idea what to do.
Also, is the a RESTful way to do the first two?
You probably want something like this
root 'home#index'
get 'home' => 'home#dashboard'
get 'home/profile' => 'home#profile'
get 'home/friends' => 'home#friends'
remember to use the command rake routes to see all your routes, where they lead and what their names are (if they have any)
I never understood what RESTful means, so someone else will have to answer that part of your question.
K M Rakibul Islam has shown you what can be called a "resourceful" way to do routes (because it uses the keyword resources) but it looks like you're just doing the static pages at this stage, so it's not necessary.
The simplest way to do routes is with this formula:
method url => controller::action, as: route_name
where method can be get, post, patch or delete so you can have different actions linked to the same URL depending on the method the request uses.
Putting a name on the route is optional, but it gives you a clean way to use the route in your views (route_name_path)
When you start making models then you'll find that using the resources keyword comes in handy. Read about it.
You can have this:
resources :home do
collection do
get :profile
end
collection do
get :friends
end
end
end
This will give you routes like this:
profile_home_index GET /home/profile(.:format) home#profile
friends_home_index GET /home/friends(.:format) home#friends
The standard way of declaring the root path:
root 'home#index'
And for the 2nd one, you have to do:
get 'home' => 'home#dashboard'
which will give you this route:
GET /home(.:format) home#dashboard
One route can be defined in many ways that works. But, Rails has conventions that should be followed while defining routes in your Rails app.
I would highly recommend you to take a look at the Rails Routing Guide

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.

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.

What does a member and collection route mean? [duplicate]

This question already has answers here:
difference between collection route and member route in ruby on rails?
(5 answers)
Closed 9 years ago.
Reading this: http://guides.rubyonrails.org/routing.html#adding-more-restful-actions
What does it mean to add a 'member route'?
or do add a route to the collection?
What is a member and a collection when talking about routes?
They're both ways to add additional actions to a resource-based route in Rails.
A member route requires an ID, because it acts on a member.
A collection route doesn't require an ID because it acts on a collection of objects.
I like to think of them in terms of RESTful URLs. Consider the basics for a resource/model Foo
GET /foo # FooController#index
GET /foo/:id # FooController#show
GET /foo/new # FooController#new
POST /foo # FooController#create
GET /foo/:id/edit # FooController#edit
PUT /foo/:id # FooController#update
DELETE /foo/:id # FooController#destroy
Notice how:
Some routes have :id placeholders for Foo.id, and so refer to a specific Foo
Some routes have no :id, and thus refer to all Foos (and/or no specific foo, as in #new and #create)
Some routes (index/create, show/update/destroy) have the same URL, and use HTTP methods to differentiate between them
Some routes (edit/show) are basically the same (method & URL prefix) except for a different suffix (including "no suffix") at the end.
Member routes and collection routes let you add additional routes/actions using the same techniques as I listed above.
A member route adds a custom action to a specific instance using the URL suffix and HTTP method you provide. So, if you had a member route declaration of :member => { :bar => :get }. you'd get an additional route of:
GET /foo/:id/bar # FooController#bar
Note how it overloads GET /foo/:id in the same way that `edit' does. This is how you'd implement a "delete" action that provides a UI for the "destroy" action.
Similarly, a collection route adds an overload to the collection and/or a non-specific instance (it's up to you to decide exactly what it implies). So, if you declared :collection => { :baz => :get }, you'd get an additional route:
GET /foo/baz # FooController#baz
...in very much the same way as new.
You can also customize the HTTP method.
For example, I just recently had a project where I needed a "reply" action on a Comment. It's basically the same idea as Comment#create (which uses POST), except that it's in reference to a specific parent Comment. So, I created a member route: :member => { :reply => :post }. This gave me:
POST /comment/:id/reply # CommentController#reply
This keeps the routes restful while still expanding upon the basic 7 actions.
The built in member routes are show, edit, update and destroy, since they handle an individual record. index would be a collection route as it returns a collection of records.
So it really depends if you want to do something with a single record (member) or multiple records (collection).
The url helpers reflect singular (member) and plural (collection). For example:
This is a member:
person_path(#person)
This is a collection:
people_path()
If you define a custom collection path, it could look like this in your routes.rb:
resources :people do
member do
put :make_manager
end
collection do
get :show_managers
end
end
To make somebody a manger:
make_manager_person_path(#person)
To list all managers:
show_managers_people_path()
I don't think that the route "cares" if you use it differently, but this is the Rails way. It will make your code easier to read and other coders will have it easier to understand and maintain your code.

Rails3: Appropriate use of routing and resources

I've recently joined the world of Rails app development (Rails3) and I may be abusing resourceful routing.
The default resourceful routing makes some really convenient helper methods for the URLs which I use constantly. My problem is that I have controllers that I specified the routing as resourceful simply to take advantage of those helper methods. I have some basic site navigation that has no business with resources.
resource :home do
member do
get 'main'
get 'about'
get 'login'
get 'help'
end
end
Is there a better way to do what I've been doing? Anything that doesn't require that I manually add routing entries each time I have a new controller action?
Just to clarify, I want to specify routing for a controller without having to explicitly add any new actions but I also want it to auto-generate helper methods. So far, I have to explicitly add routes for each action I want that for. I can get something similar by doing this (in a non-resourceful way),
match 'home/about' => 'home#about'
But I don't want to have to write that very every route that doesn't fall into the convention.
Here's another simpler one. Just add a generic route to the bottom of your routes.rb
match ":controller/:action"
and it will map directly to the specified action of the specified controller. You can be a bit more specific if you like. For example, using get instead of match to restrict to HTTP GET requests, specifying the applicaple controllers etc.
get ":controller/:action", :constraints => { :controller => /home|help/ }
You can look into your controller for public instance methods and generate routes automatically.
# routes.rb
HomeController.public_instance_methods(false).select{|m| !(m.to_s =~ /^_/)}.each do |m|
match "home/#{m}", :action => m, :controller => HomeController, :as => "home_#{m}"
end
This will take the explicit(non-inherited) public instance methods from your controller, and select the ones that don't begin with an underscore(because underscored ones are generated methods for filters, the rest are actual actions). Then it will generate a named route for each.
Keep in mind that routes.rb is processed only at server startup so you will have to restart the server after you add new actions.

Resources