Rails Restful Routing
Justin Saborouh
Posted on November 17, 2022
When building an interactive full-stack website, there are going to be cases where the user communicates with the server, and the server has to respond with or update the corresponding data. In the simplest of applications, it's the reason why a user can create, read, update, and destroy (CRUD) particular pieces of data.
This is one of the many fundamental properties that Rails simplifies for us in web applications. The User sends a request to the Controller, which manipulates the request to be interpretable by the Model so it can update the View and present it back to the User.
What is REST?
REST is REpresentational State Transfer which references an architectural design flow/pattern when routing your HTTP requests. Often times, requests like GET
, POST
, etc are used more than once for a certain controller and the definitions for each varying GET
or POST
request will look and execute messily. To avoid this, the rails backend structures 7 specific route names that translate the HTTP request smoothly once defined in the specific controller.
For example, a lot of GET
requests without this convention are written in Sinatra with
class ApplicationController < Sinatra::Base
get '/games' do
games = Game.all
games.to_json data
end
end
As you can see, to satisfy a simple SELECT *
statement, a get request is used and must used over and over again to specific different types of get
requests.
Route Setup
In your routes.db
in your Ruby application, this is where the specifications of the different routes per controller are defined.
Rails.application.routes.draw do
resources :games
end
Having no particular declarations after the :games
attribute has rails interpret all 7 routes to be implemented. If you only wanted to have a CREATE
route, then you would add
Rails.application.routes.draw do
resources :games, only: [:create]
end
Index | Show
Index and Show in a RESTful context both reference the GET
HTTP pull method. Index
would list the entire index of a certain table, and Show
would represent only one row of that table
In routes.db
, further clarification is needed
Rails.application.routes.draw do
resources :games, only: [:index, :show]
end
In the controller for the respective class (in this case, a game_controller.rb
file), the implementation is
class GameController < ApplicationController
def index
render json: Games.all
end
end
Here, we tell the database to get all indices of a table and render them back to the Model
You can also tack on a status code if need be
render json: Games.all, status: :ok
Show
represents the GET
HTTP method as well, but with a parameter (typically an ID of the index you'd like)
def show
render json: Games.find(params[:id]), status: :ok
end
If you need to cover for the situation in which the ID wasn't found then you can nest it in an if-else scenario
def show
game = Games.find(params[:id])
if game
render json: game, status: :ok
else
render json: {error: "Game not found"}, status: :not_found
end
end
Create | Update | Destroy
Once again we specify our routes.db
to include these HTTP requests
Rails.application.routes.draw do
resources :games, only: [:index, :show, :create, :update, :delete]
end
When creating entries, rails can not automatically assume the contents of a POST
request, we need to re-orient the parameters so that the body of the post request can be interpreted to create new entries
Adding this wrap_parameters
declaration tells rails to re-format the data into a []
array-like format, so that we can break apart what was sent and see if there are extra, or lacking pieces of data
class GameController < ApplicationController
wrap_parameters format: []
The CREATE
request is defined as follows
def create
game = Games.create(game_params)
render json: game, status: :created
end
private
def game_params
params.permit(:name, :publisher, :release_year, :console)
end
The game_params function takes the params and assigns each attribute to be returned as parameters, so it is properly formatted when passed into the create
function
Update
would replicate the show
route but with a small change when rendering
def update
game = Games.find(params[:id])
if game
game.update(game_params)
render json: game, status: :accepted
else
render json: {error: "Game not found"}, status: :not_found
end
end
Destroy
would be similar to above as well, the only niche is that since there isn't necessarily any data to render back to the Model, you would return head :no_content
def destroy
game = Games.find(params[:id])
if game
game.destroy
head :no_content
else
render json: {error: "Game not found"}, status: :not_found
end
end
Conclusion
Overall, as fundamentally standard CRUD is across all forms of database management/administration, the only extra step needed when adapting to new environments is following the conventions. New
and Edit
would require a form to be presented and rendered, in which case the Create
and Update
definitions would be invoked afterwards.
Happy CRUDing!
References
MVC: Model, View, Controller (https://www.codecademy.com/article/mvc)
ThoughtBot: REST Tutorial (https://thoughtbot.com/upcase/videos/rest)
RailsGuides: Rails Routing (https://guides.rubyonrails.org/routing.html)
MakandraCards: wrap_parameters for your API
(https://makandracards.com/makandra/46405-rails-wrap_parameters-for-your-api)
Posted on November 17, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.