contextual_config 0.1.0 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f59ad6d1973f90217140881d7cf16006e9a0447641b4f7cc2cd18116a97e2cb
4
- data.tar.gz: ec5e9472a87b390f4f1b720c9a970cd39dbe498acf0e501722662d9bcfb5bc16
3
+ metadata.gz: 7c7a19182d63d0f03712caf91b5b996bd99af38f3a6eed254d8497ff3c78af46
4
+ data.tar.gz: a55ebca13917e3b7fe37f45a0d14a7279a860705ec3c1461eb0071cc1acf67c1
5
5
  SHA512:
6
- metadata.gz: 96eec3b206c9f1138954885d5ba7af8c4f64751677ce41061829953278dd3e71c9f5f3f18de2a159b61259a7fae06c976b5c6dcfda0d04e99d30d85e62cb6f5b
7
- data.tar.gz: 11bb322511c97d471442f84949f182758c97b45dba6938c59e03c7bbb9160bc2163f15565ba71a81d8a0e26710941149d882e1eee3a4a8ad44df15efd29468c9
6
+ metadata.gz: dfb97ec675a0d72ce010f4e9f809e25cc01baff02dbafbfd83a17ae62eba7bd2a5cf9dfc39fe670075c09ef17f205297fc63c074ecac3fa7b422faa08811ba95
7
+ data.tar.gz: 941b9502f8cbcaa055f57d668c87c622dcc0f3efd3d1ef929cf5dc2533a487820badd234557187080870d36a7c11460887c624944da9511091a8d769e85e0f7a
data/CLAUDE.md CHANGED
@@ -18,7 +18,7 @@ ContextualConfig is a Ruby gem for context-aware configuration management built
18
18
 
19
19
  ### 1. Core Concerns (lib/contextual_config/concern/)
20
20
 
21
- - **Configurable**: Provides validations, scopes, and basic configuration functionality. Expects database columns: `key`, `config_data` (jsonb), `scoping_rules` (jsonb), `priority` (integer), `is_active` (boolean)
21
+ - **Configurable**: Provides validations, scopes, and basic configuration functionality. Expects database columns: `key`, `config_data` (jsonb), `scoping_rules` (jsonb), `priority` (integer), `deleted_at` (datetime)
22
22
  - **Lookupable**: Handles configuration lookup logic with `find_applicable_config(key:, context:)` and `find_all_applicable_configs(context:)`
23
23
  - **SchemaDrivenValidation**: Optional JSON schema validation for `config_data` and `scoping_rules`
24
24
 
@@ -51,7 +51,7 @@ t.string :key, null: false
51
51
  t.jsonb :config_data, null: false, default: {}
52
52
  t.jsonb :scoping_rules, null: false, default: {}
53
53
  t.integer :priority, null: false, default: 100
54
- t.boolean :is_active, null: false, default: true
54
+ t.datetime :deleted_at
55
55
  t.text :description
56
56
  t.string :type # For STI if needed
57
57
  ```
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in contextual_config.gemspec
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- contextual_config (0.1.0)
4
+ contextual_config (0.3.0)
5
5
  activerecord (>= 6.0)
6
6
  activesupport (>= 6.0)
7
7
  json-schema (~> 5.1)
data/README.md CHANGED
@@ -111,7 +111,7 @@ Each configuration record contains:
111
111
  - **config_data**: The actual configuration payload (JSONB)
112
112
  - **scoping_rules**: Rules defining when this config applies (JSONB)
113
113
  - **priority**: Lower numbers = higher priority (integer)
114
- - **is_active**: Enable/disable configurations (boolean)
114
+ - **deleted_at**: Soft delete timestamp (datetime, null for active)
115
115
  - **type**: For Single Table Inheritance (string, optional)
116
116
 
117
117
  ### Context Matching
@@ -229,7 +229,7 @@ def change
229
229
  t.jsonb :config_data, null: false, default: {}
230
230
  t.jsonb :scoping_rules, null: false, default: {}
231
231
  t.integer :priority, null: false, default: 100
232
- t.boolean :is_active, null: false, default: true
232
+ t.datetime :deleted_at
233
233
  t.text :description
234
234
  t.string :type
235
235
 
@@ -665,21 +665,71 @@ Benefits::InsuranceConfig.create!(
665
665
 
666
666
  ## Performance Considerations
667
667
 
668
+ ### Caching System
669
+
670
+ ContextualConfig includes a sophisticated two-level caching system for optimal performance:
671
+
672
+ #### 1. Candidates Cache
673
+ Caches the database query results (list of potential configurations) to avoid repeated database hits for the same key/context combinations.
674
+
675
+ #### 2. Results Cache
676
+ Caches the final matched configuration after processing all matching logic, providing the fastest possible lookups for repeated requests.
677
+
678
+ ### Cache Configuration
679
+
680
+ ```ruby
681
+ # Enable caching globally
682
+ ContextualConfig.configure do |config|
683
+ config.cache_enabled = true
684
+ config.cache_ttl = 300 # 5 minutes
685
+ config.cache_store = Rails.cache
686
+ end
687
+
688
+ # Fine-grained cache control in your models
689
+ class CustomConfig < ApplicationRecord
690
+ include ContextualConfig::Concern::Configurable
691
+ include ContextualConfig::Concern::Lookupable
692
+
693
+ protected
694
+
695
+ # Override to disable candidate caching for specific scenarios
696
+ def self.cache_candidates?(key:, context:)
697
+ # Only cache for stable contexts, not time-sensitive ones
698
+ !context.key?(:current_timestamp)
699
+ end
700
+
701
+ # Override to disable result caching for specific scenarios
702
+ def self.cache_results?(key:, context:)
703
+ # Cache results except for admin users who need real-time data
704
+ context[:user_role] != 'admin'
705
+ end
706
+
707
+ # Override to customize candidate selection
708
+ def self.lookup_candidates(key:, context:)
709
+ # Custom logic for fetching candidates
710
+ active.where(key: key.to_s, department: context[:department]).order_by_priority
711
+ end
712
+ end
713
+ ```
714
+
668
715
  ### Benchmarks
669
716
 
670
717
  Based on testing with the JisrHR application:
671
718
 
672
- - **Average lookup time**: ~3ms per configuration lookup
673
- - **100 concurrent lookups**: Completed in ~0.3 seconds
719
+ - **Average lookup time**: ~3ms per configuration lookup (uncached)
720
+ - **Cached lookup time**: ~0.1ms per configuration lookup
721
+ - **100 concurrent lookups**: Completed in ~0.3 seconds (uncached), ~0.03 seconds (cached)
674
722
  - **Database indexes**: Automatically created by the generator for optimal performance
675
- - **Memory usage**: Minimal - configurations are loaded on-demand
723
+ - **Memory usage**: Minimal - configurations are loaded on-demand with intelligent caching
676
724
 
677
725
  ### Optimization Tips
678
726
 
679
- 1. **Use appropriate indexes**: The generator creates optimal indexes for common queries
680
- 2. **Cache frequently accessed configurations**: Consider caching at the application level for high-traffic scenarios
681
- 3. **Limit scoping rule complexity**: Simpler rules = faster matching
682
- 4. **Use database-level constraints**: Leverage PostgreSQL JSONB indexes for complex queries
727
+ 1. **Enable caching**: Use the built-in two-level caching system for production deployments
728
+ 2. **Use appropriate indexes**: The generator creates optimal indexes for common queries
729
+ 3. **Customize cache behavior**: Override cache control methods for fine-grained performance tuning
730
+ 4. **Limit scoping rule complexity**: Simpler rules = faster matching
731
+ 5. **Use database-level constraints**: Leverage PostgreSQL JSONB indexes for complex queries
732
+ 6. **Monitor cache hit rates**: Use logging to monitor cache effectiveness
683
733
 
684
734
  ### Production Deployment
685
735
 
@@ -740,6 +790,13 @@ Provides configuration lookup methods:
740
790
  - `find_applicable_config(key:, context:)` - Find best match
741
791
  - `find_all_applicable_configs(context:)` - Find all matches
742
792
 
793
+ **Protected Methods for Customization:**
794
+
795
+ - `lookup_candidates(key:, context:)` - Override to customize candidate selection for specific keys
796
+ - `lookup_all_candidates(context:)` - Override to customize candidate selection for all configurations
797
+ - `cache_candidates?(key:, context:)` - Override to control candidate caching behavior
798
+ - `cache_results?(key:, context:)` - Override to control result caching behavior
799
+
743
800
  #### ContextualConfig::Concern::SchemaDrivenValidation
744
801
 
745
802
  Provides JSON schema validation:
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rspec/core/rake_task'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'lib/contextual_config/version'
2
4
 
3
5
  Gem::Specification.new do |spec|
@@ -10,7 +12,7 @@ Gem::Specification.new do |spec|
10
12
  spec.description = 'ContextualConfig provides a flexible framework for managing configurations that can be applied based on contextual rules, priorities, and scoping. Perfect for complex applications requiring dynamic configuration resolution.'
11
13
  spec.homepage = 'https://github.com/bazinga012/contextual_config'
12
14
  spec.license = 'MIT'
13
- spec.required_ruby_version = '>= 3.0.0' # rubocop:disable Gemspec/RequiredRubyVersion
15
+ spec.required_ruby_version = '>= 3.0.0'
14
16
 
15
17
  spec.metadata['allowed_push_host'] = 'https://rubygems.org'
16
18
  spec.metadata['homepage_uri'] = spec.homepage
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/concern'
2
4
 
3
5
  module ContextualConfig
@@ -13,7 +15,7 @@ module ContextualConfig
13
15
  # - config_data:jsonb
14
16
  # - scoping_rules:jsonb
15
17
  # - priority:integer (default should be a higher number for lower priority)
16
- # - is_active:boolean (default should be true)
18
+ # - deleted_at:datetime (null for active records)
17
19
  # - type:string (if STI is to be used on the including model)
18
20
  # - created_at:datetime
19
21
  # - updated_at:datetime
@@ -33,9 +35,6 @@ module ContextualConfig
33
35
  # It's often better to add this uniqueness validation directly in the consuming model
34
36
  # where the scope is clearer.
35
37
 
36
- # Ensures 'is_active' is either true or false.
37
- validates :is_active, inclusion: { in: [true, false], message: 'is not included in the list' }
38
-
39
38
  # Ensures 'priority' is an integer.
40
39
  validates :priority, numericality: { only_integer: true, message: 'is not a number' }
41
40
 
@@ -43,7 +42,7 @@ module ContextualConfig
43
42
 
44
43
  # Scope to retrieve only active configurations.
45
44
  # Usage: YourModel.active
46
- scope :active, -> { where(is_active: true) }
45
+ scope :active, -> { where(deleted_at: nil) }
47
46
 
48
47
  # Scope to order configurations by priority.
49
48
  # Lower numbers indicate higher priority. Secondary sort by ID for deterministic ordering.
@@ -51,9 +50,8 @@ module ContextualConfig
51
50
  scope :order_by_priority, -> { order(priority: :asc, id: :asc) } # 'asc' for priority means 1 is higher than 10
52
51
 
53
52
  # --- Default Values ---
54
- # It's generally recommended to set database-level defaults for `is_active` (true)
55
- # and `priority` (e.g., a high number like 100 for low priority).
56
- # If not set at DB level, you can use `after_initialize` here, but DB defaults are safer.
53
+ # It's generally recommended to set database-level defaults for `priority`
54
+ # (e.g., a high number like 100 for low priority). `deleted_at` defaults to NULL for active records.
57
55
  after_initialize :set_default_values, if: :new_record?
58
56
  end
59
57
 
@@ -63,10 +61,8 @@ module ContextualConfig
63
61
 
64
62
  def set_default_values
65
63
  # Only set defaults if the model is completely new (not loaded from DB and no explicit values set)
66
- if new_record? && !attributes_before_type_cast.key?('is_active')
67
- self.is_active = true
68
- end
69
-
64
+ # deleted_at defaults to NULL (active), no need to set explicitly
65
+
70
66
  # Override database default priority with module-specific priority if available
71
67
  set_module_default_priority if should_use_module_default_priority?
72
68
  end
@@ -100,7 +96,7 @@ module ContextualConfig
100
96
 
101
97
  # Helper to quickly check if a configuration is currently active
102
98
  def active?
103
- is_active
99
+ deleted_at.nil?
104
100
  end
105
101
 
106
102
  # Get the module configuration for this model if available
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/concern'
2
4
  require 'digest'
3
5
 
@@ -17,18 +19,17 @@ module ContextualConfig
17
19
  # and fetches only those.
18
20
  # @return [ActiveRecord::Base, nil] The instance of the best matching configuration, or nil if no match.
19
21
  def find_applicable_config(key:, context:, fetch_all_of_key: false) # rubocop:disable Lint/UnusedMethodArgument
20
- # Check cache first if enabled
21
- if ContextualConfig.configuration.cache_enabled?
22
+ # Check result cache first if enabled
23
+ if cache_results?(key: key, context: context)
24
+ cache_key = generate_cache_key(key, context)
22
25
  begin
23
- cache_key = generate_cache_key(key, context)
24
26
  cached_result = ContextualConfig.configuration.cache_store.read(cache_key)
25
27
  if cached_result
26
- log_lookup_attempt(key, context, 'cache_hit')
28
+ log_lookup_attempt(key, context, 'result_cache_hit')
27
29
  return cached_result
28
30
  end
29
- rescue => e
30
- # Log cache error but continue with database lookup
31
- log_cache_error('read', e)
31
+ rescue StandardError => e
32
+ log_lookup_attempt(key, context, "result_cache_error: #{e.message}")
32
33
  end
33
34
  end
34
35
 
@@ -37,13 +38,41 @@ module ContextualConfig
37
38
 
38
39
  # Start with active configurations, ordered by priority (higher priority first).
39
40
  # The `Configurable` concern is expected to provide `active` and `order_by_priority` scopes.
40
- unless self.respond_to?(:active) && self.respond_to?(:order_by_priority)
41
- raise(NoMethodError, "#{self.name} must include ContextualConfig::Concern::Configurable to use Lookupable.")
41
+ unless respond_to?(:active) && respond_to?(:order_by_priority)
42
+ raise(NoMethodError, "#{name} must include ContextualConfig::Concern::Configurable to use Lookupable.")
42
43
  end
43
44
 
44
- log_lookup_attempt(key, context, 'database_lookup')
45
+ # Check candidates cache if enabled
46
+ candidates = nil
47
+ if cache_candidates?(key: key, context: context)
48
+ candidates_cache_key = generate_candidates_cache_key(key, context)
49
+ begin
50
+ candidates = ContextualConfig.configuration.cache_store.read(candidates_cache_key)
51
+ log_lookup_attempt(key, context, 'candidates_cache_hit') if candidates
52
+ rescue StandardError => e
53
+ log_lookup_attempt(key, context, "candidates_cache_error: #{e.message}")
54
+ end
55
+ end
45
56
 
46
- candidates = self.active.where(key: key.to_s).order_by_priority
57
+ # Fetch candidates from database if not cached
58
+ if candidates.nil?
59
+ log_lookup_attempt(key, context, 'database_lookup')
60
+ candidates = lookup_candidates(key: key, context: context)
61
+ # Cache candidates if enabled
62
+ if cache_candidates?(key: key, context: context)
63
+ candidates_cache_key = generate_candidates_cache_key(key, context)
64
+ begin
65
+ ContextualConfig.configuration.cache_store.write(
66
+ candidates_cache_key,
67
+ candidates.to_a, # Convert to array to avoid caching AR relations
68
+ expires_in: ContextualConfig.configuration.cache_ttl
69
+ )
70
+ log_lookup_attempt(key, context, 'candidates_cache_stored')
71
+ rescue StandardError => e
72
+ log_lookup_attempt(key, context, "candidates_cache_write_error: #{e.message}")
73
+ end
74
+ end
75
+ end
47
76
 
48
77
  # Delegate to the ContextualMatcher service to find the best match from the candidates.
49
78
  # The service will handle the logic of iterating through candidates, evaluating scoping_rules,
@@ -54,18 +83,17 @@ module ContextualConfig
54
83
  )
55
84
 
56
85
  # Cache the result if caching is enabled
57
- if ContextualConfig.configuration.cache_enabled? && result
86
+ if cache_results?(key: key, context: context) && result
87
+ cache_key = generate_cache_key(key, context)
58
88
  begin
59
- cache_key = generate_cache_key(key, context)
60
89
  ContextualConfig.configuration.cache_store.write(
61
90
  cache_key,
62
91
  result,
63
92
  expires_in: ContextualConfig.configuration.cache_ttl
64
93
  )
65
- log_lookup_attempt(key, context, 'cache_stored')
66
- rescue => e
67
- # Log cache error but don't fail the operation
68
- log_cache_error('write', e)
94
+ log_lookup_attempt(key, context, 'result_cache_stored')
95
+ rescue StandardError => e
96
+ log_lookup_attempt(key, context, "result_cache_write_error: #{e.message}")
69
97
  end
70
98
  end
71
99
 
@@ -80,14 +108,14 @@ module ContextualConfig
80
108
  # @param context [Hash] A hash representing the current context.
81
109
  # @return [Array<ActiveRecord::Base>] An array of all matching configuration instances, ordered by priority.
82
110
  def find_all_applicable_configs(context:)
83
- unless self.respond_to?(:active) && self.respond_to?(:order_by_priority)
84
- raise(NoMethodError, "#{self.name} must include ContextualConfig::Concern::Configurable to use Lookupable.")
111
+ unless respond_to?(:active) && respond_to?(:order_by_priority)
112
+ raise(NoMethodError, "#{name} must include ContextualConfig::Concern::Configurable to use Lookupable.")
85
113
  end
86
114
 
87
115
  log_lookup_attempt('all_configs', context, 'all_configs_lookup')
88
116
 
89
117
  # Fetch all active configurations of this type, ordered by priority.
90
- all_candidates = self.active.order_by_priority
118
+ all_candidates = lookup_all_candidates(context: context)
91
119
 
92
120
  # Delegate to the ContextualMatcher service.
93
121
  ContextualConfig::Services::ContextualMatcher.find_all_matches(
@@ -96,12 +124,60 @@ module ContextualConfig
96
124
  )
97
125
  end
98
126
 
127
+ protected
128
+
129
+ # Override this method in your configuration class to customize candidate selection
130
+ # for specific key lookups. This allows fine-grained control over which configurations
131
+ # are considered for matching.
132
+ #
133
+ # @param key [String, Symbol] The configuration key to look for
134
+ # @param context [Hash] The lookup context
135
+ # @return [ActiveRecord::Relation] The candidates for matching
136
+ def lookup_candidates(key:, context:) # rubocop:disable Lint/UnusedMethodArgument
137
+ active.where(key: key.to_s).order_by_priority
138
+ end
139
+
140
+ # Override this method in your configuration class to customize candidate selection
141
+ # for all configurations (used by find_all_applicable_configs).
142
+ #
143
+ # @param context [Hash] The lookup context
144
+ # @return [ActiveRecord::Relation] All candidates for matching
145
+ def lookup_all_candidates(context:) # rubocop:disable Lint/UnusedMethodArgument
146
+ active.order_by_priority
147
+ end
148
+
149
+ # Override this method to control whether candidates should be cached.
150
+ # By default, candidates are cached when caching is enabled globally.
151
+ #
152
+ # @param key [String, Symbol] The configuration key
153
+ # @param context [Hash] The lookup context
154
+ # @return [Boolean] Whether to cache candidates for this lookup
155
+ def cache_candidates?(key:, context:) # rubocop:disable Lint/UnusedMethodArgument
156
+ ContextualConfig.configuration.cache_enabled?
157
+ end
158
+
159
+ # Override this method to control whether results should be cached.
160
+ # By default, results are cached when caching is enabled globally.
161
+ #
162
+ # @param key [String, Symbol] The configuration key
163
+ # @param context [Hash] The lookup context
164
+ # @return [Boolean] Whether to cache the final result for this lookup
165
+ def cache_results?(key:, context:) # rubocop:disable Lint/UnusedMethodArgument
166
+ ContextualConfig.configuration.cache_enabled?
167
+ end
168
+
99
169
  private
100
170
 
101
171
  # Generate cache key for configuration lookup
102
172
  def generate_cache_key(key, context)
103
173
  context_hash = context.is_a?(Hash) ? context.sort.to_h : context
104
- "contextual_config:#{self.name}:#{key}:#{Digest::MD5.hexdigest(context_hash.to_s)}"
174
+ "contextual_config:#{name}:#{key}:#{Digest::MD5.hexdigest(context_hash.to_s)}"
175
+ end
176
+
177
+ # Generate cache key for candidates
178
+ def generate_candidates_cache_key(key, context)
179
+ context_hash = context.is_a?(Hash) ? context.sort.to_h : context
180
+ "contextual_config_candidates:#{name}:#{key}:#{Digest::MD5.hexdigest(context_hash.to_s)}"
105
181
  end
106
182
 
107
183
  # Log configuration lookup attempts
@@ -110,17 +186,7 @@ module ContextualConfig
110
186
 
111
187
  logger = ContextualConfig.configuration.effective_logger
112
188
  logger.info(
113
- "ContextualConfig::Lookupable: #{operation} for #{self.name} - Key: #{key}, Context: #{context}"
114
- )
115
- end
116
-
117
- # Log cache errors
118
- def log_cache_error(operation, error)
119
- return unless ContextualConfig.configuration.enable_logging?
120
-
121
- logger = ContextualConfig.configuration.effective_logger
122
- logger.warn(
123
- "ContextualConfig::Lookupable: Cache #{operation} error - #{error.class}: #{error.message}"
189
+ "ContextualConfig::Lookupable: #{operation} for #{name} - Key: #{key}, Context: #{context}"
124
190
  )
125
191
  end
126
192
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/concern'
2
4
  require 'json-schema'
3
5
 
@@ -42,7 +44,7 @@ module ContextualConfig
42
44
 
43
45
  begin
44
46
  # Using fully_validate to get an array of all errors.
45
- errors_found = JSON::Validator.fully_validate(schema, self.config_data, strict: false, insert_defaults: false)
47
+ errors_found = JSON::Validator.fully_validate(schema, config_data, strict: false, insert_defaults: false)
46
48
  if errors_found.any?
47
49
  errors_found.each do |error_message|
48
50
  # Add a somewhat generic error. More specific parsing of error_message could provide better field-level errors.
@@ -67,7 +69,8 @@ module ContextualConfig
67
69
  return unless schema # Skip if no schema defined
68
70
 
69
71
  begin
70
- errors_found = JSON::Validator.fully_validate(schema, self.scoping_rules, strict: false, insert_defaults: false)
72
+ errors_found = JSON::Validator.fully_validate(schema, scoping_rules, strict: false,
73
+ insert_defaults: false)
71
74
  if errors_found.any?
72
75
  errors_found.each do |error_message|
73
76
  errors.add(:scoping_rules, "is invalid - #{error_message}")
@@ -55,13 +55,9 @@ module ContextualConfig
55
55
  raise(ConfigurationError, 'Cache is enabled but cache_store is not properly configured')
56
56
  end
57
57
 
58
- if cache_ttl <= 0
59
- raise(ConfigurationError, 'cache_ttl must be a positive number')
60
- end
58
+ raise(ConfigurationError, 'cache_ttl must be a positive number') if cache_ttl <= 0
61
59
 
62
- if default_priority.negative?
63
- raise(ConfigurationError, 'default_priority must be non-negative')
64
- end
60
+ raise(ConfigurationError, 'default_priority must be non-negative') if default_priority.negative?
65
61
 
66
62
  true
67
63
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rails/generators'
2
4
 
3
5
  # Load all generators
@@ -10,7 +10,7 @@ module ContextualConfig
10
10
  end
11
11
 
12
12
  # Register a module with its configuration
13
- def register(module_name, &block)
13
+ def register(module_name)
14
14
  module_sym = module_name.to_sym
15
15
  config = @modules[module_sym] || ModuleConfig.new(module_name)
16
16
  yield(config) if block_given?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'date'
2
4
 
3
5
  module ContextualConfig
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ContextualConfig
2
- VERSION = '0.1.0'.freeze
4
+ VERSION = '0.3.0'
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'contextual_config/version'
2
4
  require 'active_support'
3
5
  require 'active_record'
@@ -66,6 +68,4 @@ module ContextualConfig
66
68
  end
67
69
 
68
70
  # Load Rails generators if Rails is available
69
- if defined?(Rails)
70
- require 'contextual_config/generators'
71
- end
71
+ require 'contextual_config/generators' if defined?(Rails)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rails/generators/active_record'
2
4
 
3
5
  module ContextualConfig
@@ -18,8 +18,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
18
18
  # Lower numbers generally indicate higher priority.
19
19
  t.integer :priority, null: false, default: 100
20
20
 
21
- # 'is_active' allows for soft-deleting or disabling configurations.
22
- t.boolean :is_active, null: false, default: true
21
+ # 'deleted_at' allows for soft-deleting configurations.
22
+ # NULL indicates active configuration, timestamp indicates soft-deleted.
23
+ t.datetime :deleted_at
23
24
 
24
25
  # 'description' provides a human-readable summary of the configuration's purpose.
25
26
  t.text :description
@@ -41,8 +42,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
41
42
  # Note: If 'type' is not used or key should be globally unique for this table,
42
43
  # a simpler index might be `add_index :<%= @migration_table_name %>, :key, unique: true`
43
44
 
44
- # Index to efficiently query active configurations by priority.
45
- add_index :<%= @migration_table_name %>, [:is_active, :priority], name: 'idx_<%= @migration_table_name.first(40) %>_on_active_priority'
45
+ # Functional index to efficiently query active configurations by priority.
46
+ # Uses expression index on (deleted_at IS NULL) for optimal soft-delete performance.
47
+ add_index :<%= @migration_table_name %>, ['(deleted_at IS NULL)', :priority], name: 'idx_<%= @migration_table_name.first(40) %>_on_active_priority'
46
48
 
47
49
  # Optional: GIN indexes for jsonb columns if you frequently query directly into them.
48
50
  # These can be large and have write performance implications, so add if needed.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contextual_config
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - bazinga012
@@ -162,7 +162,6 @@ files:
162
162
  - lib/contextual_config/module_registry.rb
163
163
  - lib/contextual_config/services/contextual_matcher.rb
164
164
  - lib/contextual_config/version.rb
165
- - lib/generators/contextual_config/configurable_table/USAGE
166
165
  - lib/generators/contextual_config/configurable_table/configurable_table_generator.rb
167
166
  - lib/generators/contextual_config/configurable_table/templates/migration.rb.tt
168
167
  homepage: https://github.com/bazinga012/contextual_config
@@ -1,33 +0,0 @@
1
- Description:
2
- Generates a migration file for a table that's compatible with ContextualConfig concerns.
3
- The table includes all necessary columns for storing contextual configurations including
4
- key, config_data (JSONB), scoping_rules (JSONB), priority, is_active, and type (for STI).
5
-
6
- Example:
7
- ```rails generate contextual_config:configurable_table YourModel```
8
-
9
- This will create:
10
- db/migrate/create_your_models.rb
11
-
12
- Options:
13
- --table-name=TABLE_NAME # Override the default table name
14
- # Default: pluralized, underscored version of the model name
15
-
16
- Examples:
17
- rails generate contextual_config:configurable_table Finance::Configuration
18
- rails generate contextual_config:configurable_table UserPreference --table-name=user_prefs
19
- rails generate contextual_config:configurable_table PayrollConfig
20
-
21
- The generated migration creates a table with:
22
- - key (string): Identifier for the configuration
23
- - config_data (jsonb): Main configuration payload
24
- - scoping_rules (jsonb): Rules for when configuration applies
25
- - priority (integer): Priority for conflict resolution
26
- - is_active (boolean): Enable/disable configurations
27
- - description (text): Human-readable description
28
- - type (string): For Single Table Inheritance
29
- - timestamps: created_at and updated_at
30
-
31
- Indexes are automatically created for:
32
- - [type, key] (unique)
33
- - [is_active, priority] (for efficient querying)