contextual_config 0.2.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 +4 -4
- data/CLAUDE.md +2 -2
- data/Gemfile +2 -0
- data/Gemfile.lock +1 -1
- data/README.md +2 -2
- data/Rakefile +2 -0
- data/contextual_config.gemspec +3 -1
- data/lib/contextual_config/concern/configurable.rb +9 -13
- data/lib/contextual_config/concern/lookupable.rb +10 -10
- data/lib/contextual_config/concern/schema_driven_validation.rb +5 -2
- data/lib/contextual_config/configuration.rb +2 -6
- data/lib/contextual_config/generators.rb +2 -0
- data/lib/contextual_config/module_registry.rb +1 -1
- data/lib/contextual_config/services/contextual_matcher.rb +2 -0
- data/lib/contextual_config/version.rb +3 -1
- data/lib/contextual_config.rb +3 -3
- data/lib/generators/contextual_config/configurable_table/configurable_table_generator.rb +2 -0
- data/lib/generators/contextual_config/configurable_table/templates/migration.rb.tt +6 -4
- metadata +1 -2
- data/lib/generators/contextual_config/configurable_table/USAGE +0 -33
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7c7a19182d63d0f03712caf91b5b996bd99af38f3a6eed254d8497ff3c78af46
|
|
4
|
+
data.tar.gz: a55ebca13917e3b7fe37f45a0d14a7279a860705ec3c1461eb0071cc1acf67c1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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), `
|
|
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.
|
|
54
|
+
t.datetime :deleted_at
|
|
55
55
|
t.text :description
|
|
56
56
|
t.string :type # For STI if needed
|
|
57
57
|
```
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
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
|
-
- **
|
|
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.
|
|
232
|
+
t.datetime :deleted_at
|
|
233
233
|
t.text :description
|
|
234
234
|
t.string :type
|
|
235
235
|
|
data/Rakefile
CHANGED
data/contextual_config.gemspec
CHANGED
|
@@ -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'
|
|
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
|
-
# -
|
|
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(
|
|
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 `
|
|
55
|
-
#
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -36,8 +38,8 @@ module ContextualConfig
|
|
|
36
38
|
|
|
37
39
|
# Start with active configurations, ordered by priority (higher priority first).
|
|
38
40
|
# The `Configurable` concern is expected to provide `active` and `order_by_priority` scopes.
|
|
39
|
-
unless
|
|
40
|
-
raise(NoMethodError, "#{
|
|
41
|
+
unless respond_to?(:active) && respond_to?(:order_by_priority)
|
|
42
|
+
raise(NoMethodError, "#{name} must include ContextualConfig::Concern::Configurable to use Lookupable.")
|
|
41
43
|
end
|
|
42
44
|
|
|
43
45
|
# Check candidates cache if enabled
|
|
@@ -46,9 +48,7 @@ module ContextualConfig
|
|
|
46
48
|
candidates_cache_key = generate_candidates_cache_key(key, context)
|
|
47
49
|
begin
|
|
48
50
|
candidates = ContextualConfig.configuration.cache_store.read(candidates_cache_key)
|
|
49
|
-
if candidates
|
|
50
|
-
log_lookup_attempt(key, context, 'candidates_cache_hit')
|
|
51
|
-
end
|
|
51
|
+
log_lookup_attempt(key, context, 'candidates_cache_hit') if candidates
|
|
52
52
|
rescue StandardError => e
|
|
53
53
|
log_lookup_attempt(key, context, "candidates_cache_error: #{e.message}")
|
|
54
54
|
end
|
|
@@ -108,8 +108,8 @@ module ContextualConfig
|
|
|
108
108
|
# @param context [Hash] A hash representing the current context.
|
|
109
109
|
# @return [Array<ActiveRecord::Base>] An array of all matching configuration instances, ordered by priority.
|
|
110
110
|
def find_all_applicable_configs(context:)
|
|
111
|
-
unless
|
|
112
|
-
raise(NoMethodError, "#{
|
|
111
|
+
unless respond_to?(:active) && respond_to?(:order_by_priority)
|
|
112
|
+
raise(NoMethodError, "#{name} must include ContextualConfig::Concern::Configurable to use Lookupable.")
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
log_lookup_attempt('all_configs', context, 'all_configs_lookup')
|
|
@@ -171,13 +171,13 @@ module ContextualConfig
|
|
|
171
171
|
# Generate cache key for configuration lookup
|
|
172
172
|
def generate_cache_key(key, context)
|
|
173
173
|
context_hash = context.is_a?(Hash) ? context.sort.to_h : context
|
|
174
|
-
"contextual_config:#{
|
|
174
|
+
"contextual_config:#{name}:#{key}:#{Digest::MD5.hexdigest(context_hash.to_s)}"
|
|
175
175
|
end
|
|
176
176
|
|
|
177
177
|
# Generate cache key for candidates
|
|
178
178
|
def generate_candidates_cache_key(key, context)
|
|
179
179
|
context_hash = context.is_a?(Hash) ? context.sort.to_h : context
|
|
180
|
-
"contextual_config_candidates:#{
|
|
180
|
+
"contextual_config_candidates:#{name}:#{key}:#{Digest::MD5.hexdigest(context_hash.to_s)}"
|
|
181
181
|
end
|
|
182
182
|
|
|
183
183
|
# Log configuration lookup attempts
|
|
@@ -186,7 +186,7 @@ module ContextualConfig
|
|
|
186
186
|
|
|
187
187
|
logger = ContextualConfig.configuration.effective_logger
|
|
188
188
|
logger.info(
|
|
189
|
-
"ContextualConfig::Lookupable: #{operation} for #{
|
|
189
|
+
"ContextualConfig::Lookupable: #{operation} for #{name} - Key: #{key}, Context: #{context}"
|
|
190
190
|
)
|
|
191
191
|
end
|
|
192
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,
|
|
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,
|
|
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
|
|
@@ -10,7 +10,7 @@ module ContextualConfig
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
# Register a module with its configuration
|
|
13
|
-
def register(module_name
|
|
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?
|
data/lib/contextual_config.rb
CHANGED
|
@@ -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)
|
|
@@ -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
|
-
# '
|
|
22
|
-
|
|
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
|
-
#
|
|
45
|
-
|
|
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.
|
|
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)
|