magick-feature-flags 0.9.24 → 0.9.25

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.
@@ -5,11 +5,14 @@ module Magick
5
5
  class Registry
6
6
  CACHE_INVALIDATION_CHANNEL = 'magick:cache:invalidate'
7
7
 
8
- def initialize(memory_adapter, redis_adapter = nil, circuit_breaker: nil, async: false)
8
+ def initialize(memory_adapter, redis_adapter = nil, active_record_adapter: nil, circuit_breaker: nil,
9
+ async: false, primary: nil)
9
10
  @memory_adapter = memory_adapter
10
11
  @redis_adapter = redis_adapter
12
+ @active_record_adapter = active_record_adapter
11
13
  @circuit_breaker = circuit_breaker || Magick::CircuitBreaker.new
12
14
  @async = async
15
+ @primary = primary || :memory # :memory, :redis, or :active_record
13
16
  @subscriber_thread = nil
14
17
  @subscriber = nil
15
18
  # Only start Pub/Sub subscriber if Redis is available
@@ -19,7 +22,7 @@ module Magick
19
22
 
20
23
  def get(feature_name, key)
21
24
  # Try memory first (fastest) - no Redis calls needed thanks to Pub/Sub invalidation
22
- value = memory_adapter.get(feature_name, key)
25
+ value = memory_adapter.get(feature_name, key) if memory_adapter
23
26
  return value unless value.nil?
24
27
 
25
28
  # Fall back to Redis if available
@@ -27,10 +30,25 @@ module Magick
27
30
  begin
28
31
  value = redis_adapter.get(feature_name, key)
29
32
  # Update memory cache if found in Redis
30
- memory_adapter.set(feature_name, key, value) if value
33
+ if value && memory_adapter
34
+ memory_adapter.set(feature_name, key, value)
35
+ return value
36
+ end
37
+ # If Redis returns nil, continue to next adapter
38
+ rescue StandardError, AdapterError
39
+ # Redis failed, continue to next adapter
40
+ end
41
+ end
42
+
43
+ # Fall back to Active Record if available
44
+ if active_record_adapter
45
+ begin
46
+ value = active_record_adapter.get(feature_name, key)
47
+ # Update memory cache if found in Active Record
48
+ memory_adapter.set(feature_name, key, value) if value && memory_adapter
31
49
  return value
32
- rescue AdapterError
33
- # Redis failed, return nil
50
+ rescue StandardError, AdapterError
51
+ # Active Record failed, return nil
34
52
  nil
35
53
  end
36
54
  end
@@ -40,55 +58,88 @@ module Magick
40
58
 
41
59
  def set(feature_name, key, value)
42
60
  # Update memory first (always synchronous)
43
- memory_adapter.set(feature_name, key, value)
61
+ memory_adapter&.set(feature_name, key, value)
44
62
 
45
63
  # Update Redis if available
46
- return unless redis_adapter
64
+ if redis_adapter
65
+ update_redis = proc do
66
+ circuit_breaker.call do
67
+ redis_adapter.set(feature_name, key, value)
68
+ end
69
+ rescue AdapterError => e
70
+ # Log error but don't fail - memory is updated
71
+ warn "Failed to update Redis: #{e.message}" if defined?(Rails) && Rails.env.development?
72
+ end
47
73
 
48
- update_redis = proc do
49
- circuit_breaker.call do
50
- redis_adapter.set(feature_name, key, value)
74
+ if @async && defined?(Thread)
75
+ # For async updates, publish cache invalidation synchronously
76
+ # This ensures other processes are notified immediately, even if Redis update is delayed
77
+ publish_cache_invalidation(feature_name)
78
+ Thread.new { update_redis.call }
79
+ else
80
+ update_redis.call
51
81
  # Publish cache invalidation message to notify other processes
52
82
  publish_cache_invalidation(feature_name)
53
83
  end
54
- rescue AdapterError => e
55
- # Log error but don't fail - memory is updated
56
- warn "Failed to update Redis: #{e.message}" if defined?(Rails) && Rails.env.development?
57
84
  end
58
85
 
59
- if @async && defined?(Thread)
60
- Thread.new { update_redis.call }
61
- else
62
- update_redis.call
86
+ # Always update Active Record if available (as fallback/persistence layer)
87
+ return unless active_record_adapter
88
+
89
+ begin
90
+ active_record_adapter.set(feature_name, key, value)
91
+ rescue AdapterError => e
92
+ # Log error but don't fail
93
+ warn "Failed to update Active Record: #{e.message}" if defined?(Rails) && Rails.env.development?
63
94
  end
64
95
  end
65
96
 
66
97
  def delete(feature_name)
67
- memory_adapter.delete(feature_name)
68
- return unless redis_adapter
98
+ memory_adapter&.delete(feature_name)
99
+
100
+ if redis_adapter
101
+ begin
102
+ redis_adapter.delete(feature_name)
103
+ # Publish cache invalidation message
104
+ publish_cache_invalidation(feature_name)
105
+ rescue AdapterError
106
+ # Continue even if Redis fails
107
+ end
108
+ end
109
+
110
+ return unless active_record_adapter
69
111
 
70
112
  begin
71
- redis_adapter.delete(feature_name)
72
- # Publish cache invalidation message
73
- publish_cache_invalidation(feature_name)
113
+ active_record_adapter.delete(feature_name)
74
114
  rescue AdapterError
75
- # Continue even if Redis fails
115
+ # Continue even if Active Record fails
76
116
  end
77
117
  end
78
118
 
79
119
  def exists?(feature_name)
80
- memory_adapter.exists?(feature_name) || (redis_adapter&.exists?(feature_name) == true)
120
+ return true if memory_adapter&.exists?(feature_name)
121
+ return true if redis_adapter&.exists?(feature_name) == true
122
+ return true if active_record_adapter&.exists?(feature_name) == true
123
+
124
+ false
81
125
  end
82
126
 
83
127
  def all_features
84
- memory_features = memory_adapter.all_features
85
- redis_features = redis_adapter&.all_features || []
86
- (memory_features + redis_features).uniq
128
+ features = []
129
+ features += memory_adapter.all_features if memory_adapter
130
+ features += redis_adapter.all_features if redis_adapter
131
+ features += active_record_adapter.all_features if active_record_adapter
132
+ features.uniq
87
133
  end
88
134
 
89
135
  # Explicitly trigger cache invalidation for a feature
90
136
  # This is useful for targeting updates that need immediate cache invalidation
137
+ # Invalidates memory cache in current process AND publishes to Redis for other processes
91
138
  def invalidate_cache(feature_name)
139
+ # Invalidate memory cache in current process immediately
140
+ memory_adapter&.delete(feature_name)
141
+
142
+ # Publish to Redis Pub/Sub to invalidate cache in other processes
92
143
  publish_cache_invalidation(feature_name)
93
144
  end
94
145
 
@@ -104,11 +155,9 @@ module Magick
104
155
  redis_adapter.instance_variable_get(:@redis)
105
156
  end
106
157
 
107
- private
108
-
109
- attr_reader :memory_adapter, :redis_adapter, :circuit_breaker
110
-
111
- # Publish cache invalidation message to Redis Pub/Sub
158
+ # Publish cache invalidation message to Redis Pub/Sub (without deleting local memory cache)
159
+ # This is useful when you've just updated the cache and want to notify other processes
160
+ # but keep the local memory cache intact
112
161
  def publish_cache_invalidation(feature_name)
113
162
  return unless redis_adapter
114
163
 
@@ -121,6 +170,10 @@ module Magick
121
170
  end
122
171
  end
123
172
 
173
+ private
174
+
175
+ attr_reader :memory_adapter, :redis_adapter, :active_record_adapter, :circuit_breaker
176
+
124
177
  # Start a background thread to listen for cache invalidation messages
125
178
  def start_cache_invalidation_subscriber
126
179
  return unless redis_adapter && defined?(Thread)
@@ -132,8 +185,28 @@ module Magick
132
185
  @subscriber = redis_client.dup
133
186
  @subscriber.subscribe(CACHE_INVALIDATION_CHANNEL) do |on|
134
187
  on.message do |_channel, feature_name|
188
+ feature_name_str = feature_name.to_s
189
+
135
190
  # Invalidate memory cache for this feature
136
- memory_adapter.delete(feature_name)
191
+ memory_adapter.delete(feature_name_str) if memory_adapter
192
+
193
+ # Also reload the feature instance in Magick.features if it exists
194
+ # This ensures the feature instance has the latest targeting and values
195
+ if defined?(Magick) && Magick.features.key?(feature_name_str)
196
+ feature = Magick.features[feature_name_str]
197
+ if feature.respond_to?(:reload)
198
+ feature.reload
199
+ # Log for debugging (only in development)
200
+ if defined?(Rails) && Rails.env.development?
201
+ Rails.logger.debug "Magick: Reloaded feature '#{feature_name_str}' after cache invalidation"
202
+ end
203
+ end
204
+ end
205
+ rescue StandardError => e
206
+ # Log error but don't crash the subscriber thread
207
+ if defined?(Rails) && Rails.env.development?
208
+ warn "Magick: Error processing cache invalidation for '#{feature_name}': #{e.message}"
209
+ end
137
210
  end
138
211
  end
139
212
  rescue StandardError => e
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ Magick::AdminUI::Engine.routes.draw do
4
+ root 'features#index'
5
+ resources :features, only: %i[index show edit update] do
6
+ member do
7
+ put :enable
8
+ put :disable
9
+ put :enable_for_user
10
+ end
11
+ end
12
+ resources :stats, only: [:show]
13
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Configure inflector immediately when this file loads
4
+ # This ensures AdminUI stays as AdminUI (not AdminUi) before Rails processes routes
5
+ if defined?(ActiveSupport::Inflector)
6
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
7
+ inflect.acronym 'AdminUI'
8
+ inflect.acronym 'UI'
9
+ end
10
+ end
11
+
12
+ module Magick
13
+ module AdminUI
14
+ class Engine < ::Rails::Engine
15
+ isolate_namespace Magick::AdminUI
16
+
17
+ engine_name 'magick_admin_ui'
18
+
19
+ # Rails engines automatically detect app/views and app/controllers directories
20
+ # With isolate_namespace, views should be at:
21
+ # app/views/magick/adminui/[controller]/[action].html.erb
22
+ # Controllers should be at:
23
+ # app/controllers/magick/adminui/[controller]_controller.rb
24
+ # Rails handles this automatically, but we explicitly add app/controllers to autoload paths
25
+ # to ensure controllers are found
26
+ config.autoload_paths += %W[#{root}/app/controllers] if root.join('app', 'controllers').exist?
27
+
28
+ # Explicitly add app/views to view paths
29
+ # Rails engines should do this automatically, but we ensure it's configured
30
+ initializer 'magick.admin_ui.append_view_paths', after: :add_view_paths do |app|
31
+ app.paths['app/views'] << root.join('app', 'views').to_s if root.join('app', 'views').exist?
32
+ end
33
+
34
+ # Also ensure view paths are added when ActionController loads
35
+ initializer 'magick.admin_ui.append_view_paths_to_controller', after: :add_view_paths do
36
+ ActiveSupport.on_load(:action_controller) do
37
+ view_path = Magick::AdminUI::Engine.root.join('app', 'views').to_s
38
+ append_view_path view_path unless view_paths.include?(view_path)
39
+ end
40
+ end
41
+
42
+ # Ensure view paths are added in to_prepare (runs before each request in development)
43
+ config.to_prepare do
44
+ view_path = Magick::AdminUI::Engine.root.join('app', 'views').to_s
45
+ if File.directory?(view_path)
46
+ if defined?(Magick::AdminUI::FeaturesController)
47
+ Magick::AdminUI::FeaturesController.append_view_path(view_path)
48
+ end
49
+ Magick::AdminUI::StatsController.append_view_path(view_path) if defined?(Magick::AdminUI::StatsController)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ # Draw routes directly - Rails engines should auto-load config/routes.rb
57
+ # but for gems we need to ensure routes are drawn at the right time
58
+ # Use both to_prepare (for development reloading) and an initializer (for production)
59
+ if defined?(Rails)
60
+ # Initializer runs once during app initialization
61
+ Magick::AdminUI::Engine.initializer 'magick.admin_ui.draw_routes', after: :load_config_initializers do
62
+ Magick::AdminUI::Engine.routes.draw do
63
+ root 'features#index'
64
+ resources :features, only: %i[index show edit update] do
65
+ member do
66
+ put :enable
67
+ put :disable
68
+ put :enable_for_user
69
+ put :enable_for_role
70
+ put :disable_for_role
71
+ put :update_targeting
72
+ end
73
+ end
74
+ resources :stats, only: [:show]
75
+ end
76
+ end
77
+
78
+ # to_prepare runs before each request in development (for code reloading)
79
+ Magick::AdminUI::Engine.config.to_prepare do
80
+ # Routes are already drawn by initializer, but redraw if needed for development reloading
81
+ if Magick::AdminUI::Engine.routes.routes.empty?
82
+ Magick::AdminUI::Engine.routes.draw do
83
+ root 'features#index'
84
+ resources :features, only: %i[index show edit update] do
85
+ member do
86
+ put :enable
87
+ put :disable
88
+ put :enable_for_user
89
+ put :enable_for_role
90
+ put :disable_for_role
91
+ put :update_targeting
92
+ end
93
+ end
94
+ resources :stats, only: [:show]
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Magick
4
+ module AdminUI
5
+ module Helpers
6
+ def self.feature_status_badge(status)
7
+ case status.to_sym
8
+ when :active
9
+ '<span class="badge badge-success">Active</span>'
10
+ when :deprecated
11
+ '<span class="badge badge-warning">Deprecated</span>'
12
+ when :inactive
13
+ '<span class="badge badge-danger">Inactive</span>'
14
+ else
15
+ '<span class="badge">Unknown</span>'
16
+ end
17
+ end
18
+
19
+ def self.feature_type_label(type)
20
+ case type.to_sym
21
+ when :boolean
22
+ 'Boolean'
23
+ when :string
24
+ 'String'
25
+ when :number
26
+ 'Number'
27
+ else
28
+ type.to_s.capitalize
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Routes file - users should add this to their Rails app's config/routes.rb:
4
+ # mount Magick::AdminUI::Engine, at: '/magick'
5
+ #
6
+ # Or define routes manually:
7
+ # Magick::AdminUI::Engine.routes.draw do
8
+ # root 'features#index'
9
+ # resources :features, only: [:index, :show, :edit, :update] do
10
+ # member do
11
+ # put :enable
12
+ # put :disable
13
+ # put :enable_for_user
14
+ # end
15
+ # end
16
+ # resources :stats, only: [:show]
17
+ # end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'admin_ui/engine'
4
+ # Controllers are in app/controllers and will be auto-loaded by Rails engine
5
+ # But we explicitly require them to ensure they're available when needed
6
+ if defined?(Rails) && Rails.env
7
+ # In Rails, controllers are auto-loaded from app/controllers
8
+ # But we can explicitly require them if needed for console access
9
+ engine_root = Magick::AdminUI::Engine.root
10
+ if engine_root.join('app', 'controllers', 'magick', 'adminui', 'features_controller.rb').exist?
11
+ require engine_root.join('app', 'controllers', 'magick', 'adminui', 'features_controller').to_s
12
+ end
13
+ if engine_root.join('app', 'controllers', 'magick', 'adminui', 'stats_controller.rb').exist?
14
+ require engine_root.join('app', 'controllers', 'magick', 'adminui', 'stats_controller').to_s
15
+ end
16
+ end
17
+ require_relative 'admin_ui/helpers'
18
+
19
+ module Magick
20
+ module AdminUI
21
+ class << self
22
+ def configure
23
+ yield config if block_given?
24
+ end
25
+
26
+ def config
27
+ @config ||= Configuration.new
28
+ end
29
+
30
+ class Configuration
31
+ attr_accessor :theme, :brand_name, :require_role, :available_roles
32
+
33
+ def initialize
34
+ @theme = :light
35
+ @brand_name = 'Magick'
36
+ @require_role = nil
37
+ @available_roles = [] # Can be populated via DSL: admin_ui { roles ['admin', 'user', 'manager'] }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
data/lib/magick/config.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Magick
4
4
  class Config
5
5
  attr_accessor :adapter_registry, :performance_metrics, :audit_log, :versioning, :warn_on_deprecated,
6
- :async_updates, :memory_ttl, :circuit_breaker_threshold, :circuit_breaker_timeout, :redis_url, :redis_namespace, :redis_db, :environment
6
+ :async_updates, :memory_ttl, :circuit_breaker_threshold, :circuit_breaker_timeout, :redis_url, :redis_namespace, :redis_db, :environment, :active_record_model_class, :enable_admin_ui
7
7
 
8
8
  def initialize
9
9
  @warn_on_deprecated = false
@@ -14,6 +14,7 @@ module Magick
14
14
  @redis_namespace = 'magick:features'
15
15
  @redis_db = nil # Use default database (0) unless specified
16
16
  @environment = defined?(Rails) ? Rails.env.to_s : 'development'
17
+ @enable_admin_ui = false # Admin UI disabled by default
17
18
  end
18
19
 
19
20
  # DSL methods for configuration
@@ -23,6 +24,8 @@ module Magick
23
24
  configure_memory_adapter(**options)
24
25
  when :redis
25
26
  configure_redis_adapter(**options)
27
+ when :active_record
28
+ configure_active_record_adapter(**options)
26
29
  when :registry
27
30
  if block_given?
28
31
  instance_eval(&block)
@@ -58,6 +61,7 @@ module Magick
58
61
  end
59
62
  else
60
63
  memory_adapter = configure_memory_adapter
64
+ active_record_adapter = configure_active_record_adapter if defined?(::ActiveRecord::Base)
61
65
  cb = Magick::CircuitBreaker.new(
62
66
  failure_threshold: @circuit_breaker_threshold,
63
67
  timeout: @circuit_breaker_timeout
@@ -65,6 +69,7 @@ module Magick
65
69
  @adapter_registry = Adapters::Registry.new(
66
70
  memory_adapter,
67
71
  redis_adapter,
72
+ active_record_adapter: active_record_adapter,
68
73
  circuit_breaker: cb,
69
74
  async: @async_updates
70
75
  )
@@ -73,6 +78,40 @@ module Magick
73
78
  redis_adapter
74
79
  end
75
80
 
81
+ def active_record(model_class: nil, primary: false, **options)
82
+ @active_record_model_class = model_class if model_class
83
+ @active_record_primary = primary
84
+ active_record_adapter = configure_active_record_adapter(model_class: model_class, **options)
85
+
86
+ # Automatically create Registry adapter if it doesn't exist
87
+ if @adapter_registry
88
+ # If registry already exists, update it with the new Active Record adapter
89
+ if active_record_adapter && @adapter_registry.is_a?(Adapters::Registry)
90
+ @adapter_registry.instance_variable_set(:@active_record_adapter, active_record_adapter)
91
+ # Update primary if specified
92
+ @adapter_registry.instance_variable_set(:@primary, :active_record) if primary
93
+ end
94
+ else
95
+ memory_adapter = configure_memory_adapter
96
+ redis_adapter = configure_redis_adapter
97
+ cb = Magick::CircuitBreaker.new(
98
+ failure_threshold: @circuit_breaker_threshold,
99
+ timeout: @circuit_breaker_timeout
100
+ )
101
+ primary_adapter = primary ? :active_record : :memory
102
+ @adapter_registry = Adapters::Registry.new(
103
+ memory_adapter,
104
+ redis_adapter,
105
+ active_record_adapter: active_record_adapter,
106
+ circuit_breaker: cb,
107
+ async: @async_updates,
108
+ primary: primary_adapter
109
+ )
110
+ end
111
+
112
+ active_record_adapter
113
+ end
114
+
76
115
  def performance_metrics(enabled: true, redis_tracking: nil, batch_size: 100, flush_interval: 60, **_options)
77
116
  return unless enabled
78
117
 
@@ -122,6 +161,10 @@ module Magick
122
161
  @environment = name.to_s
123
162
  end
124
163
 
164
+ def admin_ui(enabled: true)
165
+ @enable_admin_ui = enabled
166
+ end
167
+
125
168
  def apply!
126
169
  # Apply configuration to Magick module
127
170
  Magick.adapter_registry = adapter_registry if adapter_registry
@@ -151,6 +194,11 @@ module Magick
151
194
  Magick.audit_log = audit_log if audit_log
152
195
  Magick.versioning = versioning if versioning
153
196
  Magick.warn_on_deprecated = warn_on_deprecated
197
+
198
+ # Load optional components if enabled
199
+ return unless @enable_admin_ui && defined?(Rails)
200
+
201
+ require_relative '../magick/admin_ui' unless defined?(Magick::AdminUI)
154
202
  end
155
203
 
156
204
  private
@@ -175,7 +223,11 @@ module Magick
175
223
 
176
224
  if url
177
225
  # Parse URL to extract database number if present
178
- parsed_url = URI.parse(url) rescue nil
226
+ parsed_url = begin
227
+ URI.parse(url)
228
+ rescue StandardError
229
+ nil
230
+ end
179
231
  db_from_url = nil
180
232
  if parsed_url && parsed_url.path && parsed_url.path.length > 1
181
233
  # Redis URL format: redis://host:port/db_number
@@ -200,7 +252,11 @@ module Magick
200
252
  # If db was specified but not in URL, select it explicitly
201
253
  # This handles cases where URL doesn't include db number
202
254
  if db && url
203
- parsed_url = URI.parse(url) rescue nil
255
+ parsed_url = begin
256
+ URI.parse(url)
257
+ rescue StandardError
258
+ nil
259
+ end
204
260
  url_has_db = parsed_url && parsed_url.path && parsed_url.path.length > 1
205
261
  unless url_has_db
206
262
  begin
@@ -216,9 +272,23 @@ module Magick
216
272
  adapter
217
273
  end
218
274
 
219
- def configure_registry_adapter(memory: nil, redis: nil, async: nil, circuit_breaker: nil)
275
+ def configure_active_record_adapter(model_class: nil, **_options)
276
+ return nil unless defined?(::ActiveRecord::Base)
277
+
278
+ model_class ||= @active_record_model_class
279
+ Adapters::ActiveRecord.new(model_class: model_class)
280
+ rescue StandardError => e
281
+ if defined?(Rails) && Rails.env.development?
282
+ warn "Magick: Failed to initialize ActiveRecord adapter: #{e.message}"
283
+ end
284
+ nil
285
+ end
286
+
287
+ def configure_registry_adapter(memory: nil, redis: nil, active_record: nil, async: nil, circuit_breaker: nil,
288
+ primary: nil)
220
289
  memory_adapter = memory || configure_memory_adapter
221
290
  redis_adapter = redis || configure_redis_adapter
291
+ active_record_adapter = active_record || configure_active_record_adapter
222
292
 
223
293
  cb = circuit_breaker || Magick::CircuitBreaker.new(
224
294
  failure_threshold: @circuit_breaker_threshold,
@@ -226,12 +296,15 @@ module Magick
226
296
  )
227
297
 
228
298
  async_enabled = async.nil? ? @async_updates : async
299
+ primary_adapter = primary || (@active_record_primary ? :active_record : :memory)
229
300
 
230
301
  @adapter_registry = Adapters::Registry.new(
231
302
  memory_adapter,
232
303
  redis_adapter,
304
+ active_record_adapter: active_record_adapter,
233
305
  circuit_breaker: cb,
234
- async: async_enabled
306
+ async: async_enabled,
307
+ primary: primary_adapter
235
308
  )
236
309
  end
237
310
 
@@ -239,7 +312,8 @@ module Magick
239
312
  @default_adapter_registry ||= begin
240
313
  memory_adapter = Adapters::Memory.new
241
314
  redis_adapter = configure_redis_adapter
242
- Adapters::Registry.new(memory_adapter, redis_adapter)
315
+ active_record_adapter = configure_active_record_adapter if defined?(::ActiveRecord::Base)
316
+ Adapters::Registry.new(memory_adapter, redis_adapter, active_record_adapter: active_record_adapter)
243
317
  end
244
318
  end
245
319
  end