From ded0557ad50582121e5b5f50c9ce43882fc92de4 Mon Sep 17 00:00:00 2001 From: Nathan Jones Date: Sun, 28 Sep 2025 22:42:43 -0500 Subject: [PATCH] Parse json field params Json fields in resource forms submit string params, parse to json before persisting. --- Gemfile | 4 +- app/controllers/madmin/resource_controller.rb | 34 ++++++++--- test/integration/json_fields_test.rb | 60 +++++++++++++++++++ 3 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 test/integration/json_fields_test.rb diff --git a/Gemfile b/Gemfile index f026a798..edd6e719 100644 --- a/Gemfile +++ b/Gemfile @@ -21,10 +21,10 @@ gem "standardrb" gem "web-console", group: :development # Databases to test against -gem "pg" gem "mysql2" +gem "pg" gem "sqlite3" gem "propshaft" -gem "turbo-rails" gem "stimulus-rails" +gem "turbo-rails" diff --git a/app/controllers/madmin/resource_controller.rb b/app/controllers/madmin/resource_controller.rb index bcd46ecb..3f8de687 100644 --- a/app/controllers/madmin/resource_controller.rb +++ b/app/controllers/madmin/resource_controller.rb @@ -2,7 +2,7 @@ module Madmin class ResourceController < Madmin::ApplicationController include SortHelper - before_action :set_record, except: [:index, :new, :create] + before_action :set_record, except: %i[index new create] # Assign current_user for paper_trail gem before_action :set_paper_trail_whodunnit, if: -> { respond_to?(:set_paper_trail_whodunnit, true) } @@ -12,9 +12,9 @@ def index respond_to do |format| format.html - format.json { + format.json do render json: @records.map { |r| {name: @resource.display_name(r), id: r.id} } - } + end end end @@ -77,9 +77,16 @@ def valid_scope end def resource_params - params.require(resource.param_key) + permitted_params = params.require(resource.param_key) .permit(*resource.permitted_params) .transform_values { |v| change_polymorphic(v) } + + # Transform JSON fields + permitted_params.each do |key, value| + permitted_params[key] = parse_json_attribute(key, value) + end + + permitted_params end def new_resource_params @@ -91,11 +98,20 @@ def new_resource_params def change_polymorphic(data) return data unless data.is_a?(ActionController::Parameters) && data[:type] - if data[:type] == "polymorphic" - GlobalID::Locator.locate(data[:value]) - else - raise "Unrecognised param data: #{data.inspect}" - end + raise "Unrecognised param data: #{data.inspect}" unless data[:type] == "polymorphic" + + GlobalID::Locator.locate(data[:value]) + end + + def parse_json_attribute(key, value) + return value unless json_attribute?(key) + + JSON.parse(value) + end + + def json_attribute?(key) + attribute = resource.get_attribute(key.to_sym) + attribute&.type == :json end def search_term diff --git a/test/integration/json_fields_test.rb b/test/integration/json_fields_test.rb new file mode 100644 index 00000000..ae8b1a6f --- /dev/null +++ b/test/integration/json_fields_test.rb @@ -0,0 +1,60 @@ +require "test_helper" + +class JsonFieldsTest < ActionDispatch::IntegrationTest + test "can update model with JSON field" do + post = posts(:one) + json_metadata = '{"tags": ["ruby", "rails"], "priority": "high"}' + + put madmin_post_path(post), params: { + post: { + title: "Updated Post", + metadata: json_metadata + } + } + + assert_response :redirect + + post.reload + assert_equal "Updated Post", post.title + assert_equal({"tags" => %w[ruby rails], "priority" => "high"}, post.metadata) + assert_instance_of Hash, post.metadata + end + + test "handles invalid JSON gracefully in update" do + post = posts(:one) + invalid_json = '{"tags": ["ruby", "rails", "priority": "high"}' # Missing closing bracket + + put madmin_post_path(post), params: { + post: { + title: "Updated Post", + metadata: invalid_json + } + } + + assert_response :redirect + + post.reload + assert_equal "Updated Post", post.title + # Should keep the invalid JSON as a string since parsing failed + assert_equal invalid_json, post.metadata + end + + test "does not affect non-JSON fields during update" do + post = posts(:one) + + put madmin_post_path(post), params: { + post: { + title: "Simple Title", + metadata: '{"test": true}' + } + } + + assert_response :redirect + + post.reload + assert_equal "Simple Title", post.title + assert_instance_of String, post.title + assert_equal({"test" => true}, post.metadata) + assert_instance_of Hash, post.metadata + end +end