To get started, download this skeleton. Run bundle install
, then run bundle exec rake db:setup
. To get the test database setup correctly, run bundle exec rake db:seed RAILS_ENV=test --trace
.
Once you're set up, run the specs using bundle exec rspec spec
. These are
testing whether our API is sending the correct information. We're going to
be writing some Jbuilder templates to make them pass.
The first thing we need to do is set up our routes. All of our controllers are
defined under the Api
namespace (have a look through the controllers, noting
the class name), so we need to specify that in our routes as well. We're only
testing show
and index
today. For gifts, nest the index
route under the
guests resource. Use the below as a guide for the formatting:
namespace :api, defaults: { format: :json } do
# Your routes here
end
Run rake routes
to ensure this is working as intended.
Previously, we would often render our JSON by putting something like render json: @users
at the end of our controller actions. This has some unfortuante
side effects --- for one, we'll send down everything, including password
digests, to the client. This is bad. It also gives us no flexibility for
including associated data.
Jbuilder allows us to construct views that curate our data. It is also quite
straightforward to use once you've made a few templates. When making a Jbuilder
template, place it in the same place you would put your HTML views, but instead
of using .html.erb
as your file extension, use .json.jbuilder
. Like ERB,
Jbuilder will be compiled by Rails and you'll be left with a JSON template.
Since we set the default format of our resources to :json
, Rails will
automatically look for a .json
file when you pass a template name to render
(render :index
for example).
Make sure you keep the Jbuilder GitHub up for reference as you work
through this. A few notes before we begin. First, we can run any ruby code we
want in a Jbuilder template, including conditionals. This ends up being really
helpful when, for example, we only want to send certain user data if the user
requesting it is logged in. Second, we can build Jbuilder partials as we did
with HTML and ERB and render them using json.partial!
in our template, with a
very similar argument syntax to render partial: ...
. Last, we can nest our
data by opening blocks for a given key in our object. This is demonstrated in an
example from GitHub page:
json.author do
json.name @message.creator.name.familiar
json.email_address @message.creator.email_address_with_name
json.url url_for(@message.creator, format: :json)
end
Yields:
"author": {
"name": "David H.",
"email_address": "'David Heinemeier Hansson' <david@heinemeierhansson.com>",
"url": "http://example.com/users/1-david.json"
}
Note not only the nested object, but also the use of associations
(@message.creator
) and view helpers (url_for
).
Let's make some templates! Start by making a show.json.jbuilder
view for your
guest resource. Use json.extract!
to include the guest's name, age, and
favorite color. Make sure you don't include created_at
or updated_at
. Test
by running the specs but also by visiting the url directly (e,g.
'api/guests/1'). If you don't have a JSON formatter installed, I recommend
this one.
Once you have it working, go ahead and create an index view. For this, use
json.array!
and pass @guests. Use a block to specify what you want to render
for each guest. This time, don't use json.extract!
--- instead, specify each
component individually. For example:
#...
json.name guest.name
json.age ...
#...
It's good to get familiar with both methods. Since our two templates do very similar
things, let's go ahead and refactor the single guest details into a partial.
The naming convention for the partial itself is the same as for HTML views, e,g.
_guest.json.jbuilder
. Make sure to include 'api/' in your partial path.
Next, let's add some associated data. We want to see gifts for individual
guests, but not when we're looking at all guests (this may be too much data). In
your show view, render a guest's gifts. Only include the title and description.
NB: Using json.array!
at the top level here will cause our other guest
information to break. Nest your data by passing it as an argument to
json.gifts
.
Time to do some on your own. Make both a gift show and index view. Get the specs to pass.
To run the bonus specs, simply go to that spec file and remove the line before { pending('Bonus') }
.
Bonus: Make the party show and index views. In the index view, show all parties, and include all of their guests. In the show view, include not only all guests, but all of the guests' gifts as well.
Double Bonus: Change your guest index view to only show guests who are between 40 and 50 years old. Normally we would always do this kind of selection using Active Record, but this gives us an opportunity to practice using Ruby in Jbuilder.
Triple Bonus: In writing these views, you've generated some gnarly N+1 queries. Find them and defeat them. Hint: play around with your API in development and watch your server logs.