# frozen_string_literal: true require "togglefy/version" require "togglefy/engine" if defined?(Rails) require "togglefy/featureable" require "togglefy/assignable" require "togglefy/feature_assignable_manager" require "togglefy/feature_manager" require "togglefy/feature_query" require "togglefy/scoped_bulk_wrapper" require "togglefy/errors" # The Togglefy module provides a feature management system. # It includes methods for querying, creating, updating, toggling, and managing features. # It also provides a way to manage features for assignable objects. # # == Features # # The Togglefy module provides a variety of features, including: # # - Querying features by type, group, environment, tenant, and custom filters # - Creating, updating, and deleting features # - Managing features for Assignables # # For more detailed information on each method, # please refer to the {file:README.md README}, individual method documentation in this file or the usage documentation. # # == Usage # # Main usage for this always starts with the {Togglefy} module. # # Below are a few examples on how to use Togglefy: # # === Examples # # - +Togglefy.feature(:super_powers)+ # - +Togglefy.for_type(User)+ # - +Togglefy.for_group(group)+ # - +Togglefy.for_filters(filters: {group: :admin})+ # - +Togglefy.with_status(:active)+ # - +Togglefy.create(name: "Feature Name", identifier: :feature_name, description: "Feature description")+ # - +Togglefy.update(:feature_name, name: "Updated Feature Name")+ # - +Togglefy.destroy(:feature_name)+ # - +Togglefy.toggle(:feature_name)+ # - +Togglefy.inactive!(:feature_name)+ # - +Togglefy.for(assignable).enable(:feature_name)+ # - +Togglefy.for(assignable).has?(:feature_name)+ # - +Togglefy.mass_for(Assignable).bulk.enable(:feature_name)+ # - +Togglefy.mass_for(Assignable).bulk.enable(:feature_name, percentage: 35)+ # - +Togglefy.mass_for(Assignable).bulk.disable([:feature_name, :another_feature])+ # # == Aliases # # The following aliases are available for convenience: # # - +for_role+ is an alias for +for_group+ # - +without_role+ is an alias for +without_group+ # - +for_env+ is an alias for +for_environment+ # - +without_env+ is an alias for +without_environment+ # - +create_feature+ is an alias for +create+ # - +update_feature+ is an alias for +update+ # - +toggle_feature+ is an alias for +toggle+ # - +activate_feature+ is an alias for +active!+ # - +inactivate_feature+ is an alias for +inactive!+ # - +destroy_feature+ is an alias for +destroy+ # module Togglefy # Returns all features. # @return [Array] List of all features. def self.features FeatureQuery.new.features end # Finds a feature by its identifier. # @param identifier [String, Symbol] The unique identifier of the feature. # @return [Feature] The feature object. # @raise [Togglefy::FeatureNotFound] If the feature is not found by the identifier. def self.feature(identifier) FeatureQuery.new.feature(identifier) rescue ActiveRecord::RecordNotFound raise Togglefy::FeatureNotFound, "Couldn't find Togglefy::Feature with identifier '#{identifier}'" end # Queries features for a specific type. # @param klass [Class] The class type to filter features by. # @return [Array] List of features for the given type. def self.for_type(klass) FeatureQuery.new.for_type(klass) end # Queries features for a specific group. # @param group [String, Symbol] The group name to filter features by. # @return [Array] List of features for the given group. def self.for_group(group) FeatureQuery.new.for_group(group) end # Queries features without a group. # @return [Array] List of features without a group. def self.without_group FeatureQuery.new.without_group end # Queries features for a specific environment. # @param environment [String, Symbol] The environment name to filter features by. # @return [Array] List of features for the given environment. def self.for_environment(environment) FeatureQuery.new.for_environment(environment) end # Queries features without an environment. # @return [Array] List of features without an environment. def self.without_environment FeatureQuery.new.without_environment end # Queries features for a specific tenant. # @param tenant_id [String] The tenant ID to filter features by. # @return [Array] List of features for the given tenant. def self.for_tenant(tenant_id) FeatureQuery.new.for_tenant(tenant_id) end # Queries features without a tenant. # @return [Array] List of features without a tenant. def self.without_tenant FeatureQuery.new.without_tenant end # Queries features based on custom filters. # @param filters [Hash] A hash of filters to apply. # @return [Array] List of features matching the filters. def self.for_filters(filters: {}) FeatureQuery.new.for_filters(filters) end # Queries features by their status. # @param status [String, Symbol, Integer] The status to filter features by. # @return [Array] List of features with the given status. def self.with_status(status) FeatureQuery.new.with_status(status) end # Creates a new feature. # @note All parameters are optional, except for the name. If sent, it should be a keyword argument. # # @param name [String] The name of the feature. # @param identifier [Symbol, String, nil] The unique identifier for the feature. Optional, it can also be nil or blank # @param description [String] A description of the feature. # @param group [String, Symbol, nil] The group the feature belongs to. # @param environment [String, Symbol, nil] The environment the feature is for. # @param tenant_id [String] The tenant ID the feature is for. # @param status [String, Symbol, Integer] The status of the feature. # @return [Feature] The created feature. # @example # Togglefy.create(name: "New Feature", identifier: :new_feature, description: "A new feature") # Togglefy.create(name: "New Feature", identifier: nil, description: "A new feature", group: :admin) # Togglefy.create(name: "New Feature", description: "A new feature", environment: :production, tenant_id: "123abc") def self.create(**params) FeatureManager.new.create(**params) end # Updates an existing feature. # @note All parameters but the first (identifier) should be keyword arguments. # # @param identifier [Symbol, String] The unique identifier of the feature. # @param name [String] The name of the feature. # @param identifier [Symbol, String, nil] The unique identifier for the feature. Optional, it can also be nil or blank # @param description [String] A description of the feature. # @param group [String, Symbol, nil] The group the feature belongs to. # @param environment [String, Symbol, nil] The environment the feature is for. # @param tenant_id [String] The tenant ID the feature is for. # @param status [String, Symbol, Integer] The status of the feature. # @return [Feature] The updated feature. # @raise [Togglefy::FeatureNotFound] If the feature is not found by the identifier. # @example # Togglefy.update(:new_feature, name: "Updated Feature", description: "Updated feature description") # Togglefy.update(:new_feature, identifier: :updated_feature, group: :support) # Togglefy.update(:new_feature, environment: :staging, tenant_id: "abc123") def self.update(identifier, **params) FeatureManager.new(identifier).update(**params) rescue ActiveRecord::RecordNotFound raise Togglefy::FeatureNotFound, "Couldn't find Togglefy::Feature with identifier '#{identifier}'" end # Deletes a feature. # @param identifier [Symbol, String] The unique identifier of the feature. # @return [boolean] True if the feature was deleted, false otherwise. # @raise [Togglefy::FeatureNotFound] If the feature is not found by the identifier. def self.destroy(identifier) FeatureManager.new(identifier).destroy rescue ActiveRecord::RecordNotFound raise Togglefy::FeatureNotFound, "Couldn't find Togglefy::Feature with identifier '#{identifier}'" end # Toggles the status of a feature. # @param identifier [Symbol, String] The unique identifier of the feature. # @return [boolean] True if the feature was toggled, false otherwise. # @raise [Togglefy::FeatureNotFound] If the feature is not found by the identifier. def self.toggle(identifier) FeatureManager.new(identifier).toggle rescue ActiveRecord::RecordNotFound raise Togglefy::FeatureNotFound, "Couldn't find Togglefy::Feature with identifier '#{identifier}'" end # Activates a feature. # @param identifier [Symbol, String] The unique identifier of the feature. # @return [boolean] True if the feature was activated, false otherwise. # @raise [Togglefy::FeatureNotFound] If the feature is not found by the identifier. def self.active!(identifier) FeatureManager.new(identifier).active! rescue ActiveRecord::RecordNotFound raise Togglefy::FeatureNotFound, "Couldn't find Togglefy::Feature with identifier '#{identifier}'" end # Deactivates a feature. # @param identifier [Symbol, String] The unique identifier of the feature. # @return [boolean] True if the feature was inactivated, false otherwise. # @raise [Togglefy::FeatureNotFound] If the feature is not found by the identifier. def self.inactive!(identifier) FeatureManager.new(identifier).inactive! rescue ActiveRecord::RecordNotFound raise Togglefy::FeatureNotFound, "Couldn't find Togglefy::Feature with identifier '#{identifier}'" end # Manages features for a specific assignable object. # @param assignable [Object] The assignable object. # @return [FeatureAssignableManager] The manager for the assignable object. def self.for(assignable) FeatureAssignableManager.new(assignable) end # Provides bulk management for a specific class. # @param klass [Class] The class to manage features for. # @return [ScopedBulkWrapper] The bulk wrapper for the class. def self.mass_for(klass) Togglefy::ScopedBulkWrapper.new(klass) end class << self # Aliases for group-related Features. alias for_role for_group alias without_role without_group # Aliases for environment-related Features. alias for_env for_environment alias without_env without_environment # Aliases for feature management Features. alias create_feature create alias update_feature update alias toggle_feature toggle alias activate_feature active! alias inactivate_feature inactive! alias destroy_feature destroy end end