ruby-on-railsruby-on-rails-5fastjsonapi

Why does the JSON patch data from relationships not get saved via Ruby on Rails?


I have an Ruby on Rails api where the data is handled in JSON. When I want to update an entity all the attributes are getting updated persistently but changed relationships arent' getting handled correctly, the entity stays the same as before.

JSON data before and after the PATCH:

{"data":{"id":"26","type":"candidate","attributes":
{"place":"Ort","zip_code":"PLZ","address":"Adresse",
"date_of_birth":"2019-01-01T00:00:00.000Z","title":"Frau",
"first_name":"Vorname","last_name":"Nachname",
"email_address":"email@example.ch",
"confirm_terms_and_conditions":true},"relationships": 
{"occupational_fields":{"data":[]}}}}

PATCH input:

Started PATCH "/candidates/26" for 127.0.0.1 at 2019-01-22 
19:40:53 +0100
Processing by CandidatesController#update as JSON
Parameters: {"data"=>{"id"=>"26", "attributes"=>{"place"=>"Ort", 
"zip_code"=>"PLZ", "address"=>"Adresse", "title"=>"Frau", 
"first_name"=>"Vorname", "last_name"=>"Nachname", 
"email_address"=>"email@example.ch", 
"confirm_terms_and_conditions"=>true, "date_of_birth"=>"2019-01- 
01T00:00:00.000Z"}, "relationships"=>{"occupational_fields"=> 
{"data"=>[{"type"=>"occupational-fields", "id"=>“4“}]}}, 
"type"=>"candidates"}, "id"=>"26", "candidate"=>{}}

This are my models, Candidates and OccupationalFields are related via a has_many belongs_to_many relationship through one CandidatesOccupationalField:

class Candidate < ApplicationRecord
  has_many :candidates_occupational_fields, dependent: :destroy
  has_many :occupational_fields, through: 
    :candidates_occupational_fields, dependent: :nullify
end


class CandidatesOccupationalField < ApplicationRecord
  belongs_to :candidate
  belongs_to :occupational_field
end


class OccupationalField < ApplicationRecord
  has_many :candidates_occupational_fields, dependent: :destroy
  has_many :candidates, through: :candidates_occupational_fields,
    dependent: :nullify
end

This is the used controller:

class CandidatesController < ApplicationController
  before_action :set_candidate, only: %i[show update destroy]

  # GET /candidates
  def index
    @candidates = Candidate.all
    render json: CandidateSerializer.new(@candidates).serialized_json
  end

  # GET /candidates/1
  def show
    @candidate = Candidate.find(params[:id])
    render json: CandidateSerializer.new(@candidate).serialized_json
  end

  # POST /candidates
  def create
    @candidate = Candidate.new(candidate_params)

    if @candidate.save
      render json: CandidateSerializer.new(@candidate), status: :created
    else
      render json: @candidate.errors, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /candidates/1
  def update
    @candidate = Candidate.find(params[:id])
    if @candidate.update(candidate_params)
      render json: CandidateSerializer.new(@candidate)
    else
      render status: :unprocessable_entity
    end
  end

  # DELETE /candidates/1
  def destroy
    @candidate.destroy
  end

  private

  # Use callbacks to share common setup or constraints between actions.
  def set_candidate
    @candidate = Candidate.find(params[:id])
  end

  # Only allow a trusted parameter "white list" through.
  def candidate_params
    params.require(:data)[:attributes]
          .permit(:place, :zip_code, :address,
                  :date_of_birth, :title, :first_name,
                  :last_name, :email_address, 
                  :confirm_terms_and_conditions,
                  occupational_field_ids: [])
  end
end

The JSON formatting is handled by fastjsonapi, this are the used serializers:

class CandidateSerializer
  include FastJsonapi::ObjectSerializer
  attributes :place, :zip_code, :address, :date_of_birth,
         :title, :first_name, :last_name, :email_address,
         :confirm_terms_and_conditions

  has_many :occupational_fields
end


class OccupationalFieldSerializer
  include FastJsonapi::ObjectSerializer
  attributes :field

  has_many :candidates
end

Thank you for your help.


Solution

  • The problem was, that the used serializer fast_jsonapi can't be used as deserializer and the Rail's strong parameters can't handle the json input. It works with the gem restful-jsonapi and modified params as shown in the example of the readme of restful-jsonapi.