Rails Resource Routing: Create
Learning Goals
- Use Rails to create a resource
- Understand the connection between the request body and
params
Introduction
In this lesson, we'll continue working on our Bird API by adding a create
action, so that clients can use our API to create new birds. To get set up, run:
$ bundle install
$ rails db:migrate db:seedThis will download all the dependencies for our app and set up the database.
| HTTP Verb | Path | Controller#Action | Description |
|---|---|---|---|
| GET | /birds | birds#index | Show all birds |
| POST | /birds | birds#create | Create a new bird |
| GET | /birds/:id | birds#show | Show a specific bird |
| PATCH or PUT | /birds/:id | birds#update | Update a specific bird |
| DELETE | /birds/:id | birds#destroy | Delete a specific bird |
Video Walkthrough
<iframe width="560" height="315" src="https://www.youtube.com/embed/wuzfkmOCe_U?rel=0&showinfo=0" frameborder="0" allowfullscreen></iframe>Creating New Birds
As always, the first thing we'll need to do to add a new endpoint to our API is
update our routes. Following REST conventions, we'll want our clients to make a
POST request to /birds to create a new bird. Using the resources method, we
can create this route by adding in create to the list of actions we want
handled:
Rails.application.routes.draw do
resources :birds, only: [:index, :show, :create]
endAfter updating our routes, run rails routes to check what routes are now
available:
Prefix Verb URI Pattern Controller#Action
birds GET /birds(.:format) birds#index
POST /birds(.:format) birds#create
bird GET /birds/:id(.:format) birds#showAwesome! We've successfully added a POST /birds route, which will run the
create in our BirdsController. Since we haven't set up that action, let's do
so now. For the time being, let's add in a byebug so that we can test out this
route and see what we'll need to do in order to create a new bird:
class BirdsController < ApplicationController
# POST /birds
def create
byebug
end
# etc
endRun your server now with rails s.
Now, we'll need to make a POST /birds with some data about the bird we're
trying to create. Recall from our schema that our birds table
has name and species columns:
create_table "birds", force: :cascade do |t|
t.string "name"
t.string "species"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
endTo create a new Bird instance, we'll need to provide values for these two
attributes. If we were making this request using fetch, it'd look like this:
fetch("http://localhost:3000/birds", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Monk Parakeet",
species: "Myiopsitta monachus",
}),
});Let's make that request using Postman (remember to add Content-Type:
application/json to the headers as well):
After making the request, we'll hit our byebug debugger, so we can see all the
data about the request that we have access to:
1: class BirdsController < ApplicationController
2:
3: # POST /birds
4: def create
5: byebug
=> 6: end
7:
8: # GET /birds
9: def index
10: birds = Bird.allFrom the byebug session, let's see how we can get access to the data we need
to handle this request. Remember, our goal in this action is to create a new
bird and send the new bird object in the response, so ultimately, we'll
want some code like this:
def create
bird = Bird.create(name: ???, species: ???)
render json: bird, status: :created
endThe
status: :createdoption will send a 201 status code, which indicates that the request has succeeded and has led to the creation of a resource.
To fill in the blanks, we'll need to figure out how to get data from the
body of the request, where our client sent the name and species for this
new bird.
In the byebug session, we can access the entire request object by using
the request method:
(byebug) request
#<ActionDispatch::Request POST "http://localhost:3000/birds" for ::1>This request object has all kinds of info about what was sent in the request.
Try some of these methods out in your byebug session:
request.request_methodrequest.headers["Content-Type"]request.body.read
The last one, request.body.read, will read the body of the request as a
string. Nice! We could take it a step further, and parse that string as json:
(byebug) JSON.parse(request.body.read)
{"name"=>"Monk Parakeet", "species"=>"Myiopsitta monachus"}This will return a Ruby hash of key-value pairs by parsing the JSON string from
the request body. However, that's a lot of steps for a fairly common task as a
Rails developer. Wouldn't it be nice if there was a bit of ✨ Rails magic ✨ to
make it easier to access that parsed request data? Enter the params hash!
Using Params
We can more easily access all the information from the request body by using
params:
(byebug) params
#<ActionController::Parameters {"name"=>"Monk Parakeet", "species"=>"Myiopsitta monachus", "controller"=>"birds", "action"=>"create", "bird"=>{"name"=>"Monk Parakeet", "species"=>"Myiopsitta monachus"}} permitted: false>We've seen params once before, as a way to access the dynamic part of the URL:
# GET /birds/:id
def show
# params[:id] refers to the dynamic part of our route, defined by :id
# a request to /birds/2 would give params[:id] a value of 2
bird = Bird.find_by(id: params[:id])
render json: bird
endHere, we can also see all the data from the body of our request added to this
params hash as well! Let's use that to create our bird. Exit the byebug
session by typing continue or c and hit enter. Then, update your controller
action like so:
def create
bird = Bird.create(name: params[:name], species: params[:species])
render json: bird, status: :created
endBack in Postman, send the same request through. Now, you should see a response in Postman with the newly created bird! You should also see in your Rails server log the SQL that was executed when the bird was created:
Started POST "/birds" for ::1 at 2021-05-02 10:09:03 -0400
(0.1ms) SELECT sqlite_version(*)
Processing by BirdsController#create as */*
Parameters: {"name"=>"Monk Parakeet", "species"=>"Myiopsitta monachus", "bird"=>{"name"=>"Monk Parakeet", "species"=>"Myiopsitta monachus"}}
TRANSACTION (0.1ms) begin transaction
↳ app/controllers/birds_controller.rb:5:in `create'
Bird Create (2.0ms) INSERT INTO "birds" ("name", "species", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "Monk Parakeet"], ["species", "Myiopsitta monachus"], ["created_at", "2021-05-02 14:09:03.955909"], ["updated_at", "2021-05-02 14:09:03.955909"]]
↳ app/controllers/birds_controller.rb:5:in `create'
TRANSACTION (0.9ms) commit transaction
↳ app/controllers/birds_controller.rb:5:in `create'
Completed 201 Created in 15ms (Views: 0.5ms | ActiveRecord: 4.1ms | Allocations: 4408)Success!
Experiment using Postman and byebug. What would you change if you wanted to
add additional keys to the params hash? What would you expect the server to
return if the new bird wasn't created successfully?
Conclusion
We've now seen how to handle the create action, and take data from the
body of a request using the params hash. When Rails receives a request
where the Content-Type is set to application/json, it will automatically
load the request body into the params hash.
In the next lesson, we'll explore the params hash further, and talk about ways
to refactor our code using additional features of the params hash.
