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.
@@ -0,0 +1,201 @@
1
+ require 'date'
2
+
3
+ module ContextualConfig
4
+ module Services
5
+ class ContextualMatcher
6
+ # --- Public Class Methods ---
7
+
8
+ # Finds the single best matching configuration record from a list of candidates.
9
+ # Candidates are expected to be pre-sorted by their 'priority' (higher priority first).
10
+ #
11
+ # @param candidates [ActiveRecord::Relation, Array<ActiveRecord::Base>] A collection of candidate configuration records.
12
+ # @param context [Hash] The current runtime context.
13
+ # @return [ActiveRecord::Base, nil] The best matching record, or nil if no suitable match.
14
+ def self.find_best_match(candidates:, context:)
15
+ log_matching_attempt('find_best_match', candidates.count, context)
16
+
17
+ best_match_record = nil
18
+ highest_specificity_score = -1 # Initialize with a value lower than any possible score
19
+
20
+ candidates.each do |candidate_record|
21
+ next unless candidate_record.respond_to?(:scoping_rules) # Ensure it's a configurable record
22
+
23
+ next unless matches_context?(candidate_record.scoping_rules, context)
24
+
25
+ current_specificity_score = calculate_specificity_score(candidate_record.scoping_rules, context)
26
+
27
+ # Higher specificity wins.
28
+ # If specificity is the same, the one encountered first wins due to pre-sorting by priority.
29
+ if current_specificity_score > highest_specificity_score
30
+ highest_specificity_score = current_specificity_score
31
+ best_match_record = candidate_record
32
+ elsif current_specificity_score == highest_specificity_score && best_match_record.nil?
33
+ # This case handles if the very first matching record has a specificity of 0 (e.g. global rule)
34
+ # and becomes the initial best_match_record.
35
+ best_match_record = candidate_record
36
+ end
37
+ # If current_specificity_score == highest_specificity_score and best_match_record is not nil,
38
+ # the existing best_match_record (which came earlier in the pre-sorted list,
39
+ # thus having higher or equal priority) is preferred.
40
+ end
41
+
42
+ log_matching_result('find_best_match', best_match_record)
43
+ best_match_record
44
+ end
45
+
46
+ # Finds all configuration records that match the given context.
47
+ #
48
+ # @param candidates [ActiveRecord::Relation, Array<ActiveRecord::Base>] A collection of candidate configuration records.
49
+ # @param context [Hash] The current runtime context.
50
+ # @return [Array<ActiveRecord::Base>] An array of all matching records, maintaining original sort order (priority).
51
+ def self.find_all_matches(candidates:, context:)
52
+ log_matching_attempt('find_all_matches', candidates.count, context)
53
+
54
+ matches = candidates.select do |candidate_record|
55
+ candidate_record.respond_to?(:scoping_rules) &&
56
+ matches_context?(candidate_record.scoping_rules, context)
57
+ end
58
+
59
+ log_matching_result('find_all_matches', matches)
60
+ matches
61
+ end
62
+
63
+ # --- Private Class Methods ---
64
+
65
+ # Checks if a given set of scoping_rules matches the provided context.
66
+ #
67
+ # @param scoping_rules [Hash, nil] The rules from a configuration record.
68
+ # @param context [Hash] The runtime context.
69
+ # @return [Boolean] True if all rules match or if rules are empty/nil, false otherwise.
70
+ def self.matches_context?(scoping_rules, context)
71
+ return true if scoping_rules.blank? # An empty rule set is a global match.
72
+
73
+ scoping_rules.all? do |dimension_key, rule_value|
74
+ dimension_sym = dimension_key.to_sym
75
+ context_value = context[dimension_sym]
76
+
77
+ # --- Special Handling for 'timing' Dimension ---
78
+ if dimension_sym == :timing
79
+ # Check if timing evaluation is enabled
80
+ if ContextualConfig.configuration.timing_evaluation_enabled?
81
+ evaluate_timing_rule(rule_value, context[:current_date])
82
+ else
83
+ true # Skip timing evaluation when disabled
84
+ end
85
+ # --- Add other special dimension handlers here as elif/else if ---
86
+ # Example:
87
+ # elsif dimension_sym == :employee_location_country
88
+ # # Assuming rule_value is an array of allowed countries and context_value is the employee's country
89
+ # evaluate_location_country_rule(rule_value, context_value)
90
+ else
91
+ # --- Default Generic Dimension Matching ---
92
+ # If the rule specifies a dimension, the context *must* have a value for that dimension,
93
+ # and the values must match.
94
+ # If you want to treat a missing key in context as "doesn't matter for this rule",
95
+ # this logic would need to change. Current logic: rule implies context must provide.
96
+ context.key?(dimension_sym) && context_value == rule_value
97
+ end
98
+ end
99
+ end
100
+
101
+ # Calculates a specificity score for a set of scoping_rules against a context.
102
+ # A simple score: the number of explicitly defined (and matching) dimensions in the rules.
103
+ #
104
+ # @param scoping_rules [Hash, nil] The rules from a configuration record.
105
+ # @param context [Hash] The runtime context (used here to ensure we only score dimensions that could match).
106
+ # @return [Integer] The specificity score.
107
+ def self.calculate_specificity_score(scoping_rules, context)
108
+ return 0 if scoping_rules.blank? # Global rules have the lowest specificity.
109
+
110
+ # Score is the number of rules that actively match a corresponding key in the context.
111
+ scoping_rules.count do |dimension_key, rule_value|
112
+ dimension_sym = dimension_key.to_sym
113
+ context_value = context[dimension_sym]
114
+
115
+ if dimension_sym == :timing
116
+ # Timing rule contributes to specificity if it's valid and matches
117
+ context.key?(:current_date) && evaluate_timing_rule(rule_value, context[:current_date])
118
+ else
119
+ # Other rules contribute if context has the key and values match
120
+ context.key?(dimension_sym) && context_value == rule_value
121
+ end
122
+ end
123
+ end
124
+
125
+ # Evaluates a 'timing' rule.
126
+ # Assumes rule_value is a hash like { "start_date": "YYYY-MM-DD", "end_date": "YYYY-MM-DD" }.
127
+ # Dates are inclusive. current_date must be provided in the context.
128
+ #
129
+ # @param timing_rule_value [Hash] The timing rule definition.
130
+ # @param current_date [Date, String, nil] The current date from the context.
131
+ # @return [Boolean] True if the current_date falls within the rule's range (or if range is unbounded appropriately).
132
+ def self.evaluate_timing_rule(timing_rule_value, current_date)
133
+ return false unless timing_rule_value.is_a?(Hash) # Rule must be a hash
134
+ return false if current_date.nil? # Context must provide current_date
135
+
136
+ begin
137
+ # Ensure current_date is a Date object
138
+ effective_current_date = current_date.is_a?(Date) ? current_date : Date.parse(current_date.to_s)
139
+
140
+ rule_start_date_str = timing_rule_value['start_date'] || timing_rule_value[:start_date]
141
+ rule_end_date_str = timing_rule_value['end_date'] || timing_rule_value[:end_date]
142
+
143
+ # Parse rule dates if they exist
144
+ start_date = rule_start_date_str ? Date.parse(rule_start_date_str.to_s) : nil
145
+ end_date = rule_end_date_str ? Date.parse(rule_end_date_str.to_s) : nil
146
+
147
+ # Perform checks
148
+ matches = true
149
+ matches &&= (effective_current_date >= start_date) if start_date
150
+ matches &&= (effective_current_date <= end_date) if end_date
151
+ matches
152
+ rescue ArgumentError # Invalid date string
153
+ log_timing_error(timing_rule_value, current_date)
154
+ false
155
+ end
156
+ end
157
+
158
+ # Log matching attempt details
159
+ def self.log_matching_attempt(operation, candidate_count, context)
160
+ return unless ContextualConfig.configuration.enable_logging?
161
+
162
+ logger = ContextualConfig.configuration.effective_logger
163
+ logger.info(
164
+ "ContextualMatcher: #{operation} - Candidates: #{candidate_count}, Context: #{context}"
165
+ )
166
+ end
167
+
168
+ # Log matching results
169
+ def self.log_matching_result(operation, result)
170
+ return unless ContextualConfig.configuration.enable_logging?
171
+
172
+ logger = ContextualConfig.configuration.effective_logger
173
+ result_description = if result.is_a?(Array)
174
+ "#{result.count} matches found"
175
+ elsif result
176
+ "Match found: #{result.key} (Priority: #{result.priority})"
177
+ else
178
+ 'No match found'
179
+ end
180
+
181
+ logger.info("ContextualMatcher: #{operation} - #{result_description}")
182
+ end
183
+
184
+ # Log timing rule evaluation errors
185
+ def self.log_timing_error(timing_rule_value, current_date)
186
+ return unless ContextualConfig.configuration.enable_logging?
187
+
188
+ logger = ContextualConfig.configuration.effective_logger
189
+ logger.warn(
190
+ "ContextualMatcher: Invalid date format in timing rule: #{timing_rule_value} or current_date: #{current_date}"
191
+ )
192
+ end
193
+
194
+ # Placeholder for other specific rule evaluators if needed
195
+ # def self.evaluate_location_country_rule(rule_value_array, context_value_string)
196
+ # return false unless rule_value_array.is_a?(Array)
197
+ # rule_value_array.include?(context_value_string)
198
+ # end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,3 @@
1
+ module ContextualConfig
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,71 @@
1
+ require_relative 'contextual_config/version'
2
+ require 'active_support'
3
+ require 'active_record'
4
+
5
+ module ContextualConfig
6
+ class Error < StandardError; end
7
+
8
+ # Autoload concerns
9
+ autoload :Configurable, 'contextual_config/concern/configurable'
10
+ autoload :Lookupable, 'contextual_config/concern/lookupable'
11
+ autoload :SchemaDrivenValidation, 'contextual_config/concern/schema_driven_validation'
12
+
13
+ # Autoload services
14
+ autoload :ContextualMatcher, 'contextual_config/services/contextual_matcher'
15
+
16
+ # Autoload configuration classes
17
+ autoload :Configuration, 'contextual_config/configuration'
18
+ autoload :ModuleRegistry, 'contextual_config/module_registry'
19
+
20
+ module Concern
21
+ autoload :Configurable, 'contextual_config/concern/configurable'
22
+ autoload :Lookupable, 'contextual_config/concern/lookupable'
23
+ autoload :SchemaDrivenValidation, 'contextual_config/concern/schema_driven_validation'
24
+ end
25
+
26
+ module Services
27
+ autoload :ContextualMatcher, 'contextual_config/services/contextual_matcher'
28
+ end
29
+
30
+ class << self
31
+ # Global configuration for the gem
32
+ def configuration
33
+ @configuration ||= Configuration.new
34
+ end
35
+
36
+ # Configure the gem
37
+ def configure
38
+ yield(configuration) if block_given?
39
+ end
40
+
41
+ # Reset configuration (useful for testing)
42
+ def reset_configuration!
43
+ @configuration = Configuration.new
44
+ end
45
+
46
+ # Module registry access
47
+ def registry
48
+ @registry ||= ModuleRegistry.new
49
+ end
50
+
51
+ # Register a module
52
+ def register_module(module_name, &block)
53
+ registry.register(module_name, &block)
54
+ end
55
+
56
+ # Get a registered module
57
+ def get_module(module_name)
58
+ registry.get(module_name)
59
+ end
60
+
61
+ # Get all registered modules
62
+ def registered_modules
63
+ registry.all
64
+ end
65
+ end
66
+ end
67
+
68
+ # Load Rails generators if Rails is available
69
+ if defined?(Rails)
70
+ require 'contextual_config/generators'
71
+ end
@@ -0,0 +1,33 @@
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)
@@ -0,0 +1,68 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ module ContextualConfig
4
+ module Generators
5
+ # Generates a migration to create a table suitable for models
6
+ # that will include ContextualConfig's concerns.
7
+ #
8
+ # To run this generator:
9
+ # rails generate contextual_config:configurable_table YourModelName
10
+ # rails generate contextual_config:configurable_table Namespace::YourModelName
11
+ # rails generate contextual_config:configurable_table UserPreference --table-name user_prefs
12
+ #
13
+ class ConfigurableTableGenerator < Rails::Generators::NamedBase
14
+ include ActiveRecord::Generators::Migration # Provides #migration_template
15
+
16
+ # Thor class options
17
+ class_option :table_name, type: :string, desc: 'Specify the table name directly, overriding the default.'
18
+
19
+ # Define where to find generator templates (e.g., migration.rb.tt)
20
+ source_root File.expand_path('templates', __dir__)
21
+
22
+ # The main method executed by the generator
23
+ def create_migration_file
24
+ # migration_template will copy the template file to the application's db/migrate directory.
25
+ # It automatically handles the timestamp for the migration filename.
26
+ # Instance variables set here (like @migration_table_name) are available in the template.
27
+ @migration_table_name = determine_table_name_for_migration
28
+
29
+ migration_template('migration.rb.tt', # Template file name
30
+ "db/migrate/create_#{@migration_table_name}.rb", # Destination path
31
+ migration_version: migration_version_string) # For Rails 5+ migration versions
32
+ end
33
+
34
+ # Required by ActiveRecord::Generators::Migration to create timestamped migration filenames.
35
+ def self.next_migration_number(dirname)
36
+ ActiveRecord::Generators::Base.next_migration_number(dirname)
37
+ end
38
+
39
+ private
40
+
41
+ # Determines the actual table name to be used in the migration.
42
+ # Uses the --table-name option if provided, otherwise derives from the generator's 'name' argument.
43
+ def determine_table_name_for_migration
44
+ # `name` is the original argument to the generator (e.g., "Finance::Configuration" or "UserPreference")
45
+ # `table_name` is a helper from NamedBase (e.g., "user_preferences" for "UserPreference")
46
+ custom_name = options[:table_name]
47
+ return custom_name.underscore if custom_name.present?
48
+
49
+ # For namespaced models like "Finance::Configuration", `name.underscore` gives "finance/configuration".
50
+ # We want "finance_configurations" as the table name.
51
+ name.underscore.tr('/', '_').pluralize
52
+ end
53
+
54
+ # Provides the Rails migration version string, e.g., "[7.0]".
55
+ def migration_version_string
56
+ "[#{ActiveRecord::Migration.current_version}]" if ActiveRecord::Migration.respond_to?(:current_version)
57
+ end
58
+
59
+ # Helper method accessible in the ERB template to form the migration class name.
60
+ # e.g., CreateFinanceConfigurations
61
+ public
62
+
63
+ def migration_class_name
64
+ "Create#{determine_table_name_for_migration.camelize}"
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,52 @@
1
+ # This file is a template used by the ContextualConfig::ConfigurableTableGenerator.
2
+ # It will be processed to create a migration file in your application's db/migrate directory.
3
+
4
+ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version_string %>
5
+ def change
6
+ create_table :<%= @migration_table_name %> do |t|
7
+ # --- Standard columns for ContextualConfig ---
8
+ # 'key' uniquely identifies a configuration entry within its type/scope.
9
+ t.string :key, null: false
10
+
11
+ # 'config_data' stores the main JSON payload of the configuration.
12
+ t.jsonb :config_data, null: false, default: {}
13
+
14
+ # 'scoping_rules' stores the JSON defining when this configuration is applicable.
15
+ t.jsonb :scoping_rules, null: false, default: {}
16
+
17
+ # 'priority' helps resolve conflicts if multiple configurations match.
18
+ # Lower numbers generally indicate higher priority.
19
+ t.integer :priority, null: false, default: 100
20
+
21
+ # 'is_active' allows for soft-deleting or disabling configurations.
22
+ t.boolean :is_active, null: false, default: true
23
+
24
+ # 'description' provides a human-readable summary of the configuration's purpose.
25
+ t.text :description
26
+
27
+ # 'type' column is standard for Single Table Inheritance (STI).
28
+ # Include this if the model using this table (e.g., Finance::Configuration)
29
+ # will itself be a parent class for more specific configuration types
30
+ # (e.g., Finance::TaxConfig, Finance::ReportConfig).
31
+ t.string :type
32
+
33
+ t.timestamps null: false
34
+ # --- End of standard columns ---
35
+ end
36
+
37
+ # --- Recommended Indexes ---
38
+ # Index for typical lookups by key, potentially scoped by STI type.
39
+ # Using a unique name for the index to avoid conflicts if multiple tables are generated.
40
+ add_index :<%= @migration_table_name %>, [:type, :key], unique: true, name: 'idx_<%= @migration_table_name.first(40) %>_on_type_and_key'
41
+ # Note: If 'type' is not used or key should be globally unique for this table,
42
+ # a simpler index might be `add_index :<%= @migration_table_name %>, :key, unique: true`
43
+
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'
46
+
47
+ # Optional: GIN indexes for jsonb columns if you frequently query directly into them.
48
+ # These can be large and have write performance implications, so add if needed.
49
+ # add_index :<%= @migration_table_name %>, :config_data, using: :gin, name: 'idx_<%= @migration_table_name.first(40) %>_on_config_data'
50
+ # add_index :<%= @migration_table_name %>, :scoping_rules, using: :gin, name: 'idx_<%= @migration_table_name.first(40) %>_on_scoping_rules'
51
+ end
52
+ end
metadata ADDED
@@ -0,0 +1,194 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: contextual_config
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - bazinga012
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activerecord
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '6.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '6.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: activesupport
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '6.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '6.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: json-schema
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '5.1'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '5.1'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rails
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '6.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '6.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rspec
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rubocop
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '1.0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '1.0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: rubocop-rails
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '2.0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '2.0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rubocop-rspec
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '3.0'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '3.0'
124
+ - !ruby/object:Gem::Dependency
125
+ name: sqlite3
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '2.1'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '2.1'
138
+ description: ContextualConfig provides a flexible framework for managing configurations
139
+ that can be applied based on contextual rules, priorities, and scoping. Perfect
140
+ for complex applications requiring dynamic configuration resolution.
141
+ email:
142
+ - vishal18593@gmail.com
143
+ executables: []
144
+ extensions: []
145
+ extra_rdoc_files: []
146
+ files:
147
+ - ".claude/settings.local.json"
148
+ - ".rspec"
149
+ - CLAUDE.md
150
+ - Gemfile
151
+ - Gemfile.lock
152
+ - LICENSE.txt
153
+ - README.md
154
+ - Rakefile
155
+ - contextual_config.gemspec
156
+ - lib/contextual_config.rb
157
+ - lib/contextual_config/concern/configurable.rb
158
+ - lib/contextual_config/concern/lookupable.rb
159
+ - lib/contextual_config/concern/schema_driven_validation.rb
160
+ - lib/contextual_config/configuration.rb
161
+ - lib/contextual_config/generators.rb
162
+ - lib/contextual_config/module_registry.rb
163
+ - lib/contextual_config/services/contextual_matcher.rb
164
+ - lib/contextual_config/version.rb
165
+ - lib/generators/contextual_config/configurable_table/USAGE
166
+ - lib/generators/contextual_config/configurable_table/configurable_table_generator.rb
167
+ - lib/generators/contextual_config/configurable_table/templates/migration.rb.tt
168
+ homepage: https://github.com/bazinga012/contextual_config
169
+ licenses:
170
+ - MIT
171
+ metadata:
172
+ allowed_push_host: https://rubygems.org
173
+ homepage_uri: https://github.com/bazinga012/contextual_config
174
+ source_code_uri: https://github.com/bazinga012/contextual_config
175
+ changelog_uri: https://github.com/bazinga012/contextual_config/blob/main/CHANGELOG.md
176
+ rubygems_mfa_required: 'true'
177
+ rdoc_options: []
178
+ require_paths:
179
+ - lib
180
+ required_ruby_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: 3.0.0
185
+ required_rubygems_version: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ requirements: []
191
+ rubygems_version: 3.6.9
192
+ specification_version: 4
193
+ summary: A Ruby gem for context-aware configuration management
194
+ test_files: []