contextual_config 0.1.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.
data/README.md ADDED
@@ -0,0 +1,782 @@
1
+ # ContextualConfig
2
+
3
+ A Ruby gem for context-aware configuration management. 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.
4
+
5
+ ## Features
6
+
7
+ - **Context-Aware Matching**: Define rules to match configurations based on runtime context
8
+ - **Priority-Based Resolution**: Handle conflicts through configurable priority systems
9
+ - **Schema Validation**: Optional JSON schema validation for configuration data
10
+ - **Rails Integration**: Built-in Rails generators and ActiveRecord concerns
11
+ - **Flexible Scoping**: Support for complex scoping rules including timing constraints
12
+ - **STI Support**: Single Table Inheritance support for different configuration types
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ gem 'contextual_config'
20
+ ```
21
+
22
+ And then execute:
23
+
24
+ $ bundle install
25
+
26
+ Or install it yourself as:
27
+
28
+ $ gem install contextual_config
29
+
30
+ ## Quick Start
31
+
32
+ ### 1. Generate a Configuration Table
33
+
34
+ ```bash
35
+ rails generate contextual_config:configurable_table YourConfig
36
+ rails db:migrate
37
+ ```
38
+
39
+ ### 2. Create Your Model
40
+
41
+ ```ruby
42
+ class YourConfig < ApplicationRecord
43
+ include ContextualConfig::Concern::Configurable
44
+ include ContextualConfig::Concern::Lookupable
45
+ include ContextualConfig::Concern::SchemaDrivenValidation # Optional
46
+ end
47
+ ```
48
+
49
+ ### 3. Create Configuration Records
50
+
51
+ ```ruby
52
+ # Global configuration
53
+ YourConfig.create!(
54
+ key: "notification_settings",
55
+ config_data: { email_enabled: true, sms_enabled: false },
56
+ scoping_rules: {}, # Empty rules = applies to everyone
57
+ priority: 100
58
+ )
59
+
60
+ # Department-specific override
61
+ YourConfig.create!(
62
+ key: "notification_settings",
63
+ config_data: { email_enabled: true, sms_enabled: true },
64
+ scoping_rules: { department_id: "engineering" },
65
+ priority: 50 # Higher priority (lower number)
66
+ )
67
+
68
+ # Time-based configuration
69
+ YourConfig.create!(
70
+ key: "notification_settings",
71
+ config_data: { email_enabled: false, sms_enabled: false },
72
+ scoping_rules: {
73
+ timing: {
74
+ start_date: "2024-12-25",
75
+ end_date: "2024-12-26"
76
+ }
77
+ },
78
+ priority: 10 # Highest priority
79
+ )
80
+ ```
81
+
82
+ ### 4. Lookup Configurations
83
+
84
+ ```ruby
85
+ # Find best matching configuration
86
+ context = {
87
+ department_id: "engineering",
88
+ current_date: Date.today
89
+ }
90
+
91
+ config = YourConfig.find_applicable_config(
92
+ key: "notification_settings",
93
+ context: context
94
+ )
95
+
96
+ # Use the configuration
97
+ if config
98
+ settings = config.config_data
99
+ puts "Email enabled: #{settings['email_enabled']}"
100
+ puts "SMS enabled: #{settings['sms_enabled']}"
101
+ end
102
+ ```
103
+
104
+ ## Core Concepts
105
+
106
+ ### Configuration Structure
107
+
108
+ Each configuration record contains:
109
+
110
+ - **key**: Identifier for the configuration type
111
+ - **config_data**: The actual configuration payload (JSONB)
112
+ - **scoping_rules**: Rules defining when this config applies (JSONB)
113
+ - **priority**: Lower numbers = higher priority (integer)
114
+ - **is_active**: Enable/disable configurations (boolean)
115
+ - **type**: For Single Table Inheritance (string, optional)
116
+
117
+ ### Context Matching
118
+
119
+ Configurations are matched based on context:
120
+
121
+ ```ruby
122
+ context = {
123
+ department_id: "sales",
124
+ employee_level: "senior",
125
+ current_date: Date.today,
126
+ location_country: "US"
127
+ }
128
+
129
+ # This will match configs where scoping_rules contain matching values
130
+ config = YourConfig.find_applicable_config(
131
+ key: "expense_policy",
132
+ context: context
133
+ )
134
+ ```
135
+
136
+ ### Scoping Rules
137
+
138
+ Scoping rules define when configurations apply:
139
+
140
+ ```ruby
141
+ # Simple department scoping
142
+ scoping_rules: { department_id: "engineering" }
143
+
144
+ # Multiple criteria
145
+ scoping_rules: {
146
+ department_id: "sales",
147
+ employee_level: "senior"
148
+ }
149
+
150
+ # Time-based rules
151
+ scoping_rules: {
152
+ timing: {
153
+ start_date: "2024-01-01",
154
+ end_date: "2024-12-31"
155
+ }
156
+ }
157
+
158
+ # Global rule (matches everything)
159
+ scoping_rules: {}
160
+ ```
161
+
162
+ ### Priority Resolution
163
+
164
+ When multiple configurations match:
165
+
166
+ 1. **Specificity**: More specific rules (more matching criteria) win
167
+ 2. **Priority**: Lower priority numbers win when specificity is equal
168
+ 3. **Order**: First match wins when priority and specificity are equal
169
+
170
+ ## Advanced Usage
171
+
172
+ ### Schema Validation
173
+
174
+ Define JSON schemas for your configuration data:
175
+
176
+ ```ruby
177
+ class PayrollConfig < ApplicationRecord
178
+ include ContextualConfig::Concern::Configurable
179
+ include ContextualConfig::Concern::Lookupable
180
+ include ContextualConfig::Concern::SchemaDrivenValidation
181
+
182
+ def self.resolve_config_data_schema(_instance = nil)
183
+ @_config_data_schema ||= {
184
+ "type" => "object",
185
+ "properties" => {
186
+ "base_salary" => { "type" => "number", "minimum" => 0 },
187
+ "currency" => { "type" => "string", "enum" => ["USD", "EUR", "SAR"] },
188
+ "overtime_rate" => { "type" => "number", "minimum" => 1.0 }
189
+ },
190
+ "required" => ["base_salary", "currency"]
191
+ }
192
+ end
193
+ end
194
+ ```
195
+
196
+ ### Custom Matchers
197
+
198
+ Extend the ContextualMatcher for custom logic:
199
+
200
+ ```ruby
201
+ # In your application
202
+ module MyApp
203
+ class CustomMatcher < ContextualConfig::Services::ContextualMatcher
204
+ private_class_method
205
+
206
+ def self.evaluate_location_rule(rule_value, context_value)
207
+ # Custom location matching logic
208
+ allowed_locations = rule_value.is_a?(Array) ? rule_value : [rule_value]
209
+ allowed_locations.include?(context_value)
210
+ end
211
+ end
212
+ end
213
+ ```
214
+
215
+ ### Module-Specific Extensions
216
+
217
+ ContextualConfig is designed to be highly extensible for modular applications. Modules can add their own fields and customize the gem's behavior in several ways:
218
+
219
+ #### 1. Additional Database Columns
220
+
221
+ Add module-specific columns to your configuration table:
222
+
223
+ ```ruby
224
+ # In migration
225
+ def change
226
+ create_table :configurations do |t|
227
+ # Standard ContextualConfig columns (from generator)
228
+ t.string :key, null: false
229
+ t.jsonb :config_data, null: false, default: {}
230
+ t.jsonb :scoping_rules, null: false, default: {}
231
+ t.integer :priority, null: false, default: 100
232
+ t.boolean :is_active, null: false, default: true
233
+ t.text :description
234
+ t.string :type
235
+
236
+ # Custom module-specific columns
237
+ t.string :module_name
238
+ t.string :created_by_user_id
239
+ t.timestamp :effective_from
240
+ t.timestamp :expires_at
241
+ t.text :approval_notes
242
+ t.jsonb :audit_log, default: {}
243
+
244
+ t.timestamps null: false
245
+ end
246
+ end
247
+ ```
248
+
249
+ #### 2. Module-Specific Configuration Models
250
+
251
+ Create specialized models for different modules using STI:
252
+
253
+ ```ruby
254
+ # Base configuration
255
+ class BaseConfiguration < ApplicationRecord
256
+ include ContextualConfig::Concern::Configurable
257
+ include ContextualConfig::Concern::Lookupable
258
+ include ContextualConfig::Concern::SchemaDrivenValidation
259
+ end
260
+
261
+ # HR Module configurations
262
+ class HR::PolicyConfiguration < BaseConfiguration
263
+ def self.resolve_config_data_schema(_instance = nil)
264
+ {
265
+ 'type' => 'object',
266
+ 'properties' => {
267
+ 'leave_approval_workflow' => { 'type' => 'string' },
268
+ 'probation_period_days' => { 'type' => 'integer', 'minimum' => 30, 'maximum' => 365 },
269
+ 'performance_review_frequency' => { 'type' => 'string', 'enum' => ['quarterly', 'biannual', 'annual'] }
270
+ }
271
+ }
272
+ end
273
+
274
+ def requires_manager_approval?
275
+ config_data['leave_approval_workflow'] == 'manager_required'
276
+ end
277
+ end
278
+
279
+ # Finance Module configurations
280
+ class Finance::PayrollConfiguration < BaseConfiguration
281
+ def self.resolve_config_data_schema(_instance = nil)
282
+ {
283
+ 'type' => 'object',
284
+ 'properties' => {
285
+ 'overtime_rate' => { 'type' => 'number', 'minimum' => 1.0, 'maximum' => 3.0 },
286
+ 'tax_calculation_method' => { 'type' => 'string', 'enum' => ['standard', 'accelerated', 'deferred'] },
287
+ 'accounting_code' => { 'type' => 'string' },
288
+ 'cost_center_allocation' => {
289
+ 'type' => 'object',
290
+ 'properties' => {
291
+ 'department_percentage' => { 'type' => 'number' },
292
+ 'project_percentage' => { 'type' => 'number' }
293
+ }
294
+ }
295
+ }
296
+ }
297
+ end
298
+
299
+ def calculate_overtime_pay(base_salary, hours)
300
+ base_rate = base_salary / 160 # assuming 160 hours per month
301
+ overtime_multiplier = config_data['overtime_rate'] || 1.5
302
+ base_rate * overtime_multiplier * hours
303
+ end
304
+ end
305
+
306
+ # Benefits Module configurations
307
+ class Benefits::InsuranceConfiguration < BaseConfiguration
308
+ def self.resolve_scoping_rules_schema(_instance = nil)
309
+ {
310
+ 'type' => 'object',
311
+ 'properties' => {
312
+ # Standard scoping
313
+ 'department_id' => { 'type' => 'string' },
314
+ 'employee_level' => { 'type' => 'string' },
315
+
316
+ # Benefits-specific scoping
317
+ 'family_size' => { 'type' => 'integer', 'minimum' => 1, 'maximum' => 10 },
318
+ 'age_group' => { 'type' => 'string', 'enum' => ['under_30', '30_to_50', 'over_50'] },
319
+ 'employment_tenure_months' => { 'type' => 'integer', 'minimum' => 0 },
320
+ 'previous_claims_count' => { 'type' => 'integer', 'minimum' => 0 }
321
+ }
322
+ }
323
+ end
324
+
325
+ def self.find_for_employee_insurance(employee, family_size: 1)
326
+ context = {
327
+ department_id: employee.department_id.to_s,
328
+ employee_level: employee.level,
329
+ family_size: family_size,
330
+ age_group: employee.age_group,
331
+ employment_tenure_months: employee.tenure_in_months
332
+ }
333
+
334
+ find_applicable_config(key: 'insurance_policy', context: context)
335
+ end
336
+ end
337
+ ```
338
+
339
+ #### 3. Extended JSONB Schema
340
+
341
+ Modules can extend the `config_data` schema to include their specific fields:
342
+
343
+ ```ruby
344
+ class MultiModuleConfiguration < ApplicationRecord
345
+ include ContextualConfig::Concern::Configurable
346
+ include ContextualConfig::Concern::Lookupable
347
+ include ContextualConfig::Concern::SchemaDrivenValidation
348
+
349
+ def self.resolve_config_data_schema(_instance = nil)
350
+ {
351
+ 'type' => 'object',
352
+ 'properties' => {
353
+ # Core fields
354
+ 'description' => { 'type' => 'string' },
355
+
356
+ # HR Module fields
357
+ 'hr_approval_required' => { 'type' => 'boolean' },
358
+ 'hr_notification_emails' => {
359
+ 'type' => 'array',
360
+ 'items' => { 'type' => 'string', 'format' => 'email' }
361
+ },
362
+
363
+ # Finance Module fields
364
+ 'accounting_code' => { 'type' => 'string' },
365
+ 'budget_impact' => { 'type' => 'number' },
366
+
367
+ # Benefits Module fields
368
+ 'benefits_integration' => {
369
+ 'type' => 'object',
370
+ 'properties' => {
371
+ 'medical_coverage_level' => { 'type' => 'string', 'enum' => ['basic', 'premium', 'executive'] },
372
+ 'vacation_carryover_allowed' => { 'type' => 'boolean' }
373
+ }
374
+ },
375
+
376
+ # Attendance Module fields
377
+ 'attendance_tracking' => {
378
+ 'type' => 'object',
379
+ 'properties' => {
380
+ 'flexible_hours_enabled' => { 'type' => 'boolean' },
381
+ 'remote_work_percentage' => { 'type' => 'number', 'minimum' => 0, 'maximum' => 100 }
382
+ }
383
+ }
384
+ },
385
+ 'additionalProperties' => false
386
+ }
387
+ end
388
+ end
389
+ ```
390
+
391
+ #### 4. Module Registration Pattern
392
+
393
+ Create a registry system for modules:
394
+
395
+ ```ruby
396
+ # config/initializers/contextual_config_modules.rb
397
+ module ContextualConfig
398
+ class ModuleRegistry
399
+ @modules = {}
400
+
401
+ def self.register(module_name, &block)
402
+ config = ModuleConfig.new
403
+ block.call(config) if block_given?
404
+ @modules[module_name] = config
405
+ end
406
+
407
+ def self.get(module_name)
408
+ @modules[module_name]
409
+ end
410
+
411
+ def self.all
412
+ @modules
413
+ end
414
+ end
415
+
416
+ class ModuleConfig
417
+ attr_accessor :model_class, :default_priority, :schema_file, :key_prefix
418
+ end
419
+ end
420
+
421
+ # Register modules
422
+ ContextualConfig::ModuleRegistry.register(:hr) do |config|
423
+ config.model_class = HR::PolicyConfiguration
424
+ config.default_priority = 50
425
+ config.key_prefix = 'hr'
426
+ config.schema_file = Rails.root.join('config/schemas/hr_policies.json')
427
+ end
428
+
429
+ ContextualConfig::ModuleRegistry.register(:finance) do |config|
430
+ config.model_class = Finance::PayrollConfiguration
431
+ config.default_priority = 25
432
+ config.key_prefix = 'finance'
433
+ config.schema_file = Rails.root.join('config/schemas/finance_payroll.json')
434
+ end
435
+ ```
436
+
437
+ #### 5. Best Practices for Module Extensions
438
+
439
+ 1. **Use meaningful key prefixes**: `hr.leave_policy`, `finance.overtime_rules`, `benefits.insurance_tiers`
440
+
441
+ 2. **Leverage JSON schema validation**: Each module should define comprehensive schemas
442
+
443
+ 3. **Create module-specific helper methods**: Add convenience methods for common operations
444
+
445
+ 4. **Use consistent scoping patterns**: Define standard context fields that work across modules
446
+
447
+ 5. **Plan for schema evolution**: Design schemas that can evolve as modules add new features
448
+
449
+ ### STI (Single Table Inheritance)
450
+
451
+ Use STI for different configuration types:
452
+
453
+ ```ruby
454
+ class BaseConfig < ApplicationRecord
455
+ include ContextualConfig::Concern::Configurable
456
+ include ContextualConfig::Concern::Lookupable
457
+ end
458
+
459
+ class PayrollConfig < BaseConfig
460
+ validates :key, uniqueness: { scope: :type }
461
+ end
462
+
463
+ class NotificationConfig < BaseConfig
464
+ validates :key, uniqueness: { scope: :type }
465
+ end
466
+ ```
467
+
468
+ ## Real-World Examples
469
+
470
+ ### Example 1: Payroll Configuration System
471
+
472
+ ```ruby
473
+ # Global overtime policy (lowest priority)
474
+ PayrollConfig.create!(
475
+ key: 'overtime_policy',
476
+ config_data: {
477
+ overtime_rate: 1.5,
478
+ overtime_threshold_hours: 8,
479
+ weekend_rate_multiplier: 2.0,
480
+ max_overtime_hours_per_month: 40
481
+ },
482
+ scoping_rules: {},
483
+ priority: 100,
484
+ description: 'Standard company overtime policy'
485
+ )
486
+
487
+ # Engineering department gets better rates
488
+ PayrollConfig.create!(
489
+ key: 'overtime_policy',
490
+ config_data: {
491
+ overtime_rate: 1.75,
492
+ overtime_threshold_hours: 8,
493
+ weekend_rate_multiplier: 2.2,
494
+ max_overtime_hours_per_month: 60
495
+ },
496
+ scoping_rules: { department_id: 'engineering' },
497
+ priority: 50,
498
+ description: 'Engineering overtime policy'
499
+ )
500
+
501
+ # Senior engineers get premium rates
502
+ PayrollConfig.create!(
503
+ key: 'overtime_policy',
504
+ config_data: {
505
+ overtime_rate: 2.0,
506
+ overtime_threshold_hours: 8,
507
+ weekend_rate_multiplier: 2.5,
508
+ max_overtime_hours_per_month: 80
509
+ },
510
+ scoping_rules: {
511
+ department_id: 'engineering',
512
+ employee_level: 'senior'
513
+ },
514
+ priority: 25,
515
+ description: 'Senior engineering overtime policy'
516
+ )
517
+
518
+ # Holiday season enhanced rates (highest priority)
519
+ PayrollConfig.create!(
520
+ key: 'overtime_policy',
521
+ config_data: {
522
+ overtime_rate: 2.5,
523
+ overtime_threshold_hours: 6,
524
+ weekend_rate_multiplier: 3.0,
525
+ max_overtime_hours_per_month: 100
526
+ },
527
+ scoping_rules: {
528
+ timing: {
529
+ start_date: '2024-12-01',
530
+ end_date: '2024-12-31'
531
+ }
532
+ },
533
+ priority: 10,
534
+ description: 'Holiday season enhanced overtime'
535
+ )
536
+
537
+ # Usage in payroll calculation
538
+ def calculate_employee_overtime(employee, hours_worked)
539
+ context = {
540
+ department_id: employee.department_id.to_s,
541
+ employee_level: employee.level,
542
+ current_date: Date.current
543
+ }
544
+
545
+ policy = PayrollConfig.find_applicable_config(
546
+ key: 'overtime_policy',
547
+ context: context
548
+ )
549
+
550
+ return 0 unless policy
551
+
552
+ base_rate = employee.hourly_rate
553
+ overtime_rate = policy.config_data['overtime_rate']
554
+
555
+ base_rate * overtime_rate * hours_worked
556
+ end
557
+ ```
558
+
559
+ ### Example 2: Dynamic Working Hours Policy
560
+
561
+ ```ruby
562
+ # Standard working hours
563
+ WorkingHoursConfig.create!(
564
+ key: 'working_hours',
565
+ config_data: {
566
+ daily_hours: 8,
567
+ weekly_hours: 40,
568
+ break_minutes: 60,
569
+ flexible_hours: false,
570
+ remote_work_allowed: false
571
+ },
572
+ scoping_rules: {},
573
+ priority: 100
574
+ )
575
+
576
+ # Senior staff get reduced hours
577
+ WorkingHoursConfig.create!(
578
+ key: 'working_hours',
579
+ config_data: {
580
+ daily_hours: 7,
581
+ weekly_hours: 35,
582
+ flexible_hours: true,
583
+ remote_work_allowed: true
584
+ },
585
+ scoping_rules: { employee_level: 'senior' },
586
+ priority: 50
587
+ )
588
+
589
+ # Individual employee exception
590
+ WorkingHoursConfig.create!(
591
+ key: 'working_hours',
592
+ config_data: {
593
+ daily_hours: 6,
594
+ weekly_hours: 30,
595
+ flexible_hours: true,
596
+ remote_work_allowed: true
597
+ },
598
+ scoping_rules: { employee_id: 'EMP001' },
599
+ priority: 10
600
+ )
601
+ ```
602
+
603
+ ### Example 3: Benefits Configuration by Demographics
604
+
605
+ ```ruby
606
+ class Benefits::InsuranceConfig < ApplicationRecord
607
+ include ContextualConfig::Concern::Configurable
608
+ include ContextualConfig::Concern::Lookupable
609
+ include ContextualConfig::Concern::SchemaDrivenValidation
610
+
611
+ def self.resolve_config_data_schema(_instance = nil)
612
+ {
613
+ 'type' => 'object',
614
+ 'properties' => {
615
+ 'medical_premium' => { 'type' => 'number', 'minimum' => 0 },
616
+ 'dental_included' => { 'type' => 'boolean' },
617
+ 'vision_included' => { 'type' => 'boolean' },
618
+ 'family_coverage_multiplier' => { 'type' => 'number', 'minimum' => 1.0 },
619
+ 'max_annual_coverage' => { 'type' => 'number', 'minimum' => 0 }
620
+ }
621
+ }
622
+ end
623
+
624
+ def self.resolve_scoping_rules_schema(_instance = nil)
625
+ {
626
+ 'type' => 'object',
627
+ 'properties' => {
628
+ 'age_group' => { 'type' => 'string', 'enum' => ['under_30', '30_to_50', 'over_50'] },
629
+ 'family_size' => { 'type' => 'integer', 'minimum' => 1, 'maximum' => 10 },
630
+ 'employment_tenure_months' => { 'type' => 'integer', 'minimum' => 0 },
631
+ 'employee_level' => { 'type' => 'string' }
632
+ }
633
+ }
634
+ end
635
+ end
636
+
637
+ # Young employees get basic coverage
638
+ Benefits::InsuranceConfig.create!(
639
+ key: 'health_insurance',
640
+ config_data: {
641
+ medical_premium: 200,
642
+ dental_included: false,
643
+ vision_included: false,
644
+ family_coverage_multiplier: 2.0,
645
+ max_annual_coverage: 50000
646
+ },
647
+ scoping_rules: { age_group: 'under_30' },
648
+ priority: 50
649
+ )
650
+
651
+ # Families get enhanced coverage
652
+ Benefits::InsuranceConfig.create!(
653
+ key: 'health_insurance',
654
+ config_data: {
655
+ medical_premium: 150, # Discounted rate for families
656
+ dental_included: true,
657
+ vision_included: true,
658
+ family_coverage_multiplier: 1.5,
659
+ max_annual_coverage: 100000
660
+ },
661
+ scoping_rules: { family_size: 3 }, # 3 or more family members
662
+ priority: 30
663
+ )
664
+ ```
665
+
666
+ ## Performance Considerations
667
+
668
+ ### Benchmarks
669
+
670
+ Based on testing with the JisrHR application:
671
+
672
+ - **Average lookup time**: ~3ms per configuration lookup
673
+ - **100 concurrent lookups**: Completed in ~0.3 seconds
674
+ - **Database indexes**: Automatically created by the generator for optimal performance
675
+ - **Memory usage**: Minimal - configurations are loaded on-demand
676
+
677
+ ### Optimization Tips
678
+
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
683
+
684
+ ### Production Deployment
685
+
686
+ ```ruby
687
+ # config/initializers/contextual_config.rb
688
+ if Rails.env.production?
689
+ # Configure the gem for production use
690
+ ContextualConfig.configure do |config|
691
+ config.cache_enabled = true
692
+ config.cache_ttl = 300 # 5 minutes in seconds
693
+ config.cache_store = Rails.cache
694
+ config.enable_logging = true
695
+ config.logger = Rails.logger
696
+ end
697
+ end
698
+ ```
699
+
700
+ ### Global Configuration
701
+
702
+ ContextualConfig provides global configuration options:
703
+
704
+ ```ruby
705
+ # Configure the gem
706
+ ContextualConfig.configure do |config|
707
+ config.cache_enabled = true # Enable caching
708
+ config.cache_ttl = 300 # Cache TTL in seconds
709
+ config.cache_store = Rails.cache # Cache store to use
710
+ config.default_priority = 100 # Default priority for configs
711
+ config.enable_logging = true # Enable gem logging
712
+ config.logger = Rails.logger # Logger to use
713
+ config.timing_evaluation_enabled = true # Enable timing rule evaluation
714
+ end
715
+
716
+ # Access current configuration
717
+ config = ContextualConfig.configuration
718
+ puts "Cache enabled: #{config.cache_enabled?}"
719
+
720
+ # Reset configuration (useful for testing)
721
+ ContextualConfig.reset_configuration!
722
+ ```
723
+
724
+ ## API Reference
725
+
726
+ ### Concerns
727
+
728
+ #### ContextualConfig::Concern::Configurable
729
+
730
+ Provides basic configuration functionality:
731
+
732
+ - Validations for key, priority, is_active
733
+ - Scopes: `active`, `order_by_priority`
734
+ - Database expectations for required columns
735
+
736
+ #### ContextualConfig::Concern::Lookupable
737
+
738
+ Provides configuration lookup methods:
739
+
740
+ - `find_applicable_config(key:, context:)` - Find best match
741
+ - `find_all_applicable_configs(context:)` - Find all matches
742
+
743
+ #### ContextualConfig::Concern::SchemaDrivenValidation
744
+
745
+ Provides JSON schema validation:
746
+
747
+ - Validates `config_data` and `scoping_rules` against schemas
748
+ - Override `resolve_config_data_schema` and `resolve_scoping_rules_schema`
749
+
750
+ ### Services
751
+
752
+ #### ContextualConfig::Services::ContextualMatcher
753
+
754
+ Core matching logic:
755
+
756
+ - `find_best_match(candidates:, context:)` - Find single best match
757
+ - `find_all_matches(candidates:, context:)` - Find all matches
758
+
759
+ ### Generators
760
+
761
+ #### contextual_config:configurable_table
762
+
763
+ Generate migration for configuration table:
764
+
765
+ ```bash
766
+ rails generate contextual_config:configurable_table ModelName
767
+ rails generate contextual_config:configurable_table Finance::Config --table-name=finance_configs
768
+ ```
769
+
770
+ ## Contributing
771
+
772
+ Bug reports and pull requests are welcome on GitHub at https://github.com/bazinga012/contextual_config.
773
+
774
+ ## License
775
+
776
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
777
+
778
+ ## Development
779
+
780
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
781
+
782
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).