Rails Routing -- Adding Custom Route - ruby-on-rails

I'm almost certain that someone has asked this question before, but I can't seem to hit on the right series of words to find it.
I have a resource, Games, with all of the normal resource-y paths. Create, Edit, etc.
I've created a new action within GamesController called json that I want to be able to access at mydomain.com/games/json but the routing keeps picking up 'json' as the ID and routing it to the 'show' action instead.
Presumably this is because of the default route:
match ':controller(/:action(/:id))'
I've tried a number of things, but no matter what I do it keeps routing to 'show.' I've been attempting to figure it out using this guide, but for someone that's pretty new to rails its quite a bit to take in and apply.
I'd like to say that for any controller /json would take you to the json action (instead of show with id 'json'), but I'd settle for having to specify it for every controller individually.
Any help is greatly appreciated. (Even if that's just pointing me to the already answered question.) In all cases I've been placing the route I'm attempting to create above the default route.
My routes:
root :to => 'home#index'
resources :events, :players, :sets, :matches, :characters, :videos, :games, :event_instances, :game_instances
resource :user_session
resource :account, :controller => "users"
resources :users
match '/login', :to => 'user_sessions#new', :as => 'login'
match '/logout', :to => 'user_sessions#destroy', :as => 'logout'
match '/register', :to => 'users#create', :as => 'register'
match '/games/json', :to => 'games#json', :as => 'gameList'
match ':controller(/:action(/:id))'

Ok so what you need to do is put your custom json route above your other resources routes and make it default like so:
get '/:controller/json(/:id)', action: 'json'
resources :events, :players, :sets, :matches, :characters, :videos, :games, :event_instances, :game_instances
...
Your confusion was linked to the way the routes are built and urls are parsed
Think of your routes.rb file as a set of consecutive filters to be applied to the url requested by the client.
If you put a default route above another it will catch all request that match the given pattern => this means that when you were adding all your resources routes above your json route, all urls matching the /#{resources}/:id pattern (and /games/json is one of them) were caught before they could reach the json custom route.
By putting your custom route above the others, you make it catch any request matching the pattern, that means in that case all requests with
/#{controller_name or resource_name)}/json(/:id)
NB:
I am not quite sure this is the best design pattern to use for your routes and I'd rather go with conventional REST routes with basic CRUD actions that would implement the respond_to pattern to answer specific json requests.
See docs for this: http://apidock.com/rails/ActionController/MimeResponds/InstanceMethods/respond_to
Adding such 'catch all' routes on top of all your resources may become risky when your app grows.
But you may have your reasons to go this way, in that case, I think my answer matches what you need.

resources :games do
collection do
get :json
end
end
if you also wanted to add an action that would take an :id path segment and return, say, json representation of a specific game, you'd do that with
resources :games do
collection do
get :json
end
member do
get :json
end
end
I'd add that when I was brand new to Rails (less than 1.5 years ago), the routing dsl was really hard for me to get my head around for some reason. But it's worth studying that guide, despite the fact that it's a bit overwhelming at first. Otherwise you'll end up with a bunch of match '/route/to/somewhere' => 'controller#action', :as => :some_helper, which ends up being a pain to read & maintain and will make it harder to have a consistent "grammar" for your urls.
Edit: looking at your now-posted routes file, the problem is that you declare the games/json route after the resources :games declaration. Route-matching precedence proceeds from the top of the file down, so the resourceful route for games matches first, and the specific route declared later is never checked. If you use the syntax above to make your json route a part of the resource declaration, this won't happen.

Related

Understanding rails routing that include (=>) and (:as)

While reading about rails routing I found routing that include =>. But I don't understnad what it means. Also I found some routing example with :as. It would be nice if someone explained a little bit about it. I have read rails guide but still I am not quite clear about them.
Please explain what this means
get 'customer_details' => "customers#details"
and
get 'customer_details' => "customers#details", :as => :customerdetails
Each time you define a route, you have to define a controller#action for that route:
#config/routes.rb
get :customer_details => "customers#details"
get :customer_details, to: "customers#details"
get :customer_details, controller: :customers, action: :details
The routing module provides URL rewriting in native Ruby. It's a way to redirect incoming requests to controllers and actions.
The following symbols are special:
:controller maps to your controller name
:action maps to an action with your controllers
Other names simply map to a parameter as in the case of :id.
Using => is simply a shortcut for the to: option...
When a pattern points to an internal route, the route's :action and :controller should be set in options or hash shorthand. Examples:
match 'photos/:id' => 'photos#show', via: :get
match 'photos/:id', to: 'photos#show', via: :get
match 'photos/:id', controller: 'photos', action: 'show', via: :get
In short, it's another way to pass the required "controller#action" arguments to the Rails router.
--
Of course, this is negated by using the resources directive, which sets the controller & actions implicitly:
#config/routes.rb
resources :customers, only: [], path: "" do
get :customer_details, on: :collection #-> url.com/customer_details
end
The routing middleware (ActionDispatch::Routing) takes inbound URL paths, and matches them against your defined routes -- sending the user to the appropriate controller / action.
The entire routing structure (even when you use link_to) depends on having the controller & action set for a route; especially true with a path helper.
Setting as: gives you the ability to explicitly define the name of the path, for example:
#config/routes.rb
get "search/:query", to: "application#search", as: :app_search
... the above will create the helper <%= app_search %>
Update
In response to your comment, you'll want to use either of the following:
#config/routes.rb
get "customers/details", to: "customers#details" #-> url.com/customers/details
- or -
resources :customers do
get :details, on: :collection #-> url.com/customers/details
end
If you're defining a single route, only use symbols if Ruby can interpret that data without any interpolation. For example get :details can be treated as get "details", however get "customers/details" cannot be treated as a symbol.

Rails custom route not working

Feel like I'm doing this right, but apparently not.
I have a restful resource, Posts, with index, show, new, update, edit, etc actions in the controller. In routes, I have
resources :posts
I wanted to make the index action occur at the URL '/archive' instead of '/posts'
So I added this line in the routes.rb file, after the resources one:
match '/archive', to: "posts#index"
But when I click on a link to posts_path, it still goes to /post (though if I type in /archive as a url, it works -- not ideal, though). Confused. Could this have to do with my having installed friendly_id?
resources :posts, except: [:index]
get 'archive' => 'posts#index', as: :posts
You need to use something like match '/archive', :to => 'posts#index', :as => 'archived'. Then you will have a new route to the tune of archived_posts_path. The method posts_path does not dynamically changed based on custom matchers. You can always run rake routes to see a list of routes for your site.

Rails 3: How do I route a controllers index and show actions to root while sending new/edit/destroy to /admin?

First off, I'm using rails 3.0.8 with friendly_id 3.2.1.1.
I'd like to be able to view the posts at website.com/:title, so drop the "/posts".
But I'd also like to have an /admin view. From there, a user should be able to create/edit/destroy posts. I already have a admin.html.erb view with links to various actions.
Right now my routes file look like:
MyApp::Application.routes.draw do
root :to => 'posts#index'
resources :posts
match "/:id" => "posts#show"
match "/admin" => "posts#admin"
end
This works for website.com/:title, but for website.com/admin I get an error:
Couldn't find Post with ID=admin
.... which makes sense. But I'm not sure how to solve this problem.
The rules are run through top to bottom. So put your admin rule on top of the resource definition.
If you put /admin first then it will work (as noted by cellcortex). You can also use :constraints if you can neatly separate your :id from 'admin'; for example, if your :id values are numeric, then something like this should work:
match '/:id' => 'posts#show', :constraints => { :id => /\d+/ }
match '/admin' => 'posts#admin'
In a simple case like yours, putting things in the right order will work fine. However, if your routing is more complicated, then the :constraints approach might work better and avoid a some confusion and chaos.
Use this
resource :posts, :path => '/'
with this all of your article will be directly under root
So in Posts Class you may add this:
def to_param
"#{id}-#{title.parameterize}"
end

Routing nested resources in Rails 3

I have a pretty common case for nested routes, I feel like, that looks something like this (in some sort of pseudonotation):
'/:username/photos' => Show photos for User.find_by_username
'/photos' => Show photos for User.all
In a nutshell: I have users. They have photos. I want to be able to show their photos on their page. I also want to be able to show all photos, regardless of the user. I'd like to keep my routes RESTful and using the built-in resource methods feels like the right way to do it.
Option 1 for doing this is to have PhotosController#index use a conditional to check which params are given and get the list of photos and set the view (different for a user's photos than for all photos). It's even easy to route it:
resources :photos, :only => [:index]
scope ':/username' do
resources :photos
end
Boom. It'd seem like Rails was setup for this. After the routes, though, things get more complicated. That conditional back in the PhotosController#index action is just getting more and more bloated and is doing an awful lot of delgation. As the application grows and so do the number of ways I want to show photos, it is only going to get worse.
Option 2 might be to have a User::PhotosController to handle user photos, and a PhotosController to handle showing all photos.
resources :photos, :only => [:index]
namespace :user, :path => '/:username' do
resources :photos
end
That generates the following routes:
photos GET /photos(.:format) {:action=>"index", :controller=>"photos"}
user_photos GET /:username/photos(.:format) {:action=>"index", :controller=>"user/photos"}
POST /:username/photos(.:format) {:action=>"create", :controller=>"user/photos"}
new_user_photo GET /:username/photos/new(.:format) {:action=>"new", :controller=>"user/photos"}
edit_user_photo GET /:username/photos/:id/edit(.:format) {:action=>"edit", :controller=>"user/photos"}
user_photo GET /:username/photos/:id(.:format) {:action=>"show", :controller=>"user/photos"}
PUT /:username/photos/:id(.:format) {:action=>"update", :controller=>"user/photos"}
DELETE /:username/photos/:id(.:format) {:action=>"destroy", :controller=>"user/photos"}
This works pretty well, I think, but everything is under a User module and I feel like that might end up causing problems when I integrate it with other things.
Questions
Does anybody have experience with something like this?
Can anybody share a better way of handling this?
Any additional pros and cons to consider with either of these options?
Update: I've gone ahead implementing Option 2 because it feels cleaner allowing Rails' logic to work rather than overriding it. So far things are going well, but I also needed to rename my namespace to :users and add an :as => :user to keep it from clashing with my User model. I've also overridden the to_param method on the User model to return the username. Path helpers still work this way, too.
I'd still appreciate feedback on this method. Am I doing things the expected way, or am I misusing this functionality?
Have you considered using a shallow nested route in this case?
Shallow Route Nesting
At times, nested resources can produce cumbersome URLs. A solution to this
is to use shallow route nesting:
resources :products, :shallow => true do
resources :reviews
end
This will enable the recognition of the following routes:
/products/1 => product_path(1)
/products/1/reviews => product_reviews_index_path(1)
/reviews/2 => reviews_path(2)
The best way to do this depends on the application, but in my case it is certainly Option B. Using namespaced routes I'm able to use a module to keep different concerns separated out into different controllers in a very clean way. I'm also using a namespace-specific controller to add shared functionality to all controllers in a particular namespace (adding, for example, a before_filter to check for authentication and permission for all resources in the namespace).
I did something similar to this in one of my apps. You're on the right track. What I did was declare nested resources, and build the query using the flexible arel-based syntax of Active Record in Rails 3. In your case it might look something like this:
# config/routes.rb
resources :photos, :only => :index
resources :users do
resources :photos
end
# app/controllers/photos_controller.rb
def index
#photos = Photo.scoped
#photos = #photos.by_user(params[:user_id]) if params[:user_id]
# ...
end
You could define a seperate route for getting the photos for one user like so:
get '(:username)/photos', :to => 'photos#index'
But I would advise just using the nested resource that Jimmy posted above since that is the most flexible solution.
Example::Application.routes.draw do
resources :accounts, :path => '' do
resources :projects, :path => '', :except => [:index]
end
end
Got the example from:
http://jasoncodes.com/posts/rails-3-nested-resource-slugs
Just applied that in my current project.

Routing trouble on Rails 3 (related to singular/plural)

I've read around the resources on how routing in Rails 3 works, but am running into some difficulties.
In my app there are Blogs and my routes.rb contains:
resources :blogs
root :to => "home#index"
URLs containing 'blogs' are work fine.
However what I'd like to do is have 'blog' in the URLs. Specifically, /blog/:id (for show) and /blog (for index).
If I add the line:
match 'blog' => 'blogs#index'
Then /blog does show the index, however it breaks my blog edit form as the action URL changes from /blog/:id to /blog.:id
Any ideas on how to use blog instead of blogs, and also allow the blog index to be on /blog? I've tried quite a few things (like resource :blog, and also resources :blogs, :as => 'blog') and not getting anywhere. Assuming there's a way to do this without manually defining each route for show, edit, destroy and index.
You need use the :path option
resources :blogs, :path => 'blog'
Remove your match line, and change your resouces line to:
resources :blogs, :path => 'blog'
Check: http://edgeguides.rubyonrails.org/routing.html#translated-paths

Resources