diff --git a/.rubocop.yml b/.rubocop.yml index b0d23ab..3cba502 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -26,3 +26,6 @@ RSpec/ExampleLength: # ========= Metrics ========= Metrics/ClassLength: Max: 150 + +Metrics/MethodLength: + Max: 20 diff --git a/CHANGELOG.md b/CHANGELOG.md index 27f96a9..715cb5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,21 +1,5 @@ # Changelog -## [0.4.1] - 2025-09-09 - -## What's Changed -* Update attributes method signatures to use optional keyword arguments… by @Syati in https://github.com/Syati/structured_params/pull/8 - - -**Full Changelog**: https://github.com/Syati/structured_params/compare/v0.4.0...v0.4.1 - -## [0.4.0] - 2025-09-08 - -## What's Changed -* Add compact option to attributes and serialize_structured_value methods by @Syati in https://github.com/Syati/structured_params/pull/7 - - -**Full Changelog**: https://github.com/Syati/structured_params/compare/v0.3.0...v0.4.0 - ## [0.3.0] - 2025-09-06 ## What's Changed diff --git a/lib/structured_params/params.rb b/lib/structured_params/params.rb index 1441a8f..834e9f1 100644 --- a/lib/structured_params/params.rb +++ b/lib/structured_params/params.rb @@ -73,18 +73,26 @@ def errors end # Convert structured objects to Hash and get attributes - #: (?symbolize: false, ?compact: bool) -> Hash[String, untyped] - #: (?symbolize: true, ?compact: bool) -> Hash[Symbol, untyped] - def attributes(symbolize: false, compact: false) + #: (?symbolize: false, ?compact_mode: :none | :nil_only | :all_blank) -> Hash[String, untyped] + #: (?symbolize: true, ?compact_mode: :none | :nil_only | :all_blank) -> Hash[Symbol, untyped] + def attributes(symbolize: false, compact_mode: :none) attrs = super() self.class.structured_attributes.each_key do |name| value = attrs[name.to_s] - attrs[name.to_s] = serialize_structured_value(value, compact: compact) + attrs[name.to_s] = serialize_structured_value(value, compact_mode: compact_mode) end result = symbolize ? attrs.deep_symbolize_keys : attrs - compact ? result.compact : result + + case compact_mode + when :all_blank + result.compact_blank + when :nil_only + result.compact + else + result + end end private @@ -152,14 +160,22 @@ def format_error_path(attr_name, index = nil) end # Serialize structured values - #: (bool, ?compact: bool) -> untyped - def serialize_structured_value(value, compact: false) + #: (untyped, ?compact_mode: :none | :nil_only | :all_blank) -> untyped + def serialize_structured_value(value, compact_mode: :none) case value when Array - result = value.map { |item| item.attributes(symbolize: false, compact: compact) } - compact ? result.compact : result + result = value.map { |item| item.attributes(symbolize: false, compact_mode: compact_mode) } + + case compact_mode + when :all_blank + result.compact_blank + when :nil_only + result.compact + else + result + end when StructuredParams::Params - value.attributes(symbolize: false, compact: compact) + value.attributes(symbolize: false, compact_mode: compact_mode) else value end diff --git a/sig/structured_params/params.rbs b/sig/structured_params/params.rbs index 5039774..e22342a 100644 --- a/sig/structured_params/params.rbs +++ b/sig/structured_params/params.rbs @@ -38,10 +38,10 @@ module StructuredParams def errors: () -> ::StructuredParams::Errors # Convert structured objects to Hash and get attributes - # : (?symbolize: false, ?compact: bool) -> Hash[String, untyped] - # : (?symbolize: true, ?compact: bool) -> Hash[Symbol, untyped] - def attributes: (?symbolize: false, ?compact: bool) -> Hash[String, untyped] - | (?symbolize: true, ?compact: bool) -> Hash[Symbol, untyped] + # : (?symbolize: false, ?compact_mode: :none | :nil_only | :all_blank) -> Hash[String, untyped] + # : (?symbolize: true, ?compact_mode: :none | :nil_only | :all_blank) -> Hash[Symbol, untyped] + def attributes: (?symbolize: false, ?compact_mode: :none | :nil_only | :all_blank) -> Hash[String, untyped] + | (?symbolize: true, ?compact_mode: :none | :nil_only | :all_blank) -> Hash[Symbol, untyped] private @@ -70,8 +70,8 @@ module StructuredParams def format_error_path: (Symbol, Integer?) -> String # Serialize structured values - # : (bool, ?compact: bool) -> untyped - def serialize_structured_value: (bool, ?compact: bool) -> untyped + # : (untyped, ?compact_mode: :none | :nil_only | :all_blank) -> untyped + def serialize_structured_value: (untyped, ?compact_mode: :none | :nil_only | :all_blank) -> untyped # Integrate structured parameter errors into parent errors # : (untyped, String) -> void diff --git a/spec/params_spec.rb b/spec/params_spec.rb index 23abc4a..b45cd98 100644 --- a/spec/params_spec.rb +++ b/spec/params_spec.rb @@ -161,11 +161,11 @@ describe '#attributes' do subject(:attributes) do - build(:user_parameter, **user_param_attributes).attributes(symbolize: symbolize, compact: compact) + build(:user_parameter, **user_param_attributes).attributes(symbolize: symbolize, compact_mode: compact_mode) end let(:user_param_attributes) { attributes_for(:user_parameter) } - let(:compact) { false } + let(:compact_mode) { :none } context 'when symbolize: false' do let(:symbolize) { false } @@ -179,9 +179,9 @@ it { is_expected.to eq user_param_attributes.deep_symbolize_keys } end - context 'with compact: true' do + context 'with compact_mode: :nil_only' do let(:symbolize) { false } - let(:compact) { true } + let(:compact_mode) { :nil_only } context 'with nil values' do let(:user_param_attributes) do @@ -224,9 +224,9 @@ end end - context 'with symbolize: true and compact: true' do + context 'with symbolize: true and compact_mode: :nil_only' do let(:symbolize) { true } - let(:compact) { true } + let(:compact_mode) { :nil_only } let(:user_param_attributes) do { @@ -260,6 +260,52 @@ end end end + + context 'with compact_mode: :all_blank' do + let(:symbolize) { false } + let(:compact_mode) { :all_blank } + + context 'with blank values' do + let(:user_param_attributes) do + { + name: 'Tanaka Taro', + email: '', + age: 30, + address: { + postal_code: '123-4567', + prefecture: nil, + city: 'Shibuya-ku', + street: '' + }, + hobbies: [ + { name: 'programming', level: 3, years_experience: nil }, + { name: '', level: 2, years_experience: 5 } + ], + tags: %w[Ruby Rails Web] + } + end + + let(:expected_result) do + { + 'name' => 'Tanaka Taro', + 'age' => 30, + 'address' => { + 'postal_code' => '123-4567', + 'city' => 'Shibuya-ku' + }, + 'hobbies' => [ + { 'name' => 'programming', 'level' => 3 }, + { 'level' => 2, 'years_experience' => 5 } + ], + 'tags' => %w[Ruby Rails Web] + } + end + + it 'removes blank values (nil, empty string, etc.) recursively' do + expect(attributes).to eq(expected_result) + end + end + end end describe 'edge cases' do