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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca628e95f0ec561248db3eb04ce08f9192784c6745b2a8a3b0679a8a2d47cfe1
4
- data.tar.gz: 0b4c30ef65f92b82cb0746b68067d85324ce4154b1db035b286db52802af5307
3
+ metadata.gz: 7c7a19182d63d0f03712caf91b5b996bd99af38f3a6eed254d8497ff3c78af46
4
+ data.tar.gz: a55ebca13917e3b7fe37f45a0d14a7279a860705ec3c1461eb0071cc1acf67c1
5
5
  SHA512:
6
- metadata.gz: a62db7b905b29b4d5a04bd9bf1a090426ac81276ca4c555c14fa90d932e98cc97d8bbfe25789a026a98ea48d5efd5b98a4459946ffe2d8c62d7574f2a16a85ac
7
- data.tar.gz: 349f24f5d3a87beb861b0d063349ab9fb1372d398d9aa5abe00c0f75f1d7dafbf6956659cd1de88fa86c314354199fcb44cf78d2a161a4f2d1b937d20c298f5e
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.2.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
 
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
 
@@ -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 self.respond_to?(:active) && self.respond_to?(:order_by_priority)
40
- 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.")
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 self.respond_to?(:active) && self.respond_to?(:order_by_priority)
112
- 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.")
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:#{self.name}:#{key}:#{Digest::MD5.hexdigest(context_hash.to_s)}"
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:#{self.name}:#{key}:#{Digest::MD5.hexdigest(context_hash.to_s)}"
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 #{self.name} - Key: #{key}, Context: #{context}"
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, 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.2.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.2.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)