Rails API With Nested Resources
Cocoon is a very popular gem to add nested resources functionality in Rails. It allows you to add or remove “mini forms” of another model into a main model's form and create all objects at once when the form is saved.
I was recently finding myself trying to implement a similar functionality using only Rails 5 API and JSON API specification.
As an example, we will try to implement the commonly seen Work Experience and Educational Background features seen in sites such as LinkedIn, where the user can add many items for these categories. The user's profile and all items for those categories (which are separate models) should all be saved at once, once the form is submitted.
accepts_nested_attributes_for
accepts_nested_attributes_for
allows us to save attributes on associated records (work experience, educational background) through a parent record (user). By default nested attribute updating is turned off and you can enable it using the #accepts_nested_attributes_for
class method.
Let's take a look on how the models could be defined and implement accepts_nested_attributes_for
:
class User < ApplicationRecord
has_many :work_experiences
has_many :educational_backgrounds
accepts_nested_attributes_for :work_experiences, allow_destroy: true
accepts_nested_attributes_for :educational_backgrounds, allow_destroy: true
end
class WorkExperience < ApplicationRecord
belongs_to :user, optional: true
end
class EducationalBackground < ApplicationRecord
belongs_to :user, optional: true
end
The :allow_destroy
option allows the deletion of nested work experience or educational background items.
For the “child” models, the presence of the user is defined as optional. This attribute is important because without it, the model validation will fail with User must exist when it tries to create Work Experiences for which an Event does not yet exist.
The accepts_nested_attributes_for
method defines an attribute writer on the parent model. This is a method which by convention is named after the nested model, with the postfix _attributes
. In our case this becomes work_experiences_attributes
and educational_backgrounds_attributes
.
Strong Parameters
Strong parameters in the parent resource's controller must be configured correctly to accept the nested attributes from the other resources. This can be done in the following manner:
# users_controller.rb
def user_params
params.require(:user).permit(:name, :email, work_experiences_attributes: [:id, :title, :company, :location, :description], educational_background_attributes: [:id, :institution, :career, :description])
end
This works fine in typical scenarios. But what if we are using JSON API specification?
JSON API
Parameters are required and permitted a bit differently when the API follows the JSON API specification.
We can re-write the method above to reflect this:
def user_params
params.require(:data).require(:attributes).permit(:name, :email, :email, work_experiences_attributes: [:id, :title, :company, :location, :description], educational_background_attributes: [:id, :institution, :career, :description])
end