Tutorial
Step 2: Has Many
We’ll be adding the database table positions
:
id | employee_id | title | active | historical_index | created_at | updated_at |
---|---|---|---|---|---|---|
1 | 900 | Engineer | true | 1 | 2018-09-04 | 2018-09-04 |
2 | 900 | Intern | true | 2 | 2018-09-04 | 2018-09-04 |
3 | 800 | Manager | true | 1 | 2018-09-04 | 2018-09-04 |
Because this table tracks all historical positions, we have the
historical_index
column. This tells the order the employee moved
through each position, where 1
is most recent.
The Rails Stuff 🚂
Generate the Position
model:
Update the Employee
model with the association, too:
And update our seed data:
The Graphiti Stuff 🎨
Let’s start by running the same command as before to create
PositionResource
:
We’ll need to add the association, just like ActiveRecord:
…and a corresponding filter:
If you visit /api/v1/employees
, you’ll see a number of HTTP
Links
that allow lazy-loading positions. Or, if you visit
/api/v1/employees?include=positions
, you’ll load the employees and
positions in a single request. We’ll dig a bit deeper into this logic
in the section below.
Before we get there, let’s revisit the historical_index
column. For now, let’s
treat this as an implementation detail that the API should not expose -
let’s say we want to support sorting on this attribute but nothing else:
We’re almost done, but if you run your tests you’ll see two outstanding
errors. This is because Rails 5 belongs_to associations are required by
default. We can’t save a Position
without its corresponding Employee
.
We can solve this in three ways:
- Turn this off globally, with config.active_record.belongs_to_required_by_default. You may want to do this in test-mode only.
- Turn this off for the specific association:
belongs_to :employee, optional: true
. - Associate an
Employee
as part of the API request.
We’ll take for the last option. Look at
spec/resources/position/writes_spec.rb
:
When running our tests, let’s make sure the historical_index
column
reflects the order we created the positions. This code recalculates
everything after a record is saved:
Let’s associate an Employee
. Start by seeding the data:
And associate via relationships
:
To ensure the PositionResource
will process this relationship, the
last step is to add it:
This will associate the Position
to the Employee
as part of the
creation process. The test should now pass - make the same change to
spec/api/v1/positions/create_spec.rb
to get a fully-passing test
suite.
Digging Deeper 🧐
Why did we need the employee_id
filter above? To explain that, let’s dive deeper into the logic connecting Resources.
If you hit /api/v1/employees
, you’ll see a number of
Links in the
response. These are useful for lazy-loading, but the same logic
applies to eager loading. Let’s take a look at a Link to see how these
Resources connect together:
The salient bit: /positions?filter[employee_id]=1
. In other words,
fetch all Positions for the given Employee id.That means, whether we’re lazy-loading data in separate requests or
eager-loading in a single request, the same logic fires
under-the-hood:
This means we need filter :employee_id, :integer
to satisfy the query.
We can customize the logic connecting Resources in a few different ways. First some simple options:
So far so good. The logic, and corresponding Link, both update as you’d
expect (though we’d of course need a corresponding filter
:emp_id, :integer
on PositionResource
).
Those options are just simple versions of parameter customization.
You can customize parameters connecting Resources with the params
block:
Customizing these params affects the Link as well as the eager-load
logic. Remember the parameters here should reflect the JSON:API
specification, or anything PositionResource.all
accepts.
These are the most common options, but there’s a bunch more. Check out the Resource Relationships Guide to dig even deeper.