JSON fields are pre-processed by ActiveElement before they arrive as controller params
. Read the Behind the Scenes section if you want to know exactly what happens during this pre-processing.
Pre-processing JSON parameters means that you get a regular Rails controller params
object (i.e. an instance of ActionController::Parameters
) with all of the JSON parameters available as though they were normal form parameters, so you can use them with params.require(...).permit(...)
, pass them to create
or update
and let ActiveRecord translate them back to JSON.
ActiveElement does not automatically permit
parameters but it does map types for you based on the defined schema. This means you don’t have to manually parse dates, decimals, etc. if you need to work with them in your controller.
Each mapped type is specifically chosen to be safe to serialize back into JSON. This JSON data can then be edited by ActiveElement’s json_field
in your forms, allowing you to build complex forms with minimal effort and without having to work directly with the underlying JSON.
# config/forms/user/pets.yml
---
type: array
shape:
type: object
shape:
fields:
- name: name
type: string
- name: age
type: integer
- name: animal
type: string
options:
- Cat
- Dog
- Polar Bear
- name: favorite_foods
type: array
shape:
type: string
options:
- Biscuits
- Plants
- Carpet
Below you can see that the email
param (a regular Rails email_field
or text_field
) is used in conjunction with the pets
param (an ActiveElemen json_field
).
See the official Rails ActionController::Parameters documentation for more details.
class UsersController < ActiveElement::ApplicationController
def update
@user = User.find(params[:id])
if user.update(user_params)
flash.notice = 'User updated'
redirect_to user_path(@user)
else
flash.alert = 'User update failed'
render :edit
end
end
private
def user_params
params.require(:user).permit(:email, pets: [:name, :age, :animal, favorite_foods: []])
end
end
ActiveElement tries to avoid behind-the-scenes magic where possible but, in this case, allowing fully transparent bi-directional JSON parsing and type coercion is so convenient that we make an exception. Here’s what happens when you generate a form with a JSON field and then submit the form back to your Rails application:
hidden
field __json_fields[]
is created with its value
set to the name of the field in dot notation, e.g. user.pets
.hidden
field __json_field_schemas[users][pets]
is created with its value
set to an empty string. The front end Javascript updates this when the form loads with the full schema. This ensures the data and schema are always consistent, even if the schema file changes between loading the form and submitting it.value
for the main input
field is set to the full state of the field’s data structure, as a JSON string.before_action
in ActiveElement::ApplicationController
intercepts the request and parses the JSON data structure for any fields listed in the __json_fields
array.Array
or Hash
) is then traversed recursively, applying type coercion to any fields specified in the schema that require it, e.g. a decimal
schema definition produces a BigDecimal
for all applicable values in the data structure.ActionController::Parameters
object is created with all the regular fields (text_field
, etc.) included, plus the transformed data structures for the JSON fields.__json_fields
and __json_field_schemas
are removed from the result. You’ll see them in the logs but they won’t get in the way in your controller.request.params
object is re-assigned to the newly-constructed ActionController::Parameters
object and the request continues as normal.If you have worked with submitting JSON to Rails controllers before then you likely will have come across numerous edge cases and difficulties with handling different parameter types, deeply nested values, parser errors, etc. that required custom code to handle.
ActiveElement aims to remove that effort completely and provide a params
object that is familiar and consistent with Rails conventions, so all you need to do is pass the params to your ActiveRecord create
/update
methods and everything should work seamlessly (submit a bug report if it doesn’t!), while also giving you the benefit of being able to work with Ruby objects.
For example, if you need to sort an array of objects by date before saving back to the database then it’s as simple as:
def sort_family_by_date_of_birth
user_params[:family].sort_by! { |family_member| family_member[:date_of_birth] }
end
def user_params
params.require(:user).permit(family: [:date_of_birth, :name, :relation])
end