jsonruby-on-railstagify

Tagify with Rails 7 combining JSON tags after submit


I'm using @yairEO/tagify on my Rails 7 app. I have it almost working. It will save to the database as valid JSON. If I submit the form using tagify with 3 tags, for example tag1, tag2, tag3, after I go back to the form to edit, tagify shows these 3 separate tags combined into one single tag.

From the database:

[
  {
    "value":"tag1"
  },
  {
    "value":"tag2"
  },
  {
    "value":"tag3"
  }
]

From irb recipe.tags:

[
  {
    "value"=>"tag1"
  },
  {
    "value"=>"tag2"
  },
  {
    "value"=>"tag3"
  }
]

Before submitting: before

After submitting: after

Things quickly get out of control when adding to the bad response. Before submitting: enter image description here

After submitting: enter image description here

application.js

import Tagify from "@yaireo/tagify"

document.addEventListener('turbo:load', (event) => {
  new Tagify(document.querySelector('#recipe_tags'));
});

recipes_controller.rb

def update
  @recipe.update(tags: JSON.parse(params[:recipe][:tags]))

  if @recipe.update(recipe_params)
    redirect_to @recipe
  else
    render :edit, status: :unprocessable_entity
  end
end
def recipe_params
  params[:recipe][:tags] = JSON.parse(params[:recipe][:tags])
  params.require(:recipe).permit(
    :title, :subtitle, :tags
  )
end

edit.html.erb

<div>
  <%= form.label :tags %><br>
  <%= form.text_field :tags %>
  <% recipe.errors.full_messages_for(:tags).each do |message| %>
    <div><%= message %></div>
  <% end %>
</div>

schema.rb

  create_table "recipes", force: :cascade do |t|
    t.string "title"
    t.string "subtitle"
    t.jsonb "tags", default: {}, null: false
    t.index ["user_id"], name: "index_recipes_on_user_id"
  end

Solution

  • The problem was Rails was sending back a Hash, but the form wanted JSON.

    To fix this, I converted the data brought into the form to json:

    <%= form.label tags %>
    <%= form.text_field(tags, value: recipe[tags].to_json) %>
    

    Also, to DRY this up, I went ahead and removed these from the recipes_controller.rb:

    @recipe.update(tags: JSON.parse(params[:recipe][:tags]))

    and

    params[:recipe][:tags] = JSON.parse(params[:recipe][:tags])

    Then added this to the recipe model:

    protected
    
    def convert_to_json
      self.tags = JSON.parse(tags) unless tags.empty?
    end
    

    but we'd need to make sure it's valid JSON before converting, otherwise we end up with the same mess we had in the first place:

    private
    
    def valid_json?(value)
      result = JSON.parse(value)
    
      result.is_a?(Hash) || result.is_a?(Array)
    rescue JSON::ParserError, TypeError
      false
    end
    

    then convert_to_json becomes ... unless tags.empty? || !valid_json?(tags)

    Seems to be working properly now! If anyone has any tips on how to make this even cleaner/faster/better, let us know. Thanks