From 3c762aff451141fa221efaa43c9c591ed87bf7cd Mon Sep 17 00:00:00 2001 From: co-marat-k Date: Sun, 31 Aug 2025 21:40:09 +0300 Subject: [PATCH 01/10] add required option --- examples/usual/example1/main_feature.rb | 1 + lib/featury/actions/service/factory.rb | 6 +- lib/featury/resources/collection.rb | 4 ++ lib/featury/resources/resource.rb | 5 ++ .../usual/example1/main_feature_spec.rb | 72 ++++++++++++------- 5 files changed, 59 insertions(+), 29 deletions(-) diff --git a/examples/usual/example1/main_feature.rb b/examples/usual/example1/main_feature.rb index b3110ca..a8c9cb6 100644 --- a/examples/usual/example1/main_feature.rb +++ b/examples/usual/example1/main_feature.rb @@ -10,6 +10,7 @@ class MainFeature < Usual::Example1::Base resource :record, type: Record, nested: true resource :user, type: User, option: true + resource :thing, type: User, required: false, option: true condition ->(resources:) { resources.record.id == "123" } diff --git a/lib/featury/actions/service/factory.rb b/lib/featury/actions/service/factory.rb index cb5ec7a..9312983 100644 --- a/lib/featury/actions/service/factory.rb +++ b/lib/featury/actions/service/factory.rb @@ -26,7 +26,7 @@ def create_service_class # rubocop:disable Metrics/MethodLength, Metrics/AbcSize Class.new(Featury::Service::Builder) do collection_of_resources.each do |resource| - input resource.name, **resource.options + input resource.name, required: resource.required?, **resource.options end input :action, type: Featury::Actions::Action @@ -76,7 +76,7 @@ def conditions_are_true end def features_are_true - options = inputs.collection_of_resources.only_option.to_h do |resource| + options = inputs.collection_of_resources.only_option.only_required.to_h do |resource| [resource.name, inputs.public_send(resource.name)] end @@ -84,7 +84,7 @@ def features_are_true end def groups_are_true - arguments = inputs.collection_of_resources.only_nested.to_h do |resource| + arguments = inputs.collection_of_resources.only_nested.only_required.to_h do |resource| [resource.name, inputs.public_send(resource.name)] end diff --git a/lib/featury/resources/collection.rb b/lib/featury/resources/collection.rb index d1b8147..4fb531e 100644 --- a/lib/featury/resources/collection.rb +++ b/lib/featury/resources/collection.rb @@ -19,6 +19,10 @@ def only_option Collection.new(filter(&:option?)) end + def only_required + Collection.new(filter(&:required?)) + end + def names map(&:name) end diff --git a/lib/featury/resources/resource.rb b/lib/featury/resources/resource.rb index 1616f5d..bed3682 100644 --- a/lib/featury/resources/resource.rb +++ b/lib/featury/resources/resource.rb @@ -10,6 +10,7 @@ def initialize(name, **options) @nested = options.delete(:nested) || false @option = options.delete(:option) || false + @required = options.delete(:required) { true } @options = options end @@ -21,6 +22,10 @@ def nested? def option? @option end + + def required? + @required + end end end end diff --git a/spec/examples/usual/example1/main_feature_spec.rb b/spec/examples/usual/example1/main_feature_spec.rb index 50c83bf..4b0fad5 100644 --- a/spec/examples/usual/example1/main_feature_spec.rb +++ b/spec/examples/usual/example1/main_feature_spec.rb @@ -4,21 +4,25 @@ let(:arguments) do { record:, - user: + user:, + thing: } end let(:record) { Usual::Example1::MainFeature::Record.new(id: "123") } let(:user) { Usual::Example1::MainFeature::User.new(id: "456") } + let(:thing) { Usual::Example1::MainFeature::User.new(id: "789") } + + let(:expected_resources_for_options) { [user, thing] } shared_examples "expected successful behavior" do describe "#enabled?" do subject(:perform) { feature_class.enabled? } before do - allow(FeatureLib).to receive(:enabled?).with(:usual_example_1_a, user).and_call_original - allow(FeatureLib).to receive(:enabled?).with(:usual_example_1_b, user).and_call_original - allow(FeatureLib).to receive(:enabled?).with(:usual_example_1_c, user).and_call_original + allow(FeatureLib).to receive(:enabled?).with(:usual_example_1_a, *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:enabled?).with(:usual_example_1_b, *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:enabled?).with(:usual_example_1_c, *expected_resources_for_options).and_call_original allow(FeatureLib).to receive(:enabled?).with(:usual_example_1_d_i).and_call_original allow(FeatureLib).to receive(:enabled?).with(:usual_example_1_d_ii).and_call_original allow(FeatureLib).to receive(:enabled?).with(:usual_example_1_d_iii).and_call_original @@ -82,9 +86,9 @@ it :aggregate_failures do perform - expect(FeatureLib).to have_received(:enabled?).with(:usual_example_1_a, user).once - expect(FeatureLib).to have_received(:enabled?).with(:usual_example_1_b, user).once - expect(FeatureLib).to have_received(:enabled?).with(:usual_example_1_c, user).once + expect(FeatureLib).to have_received(:enabled?).with(:usual_example_1_a, *expected_resources_for_options).once + expect(FeatureLib).to have_received(:enabled?).with(:usual_example_1_b, *expected_resources_for_options).once + expect(FeatureLib).to have_received(:enabled?).with(:usual_example_1_c, *expected_resources_for_options).once expect(FeatureLib).to have_received(:enabled?).with(:usual_example_1_d_i).once expect(FeatureLib).to have_received(:enabled?).with(:usual_example_1_d_ii).once expect(FeatureLib).to have_received(:enabled?).with(:usual_example_1_d_iii).once @@ -176,9 +180,9 @@ subject(:perform) { feature_class.disabled? } before do - allow(FeatureLib).to receive(:disabled?).with(:usual_example_1_a, user).and_call_original - allow(FeatureLib).to receive(:disabled?).with(:usual_example_1_b, user).and_call_original - allow(FeatureLib).to receive(:disabled?).with(:usual_example_1_c, user).and_call_original + allow(FeatureLib).to receive(:disabled?).with(:usual_example_1_a, *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:disabled?).with(:usual_example_1_b, *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:disabled?).with(:usual_example_1_c, *expected_resources_for_options).and_call_original allow(FeatureLib).to receive(:disabled?).with(:usual_example_1_d_i).and_call_original allow(FeatureLib).to receive(:disabled?).with(:usual_example_1_d_ii).and_call_original allow(FeatureLib).to receive(:disabled?).with(:usual_example_1_d_iii).and_call_original @@ -242,9 +246,9 @@ it :aggregate_failures do perform - expect(FeatureLib).to have_received(:disabled?).with(:usual_example_1_a, user).once - expect(FeatureLib).to have_received(:disabled?).with(:usual_example_1_b, user).once - expect(FeatureLib).to have_received(:disabled?).with(:usual_example_1_c, user).once + expect(FeatureLib).to have_received(:disabled?).with(:usual_example_1_a, *expected_resources_for_options).once + expect(FeatureLib).to have_received(:disabled?).with(:usual_example_1_b, *expected_resources_for_options).once + expect(FeatureLib).to have_received(:disabled?).with(:usual_example_1_c, *expected_resources_for_options).once expect(FeatureLib).not_to have_received(:disabled?).with(:usual_example_1_d_i) expect(FeatureLib).not_to have_received(:disabled?).with(:usual_example_1_d_ii) expect(FeatureLib).not_to have_received(:disabled?).with(:usual_example_1_d_iii) @@ -332,9 +336,11 @@ subject(:perform) { feature_class.enable } before do - allow(FeatureLib).to receive(:enable).with(:usual_example_1_a, user).and_call_original - allow(FeatureLib).to receive(:enable).with(:usual_example_1_b, user).and_call_original - allow(FeatureLib).to receive(:enable).with(:usual_example_1_c, user).and_call_original + puts expected_resources_for_options + puts "-"*100 + allow(FeatureLib).to receive(:enable).with(:usual_example_1_a, *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:enable).with(:usual_example_1_b, *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:enable).with(:usual_example_1_c, *expected_resources_for_options).and_call_original allow(FeatureLib).to receive(:enable).with(:usual_example_1_d_i).and_call_original allow(FeatureLib).to receive(:enable).with(:usual_example_1_d_ii).and_call_original allow(FeatureLib).to receive(:enable).with(:usual_example_1_d_iii).and_call_original @@ -398,9 +404,9 @@ it :aggregate_failures do perform - expect(FeatureLib).to have_received(:enable).with(:usual_example_1_a, user).once - expect(FeatureLib).to have_received(:enable).with(:usual_example_1_b, user).once - expect(FeatureLib).to have_received(:enable).with(:usual_example_1_c, user).once + expect(FeatureLib).to have_received(:enable).with(:usual_example_1_a, *expected_resources_for_options).once + expect(FeatureLib).to have_received(:enable).with(:usual_example_1_b, *expected_resources_for_options).once + expect(FeatureLib).to have_received(:enable).with(:usual_example_1_c, *expected_resources_for_options).once expect(FeatureLib).to have_received(:enable).with(:usual_example_1_d_i).once expect(FeatureLib).to have_received(:enable).with(:usual_example_1_d_ii).once expect(FeatureLib).to have_received(:enable).with(:usual_example_1_d_iii).once @@ -489,9 +495,9 @@ subject(:perform) { feature_class.disable } before do - allow(FeatureLib).to receive(:disable).with(:usual_example_1_a, user).and_call_original - allow(FeatureLib).to receive(:disable).with(:usual_example_1_b, user).and_call_original - allow(FeatureLib).to receive(:disable).with(:usual_example_1_c, user).and_call_original + allow(FeatureLib).to receive(:disable).with(:usual_example_1_a, *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:disable).with(:usual_example_1_b, *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:disable).with(:usual_example_1_c, *expected_resources_for_options).and_call_original allow(FeatureLib).to receive(:disable).with(:usual_example_1_d_i).and_call_original allow(FeatureLib).to receive(:disable).with(:usual_example_1_d_ii).and_call_original allow(FeatureLib).to receive(:disable).with(:usual_example_1_d_iii).and_call_original @@ -555,9 +561,9 @@ it :aggregate_failures do perform - expect(FeatureLib).to have_received(:disable).with(:usual_example_1_a, user).once - expect(FeatureLib).to have_received(:disable).with(:usual_example_1_b, user).once - expect(FeatureLib).to have_received(:disable).with(:usual_example_1_c, user).once + expect(FeatureLib).to have_received(:disable).with(:usual_example_1_a, *expected_resources_for_options).once + expect(FeatureLib).to have_received(:disable).with(:usual_example_1_b, *expected_resources_for_options).once + expect(FeatureLib).to have_received(:disable).with(:usual_example_1_c, *expected_resources_for_options).once expect(FeatureLib).to have_received(:disable).with(:usual_example_1_d_i).once expect(FeatureLib).to have_received(:disable).with(:usual_example_1_d_ii).once expect(FeatureLib).to have_received(:disable).with(:usual_example_1_d_iii).once @@ -693,6 +699,19 @@ let(:feature_class) { described_class.with(**arguments) } it_behaves_like "expected successful behavior" + + context "when non required resources not passed" do + let(:arguments) do + { + record:, + user: + } + end + + let(:expected_resources_for_options) { [user] } + + it_behaves_like "expected successful behavior" + end end context "when `with` method is not used" do @@ -739,7 +758,8 @@ it do expect(perform.resources).to contain_exactly( :record, - :user + :user, + :thing ) end From cc2c6222e45a038c9dfe40f0f6ec69ccf893bedd Mon Sep 17 00:00:00 2001 From: co-marat-k Date: Mon, 1 Sep 2025 09:57:57 +0300 Subject: [PATCH 02/10] add to readme --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index fee34ab..b1e4a29 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,12 @@ If a resource needs to be conveyed as a feature flag option, utilize the `option resource :user, type: User, option: true ``` +To call flag without passing resource utilize `required: false` parameter (e.g. to manage global state) + +```ruby +resource :user, type: User, option: true, required: false +``` + To transfer a resource to a nested group, utilize the `nested` option: ```ruby From 452fbb14529278e2b1623d8bb285b3ab0c96257e Mon Sep 17 00:00:00 2001 From: Marat Karimov <93517698+MaratKarimov21@users.noreply.github.com> Date: Mon, 1 Sep 2025 09:18:31 -0700 Subject: [PATCH 03/10] Update README.md Co-authored-by: Anton --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b1e4a29..220bd20 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ If a resource needs to be conveyed as a feature flag option, utilize the `option resource :user, type: User, option: true ``` -To call flag without passing resource utilize `required: false` parameter (e.g. to manage global state) +To call a feature without passing a resource, use the `required: false` option (e.g., for managing the global state of the feature). ```ruby resource :user, type: User, option: true, required: false From f38bdcc78d1a77befa7e50a0b2bee34340aae169 Mon Sep 17 00:00:00 2001 From: co-marat-k Date: Mon, 1 Sep 2025 20:05:44 +0300 Subject: [PATCH 04/10] fix --- examples/usual/example1/main_feature.rb | 3 ++- lib/featury/actions/service/factory.rb | 8 ++++---- lib/featury/resources/collection.rb | 4 ---- lib/featury/resources/resource.rb | 4 ---- spec/examples/usual/example1/main_feature_spec.rb | 2 +- 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/examples/usual/example1/main_feature.rb b/examples/usual/example1/main_feature.rb index a8c9cb6..123a6a2 100644 --- a/examples/usual/example1/main_feature.rb +++ b/examples/usual/example1/main_feature.rb @@ -5,12 +5,13 @@ module Example1 class MainFeature < Usual::Example1::Base Record = Struct.new(:id, keyword_init: true) User = Struct.new(:id, keyword_init: true) + Thing = Struct.new(:id, keyword_init: true) prefix :usual_example_1 resource :record, type: Record, nested: true resource :user, type: User, option: true - resource :thing, type: User, required: false, option: true + resource :thing, type: Thing, required: false, option: true condition ->(resources:) { resources.record.id == "123" } diff --git a/lib/featury/actions/service/factory.rb b/lib/featury/actions/service/factory.rb index 9312983..37a8048 100644 --- a/lib/featury/actions/service/factory.rb +++ b/lib/featury/actions/service/factory.rb @@ -76,17 +76,17 @@ def conditions_are_true end def features_are_true - options = inputs.collection_of_resources.only_option.only_required.to_h do |resource| + options = inputs.collection_of_resources.only_option.to_h do |resource| [resource.name, inputs.public_send(resource.name)] - end + end.compact inputs.action.block.call(features: inputs.collection_of_features.names, **options) end def groups_are_true - arguments = inputs.collection_of_resources.only_nested.only_required.to_h do |resource| + arguments = inputs.collection_of_resources.only_nested.to_h do |resource| [resource.name, inputs.public_send(resource.name)] - end + end.compact inputs.collection_of_groups.all? do |group| group.group_class.public_send(inputs.action.name, **arguments) diff --git a/lib/featury/resources/collection.rb b/lib/featury/resources/collection.rb index 4fb531e..d1b8147 100644 --- a/lib/featury/resources/collection.rb +++ b/lib/featury/resources/collection.rb @@ -19,10 +19,6 @@ def only_option Collection.new(filter(&:option?)) end - def only_required - Collection.new(filter(&:required?)) - end - def names map(&:name) end diff --git a/lib/featury/resources/resource.rb b/lib/featury/resources/resource.rb index bed3682..ea23e97 100644 --- a/lib/featury/resources/resource.rb +++ b/lib/featury/resources/resource.rb @@ -22,10 +22,6 @@ def nested? def option? @option end - - def required? - @required - end end end end diff --git a/spec/examples/usual/example1/main_feature_spec.rb b/spec/examples/usual/example1/main_feature_spec.rb index 4b0fad5..6bcb85b 100644 --- a/spec/examples/usual/example1/main_feature_spec.rb +++ b/spec/examples/usual/example1/main_feature_spec.rb @@ -11,7 +11,7 @@ let(:record) { Usual::Example1::MainFeature::Record.new(id: "123") } let(:user) { Usual::Example1::MainFeature::User.new(id: "456") } - let(:thing) { Usual::Example1::MainFeature::User.new(id: "789") } + let(:thing) { Usual::Example1::MainFeature::Thing.new(id: "789") } let(:expected_resources_for_options) { [user, thing] } From f4fed3d85895876e1de09a0778b0f389be4e051f Mon Sep 17 00:00:00 2001 From: co-marat-k Date: Tue, 2 Sep 2025 11:19:04 +0300 Subject: [PATCH 05/10] revert required --- lib/featury/resources/resource.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/featury/resources/resource.rb b/lib/featury/resources/resource.rb index ea23e97..bed3682 100644 --- a/lib/featury/resources/resource.rb +++ b/lib/featury/resources/resource.rb @@ -22,6 +22,10 @@ def nested? def option? @option end + + def required? + @required + end end end end From 08f3e8182fec8a76bd1e2f4d5247eb8a94697b31 Mon Sep 17 00:00:00 2001 From: co-marat-k Date: Wed, 3 Sep 2025 09:46:56 +0300 Subject: [PATCH 06/10] nested: true, required: false --- examples/usual/example1/d_feature.rb | 3 +- examples/usual/example1/e_feature.rb | 3 +- examples/usual/example1/main_feature.rb | 5 +- lib/featury/actions/service/factory.rb | 2 +- .../usual/example1/main_feature_spec.rb | 61 ++++++++++++------- 5 files changed, 46 insertions(+), 28 deletions(-) diff --git a/examples/usual/example1/d_feature.rb b/examples/usual/example1/d_feature.rb index ab7374a..76c58aa 100644 --- a/examples/usual/example1/d_feature.rb +++ b/examples/usual/example1/d_feature.rb @@ -7,8 +7,9 @@ class DFeature < Usual::Example1::Base # prefix :usual_example_1_d resource :record, type: Usual::Example1::MainFeature::Record + resource :thing_b, type: Usual::Example1::MainFeature::Thing, required: false - condition ->(resources:) { resources.record.id == "123" } + condition ->(resources:) { resources.record.id == "111" } # full » :usual_example_1_d_i feature :i, description: "D I feature" diff --git a/examples/usual/example1/e_feature.rb b/examples/usual/example1/e_feature.rb index f809cda..5e886d0 100644 --- a/examples/usual/example1/e_feature.rb +++ b/examples/usual/example1/e_feature.rb @@ -6,8 +6,9 @@ class EFeature < Usual::Example1::Base prefix :usual_example_1_e resource :record, type: Usual::Example1::MainFeature::Record + resource :thing_b, type: Usual::Example1::MainFeature::Thing, required: false - condition ->(resources:) { resources.record.id == "123" } + condition ->(resources:) { resources.record.id == "111" } # full » :usual_example_1_e_i feature :i, description: "E I feature" diff --git a/examples/usual/example1/main_feature.rb b/examples/usual/example1/main_feature.rb index 123a6a2..0350c44 100644 --- a/examples/usual/example1/main_feature.rb +++ b/examples/usual/example1/main_feature.rb @@ -11,9 +11,10 @@ class MainFeature < Usual::Example1::Base resource :record, type: Record, nested: true resource :user, type: User, option: true - resource :thing, type: Thing, required: false, option: true + resource :thing_a, type: Thing, option: true, required: false + resource :thing_b, type: Thing, nested: true, required: false - condition ->(resources:) { resources.record.id == "123" } + condition ->(resources:) { resources.record.id == "111" } # full » :usual_example_1_a feature :a, description: "A feature" diff --git a/lib/featury/actions/service/factory.rb b/lib/featury/actions/service/factory.rb index 37a8048..6468071 100644 --- a/lib/featury/actions/service/factory.rb +++ b/lib/featury/actions/service/factory.rb @@ -83,7 +83,7 @@ def features_are_true inputs.action.block.call(features: inputs.collection_of_features.names, **options) end - def groups_are_true + def groups_are_true # rubocop:disable Metrics/AbcSize arguments = inputs.collection_of_resources.only_nested.to_h do |resource| [resource.name, inputs.public_send(resource.name)] end.compact diff --git a/spec/examples/usual/example1/main_feature_spec.rb b/spec/examples/usual/example1/main_feature_spec.rb index 6bcb85b..31c296c 100644 --- a/spec/examples/usual/example1/main_feature_spec.rb +++ b/spec/examples/usual/example1/main_feature_spec.rb @@ -5,24 +5,29 @@ { record:, user:, - thing: + thing_a:, + thing_b: } end - let(:record) { Usual::Example1::MainFeature::Record.new(id: "123") } - let(:user) { Usual::Example1::MainFeature::User.new(id: "456") } - let(:thing) { Usual::Example1::MainFeature::Thing.new(id: "789") } - - let(:expected_resources_for_options) { [user, thing] } + let(:record) { Usual::Example1::MainFeature::Record.new(id: "111") } + let(:user) { Usual::Example1::MainFeature::User.new(id: "222") } + let(:thing_a) { Usual::Example1::MainFeature::Thing.new(id: "333") } + let(:thing_b) { Usual::Example1::MainFeature::Thing.new(id: "444") } + + let(:expected_resources_for_options) { [user, thing_a] } shared_examples "expected successful behavior" do describe "#enabled?" do subject(:perform) { feature_class.enabled? } before do - allow(FeatureLib).to receive(:enabled?).with(:usual_example_1_a, *expected_resources_for_options).and_call_original - allow(FeatureLib).to receive(:enabled?).with(:usual_example_1_b, *expected_resources_for_options).and_call_original - allow(FeatureLib).to receive(:enabled?).with(:usual_example_1_c, *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:enabled?).with(:usual_example_1_a, + *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:enabled?).with(:usual_example_1_b, + *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:enabled?).with(:usual_example_1_c, + *expected_resources_for_options).and_call_original allow(FeatureLib).to receive(:enabled?).with(:usual_example_1_d_i).and_call_original allow(FeatureLib).to receive(:enabled?).with(:usual_example_1_d_ii).and_call_original allow(FeatureLib).to receive(:enabled?).with(:usual_example_1_d_iii).and_call_original @@ -180,9 +185,12 @@ subject(:perform) { feature_class.disabled? } before do - allow(FeatureLib).to receive(:disabled?).with(:usual_example_1_a, *expected_resources_for_options).and_call_original - allow(FeatureLib).to receive(:disabled?).with(:usual_example_1_b, *expected_resources_for_options).and_call_original - allow(FeatureLib).to receive(:disabled?).with(:usual_example_1_c, *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:disabled?).with(:usual_example_1_a, + *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:disabled?).with(:usual_example_1_b, + *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:disabled?).with(:usual_example_1_c, + *expected_resources_for_options).and_call_original allow(FeatureLib).to receive(:disabled?).with(:usual_example_1_d_i).and_call_original allow(FeatureLib).to receive(:disabled?).with(:usual_example_1_d_ii).and_call_original allow(FeatureLib).to receive(:disabled?).with(:usual_example_1_d_iii).and_call_original @@ -336,11 +344,12 @@ subject(:perform) { feature_class.enable } before do - puts expected_resources_for_options - puts "-"*100 - allow(FeatureLib).to receive(:enable).with(:usual_example_1_a, *expected_resources_for_options).and_call_original - allow(FeatureLib).to receive(:enable).with(:usual_example_1_b, *expected_resources_for_options).and_call_original - allow(FeatureLib).to receive(:enable).with(:usual_example_1_c, *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:enable).with(:usual_example_1_a, + *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:enable).with(:usual_example_1_b, + *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:enable).with(:usual_example_1_c, + *expected_resources_for_options).and_call_original allow(FeatureLib).to receive(:enable).with(:usual_example_1_d_i).and_call_original allow(FeatureLib).to receive(:enable).with(:usual_example_1_d_ii).and_call_original allow(FeatureLib).to receive(:enable).with(:usual_example_1_d_iii).and_call_original @@ -495,9 +504,12 @@ subject(:perform) { feature_class.disable } before do - allow(FeatureLib).to receive(:disable).with(:usual_example_1_a, *expected_resources_for_options).and_call_original - allow(FeatureLib).to receive(:disable).with(:usual_example_1_b, *expected_resources_for_options).and_call_original - allow(FeatureLib).to receive(:disable).with(:usual_example_1_c, *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:disable).with(:usual_example_1_a, + *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:disable).with(:usual_example_1_b, + *expected_resources_for_options).and_call_original + allow(FeatureLib).to receive(:disable).with(:usual_example_1_c, + *expected_resources_for_options).and_call_original allow(FeatureLib).to receive(:disable).with(:usual_example_1_d_i).and_call_original allow(FeatureLib).to receive(:disable).with(:usual_example_1_d_ii).and_call_original allow(FeatureLib).to receive(:disable).with(:usual_example_1_d_iii).and_call_original @@ -698,9 +710,11 @@ context "when `with` method is used" do let(:feature_class) { described_class.with(**arguments) } - it_behaves_like "expected successful behavior" + context "when optional resources are passed" do + it_behaves_like "expected successful behavior" + end - context "when non required resources not passed" do + context "when optional resources are not passed" do let(:arguments) do { record:, @@ -759,7 +773,8 @@ expect(perform.resources).to contain_exactly( :record, :user, - :thing + :thing_a, + :thing_b ) end From aef5c5d8c87b1c389d38d1578847452a5d20413f Mon Sep 17 00:00:00 2001 From: co-marat-k Date: Wed, 3 Sep 2025 09:51:35 +0300 Subject: [PATCH 07/10] compact_blank --- lib/featury/actions/service/factory.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/featury/actions/service/factory.rb b/lib/featury/actions/service/factory.rb index 6468071..21ae59a 100644 --- a/lib/featury/actions/service/factory.rb +++ b/lib/featury/actions/service/factory.rb @@ -78,7 +78,7 @@ def conditions_are_true def features_are_true options = inputs.collection_of_resources.only_option.to_h do |resource| [resource.name, inputs.public_send(resource.name)] - end.compact + end.compact_blank inputs.action.block.call(features: inputs.collection_of_features.names, **options) end @@ -86,7 +86,7 @@ def features_are_true def groups_are_true # rubocop:disable Metrics/AbcSize arguments = inputs.collection_of_resources.only_nested.to_h do |resource| [resource.name, inputs.public_send(resource.name)] - end.compact + end.compact_blank inputs.collection_of_groups.all? do |group| group.group_class.public_send(inputs.action.name, **arguments) From 8cbb5f2136980d647c63a8fea29f7d12b76038f3 Mon Sep 17 00:00:00 2001 From: co-marat-k Date: Wed, 3 Sep 2025 11:27:31 +0300 Subject: [PATCH 08/10] comment --- examples/usual/example1/d_feature.rb | 4 +-- examples/usual/example1/e_feature.rb | 4 +-- examples/usual/example1/main_feature.rb | 6 ++-- lib/featury/actions/service/factory.rb | 12 ++++--- .../usual/example1/main_feature_spec.rb | 33 ++++++++++++++----- 5 files changed, 39 insertions(+), 20 deletions(-) diff --git a/examples/usual/example1/d_feature.rb b/examples/usual/example1/d_feature.rb index 76c58aa..10e037f 100644 --- a/examples/usual/example1/d_feature.rb +++ b/examples/usual/example1/d_feature.rb @@ -7,9 +7,9 @@ class DFeature < Usual::Example1::Base # prefix :usual_example_1_d resource :record, type: Usual::Example1::MainFeature::Record - resource :thing_b, type: Usual::Example1::MainFeature::Thing, required: false + resource :comment, type: String, required: false - condition ->(resources:) { resources.record.id == "111" } + condition ->(resources:) { resources.record.id == "123" } # full » :usual_example_1_d_i feature :i, description: "D I feature" diff --git a/examples/usual/example1/e_feature.rb b/examples/usual/example1/e_feature.rb index 5e886d0..42d350c 100644 --- a/examples/usual/example1/e_feature.rb +++ b/examples/usual/example1/e_feature.rb @@ -6,9 +6,9 @@ class EFeature < Usual::Example1::Base prefix :usual_example_1_e resource :record, type: Usual::Example1::MainFeature::Record - resource :thing_b, type: Usual::Example1::MainFeature::Thing, required: false + resource :comment, type: String, required: false - condition ->(resources:) { resources.record.id == "111" } + condition ->(resources:) { resources.record.id == "123" } # full » :usual_example_1_e_i feature :i, description: "E I feature" diff --git a/examples/usual/example1/main_feature.rb b/examples/usual/example1/main_feature.rb index 0350c44..87d0d5b 100644 --- a/examples/usual/example1/main_feature.rb +++ b/examples/usual/example1/main_feature.rb @@ -11,10 +11,10 @@ class MainFeature < Usual::Example1::Base resource :record, type: Record, nested: true resource :user, type: User, option: true - resource :thing_a, type: Thing, option: true, required: false - resource :thing_b, type: Thing, nested: true, required: false + resource :thing, type: Thing, option: true, required: false + resource :comment, type: String, nested: true, required: false - condition ->(resources:) { resources.record.id == "111" } + condition ->(resources:) { resources.record.id == "123" } # full » :usual_example_1_a feature :a, description: "A feature" diff --git a/lib/featury/actions/service/factory.rb b/lib/featury/actions/service/factory.rb index 21ae59a..fdf5dae 100644 --- a/lib/featury/actions/service/factory.rb +++ b/lib/featury/actions/service/factory.rb @@ -21,7 +21,7 @@ def create @model_class.const_set(Builder::SERVICE_CLASS_NAME, class_sample) end - def create_service_class # rubocop:disable Metrics/MethodLength, Metrics/AbcSize,Metrics/CyclomaticComplexity + def create_service_class # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity collection_of_resources = @collection_of_resources Class.new(Featury::Service::Builder) do @@ -75,10 +75,12 @@ def conditions_are_true end end - def features_are_true + def features_are_true # rubocop:disable Metrics/AbcSize options = inputs.collection_of_resources.only_option.to_h do |resource| [resource.name, inputs.public_send(resource.name)] - end.compact_blank + end + + options = options.reject { |_, v| v.blank? } inputs.action.block.call(features: inputs.collection_of_features.names, **options) end @@ -86,7 +88,9 @@ def features_are_true def groups_are_true # rubocop:disable Metrics/AbcSize arguments = inputs.collection_of_resources.only_nested.to_h do |resource| [resource.name, inputs.public_send(resource.name)] - end.compact_blank + end + + arguments = arguments.reject { |_, v| v.blank? } inputs.collection_of_groups.all? do |group| group.group_class.public_send(inputs.action.name, **arguments) diff --git a/spec/examples/usual/example1/main_feature_spec.rb b/spec/examples/usual/example1/main_feature_spec.rb index 31c296c..584a3d8 100644 --- a/spec/examples/usual/example1/main_feature_spec.rb +++ b/spec/examples/usual/example1/main_feature_spec.rb @@ -5,17 +5,17 @@ { record:, user:, - thing_a:, - thing_b: + thing:, + comment: } end - let(:record) { Usual::Example1::MainFeature::Record.new(id: "111") } - let(:user) { Usual::Example1::MainFeature::User.new(id: "222") } - let(:thing_a) { Usual::Example1::MainFeature::Thing.new(id: "333") } - let(:thing_b) { Usual::Example1::MainFeature::Thing.new(id: "444") } + let(:record) { Usual::Example1::MainFeature::Record.new(id: "123") } + let(:user) { Usual::Example1::MainFeature::User.new(id: "456") } + let(:thing) { Usual::Example1::MainFeature::Thing.new(id: "789") } + let(:comment) { "A comment" } - let(:expected_resources_for_options) { [user, thing_a] } + let(:expected_resources_for_options) { [user, thing] } shared_examples "expected successful behavior" do describe "#enabled?" do @@ -726,6 +726,21 @@ it_behaves_like "expected successful behavior" end + + context "when optional resources passed with blank values" do + let(:arguments) do + { + record:, + user:, + thing: nil, + comment: "" + } + end + + let(:expected_resources_for_options) { [user] } + + it_behaves_like "expected successful behavior" + end end context "when `with` method is not used" do @@ -773,8 +788,8 @@ expect(perform.resources).to contain_exactly( :record, :user, - :thing_a, - :thing_b + :thing, + :comment ) end From 6943755cafdbe0fcaf0efd07356bd761403e1ab9 Mon Sep 17 00:00:00 2001 From: co-marat-k Date: Wed, 3 Sep 2025 11:28:43 +0300 Subject: [PATCH 09/10] fix review comment --- lib/featury/resources/resource.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/featury/resources/resource.rb b/lib/featury/resources/resource.rb index bed3682..57e57fb 100644 --- a/lib/featury/resources/resource.rb +++ b/lib/featury/resources/resource.rb @@ -8,8 +8,8 @@ class Resource def initialize(name, **options) @name = name - @nested = options.delete(:nested) || false - @option = options.delete(:option) || false + @nested = options.delete(:nested) { false } + @option = options.delete(:option) { false } @required = options.delete(:required) { true } @options = options From 708660ccce1d294649587c08cf50a68c21c7d43e Mon Sep 17 00:00:00 2001 From: co-marat-k Date: Wed, 3 Sep 2025 12:11:28 +0300 Subject: [PATCH 10/10] cosmetic --- lib/featury/actions/service/factory.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/featury/actions/service/factory.rb b/lib/featury/actions/service/factory.rb index fdf5dae..8d51e43 100644 --- a/lib/featury/actions/service/factory.rb +++ b/lib/featury/actions/service/factory.rb @@ -21,7 +21,7 @@ def create @model_class.const_set(Builder::SERVICE_CLASS_NAME, class_sample) end - def create_service_class # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity + def create_service_class # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity collection_of_resources = @collection_of_resources Class.new(Featury::Service::Builder) do