contextual_config 0.1.0 → 0.2.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: ca628e95f0ec561248db3eb04ce08f9192784c6745b2a8a3b0679a8a2d47cfe1
4
+ data.tar.gz: 0b4c30ef65f92b82cb0746b68067d85324ce4154b1db035b286db52802af5307
5
5
  SHA512:
6
- metadata.gz: 96eec3b206c9f1138954885d5ba7af8c4f64751677ce41061829953278dd3e71c9f5f3f18de2a159b61259a7fae06c976b5c6dcfda0d04e99d30d85e62cb6f5b
7
- data.tar.gz: 11bb322511c97d471442f84949f182758c97b45dba6938c59e03c7bbb9160bc2163f15565ba71a81d8a0e26710941149d882e1eee3a4a8ad44df15efd29468c9
6
+ metadata.gz: a62db7b905b29b4d5a04bd9bf1a090426ac81276ca4c555c14fa90d932e98cc97d8bbfe25789a026a98ea48d5efd5b98a4459946ffe2d8c62d7574f2a16a85ac
7
+ data.tar.gz: 349f24f5d3a87beb861b0d063349ab9fb1372d398d9aa5abe00c0f75f1d7dafbf6956659cd1de88fa86c314354199fcb44cf78d2a161a4f2d1b937d20c298f5e
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.2.0)
5
5
  activerecord (>= 6.0)
6
6
  activesupport (>= 6.0)
7
7
  json-schema (~> 5.1)
data/README.md CHANGED
@@ -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:
@@ -17,18 +17,17 @@ module ContextualConfig
17
17
  # and fetches only those.
18
18
  # @return [ActiveRecord::Base, nil] The instance of the best matching configuration, or nil if no match.
19
19
  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?
20
+ # Check result cache first if enabled
21
+ if cache_results?(key: key, context: context)
22
+ cache_key = generate_cache_key(key, context)
22
23
  begin
23
- cache_key = generate_cache_key(key, context)
24
24
  cached_result = ContextualConfig.configuration.cache_store.read(cache_key)
25
25
  if cached_result
26
- log_lookup_attempt(key, context, 'cache_hit')
26
+ log_lookup_attempt(key, context, 'result_cache_hit')
27
27
  return cached_result
28
28
  end
29
- rescue => e
30
- # Log cache error but continue with database lookup
31
- log_cache_error('read', e)
29
+ rescue StandardError => e
30
+ log_lookup_attempt(key, context, "result_cache_error: #{e.message}")
32
31
  end
33
32
  end
34
33
 
@@ -41,9 +40,39 @@ module ContextualConfig
41
40
  raise(NoMethodError, "#{self.name} must include ContextualConfig::Concern::Configurable to use Lookupable.")
42
41
  end
43
42
 
44
- log_lookup_attempt(key, context, 'database_lookup')
43
+ # Check candidates cache if enabled
44
+ candidates = nil
45
+ if cache_candidates?(key: key, context: context)
46
+ candidates_cache_key = generate_candidates_cache_key(key, context)
47
+ begin
48
+ candidates = ContextualConfig.configuration.cache_store.read(candidates_cache_key)
49
+ if candidates
50
+ log_lookup_attempt(key, context, 'candidates_cache_hit')
51
+ end
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
 
@@ -87,7 +115,7 @@ module ContextualConfig
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,6 +124,48 @@ 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
@@ -104,6 +174,12 @@ module ContextualConfig
104
174
  "contextual_config:#{self.name}:#{key}:#{Digest::MD5.hexdigest(context_hash.to_s)}"
105
175
  end
106
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:#{self.name}:#{key}:#{Digest::MD5.hexdigest(context_hash.to_s)}"
181
+ end
182
+
107
183
  # Log configuration lookup attempts
108
184
  def log_lookup_attempt(key, context, operation)
109
185
  return unless ContextualConfig.configuration.enable_logging?
@@ -113,16 +189,6 @@ module ContextualConfig
113
189
  "ContextualConfig::Lookupable: #{operation} for #{self.name} - Key: #{key}, Context: #{context}"
114
190
  )
115
191
  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}"
124
- )
125
- end
126
192
  end
127
193
  end
128
194
  end
@@ -1,3 +1,3 @@
1
1
  module ContextualConfig
2
- VERSION = '0.1.0'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
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.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - bazinga012