togglefy 1.1.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +115 -56
- data/Rakefile +12 -0
- data/app/models/togglefy/feature.rb +93 -12
- data/app/models/togglefy/feature_assignment.rb +15 -1
- data/lib/generators/togglefy/install_generator.rb +35 -4
- data/lib/generators/togglefy/templates/create_feature_assignments.rb +3 -2
- data/lib/generators/togglefy/templates/older_rails_create_feature_assignments.rb +14 -0
- data/lib/generators/togglefy/templates/older_rails_create_features.rb +18 -0
- data/lib/togglefy/assignable.rb +42 -11
- data/lib/togglefy/engine.rb +5 -1
- data/lib/togglefy/errors/assignables_not_found.rb +8 -1
- data/lib/togglefy/errors/bulk_toggle_failed.rb +12 -1
- data/lib/togglefy/errors/dependency_missing.rb +9 -1
- data/lib/togglefy/errors/error.rb +5 -1
- data/lib/togglefy/errors/feature_not_found.rb +14 -3
- data/lib/togglefy/errors/invalid_feature_attribute.rb +8 -1
- data/lib/togglefy/errors.rb +16 -0
- data/lib/togglefy/feature_assignable_manager.rb +23 -0
- data/lib/togglefy/feature_manager.rb +35 -2
- data/lib/togglefy/feature_query.rb +67 -13
- data/lib/togglefy/featureable.rb +6 -1
- data/lib/togglefy/scoped_bulk_wrapper.rb +11 -1
- data/lib/togglefy/services/bulk_toggler.rb +158 -66
- data/lib/togglefy/version.rb +2 -1
- data/lib/togglefy.rb +167 -22
- data/togglefy.gemspec +15 -5
- metadata +109 -6
- data/lib/togglefy/exceptions.rb +0 -5
- /data/{LICENSE.txt → LICENSE} +0 -0
@@ -1,108 +1,200 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Togglefy
|
4
|
+
# The BulkToggler class provides functionality to enable or disable features
|
5
|
+
# in bulk for assignables, such as users or accounts.
|
2
6
|
class BulkToggler
|
7
|
+
# List of allowed filters for assignables.
|
3
8
|
ALLOWED_ASSIGNABLE_FILTERS = %i[group role environment env tenant_id].freeze
|
4
9
|
|
10
|
+
# Initializes a new BulkToggler instance.
|
11
|
+
#
|
12
|
+
# @param klass [Class] The assignable class (e.g., User, Account).
|
5
13
|
def initialize(klass)
|
6
14
|
@klass = klass
|
7
15
|
end
|
8
16
|
|
17
|
+
# Enables features for assignables based on filters.
|
18
|
+
# @note All parameters but the first (identifiers) should be passed as keyword arguments.
|
19
|
+
#
|
20
|
+
# @param identifiers [Array<String>, String] The feature identifiers to enable.
|
21
|
+
# @param group [String] The group name to filter assignables by.
|
22
|
+
# @param role [String] The role name to filter assignables by.
|
23
|
+
# @param environment [String] The environment name to filter assignables by.
|
24
|
+
# @param env [String] The environment name to filter assignables by.
|
25
|
+
# @param tenant_id [String] The tenant_id to filter assignables by.
|
26
|
+
# @param percentage [Integer] The percentage of assignables to include.
|
9
27
|
def enable(identifiers, **filters)
|
10
28
|
toggle(:enable, identifiers, filters)
|
29
|
+
true
|
11
30
|
end
|
12
31
|
|
32
|
+
# Disables features for assignables based on filters.
|
33
|
+
# @note All parameters but the first (identifiers) should be passed as keyword arguments.
|
34
|
+
#
|
35
|
+
# @param identifiers [Array<String>, String] The feature identifiers to disable.
|
36
|
+
# @param group [String] The group name to filter assignables by.
|
37
|
+
# @param role [String] The role name to filter assignables by.
|
38
|
+
# @param environment [String] The environment name to filter assignables by.
|
39
|
+
# @param env [String] The environment name to filter assignables by.
|
40
|
+
# @param tenant_id [String] The tenant_id to filter assignables by.
|
41
|
+
# @param percentage [Integer] The percentage of assignables to include.
|
13
42
|
def disable(identifiers, **filters)
|
14
43
|
toggle(:disable, identifiers, filters)
|
44
|
+
true
|
15
45
|
end
|
16
46
|
|
17
47
|
private
|
18
48
|
|
19
49
|
attr_reader :klass
|
20
50
|
|
51
|
+
# Toggles features for assignables based on the action.
|
52
|
+
#
|
53
|
+
# @param action [Symbol] The action to perform (:enable or :disable).
|
54
|
+
# @param identifiers [Array<String>, String] The feature identifiers.
|
55
|
+
# @param filters [Hash] Additional filters for assignables.
|
21
56
|
def toggle(action, identifiers, filters)
|
22
57
|
identifiers = Array(identifiers)
|
23
|
-
features =
|
24
|
-
filters: {identifier: identifiers}.merge(build_scope_filters(filters))
|
25
|
-
).to_a
|
26
|
-
|
27
|
-
raise Togglefy::FeatureNotFound if features.empty?
|
58
|
+
features = get_features(identifiers, filters)
|
28
59
|
|
29
60
|
feature_ids = features.map(&:id)
|
30
61
|
|
31
|
-
assignables =
|
32
|
-
klass.without_features(feature_ids)
|
33
|
-
else
|
34
|
-
klass.with_features(feature_ids)
|
35
|
-
end
|
62
|
+
assignables = get_assignables(action, feature_ids)
|
36
63
|
|
37
|
-
raise Togglefy::AssignablesNotFound.new(klass, identifiers, filters) if assignables.empty?
|
38
|
-
|
39
64
|
assignables = sample_assignables(assignables, filters[:percentage]) if filters[:percentage]
|
40
65
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
feature_id: features.map(&:id)
|
45
|
-
).pluck(:assignable_id, :feature_id)
|
46
|
-
|
47
|
-
existing_lookup = Set.new(existing_assignments)
|
48
|
-
|
49
|
-
if action == :enable
|
50
|
-
rows = []
|
51
|
-
|
52
|
-
assignables.each do |assignable|
|
53
|
-
features.each do |feature|
|
54
|
-
key = [assignable.id, feature.id]
|
55
|
-
next if existing_lookup.include?(key)
|
56
|
-
|
57
|
-
rows << {
|
58
|
-
assignable_id: assignable.id,
|
59
|
-
assignable_type: assignable.class.name,
|
60
|
-
feature_id: feature.id
|
61
|
-
}
|
62
|
-
end
|
63
|
-
end
|
66
|
+
enable_flow(assignables, features, identifiers) if action == :enable
|
67
|
+
disable_flow(assignables, features, identifiers) if action == :disable
|
68
|
+
end
|
64
69
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
elsif action == :disable
|
74
|
-
ids_to_remove = []
|
75
|
-
assignables.each do |assignable|
|
76
|
-
features.each do |feature|
|
77
|
-
key = [assignable.id, feature.id]
|
78
|
-
ids_to_remove << key if existing_lookup.include?(key)
|
79
|
-
end
|
80
|
-
end
|
70
|
+
# Retrieves features based on identifiers and filters.
|
71
|
+
#
|
72
|
+
# @param identifiers [Array<String>] The feature identifiers.
|
73
|
+
# @param filters [Hash] Additional filters for features.
|
74
|
+
# @return [Array<Togglefy::Feature>] The matching features.
|
75
|
+
# @raise [Togglefy::FeatureNotFound] If no features are found.
|
76
|
+
def get_features(identifiers, filters)
|
77
|
+
features = Togglefy.for_filters(filters: { identifier: identifiers }.merge(build_scope_filters(filters))).to_a
|
81
78
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
assignable_id: ids_to_remove.map(&:first),
|
86
|
-
assignable_type: klass.name,
|
87
|
-
feature_id: ids_to_remove.map(&:last)
|
88
|
-
).delete_all
|
89
|
-
end
|
90
|
-
rescue => error
|
91
|
-
raise Togglefy::BulkToggleFailed.new(
|
92
|
-
"Bulk toggle disable failed for #{klass.name} with identifiers #{identifiers.inspect}",
|
93
|
-
error
|
94
|
-
)
|
95
|
-
end
|
96
|
-
end
|
79
|
+
raise Togglefy::FeatureNotFound if features.empty?
|
80
|
+
|
81
|
+
features
|
97
82
|
end
|
98
83
|
|
84
|
+
# Retrieves assignables based on the action and feature IDs.
|
85
|
+
#
|
86
|
+
# @param action [Symbol] The action to perform (:enable or :disable).
|
87
|
+
# @param feature_ids [Array<Integer>] The feature IDs.
|
88
|
+
# @return [Array<Assignable>] The matching assignables.
|
89
|
+
# @raise [Togglefy::AssignablesNotFound] If no assignables are found.
|
90
|
+
def get_assignables(action, feature_ids)
|
91
|
+
assignables = klass.without_features(feature_ids) if action == :enable
|
92
|
+
assignables = klass.with_features(feature_ids) if action == :disable
|
93
|
+
|
94
|
+
raise Togglefy::AssignablesNotFound, klass if assignables.empty?
|
95
|
+
|
96
|
+
assignables
|
97
|
+
end
|
98
|
+
|
99
|
+
# Builds scope filters for assignables.
|
100
|
+
#
|
101
|
+
# @param filters [Hash] The filters to process.
|
102
|
+
# @return [Hash] The processed filters.
|
99
103
|
def build_scope_filters(filters)
|
100
104
|
filters.slice(*ALLOWED_ASSIGNABLE_FILTERS).compact
|
101
105
|
end
|
102
106
|
|
107
|
+
# Samples assignables based on a percentage.
|
108
|
+
#
|
109
|
+
# @param assignables [Array<Assignable>] The assignables to sample.
|
110
|
+
# @param percentage [Float] The percentage of assignables to include.
|
111
|
+
# @return [Array<Assignable>] The sampled assignables.
|
103
112
|
def sample_assignables(assignables, percentage)
|
104
113
|
count = (assignables.size * percentage.to_f / 100).round
|
105
114
|
assignables.sample(count)
|
106
115
|
end
|
116
|
+
|
117
|
+
# Enables features for assignables.
|
118
|
+
#
|
119
|
+
# @param assignables [Array<Assignable>] The assignables to update.
|
120
|
+
# @param features [Array<Togglefy::Feature>] The features to enable.
|
121
|
+
# @param identifiers [Array<String>] The feature identifiers.
|
122
|
+
def enable_flow(assignables, features, identifiers)
|
123
|
+
rows = []
|
124
|
+
|
125
|
+
assignables.each do |assignable|
|
126
|
+
features.each do |feature|
|
127
|
+
rows << { assignable_id: assignable.id, assignable_type: assignable.class.name, feature_id: feature.id }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
mass_insert(rows, identifiers)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Inserts feature assignments in bulk.
|
135
|
+
#
|
136
|
+
# @param rows [Array<Hash>] The rows to insert.
|
137
|
+
# @param identifiers [Array<String>] The feature identifiers.
|
138
|
+
# @raise [Togglefy::BulkToggleFailed] If the bulk insert fails.
|
139
|
+
def mass_insert(rows, identifiers)
|
140
|
+
return unless rows.any?
|
141
|
+
|
142
|
+
ActiveRecord::Base.transaction do
|
143
|
+
Togglefy::FeatureAssignment.insert_all(rows)
|
144
|
+
end
|
145
|
+
rescue Togglefy::Error => e
|
146
|
+
raise Togglefy::BulkToggleFailed.new(
|
147
|
+
"Bulk toggle enable failed for #{klass.name} with identifiers #{identifiers.inspect}",
|
148
|
+
e
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Disables features for assignables.
|
153
|
+
#
|
154
|
+
# @param assignables [Array<Assignable>] The assignables to update.
|
155
|
+
# @param features [Array<Togglefy::Feature>] The features to disable.
|
156
|
+
# @param identifiers [Array<String>] The feature identifiers.
|
157
|
+
def disable_flow(assignables, features, identifiers)
|
158
|
+
ids_to_remove = []
|
159
|
+
|
160
|
+
assignables.each do |assignable|
|
161
|
+
features.each do |feature|
|
162
|
+
ids_to_remove << [assignable.id, feature.id]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
mass_delete(ids_to_remove, identifiers)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Deletes feature assignments in bulk.
|
170
|
+
#
|
171
|
+
# @param ids_to_remove [Array<Array>] The IDs to remove.
|
172
|
+
# @param identifiers [Array<String>] The feature identifiers.
|
173
|
+
# @raise [Togglefy::BulkToggleFailed] If the bulk delete fails.
|
174
|
+
def mass_delete(ids_to_remove, identifiers)
|
175
|
+
return unless ids_to_remove.any?
|
176
|
+
|
177
|
+
ActiveRecord::Base.transaction do
|
178
|
+
Togglefy::FeatureAssignment.where(mass_delete_scope(ids_to_remove, klass.name)).delete_all
|
179
|
+
end
|
180
|
+
rescue Togglefy::Error => e
|
181
|
+
raise Togglefy::BulkToggleFailed.new(
|
182
|
+
"Bulk toggle disable failed for #{klass.name} with identifiers #{identifiers.inspect}",
|
183
|
+
e
|
184
|
+
)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Builds the scope for mass deletion.
|
188
|
+
#
|
189
|
+
# @param ids [Array<Array>] The IDs of features to delete from the assignables.
|
190
|
+
# @param klass_name [String] The class name of the assignable.
|
191
|
+
# @return [Hash] The scope for mass deletion to be used in the query.
|
192
|
+
def mass_delete_scope(ids, klass_name)
|
193
|
+
{
|
194
|
+
assignable_id: ids.map(&:first),
|
195
|
+
assignable_type: klass_name,
|
196
|
+
feature_id: ids.map(&:last)
|
197
|
+
}
|
198
|
+
end
|
107
199
|
end
|
108
200
|
end
|
data/lib/togglefy/version.rb
CHANGED
data/lib/togglefy.rb
CHANGED
@@ -8,105 +8,250 @@ require "togglefy/feature_assignable_manager"
|
|
8
8
|
require "togglefy/feature_manager"
|
9
9
|
require "togglefy/feature_query"
|
10
10
|
require "togglefy/scoped_bulk_wrapper"
|
11
|
-
require "togglefy/
|
11
|
+
require "togglefy/errors"
|
12
12
|
|
13
|
+
# The Togglefy module provides a feature management system.
|
14
|
+
# It includes methods for querying, creating, updating, toggling, and managing features.
|
15
|
+
# It also provides a way to manage features for assignable objects.
|
16
|
+
#
|
17
|
+
# == Features
|
18
|
+
#
|
19
|
+
# The Togglefy module provides a variety of features, including:
|
20
|
+
#
|
21
|
+
# - Querying features by type, group, environment, tenant, and custom filters
|
22
|
+
# - Creating, updating, and deleting features
|
23
|
+
# - Managing features for Assignables
|
24
|
+
#
|
25
|
+
# For more detailed information on each method,
|
26
|
+
# please refer to the {file:README.md README}, individual method documentation in this file or the usage documentation.
|
27
|
+
#
|
28
|
+
# == Usage
|
29
|
+
#
|
30
|
+
# Main usage for this always starts with the {Togglefy} module.
|
31
|
+
#
|
32
|
+
# Below are a few examples on how to use Togglefy:
|
33
|
+
#
|
34
|
+
# === Examples
|
35
|
+
#
|
36
|
+
# - +Togglefy.feature(:super_powers)+
|
37
|
+
# - +Togglefy.for_type(User)+
|
38
|
+
# - +Togglefy.for_group(group)+
|
39
|
+
# - +Togglefy.for_filters(filters: {group: :admin})+
|
40
|
+
# - +Togglefy.with_status(:active)+
|
41
|
+
# - +Togglefy.create(name: "Feature Name", identifier: :feature_name, description: "Feature description")+
|
42
|
+
# - +Togglefy.update(:feature_name, name: "Updated Feature Name")+
|
43
|
+
# - +Togglefy.destroy(:feature_name)+
|
44
|
+
# - +Togglefy.toggle(:feature_name)+
|
45
|
+
# - +Togglefy.inactive!(:feature_name)+
|
46
|
+
# - +Togglefy.for(assignable).enable(:feature_name)+
|
47
|
+
# - +Togglefy.for(assignable).has?(:feature_name)+
|
48
|
+
# - +Togglefy.mass_for(Assignable).bulk.enable(:feature_name)+
|
49
|
+
# - +Togglefy.mass_for(Assignable).bulk.enable(:feature_name, percentage: 35)+
|
50
|
+
# - +Togglefy.mass_for(Assignable).bulk.disable([:feature_name, :another_feature])+
|
51
|
+
#
|
52
|
+
# == Aliases
|
53
|
+
#
|
54
|
+
# The following aliases are available for convenience:
|
55
|
+
#
|
56
|
+
# - +for_role+ is an alias for +for_group+
|
57
|
+
# - +without_role+ is an alias for +without_group+
|
58
|
+
# - +for_env+ is an alias for +for_environment+
|
59
|
+
# - +without_env+ is an alias for +without_environment+
|
60
|
+
# - +create_feature+ is an alias for +create+
|
61
|
+
# - +update_feature+ is an alias for +update+
|
62
|
+
# - +toggle_feature+ is an alias for +toggle+
|
63
|
+
# - +activate_feature+ is an alias for +active!+
|
64
|
+
# - +inactivate_feature+ is an alias for +inactive!+
|
65
|
+
# - +destroy_feature+ is an alias for +destroy+
|
66
|
+
#
|
13
67
|
module Togglefy
|
14
|
-
|
15
|
-
|
16
|
-
# FeatureQuery
|
68
|
+
# Returns all features.
|
69
|
+
# @return [Array] List of all features.
|
17
70
|
def self.features
|
18
71
|
FeatureQuery.new.features
|
19
72
|
end
|
20
73
|
|
74
|
+
# Finds a feature by its identifier.
|
75
|
+
# @param identifier [String, Symbol] The unique identifier of the feature.
|
76
|
+
# @return [Feature] The feature object.
|
77
|
+
# @raise [Togglefy::FeatureNotFound] If the feature is not found by the identifier.
|
21
78
|
def self.feature(identifier)
|
22
79
|
FeatureQuery.new.feature(identifier)
|
80
|
+
rescue ActiveRecord::RecordNotFound
|
81
|
+
raise Togglefy::FeatureNotFound, "Couldn't find Togglefy::Feature with identifier '#{identifier}'"
|
23
82
|
end
|
24
|
-
|
83
|
+
|
84
|
+
# Queries features for a specific type.
|
85
|
+
# @param klass [Class] The class type to filter features by.
|
86
|
+
# @return [Array] List of features for the given type.
|
25
87
|
def self.for_type(klass)
|
26
88
|
FeatureQuery.new.for_type(klass)
|
27
89
|
end
|
28
|
-
|
90
|
+
|
91
|
+
# Queries features for a specific group.
|
92
|
+
# @param group [String, Symbol] The group name to filter features by.
|
93
|
+
# @return [Array] List of features for the given group.
|
29
94
|
def self.for_group(group)
|
30
95
|
FeatureQuery.new.for_group(group)
|
31
96
|
end
|
32
97
|
|
98
|
+
# Queries features without a group.
|
99
|
+
# @return [Array] List of features without a group.
|
33
100
|
def self.without_group
|
34
101
|
FeatureQuery.new.without_group
|
35
102
|
end
|
36
103
|
|
104
|
+
# Queries features for a specific environment.
|
105
|
+
# @param environment [String, Symbol] The environment name to filter features by.
|
106
|
+
# @return [Array] List of features for the given environment.
|
37
107
|
def self.for_environment(environment)
|
38
108
|
FeatureQuery.new.for_environment(environment)
|
39
109
|
end
|
40
110
|
|
111
|
+
# Queries features without an environment.
|
112
|
+
# @return [Array] List of features without an environment.
|
41
113
|
def self.without_environment
|
42
114
|
FeatureQuery.new.without_environment
|
43
115
|
end
|
44
116
|
|
117
|
+
# Queries features for a specific tenant.
|
118
|
+
# @param tenant_id [String] The tenant ID to filter features by.
|
119
|
+
# @return [Array] List of features for the given tenant.
|
45
120
|
def self.for_tenant(tenant_id)
|
46
121
|
FeatureQuery.new.for_tenant(tenant_id)
|
47
122
|
end
|
48
123
|
|
124
|
+
# Queries features without a tenant.
|
125
|
+
# @return [Array] List of features without a tenant.
|
49
126
|
def self.without_tenant
|
50
127
|
FeatureQuery.new.without_tenant
|
51
128
|
end
|
52
129
|
|
130
|
+
# Queries features based on custom filters.
|
131
|
+
# @param filters [Hash] A hash of filters to apply.
|
132
|
+
# @return [Array] List of features matching the filters.
|
53
133
|
def self.for_filters(filters: {})
|
54
134
|
FeatureQuery.new.for_filters(filters)
|
55
135
|
end
|
56
136
|
|
137
|
+
# Queries features by their status.
|
138
|
+
# @param status [String, Symbol, Integer] The status to filter features by.
|
139
|
+
# @return [Array] List of features with the given status.
|
57
140
|
def self.with_status(status)
|
58
141
|
FeatureQuery.new.with_status(status)
|
59
142
|
end
|
60
143
|
|
61
|
-
#
|
144
|
+
# Creates a new feature.
|
145
|
+
# @note All parameters are optional, except for the name. If sent, it should be a keyword argument.
|
146
|
+
#
|
147
|
+
# @param name [String] The name of the feature.
|
148
|
+
# @param identifier [Symbol, String, nil] The unique identifier for the feature. Optional, it can also be nil or blank
|
149
|
+
# @param description [String] A description of the feature.
|
150
|
+
# @param group [String, Symbol, nil] The group the feature belongs to.
|
151
|
+
# @param environment [String, Symbol, nil] The environment the feature is for.
|
152
|
+
# @param tenant_id [String] The tenant ID the feature is for.
|
153
|
+
# @param status [String, Symbol, Integer] The status of the feature.
|
154
|
+
# @return [Feature] The created feature.
|
155
|
+
# @example
|
156
|
+
# Togglefy.create(name: "New Feature", identifier: :new_feature, description: "A new feature")
|
157
|
+
# Togglefy.create(name: "New Feature", identifier: nil, description: "A new feature", group: :admin)
|
158
|
+
# Togglefy.create(name: "New Feature", description: "A new feature", environment: :production, tenant_id: "123abc")
|
62
159
|
def self.create(**params)
|
63
160
|
FeatureManager.new.create(**params)
|
64
161
|
end
|
65
162
|
|
163
|
+
# Updates an existing feature.
|
164
|
+
# @note All parameters but the first (identifier) should be keyword arguments.
|
165
|
+
#
|
166
|
+
# @param identifier [Symbol, String] The unique identifier of the feature.
|
167
|
+
# @param name [String] The name of the feature.
|
168
|
+
# @param identifier [Symbol, String, nil] The unique identifier for the feature. Optional, it can also be nil or blank
|
169
|
+
# @param description [String] A description of the feature.
|
170
|
+
# @param group [String, Symbol, nil] The group the feature belongs to.
|
171
|
+
# @param environment [String, Symbol, nil] The environment the feature is for.
|
172
|
+
# @param tenant_id [String] The tenant ID the feature is for.
|
173
|
+
# @param status [String, Symbol, Integer] The status of the feature.
|
174
|
+
# @return [Feature] The updated feature.
|
175
|
+
# @raise [Togglefy::FeatureNotFound] If the feature is not found by the identifier.
|
176
|
+
# @example
|
177
|
+
# Togglefy.update(:new_feature, name: "Updated Feature", description: "Updated feature description")
|
178
|
+
# Togglefy.update(:new_feature, identifier: :updated_feature, group: :support)
|
179
|
+
# Togglefy.update(:new_feature, environment: :staging, tenant_id: "abc123")
|
66
180
|
def self.update(identifier, **params)
|
67
181
|
FeatureManager.new(identifier).update(**params)
|
182
|
+
rescue ActiveRecord::RecordNotFound
|
183
|
+
raise Togglefy::FeatureNotFound, "Couldn't find Togglefy::Feature with identifier '#{identifier}'"
|
68
184
|
end
|
69
185
|
|
186
|
+
# Deletes a feature.
|
187
|
+
# @param identifier [Symbol, String] The unique identifier of the feature.
|
188
|
+
# @return [boolean] True if the feature was deleted, false otherwise.
|
189
|
+
# @raise [Togglefy::FeatureNotFound] If the feature is not found by the identifier.
|
70
190
|
def self.destroy(identifier)
|
71
191
|
FeatureManager.new(identifier).destroy
|
192
|
+
rescue ActiveRecord::RecordNotFound
|
193
|
+
raise Togglefy::FeatureNotFound, "Couldn't find Togglefy::Feature with identifier '#{identifier}'"
|
72
194
|
end
|
73
195
|
|
196
|
+
# Toggles the status of a feature.
|
197
|
+
# @param identifier [Symbol, String] The unique identifier of the feature.
|
198
|
+
# @return [boolean] True if the feature was toggled, false otherwise.
|
199
|
+
# @raise [Togglefy::FeatureNotFound] If the feature is not found by the identifier.
|
74
200
|
def self.toggle(identifier)
|
75
201
|
FeatureManager.new(identifier).toggle
|
202
|
+
rescue ActiveRecord::RecordNotFound
|
203
|
+
raise Togglefy::FeatureNotFound, "Couldn't find Togglefy::Feature with identifier '#{identifier}'"
|
76
204
|
end
|
77
205
|
|
206
|
+
# Activates a feature.
|
207
|
+
# @param identifier [Symbol, String] The unique identifier of the feature.
|
208
|
+
# @return [boolean] True if the feature was activated, false otherwise.
|
209
|
+
# @raise [Togglefy::FeatureNotFound] If the feature is not found by the identifier.
|
78
210
|
def self.active!(identifier)
|
79
211
|
FeatureManager.new(identifier).active!
|
212
|
+
rescue ActiveRecord::RecordNotFound
|
213
|
+
raise Togglefy::FeatureNotFound, "Couldn't find Togglefy::Feature with identifier '#{identifier}'"
|
80
214
|
end
|
81
215
|
|
216
|
+
# Deactivates a feature.
|
217
|
+
# @param identifier [Symbol, String] The unique identifier of the feature.
|
218
|
+
# @return [boolean] True if the feature was inactivated, false otherwise.
|
219
|
+
# @raise [Togglefy::FeatureNotFound] If the feature is not found by the identifier.
|
82
220
|
def self.inactive!(identifier)
|
83
221
|
FeatureManager.new(identifier).inactive!
|
222
|
+
rescue ActiveRecord::RecordNotFound
|
223
|
+
raise Togglefy::FeatureNotFound, "Couldn't find Togglefy::Feature with identifier '#{identifier}'"
|
84
224
|
end
|
85
225
|
|
86
|
-
#
|
226
|
+
# Manages features for a specific assignable object.
|
227
|
+
# @param assignable [Object] The assignable object.
|
228
|
+
# @return [FeatureAssignableManager] The manager for the assignable object.
|
87
229
|
def self.for(assignable)
|
88
230
|
FeatureAssignableManager.new(assignable)
|
89
231
|
end
|
90
232
|
|
91
|
-
#
|
233
|
+
# Provides bulk management for a specific class.
|
234
|
+
# @param klass [Class] The class to manage features for.
|
235
|
+
# @return [ScopedBulkWrapper] The bulk wrapper for the class.
|
92
236
|
def self.mass_for(klass)
|
93
237
|
Togglefy::ScopedBulkWrapper.new(klass)
|
94
238
|
end
|
95
239
|
|
96
|
-
class <<self
|
97
|
-
#
|
98
|
-
|
99
|
-
|
240
|
+
class << self
|
241
|
+
# Aliases for group-related Features.
|
242
|
+
alias for_role for_group
|
243
|
+
alias without_role without_group
|
100
244
|
|
101
|
-
|
102
|
-
|
245
|
+
# Aliases for environment-related Features.
|
246
|
+
alias for_env for_environment
|
247
|
+
alias without_env without_environment
|
103
248
|
|
104
|
-
#
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
249
|
+
# Aliases for feature management Features.
|
250
|
+
alias create_feature create
|
251
|
+
alias update_feature update
|
252
|
+
alias toggle_feature toggle
|
253
|
+
alias activate_feature active!
|
254
|
+
alias inactivate_feature inactive!
|
255
|
+
alias destroy_feature destroy
|
111
256
|
end
|
112
257
|
end
|
data/togglefy.gemspec
CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |spec|
|
|
6
6
|
spec.name = "togglefy"
|
7
7
|
spec.version = Togglefy::VERSION
|
8
8
|
spec.authors = ["Gabriel Azevedo"]
|
9
|
-
spec.email = ["
|
9
|
+
spec.email = ["gabriel@azeveco.com"]
|
10
10
|
|
11
11
|
spec.summary = "Simple and open source Feature Management."
|
12
12
|
spec.description = "Togglefy is a feature management Rails gem to help you control which features an user or a group has access to."
|
@@ -19,16 +19,26 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.metadata["homepage_uri"] = spec.homepage
|
20
20
|
spec.metadata["source_code_uri"] = "https://github.com/azeveco/Togglefy"
|
21
21
|
spec.metadata["changelog_uri"] = "https://github.com/azeveco/Togglefy/releases"
|
22
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/azeveco/Togglefy/issues'
|
23
|
+
spec.metadata['documentation_uri'] = 'https://rubydoc.info/github/azeveco/Togglefy'
|
22
24
|
|
23
25
|
# Specify which files should be added to the gem when it is released.
|
24
26
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
-
|
27
|
+
File.basename(__FILE__)
|
26
28
|
spec.files = Dir.glob("lib/**/*") +
|
27
|
-
|
28
|
-
|
29
|
-
|
29
|
+
Dir.glob("app/**/*") +
|
30
|
+
Dir.glob("config/**/*") +
|
31
|
+
%w[LICENSE README.md Rakefile togglefy.gemspec]
|
30
32
|
|
31
33
|
spec.bindir = "exe"
|
32
34
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
33
35
|
spec.require_paths = ["lib"]
|
36
|
+
|
37
|
+
spec.add_development_dependency "bootsnap", "~> 1.17"
|
38
|
+
spec.add_development_dependency "database_cleaner-active_record", "~> 2.1"
|
39
|
+
spec.add_development_dependency "rails", "~> 8.0.2"
|
40
|
+
spec.add_development_dependency "rspec-rails", "~> 7.1.1"
|
41
|
+
spec.add_development_dependency "sqlite3", "~> 2.1"
|
42
|
+
spec.add_development_dependency "yard", "~> 0.9.37"
|
43
|
+
spec.add_development_dependency "redcarpet", "~> 3.6"
|
34
44
|
end
|