diff --git a/README.md b/README.md index fee34ab..220bd20 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 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 +``` + To transfer a resource to a nested group, utilize the `nested` option: ```ruby diff --git a/examples/usual/example1/d_feature.rb b/examples/usual/example1/d_feature.rb index ab7374a..10e037f 100644 --- a/examples/usual/example1/d_feature.rb +++ b/examples/usual/example1/d_feature.rb @@ -7,6 +7,7 @@ class DFeature < Usual::Example1::Base # prefix :usual_example_1_d resource :record, type: Usual::Example1::MainFeature::Record + resource :comment, type: String, required: false condition ->(resources:) { resources.record.id == "123" } diff --git a/examples/usual/example1/e_feature.rb b/examples/usual/example1/e_feature.rb index f809cda..42d350c 100644 --- a/examples/usual/example1/e_feature.rb +++ b/examples/usual/example1/e_feature.rb @@ -6,6 +6,7 @@ class EFeature < Usual::Example1::Base prefix :usual_example_1_e resource :record, type: Usual::Example1::MainFeature::Record + resource :comment, type: String, required: false condition ->(resources:) { resources.record.id == "123" } diff --git a/examples/usual/example1/main_feature.rb b/examples/usual/example1/main_feature.rb index b3110ca..87d0d5b 100644 --- a/examples/usual/example1/main_feature.rb +++ b/examples/usual/example1/main_feature.rb @@ -5,11 +5,14 @@ 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: Thing, option: true, required: false + resource :comment, type: String, nested: true, required: false condition ->(resources:) { resources.record.id == "123" } diff --git a/lib/featury/actions/service/factory.rb b/lib/featury/actions/service/factory.rb index cb5ec7a..8d51e43 100644 --- a/lib/featury/actions/service/factory.rb +++ b/lib/featury/actions/service/factory.rb @@ -21,12 +21,12 @@ 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 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 @@ -75,19 +75,23 @@ 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 + options = options.reject { |_, v| v.blank? } + 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 + arguments = arguments.reject { |_, v| v.blank? } + inputs.collection_of_groups.all? do |group| group.group_class.public_send(inputs.action.name, **arguments) end diff --git a/lib/featury/resources/resource.rb b/lib/featury/resources/resource.rb index 1616f5d..57e57fb 100644 --- a/lib/featury/resources/resource.rb +++ b/lib/featury/resources/resource.rb @@ -8,8 +8,9 @@ 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 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..584a3d8 100644 --- a/spec/examples/usual/example1/main_feature_spec.rb +++ b/spec/examples/usual/example1/main_feature_spec.rb @@ -4,21 +4,30 @@ let(:arguments) do { record:, - user: + user:, + thing:, + comment: } 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(:comment) { "A comment" } + + 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 +91,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 +185,12 @@ 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 +254,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 +344,12 @@ 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 + 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 +413,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 +504,12 @@ 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 +573,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 @@ -692,7 +710,37 @@ 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 optional resources are not passed" do + let(:arguments) do + { + record:, + user: + } + end + + let(:expected_resources_for_options) { [user] } + + 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 @@ -739,7 +787,9 @@ it do expect(perform.resources).to contain_exactly( :record, - :user + :user, + :thing, + :comment ) end