How does a rails resource know to use the show view? - ruby-on-rails

I'm looking at this tutorial: https://www.railstutorial.org/book/sign_up
We get on to rails resources.
config/routes.rb
Rails.application.routes.draw do
root 'static_pages#home'
get 'help' => 'static_pages#help'
get 'about' => 'static_pages#about'
get 'contact' => 'static_pages#contact'
get 'signup' => 'users#new'
resources :users
end
app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
def new
end
end
app/views/users/show.html.erb
<%= #user.name %>, <%= #user.email %>
There is also this handy table of telling us how ruby will handle the various requests to the Users resource.
I have two questions here.
How does RoR know when accessing the /users, /users/1 etc urls, what to actually use the index, show methods.
More importantly, - when the show method is called, how it does it know the give the show.html.erb view to browser? What if I wanted to return a different view?

1. Rails knows which methods to use based on the HTTP request type (GET, POST, PUT, or DELETE) and the endpoint. So when you hit the '/users' endpoint with a GET request, it will use the index method. When you hit the '/users/:id' endpoint with a GET request, it will use the show method.
2. The show.html.erb view is used because the name matches the show method. To use a different view, just use match in the routes file like so:
match "users" => "users#show"
The example above would match the '/users' route to the '/user/:id' route.

The mechanism that is responsible for dispatching actions in response to requests is router. You define all the routes in config/routes.rb file as you said. Each line in this files define the request in clear way. resources is a shorthand to define a set of routes - all of them listed here.
There are two thins that distinct requests - the URL and the HTTP request type (GET, POST, PUT, DELETE). Based on these two things you can clearly direct the request to proper controller. Take a look on this one:
get 'help' => 'static_pages#help'
This means: If you get a request of type get directed to url of value /help dispatch the request to controller static_pages and its action help. What happens in help action is a matter of code.
This is also an answer to your second question, how does Rails know what template should it render. Assume that we still work on above example of help action. If this action has no explicit render call, Rails will use its convention over configuration and search for a template that will be in directory called as the controller and in file name as the action name. Therefore, it will render the file that is in app/views/static_pages/help.html.erb. On the other hand, the developer can call render with explicit other file name, e.g.: render "products/show".
If you want to find more please take a look at these two Rails guides:
Layouts and rendering
Rails Routing

rails is said to have convention over configuaration. this is one of the strongest feature available in Rails.in another way. Smart and less code. Here when u say method name as show. rails by default will look for show.html.erb. if you want to explicitly render some other view. You can do this in your controller methods
render :new_view_name
Regarding the routes.
resources :users
This is very helpful when we have Rest calls ,crud operations.it creates 7 urls for crud. And how it differentiate is depending on http data type.

Related

Rails render "action" as custom url

In my routes.rb I have the following:
get "contact" => "inquiries#new"
So that when I go to /contact in the browswer, it calls the InquiriesController's new action.
Now when I try to call render "new" in the create action inside InquiriesController:
def create
…
render "new"
end
The resulting url in the browser is /inquiries.
Is there a way besides calling redirect_to to render "new" but have the url as /contact in the browser?
Short answer is No. And here's why:
render is different from redirect_to. When you write redirect_to :action, you are initiating an entirely new browser request. The rails stack is hit, again routes are searched for and the corresponding action is executed. Its exactly the same as entering the url in address bar and pressing enter.
On the other hand, when you use render, you are telling which view to use for the current request. As such, the address in the address bar will generally be of the action in which you are calling render. That's because you put an address and then you tell rails to display a different page in that same request.
In a nutshell, while redirect_to begins an entirely new request cycle, render simply replaces the default view with what you choose in the same request cycle.
So if you want the address bar to change, you will have to initiate a new request to the address you want. It can be by manually entering the address, clicking a link to that address or redirecting to it from rails.
Hope this helps.
The solution is to use custom routes, if you use Restful routing, you can simply add this line to your routes.rb :
resources :inquiries, path: "contact", as: :inquiries, only: [:create]
here you tell rails to change url by default from inquiries to contact when the name of the action is create
if you want other action to match an url which is begin with contact, just add the name of the action to "only", for example : only: [:create, :update ...]
if you want all your actions in that controller (inquiries) to be customized to "contact" just remove only like this :
resources :inquiries, path: "contact", as: :inquiries
and all your routes for inquiries controller will be change from /inquiries to /contact
for more details about how to customizing restful routes please check this link
When using render :new in the create action, it will use the same URL that the form posted to.
Therefore, if you would like to set up both an inquires you can set up your routes like:
get '/contact', 'inquiries#new', as: 'contact'
post '/contact', 'inquiries#create'
You can also use the resources method as medBo references, but I just prefer plain old get and post when I'm doing custom things. Also, these routes can co-exist with your exiting inquiries routes without any ill affects.
Then with those routes sets, you can create your contact from by writing:
<%= form_tag contacts_url do %>
...
<% end %>
The important step here, is that we set up the form to post to `/contact' instead of posting to '/inquiries'.
I think you first need to understand difference between redirect_to & render
For /contact url
change
render "new"
to
redirect_to "/contact"

How to change a rails controller default router from index to other?

As we know, if we have a UsersController, when we get /users it default to /users/index. If I want to map /users to /users/show, what can I do? (This can apply to any other controllers, not just users.)
You can modified the file Global.asax.
There is a method named 'RegisterRoutes',change the 'Index' in 'action=Index' to 'show'
The only difference between URLs for the index action and the show action is the show action requires the identity of the resource. The default Rails URL for index on the users resource is /users, and the default show URL looks like /users/12, where the 12 identifies the resource. This identification part isn't optional, so you can't usefully map /users to the show action in a RESTful scheme.
For example if you want to have a profile page at localhost:3000/username you can add match '/:id' => 'users#show', :as => :user to routes.rb and <%= link_to "Profile", #user %> to your view. With a little fiddling in the controller to grab your profile info via the current user's username, this will link to localhost:3000/username.

Rails: Routing to a different controller based on request format

I'm writing an app where I want all requests for HTML to be handled by the same controller action. I have some other routes that are JSON-specific. Here's what my routes look like:
Blog::Application.routes.draw do
constraints format: :json do
resources :posts
end
match "(*path)" => "web#index"
end
The problem is that constraints is being interpreted as "this route only works with the specified format" rather than "skip this route and try the next one if the request is not in the specified format."
In other words, navigating to /posts in the browser gives me a 406 Not Acceptable because the URL is constrained to the JSON format. Instead, I want it to fall through to web#index if the request is for HTML, and hit the resourceful route if the request is for JSON. How can this be achieved?
(Using Rails 3.2.9.)
I've thought about this, and come to some conclusions.
You might not want all routes to return the single page app HTML, because there are some paths that you'll ideally want to return an HTTP 404 status code from.
Your HTTP routes will be different from your JSON routes
You definitely should be routing different formats to different controllers
It might be advantageous to define all your HTML routes in your Rails router, so that you can use it to generate a javascript router. There is at least one gem that does this
Rails does not have this functionality and this gem https://github.com/svenfuchs/routing-filter doesn't really seem like the right tool. Here is my attempt: Rails routing-filter route all html requests to one action
Having to namespace your JSON API under a module Api to avoid routing collisions is no bad thing.
Don't have content visible to Google on your single page app, or you'll get banned for duplicate content.
I took a slightly different approach, which doesn't really answer the question, but I think it has a few advantages:
Here is my config/routes.rb
FooApp::Application.routes.draw do
# Route all resources to your frontend controller. If you put this
# in a namespace, it will expect to find another frontend controller
# defined in that namespace, particularly useful if, for instance,
# that namespace is called Admin and you want a separate single page
# app for an admin interface. Also, you set a convention on the names
# of your HTML controllers. Use Rails's action_missing method to
# delegate all possible actions on your frontend controllers.
def html_resources(name, options, &block)
resources(name, options.merge(:controller => "frontend"), &block)
end
# JSON API
namespace :api do
resources :things, :except => [:edit, :new]
end
# HTML
html_resources :things, :only => [:edit, :new, :index, :show] do
get :other_action
end
end
class FrontendController < ApplicationController
def action_missing(*args, &block)
render :index
end
end
First of all I think different controllers for the same action is not the "Rails way".
Probably you can solve your problem with low level custom request handler in the middleware.
You can use format in your constraints, e.g.
match '*path', constraints: { path: /foo.*/, format: 'html'}, to: 'home#index', via: [:get]

rails rake routes where are they from

When you type
rake routes
a bunch of routes come out, but where are they defined???
I know some are default, and how about the others?
For example, this is a script from a controller, I tried to take off the 's' from do_something, but can't make it work.... are they defined somewhere else too?
Also, when do they take parameters and when not, how I know it ? Thanks!
def hello
redirect_to do_things_shop_path(shop)
end
def do_things
end
Rails routing configurations are kept in config/routes.rb file.
Taking parameters depends on many things. rake routes will show with routes take parameters. Member actions will take parameters.
posts GET /posts(.:format) posts#index
POST /posts(.:format) posts#create
edit_post GET /posts/:id/edit(.:format) posts#edit
In the last line, you will url like posts/:id/edit. This path requires :id parameter. You can call this route many ways. One of them is like:
edit_post_path(#post)
If you want to create a custom action, (say under posts controller), you can declare it as follow:
match `/posts/:id/things_with_id`, :to => 'posts#do_things_with_id', :as => 'do_things_with_id
match `/posts/things_without_id`, :to => 'posts#do_things_without_id', :as => 'do_things_without_id
First one requires an ID while the second one does not. Call them accordingly:
do_things_with_id_path(#post)
do_things_without_id()
For a resource, you can create these easily using member & collection action. Member action needs id while collection action does not.
resources :posts do
member { get 'do_thing' }
collection { get do_things' }
end
hope you got it.
By the way, you must read the following guide if you want to understand these clearly.
http://guides.rubyonrails.org/routing.html

route works one place, not others

This is kind of difficult to communicate but I'll try without pasting all my code. I have Members who have one Mailbox which has many Receipts. In the header layout I have a nav that calls
<%= link_to "Message Center", member_mailbox_path(current_user.member_id) %>
It works on most pages like trails/# , the resource pages for various models
But on other pages, seems like custom route pages, I get this error
No route matches {:action=>"show", :controller=>"mailbox", :member_id=>16}
Running rake routes shows this:
member_mailbox GET /members/:member_id/mailbox/:id(.:format) mailbox#show
Routes are confusing to me, here are my routes for this problem (show message isn't tested yet) ...
resources :members do
resources :mailbox do
resources :receipts do
member do
get :show_message
end
end
end
end
The routes for the pages that are showing the error are similar to this one
match '/my_plays', :to => "trails#my_plays"
match '/my_creations', :to => "trails#my_creations"
So not sure if my routes are right. I wonder if resources :mailbox is correct since I don't have a bunch of resources for that, it's a has_one .... THX
----EDIT--- after changing route per advice:
member_mailbox POST /members/:member_id/mailbox(.:format) mailboxes#create
new_member_mailbox GET /members/:member_id/mailbox/new(.:format) mailboxes#new
edit_member_mailbox GET /members/:member_id/mailbox/edit(.:format) mailboxes#edit
GET /members/:member_id/mailbox(.:format) mailboxes#show
PUT /members/:member_id/mailbox(.:format) mailboxes#update
DELETE /members/:member_id/mailbox(.:format) mailboxes#destroy
You may want to define a mailbox as a singular resource in your routes. Otherwise, Rails will expect you to pass in both the user id and the mailbox id for member_mailbox_path to route to mailbox#show. I believe this is why you're getting a routing error. Since each user has one mailbox, there's no need to make this extra lookup part of the route. So instead of resources :mailbox, you can do resource :mailbox:
resources :members do
resource :mailbox do
resources :receipts do
member do
get :show_message
end
end
end
end
I believe this would generate the following routes:
member_mailbox POST /members/:member_id/mailbox(.:format) mailboxes#create
new_member_mailbox GET /members/:member_id/mailbox/new(.:format) mailboxes#new
edit_member_mailbox GET /members/:member_id/mailbox/edit(.:format) mailboxes#edit
GET /members/:member_id/mailbox(.:format) mailboxes#show
PUT /members/:member_id/mailbox(.:format) mailboxes#update
DELETE /members/:member_id/mailbox(.:format) mailboxes#destroy
Notice that the lack of path names next to GET, PUT, and DELETE doesn't mean they don't exist; they're just repeats of the POST path, but each responds to different HTTP methods.
To render mailboxes#show, you'll need to add a MailboxesController with a show route, which might do a look up for the member:
class MailboxesController < ApplicationController
def show
#member = Member.find(params[:member_id])
# other mailbox code...
end
end
And you'll also create a template at app/views/mailboxes/show.html.erb to render the mailbox show page.
Also, I would recommend against deeply nesting your routes, as in third level :receipts.

Resources