Rails: "Convention over configuration"
To have a good understanding of partials, I first want you to understand the two primary ways you can create an HTTP response from the controller's point of view.
- Call render to send back to the browser
- Call redirect_to to send an HTTP redirect status code to the browser
Kind of makes sense. But I think an example explains the two ways better.
Controllers, by default, automatically render views, if the name corresponds to a valid route.
Source: Creating Responses
restaurant_controller.rb
class RestaurantController < ApplicationController
end
routes.rb
resources :restaurants
app/views/restaurants/index.html.erb
<h1>Restaurant Coming Soon</h1>
Rails provides a mapping between HTTP verbs and URLs to controller actions. We can, with a single entry, create seven different routes in our application.
Source: Routing
HTTP Verb | Path | Controller#Action | Used For |
---|---|---|---|
GET |
/restaurants | restaurants#index | display a list of all restaurants |
GET |
/restaurants/new | restaurants#new | return an HTML form for creating a new restaurant |
POST |
/restaurants | restaurants#create | create a new restaurant |
GET |
/restaurants/:id | restaurants#show | display a specific restaurant |
GET |
/restaurants/:id/edit | restaurants#edit | return an HTML form for editing a restaurant |
PATCH/PUT |
/restaurants/:id | restaurants#update | update a specific restaurant |
DELETE |
/restaurants/:id | restaurants#destroy | delete a specific restaurant |
Rails routes are matched in the order they are specified.
If you put resources at the very top, and then decided to change the path underneath, it would not work. Whatever matches first, will be run.
Without writing anything in our controller, Rails will automatically render the view located at: app/views/restaurants/index.html.erb
when we navigate to the path /restaurants/
.
Let's say instead we have this in our controller:
class RestaurantsController < ApplicationController
def index
@restaurants = Restaurant.all
end
end
We don't explictly state that we want to render a view. These kind of assumptions are all based on the 'convention over configuration' principle.
In other words, if we do not explicitly state a view to render, Rails will automatically look for action_name.html.erb
template in our view folder.
In this case, it will look for app/views/restaurants/index.html.erb
The three ways of using render:
- Render another template within the same controller
- Render a template within another controller
- Render a file somewhere in your project directory (least likely)
def update
@restaurant = Restaurant.find(params[:id])
if @restaurant.update(book_params)
redirect_to(@restaurant)
else
render "edit"
end
end
OR:
def update
@restaurant = Restaurant.find(params[:id])
if @restaurant.update(book_params)
redirect_to(@restaurant)
else
render :edit
end
end
You can use symbols or strings. The key point is that in the above examples, if the update fails, the controller will render the edit.html.erb
file belonging to the controller (Restaurants)
Let's say we have another controller called Orders, located at app/controllers/orders
.
In this specific example, if we create a new restaurant, we want immediately to render the app/views/orders/new.html.erb
page.
We can do this by instead placing:
def create
@restaurant = Restaurant.new(restaurant_params)
if @restaurant.save
render "orders/new"
else
render :new
end
end
render file: "/u/apps/random_app/current/app/views/restaurants/show"
The final way of returning responses is with redirect_to
.
render
tells Rails which view to use.
In contrast, redirect_to
tells the browser to send a new request for a different url.
Take a moment to think about that.
Example:
redirect_to(restaurants_path)
will ask the browser to send a request for:
get /restaurants
, which in turn will look to the restaurants#index
for what to do next.
You can also use redirect_back
to return the user back to where they came from.
Syntax:
redirect_back(fallback_location: root_path)
Source: redirect_to
redirect_to() and redirect_back() do not halt and return immediately from method execution, but simply set HTTP responses.
Statements occurring after them in a method will be executed.
You can halt by an explicit return, if needed.
Think twice about which action you want to take. It seems trivial, but they do different things altogether.
def index
@restaurants = Restaurant.all
end
def show
@restaurant = Restaurant.find_by(id: params[:id])
if @restaurant.nil?
render "index"
end
end
What is the problem here? What happens if a restaurant is nil?
From first glance, it makes sense.
If no restaurant, render my index page.
But a render() method does not run any code, it does not go back to the index() method and then finds @restaurants.
It throw an error.
Solution: A better fix is to use redirect_to()
def index
@restaurants = Restaurant.all
end
def show
@restaurant = Restaurant.find_by(id: params[:id])
if @restaurant.nil?
redirect_to :index
end
end