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