'Rails: JSON round trip from database to TEXTAREA converts to string on second save

(Rails 5.2) My record with JSON is saving correctly to the (Postgres 9.4) database on CREATE but is getting reformatted after UPDATE, with carriage returns (\r) and newline characters (\n) showing up in the database after UPDATE.

The :budget_json column is a jsonb type.

Here is my (simplified) Budget model:

class Budget < ApplicationRecord

  after_initialize :create_shell_json

  def create_shell_json
    self.budget_json = blank_budget if self.new_record?
  end

  def blank_budget
    {
      id: nil,
      name: nil,
      year: nil,
      [etc...]
    }
  end

end

Here is my controller:

  def new
    @budget = Budget.new(year: Time.now.year)
  end

  def create
    @budget = Budget.new(budget_params)
    @budget.budget_json = JSON.parse(budget_params[:budget_json])
    if @budget.save
        redirect_to admin_budgets_path, notice: "Budget successfully created."
    else
        render :new
    end
  end

  def edit
    @budget = Budget.find(params[:id])
  end

  def update
    @budget = Budget.find(params[:id])
    @budget.budget_json = JSON.parse(budget_params[:budget_json])
    if @budget.update(budget_params)
        redirect_to admin_budgets_path, notice: "Budget successfully updated."  
    else
        render :edit
    end
  end

And here are the relevant parts of the form. (The form is the same for CREATE and UPDATE.) The TEXTAREA contains the editable JSON should the user want to amend the default values:

<%= form_with model: [:admin, @budget], local: true, :html => {:class => "form-horizontal"} do |f| %>
  ...
  <div class="form-group">
    <div class="col-sm-2">
      <%= f.label :budget_json %>
    </div>
    <div class="col-sm-2">
      <%= text_area_tag "budget[budget_json]", JSON.pretty_generate(@budget.budget_json), id: "budget_budget_json" %>
    </div>
  </div>
  ...
<% end %>

FWIW, the form looks like this:

Screenshot of TEXTAREA as rendered in the form

As you can see here (from pgAdmin), the first record (id: 166) is clean and usable. It has only just been created. The second record (id: 167) is unusable as has been stored as a string instead:

Screenshot of database showing two records

What am I missing?



Solution 1:[1]

Jeez. How often does writing the whole thing out help you think more clearly! I have the answer: in the UPDATE action I wasn't actually using the JSON.parsed version of the parameters. By changing

if @budget.update(budget_params)

to

if @budget.save(budget_params)

everything works as it should.

Having said that, if anyone is able to suggest a more elegant way of coding these (admin interface) round trips for JSON data, I'll be happy to hear your suggestions.

Solution 2:[2]

Ran into something similar, but managed to get around by modifying the params before passing them to the update method. So in your case something like:

def update
  params = budget_params
  params[:budget_json] = JSON.parse(params[:budget_json])

  if @budget.update(params)
    redirect_to admin_budgets_path, notice: "Budget successfully updated."  
  else
    render :edit
  end
end

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 MSC
Solution 2 sn3p