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.
@@ -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 = Togglefy.for_filters(
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 = if action == :enable
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
- existing_assignments = Togglefy::FeatureAssignment.where(
42
- assignable_id: assignables.map(&:id),
43
- assignable_type: klass.name,
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
- begin
66
- Togglefy::FeatureAssignment.insert_all(rows) if rows.any?
67
- rescue => error
68
- raise Togglefy::BulkToggleFailed.new(
69
- "Bulk toggle enable failed for #{klass.name} with identifiers #{identifiers.inspect}",
70
- error
71
- )
72
- end
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
- begin
83
- if ids_to_remove.any?
84
- Togglefy::FeatureAssignment.where(
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
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Togglefy
4
- VERSION = "1.1.1"
4
+ # The VERSION constant defines the current version of the Togglefy gem.
5
+ VERSION = "1.2.1"
5
6
  end
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/exceptions"
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
- class Error < StandardError; end
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
- # FeatureManager
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
- # FeatureAssignableManager
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
- # ScopedBulkWrapper
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
- # FeatureQuery
98
- alias_method :for_role, :for_group
99
- alias_method :without_role, :without_group
240
+ class << self
241
+ # Aliases for group-related Features.
242
+ alias for_role for_group
243
+ alias without_role without_group
100
244
 
101
- alias_method :for_env, :for_environment
102
- alias_method :without_env, :without_environment
245
+ # Aliases for environment-related Features.
246
+ alias for_env for_environment
247
+ alias without_env without_environment
103
248
 
104
- # FeatureManager
105
- alias_method :create_feature, :create
106
- alias_method :update_feature, :update
107
- alias_method :toggle_feature, :toggle
108
- alias_method :activate_feature, :active!
109
- alias_method :inactivate_feature, :inactive!
110
- alias_method :destroy_feature, :destroy
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 = ["gazeveco@gmail.com"]
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
- gemspec = File.basename(__FILE__)
27
+ File.basename(__FILE__)
26
28
  spec.files = Dir.glob("lib/**/*") +
27
- Dir.glob("app/**/*") +
28
- Dir.glob("config/**/*") +
29
- %w[LICENSE.txt README.md togglefy.gemspec]
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