registry 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: 27d1f69d4b0657692ce395b76a7a20c425b57293edd712491c00e17578252b32
4
- data.tar.gz: 2c28cb4e9dbf2539aa8da08c054fd500d5a7ddfb2f5109bf342dc3f7050c99e9
3
+ metadata.gz: 60730b7e7b1943b16358e9ad787af3df00955cbdf4c3acfe44d1099679a4203f
4
+ data.tar.gz: 3aa8fa552783586504f34bf54ec7f52e1f4362ba9504ba351c379fc4ff94255c
5
5
  SHA512:
6
- metadata.gz: b3c6383bee783f57886998d07c1c321e6097771670dea87c6a95d6e5693be69bfd4764432d6779ff5bc9388737b4aa23de8f33fa26ee0139f1e6fa3bb211de38
7
- data.tar.gz: a85e5a558bcd8685b38a3b997ead89b64e316408a2d3bfbcd197454ae1f58976616074110fe5017a272b88debc59880f26ddfc849984d8efcff8a3b11c7920e3
6
+ metadata.gz: c54ebc5c5ad44ce5e8aba1668a476733e599841d200846a55c9ff12fb6baa5b38d97b9e654c27b473ceb66e66fa9aabf2770b3d8c2b29ea6bd68787fc9dab191
7
+ data.tar.gz: f860b911818a5863c521be653a3dadb9bc077f1e1015c14cc4f70e68cef015a128637ddb70cecb9932767d7a07438d85fb3754c65c181b670e7720520ac86200
data/CHANGELOG.md CHANGED
@@ -0,0 +1,35 @@
1
+ # CHANGELOG
2
+
3
+ ## [0.3.0] - TBD
4
+
5
+ ### Added
6
+
7
+ - **Thread Safety**: Added optional `thread_safe: true` parameter for concurrent
8
+ access
9
+ - **Enhanced Error Handling**: New exception hierarchy with
10
+ `Registry::IndexNotFound`, `Registry::MissingAttributeError`
11
+ - **API Enhancements**:
12
+ - `exists?(criteria)` method for checking item existence
13
+ - Better error messages with contextual information
14
+ - **Memory Management**:
15
+ - Automatic cleanup of method watching
16
+ - `cleanup!` method for manual memory management
17
+ - Tracking of watched objects to prevent memory leaks
18
+
19
+ ### Changed
20
+
21
+ - Improved error messages with more context and suggestions
22
+ - Better handling of edge cases in `where` method
23
+ - Enhanced initialization to support new features
24
+
25
+ ### Technical Improvements
26
+
27
+ - Added comprehensive test coverage for new features
28
+ - Improved code organization and documentation
29
+ - Better handling of thread safety concerns
30
+
31
+ ## [0.2.0] - Previous Release
32
+
33
+ - Basic registry functionality
34
+ - Method watching for automatic reindexing
35
+ - Core indexing and querying capabilities
data/README.md CHANGED
@@ -1,15 +1,14 @@
1
- [![Version ](https://img.shields.io/gem/v/registry.svg)](https://rubygems.org/gems/registry)
2
- [![Build Status ](https://travis-ci.org/TwilightCoders/registry.svg)](https://travis-ci.org/TwilightCoders/registry)
3
- [![Code Climate ](https://api.codeclimate.com/v1/badges/a18ae809af878357acfa/maintainability)](https://codeclimate.com/github/TwilightCoders/registry/maintainability)
4
- [![Test Coverage](https://api.codeclimate.com/v1/badges/a18ae809af878357acfa/test_coverage)](https://codeclimate.com/github/TwilightCoders/registry/test_coverage)
5
- [![Dependencies ](https://gemnasium.com/badges/github.com/TwilightCoders/registry.svg)](https://gemnasium.com/github.com/TwilightCoders/registry)
6
-
7
1
  # Registry
8
2
 
3
+ [![Version](https://img.shields.io/gem/v/registry.svg)](https://rubygems.org/gems/registry)
4
+ [![Build Status](https://github.com/TwilightCoders/registry/workflows/CI/badge.svg)](https://github.com/TwilightCoders/registry/actions)
5
+ [![Code Quality](https://img.shields.io/badge/qlty-monitored-blue)](https://qlty.sh)
6
+
9
7
  Provides a data structure for collecting homogeneous objects and indexing them for quick lookup.
10
8
 
11
9
  ## Requirements
12
- Ruby 2.3+
10
+
11
+ Ruby 3.0+
13
12
 
14
13
  ## Installation
15
14
 
@@ -21,28 +20,72 @@ gem 'registry'
21
20
 
22
21
  And then execute:
23
22
 
24
- $ bundle
23
+ ```bash
24
+ bundle
25
+ ```
25
26
 
26
27
  Or install it yourself as:
27
28
 
28
- $ gem install registry
29
+ ```bash
30
+ gem install registry
31
+ ```
29
32
 
30
33
  ## Usage
31
34
 
35
+ ### Basic Usage
36
+
37
+ ```ruby
38
+ Person = Struct.new(:id, :name, :email)
39
+
40
+ registry = Registry.new([
41
+ Person.new(1, 'Dale', 'jason@twilightcoders.net'),
42
+ Person.new(2, 'Dale', 'dale@twilightcoders.net')
43
+ ])
44
+
45
+ registry.index(:name)
46
+
47
+ # Find items using where method
48
+ results = registry.where(name: 'Dale')
49
+
50
+ # Check if items exist
51
+ registry.exists?(name: 'Dale') #=> true
52
+
53
+ # Automatic reindexing when attributes change
54
+ d = registry.where(name: 'Dale').first
55
+ d.name = "Jason"
56
+ registry.where(name: 'Jason') # Contains the updated item
57
+ ```
58
+
59
+ ### Advanced Features
60
+
61
+ #### Thread Safety
62
+
32
63
  ```ruby
33
- Person = Struct.new(:id, :name, :email)
64
+ # Create a thread-safe registry
65
+ registry = Registry.new(items, thread_safe: true)
66
+ ```
34
67
 
35
- registry = Registry.new([
36
- Person.new(1, 'Dale', 'jason@twilightcoders.net'),
37
- Person.new(2, 'Dale', 'dale@twilightcoders.net')
38
- ])
68
+ #### Memory Management
39
69
 
40
- registry.index(:name)
70
+ ```ruby
71
+ # Clean up method watching for long-lived registries
72
+ registry.cleanup!
73
+ ```
41
74
 
42
- d = registry[:name, 'Dale'].first
43
- d.name = "Jason"
75
+ #### Error Handling
44
76
 
45
- registry.find(:name, 'Jason')
77
+ ```ruby
78
+ begin
79
+ registry.where(nonexistent_index: 'value')
80
+ rescue Registry::IndexNotFound => e
81
+ puts "Index not found: #{e.message}"
82
+ end
83
+
84
+ begin
85
+ registry.add("invalid item")
86
+ rescue Registry::MissingAttributeError => e
87
+ puts "Missing required attributes: #{e.message}"
88
+ end
46
89
  ```
47
90
 
48
91
  ## Development
@@ -51,7 +94,10 @@ After checking out the repo, run `bundle` to install dependencies. Then, run `bu
51
94
 
52
95
  ## Contributing
53
96
 
54
- Bug reports and pull requests are welcome on GitHub at https://github.com/TwilightCoders/registry. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
97
+ Bug reports and pull requests are welcome on GitHub at
98
+ <https://github.com/TwilightCoders/registry>. This project is intended to be a safe,
99
+ welcoming space for collaboration, and contributors are expected to adhere to the
100
+ [Contributor Covenant](http://contributor-covenant.org) code of conduct.
55
101
 
56
102
  ## License
57
103
 
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ # Manages index storage and reindexing operations
6
+ module RegistryIndexStore
7
+ attr_reader :indexes
8
+
9
+ def initialize_index_store
10
+ @indexed = {}
11
+ @indexes = []
12
+ end
13
+
14
+ # Add one or more indexes to the registry
15
+ def index(*new_indexes)
16
+ new_indexes.each do |idx|
17
+ warn "Index #{idx} already exists!" and next if @indexed.key?(idx)
18
+
19
+ # OPTIMIZE: Build index hash directly instead of using group_by + transformation
20
+ index_hash = {}
21
+ each do |item|
22
+ watch_setter(item, idx)
23
+ add_to_watched_objects(item) # Track watched objects
24
+
25
+ # Get the index value and build the index in one pass
26
+ idx_value = item.send(idx)
27
+ (index_hash[idx_value] ||= Set.new) << item
28
+ end
29
+ @indexed[idx] = index_hash
30
+ @indexes << idx
31
+ end
32
+ end
33
+
34
+ # Rebuild all indexes from scratch
35
+ def reindex!(new_indexes = [])
36
+ @indexed.clear
37
+ @indexes.clear
38
+ index(*new_indexes)
39
+ end
40
+
41
+ # Update an index when an item's value changes
42
+ def reindex_item(idx, item, old_value, new_value)
43
+ return unless @indexed.key?(idx)
44
+
45
+ @indexed[idx][old_value].delete item
46
+ (@indexed[idx][new_value] ||= Set.new).add item
47
+ end
48
+
49
+ # Look up items by index value
50
+ def lookup_index(idx, value)
51
+ @indexed.dig(idx, value) || Set.new
52
+ end
53
+
54
+ # Check if an index exists
55
+ def index_exists?(idx)
56
+ @indexed.key?(idx)
57
+ end
58
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ # Manages method watching for automatic reindexing when object attributes change
6
+ module RegistryMethodWatcher
7
+ def initialize_method_watcher
8
+ @watched_objects = Set.new
9
+ @method_cache = {}
10
+ end
11
+
12
+ # Add an item to the watched objects set
13
+ def add_to_watched_objects(item)
14
+ @watched_objects.add(item)
15
+ end
16
+
17
+ # Remove an item from the watched objects set
18
+ def remove_from_watched_objects(item)
19
+ @watched_objects.delete(item)
20
+ end
21
+
22
+ # Set up watching on a setter method to trigger reindexing
23
+ def watch_setter(item, idx)
24
+ return if item.frozen?
25
+
26
+ ensure_registry_reference(item)
27
+ setter_method = lookup_setter_method(item.class, idx)
28
+ return unless setter_method
29
+
30
+ watched_method = :"__watched_#{setter_method}"
31
+ return if item.methods.include?(watched_method)
32
+
33
+ install_watched_method(item, idx, setter_method, watched_method)
34
+ end
35
+
36
+ # Remove watching from a setter method
37
+ def ignore_setter(item, idx)
38
+ return if item.frozen?
39
+
40
+ # Use cached method lookup
41
+ item_class = item.class
42
+ cache_key = [item_class, idx]
43
+ setter_method = @method_cache[cache_key]
44
+
45
+ return unless setter_method
46
+
47
+ original_method = setter_method
48
+ watched_method = :"__watched_#{original_method}"
49
+ renamed_method = :"__unwatched_#{original_method}"
50
+
51
+ return unless item.methods.include?(watched_method)
52
+
53
+ item.singleton_class.class_eval do
54
+ alias_method original_method, renamed_method
55
+ remove_method(watched_method)
56
+ remove_method(renamed_method)
57
+ end
58
+ end
59
+
60
+ # Clean up all watched methods from all watched objects
61
+ def cleanup_watched_methods
62
+ @watched_objects.each do |item|
63
+ indexes.each do |idx|
64
+ ignore_setter(item, idx)
65
+ end
66
+ end
67
+ @watched_objects.clear
68
+ end
69
+
70
+ # Full cleanup of watched methods and cache
71
+ def cleanup!
72
+ cleanup_watched_methods
73
+ end
74
+
75
+ private
76
+
77
+ def lookup_setter_method(item_class, idx)
78
+ cache_key = [item_class, idx]
79
+ @method_cache[cache_key] ||= begin
80
+ method_name = :"#{idx}="
81
+ item_class.instance_methods.include?(method_name) ? method_name : nil
82
+ end
83
+ end
84
+
85
+ def ensure_registry_reference(item)
86
+ item.instance_variable_set(:@__registry__, self) unless item.instance_variable_defined?(:@__registry__)
87
+ end
88
+
89
+ def install_watched_method(item, idx, setter_method, watched_method)
90
+ original_method = setter_method
91
+ renamed_method = :"__unwatched_#{original_method}"
92
+
93
+ item.singleton_class.class_eval do
94
+ define_method(watched_method) do |*args|
95
+ old_value = send(idx)
96
+ send(renamed_method, *args).tap do |new_value|
97
+ instance_variable_get(:@__registry__).send(:reindex_item, idx, self, old_value, new_value)
98
+ end
99
+ end
100
+ alias_method renamed_method, original_method
101
+ alias_method original_method, watched_method
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Manages query result caching for performance optimization
4
+ module RegistryQueryCache
5
+ CACHE_SIZE_LIMIT = 1000
6
+
7
+ def initialize_query_cache
8
+ @query_cache = {}
9
+ @cache_hits = 0
10
+ @cache_misses = 0
11
+ end
12
+
13
+ # Check cache for a query result
14
+ def check_cache(cache_key)
15
+ if @query_cache.key?(cache_key)
16
+ @cache_hits += 1
17
+ @query_cache[cache_key]
18
+ else
19
+ @cache_misses += 1
20
+ nil
21
+ end
22
+ end
23
+
24
+ # Store a query result in the cache
25
+ def store_in_cache(cache_key, result_set)
26
+ return if @query_cache.size >= CACHE_SIZE_LIMIT
27
+
28
+ @query_cache[cache_key] = result_set.dup
29
+ end
30
+
31
+ # Invalidate all cached queries (called when registry changes)
32
+ def invalidate_cache
33
+ @query_cache.clear
34
+ end
35
+
36
+ # Get cache performance statistics
37
+ def cache_stats
38
+ total_queries = @cache_hits + @cache_misses
39
+ return { hits: 0, misses: 0, hit_rate: 0.0, total_queries: 0 } if total_queries.zero?
40
+
41
+ {
42
+ hits: @cache_hits,
43
+ misses: @cache_misses,
44
+ hit_rate: (@cache_hits.to_f / total_queries * 100).round(2),
45
+ total_queries: total_queries
46
+ }
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ REGISTRY_VERSION = '0.3.0'
data/lib/registry.rb CHANGED
@@ -1,13 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'registry/version'
4
+
5
+ require 'set'
6
+ require_relative 'registry/index_store'
7
+ require_relative 'registry/query_cache'
8
+ require_relative 'registry/method_watcher'
9
+
1
10
  class Registry < Set
11
+ include RegistryIndexStore
12
+ include RegistryQueryCache
13
+ include RegistryMethodWatcher
2
14
 
3
- MoreThanOneRecordFound = Class.new(StandardError)
15
+ # Exception classes for better error handling
16
+ class RegistryError < StandardError; end
17
+ class MoreThanOneRecordFound < RegistryError; end
18
+ class IndexNotFound < RegistryError; end
19
+ class MissingAttributeError < RegistryError; end
20
+ class ThreadSafetyError < RegistryError; end
4
21
 
5
- VERSION = "0.2.0"
22
+ VERSION = REGISTRY_VERSION
6
23
 
7
24
  DEFAULT_INDEX = :object_id
8
25
 
9
- def initialize(*args, indexes: [])
10
- @indexed = {}
26
+ def initialize(*args, indexes: [], thread_safe: false)
27
+ @thread_safe = thread_safe
28
+ @mutex = Mutex.new if @thread_safe
29
+
30
+ # Initialize module-specific state
31
+ initialize_index_store
32
+ initialize_query_cache
33
+ initialize_method_watcher
34
+
11
35
  super(*args)
12
36
  reindex!(indexes)
13
37
  end
@@ -20,10 +44,6 @@ class Registry < Set
20
44
  @indexed
21
45
  end
22
46
 
23
- def indexes
24
- @indexed.keys - [:object_id]
25
- end
26
-
27
47
  def delete(item)
28
48
  @indexed.each do |idx, store|
29
49
  ignore_setter(item, idx) if include?(item)
@@ -31,11 +51,15 @@ class Registry < Set
31
51
  idx_value = item.send(idx)
32
52
  (store[idx_value] ||= Set.new).delete(item)
33
53
  store.delete(idx_value) if store[idx_value].empty?
34
- rescue NoMethodError => e
35
- raise "#{item.name} cannot be added because indexable attribute (#{idx}) is missing."
54
+ rescue NoMethodError
55
+ raise MissingAttributeError,
56
+ "Item #{item.inspect} cannot be deleted because indexable attribute '#{idx}' " \
57
+ 'is missing or not accessible.'
36
58
  end
37
59
  end
38
- super(item)
60
+ remove_from_watched_objects(item)
61
+ invalidate_cache
62
+ super
39
63
  end
40
64
 
41
65
  def add(item)
@@ -43,99 +67,143 @@ class Registry < Set
43
67
  watch_setter(item, idx) unless include?(item)
44
68
  begin
45
69
  idx_value = item.send(idx)
46
- (store[idx_value] ||= Set.new) << (item)
47
- rescue NoMethodError => e
48
- raise "#{item.name} cannot be added because indexable attribute (#{idx}) is missing."
70
+ (store[idx_value] ||= Set.new) << item
71
+ rescue NoMethodError
72
+ raise MissingAttributeError,
73
+ "Item #{item.inspect} cannot be added because indexable attribute '#{idx}' is missing or not accessible."
49
74
  end
50
75
  end
51
- super(item)
76
+ add_to_watched_objects(item) unless include?(item)
77
+ invalidate_cache
78
+ super
52
79
  end
53
80
  alias << add
54
81
 
55
82
  def find!(search_criteria)
56
- _find(search_criteria) { raise MoreThanOneRecordFound, "There were more than 1 records found" }
83
+ _find(search_criteria) { raise MoreThanOneRecordFound, 'There were more than 1 records found' }
57
84
  end
58
85
 
59
86
  def find(search_criteria)
60
- _find(search_criteria) { warn "There were more than 1 records found" }
87
+ _find(search_criteria) { warn 'There were more than 1 records found' }
61
88
  end
62
89
 
63
- def where(search_criteria)
64
- sets = search_criteria.inject([]) do |sets, (idx, value)|
65
- raise "No '#{idx}' index! Add it with '.index(:#{idx})'" unless @indexed.include?(idx)
66
- sets << (@indexed.dig(idx, value) || Set.new)
67
- end
90
+ def where(limit: nil, offset: 0, **search_criteria)
91
+ with_thread_safety do
92
+ # Handle nil or empty criteria
93
+ return new_registry_from_set(Set.new) if search_criteria.nil? || search_criteria.empty?
94
+
95
+ cache_key = [:where, search_criteria.sort, limit, offset]
96
+ cached_result = check_cache(cache_key)
97
+ return new_registry_from_set(cached_result) if cached_result
98
+
99
+ result_set = if search_criteria.size == 1
100
+ single_criteria_search(search_criteria)
101
+ else
102
+ multi_criteria_search(search_criteria)
103
+ end
104
+
105
+ # Apply pagination if specified
106
+ if limit || offset.positive?
107
+ records_array = result_set.to_a
108
+ start_idx = offset
109
+ end_idx = limit ? start_idx + limit - 1 : -1
110
+ result_set = Set.new(records_array[start_idx..end_idx] || [])
111
+ end
68
112
 
69
- subset_records = sets.reduce(sets.first, &:&)
70
- subset_registry = Registry.new(subset_records, indexes: indexes)
71
- subset_registry
113
+ store_in_cache(cache_key, result_set)
114
+ new_registry_from_set(result_set)
115
+ end
72
116
  end
73
117
 
74
- def index(*indexes)
75
- indexes.each do |idx|
76
- warn "Index #{idx} already exists!" and next if @indexed.key?(idx)
77
- each { |item| watch_setter(item, idx) }
78
- indexed_records = group_by { |a| a.send(idx) }
79
- indexed_sets = Hash[indexed_records.keys.zip(indexed_records.values.map { |e| Set.new(e) })]
80
- @indexed[idx] = indexed_sets
118
+ # Check if any items exist matching the criteria
119
+ def exists?(**search_criteria)
120
+ with_thread_safety do
121
+ search_criteria.size == 1 ? single_criteria_exists?(search_criteria) : multi_criteria_exists?(search_criteria)
81
122
  end
82
123
  end
83
124
 
84
- def reindex!(indexes = [])
85
- @indexed = {}
86
- index(*([DEFAULT_INDEX] | indexes))
125
+ # Count items matching the criteria without creating a Registry object
126
+ def count_where(**search_criteria)
127
+ with_thread_safety do
128
+ return 0 if search_criteria.nil? || search_criteria.empty?
129
+
130
+ result_set = if search_criteria.size == 1
131
+ single_criteria_search(search_criteria)
132
+ else
133
+ multi_criteria_search(search_criteria)
134
+ end
135
+ result_set.size
136
+ end
87
137
  end
88
138
 
89
- protected
90
-
91
- def reindex(idx, item, old_value, new_value)
92
- if (new_value != old_value)
93
- @indexed[idx][old_value].delete item
94
- (@indexed[idx][new_value] ||= Set.new).add item
95
- end
139
+ def reindex!(new_indexes = [])
140
+ cleanup_watched_methods # Clean up before reindexing
141
+ @indexed = {}
142
+ @indexes = []
143
+ index(*([DEFAULT_INDEX] | new_indexes))
96
144
  end
97
145
 
98
146
  private
99
147
 
100
148
  def _find(search_criteria)
101
- results = where(search_criteria)
149
+ results = where(**search_criteria)
102
150
  yield if block_given? && results.count > 1
103
151
  results.first
104
152
  end
105
153
 
106
- def watch_setter(item, idx)
107
- return if item.frozen?
108
- __registry__ = self
109
- item.public_methods.select { |m| m.match(/^#{idx}=$/) }.each do |original_method|
110
- watched_method = "__watched_#{original_method}".to_sym
111
- renamed_method = "__unwatched_#{original_method}".to_sym
112
- next if item.methods.include?(watched_method)
113
-
114
- item.singleton_class.class_eval do
115
- define_method(watched_method) do |*args|
116
- old_value = item.send(idx)
117
- send(renamed_method, *args).tap do |new_value|
118
- __registry__.send(:reindex, idx, item, old_value, new_value)
119
- end
120
- end
121
- alias_method renamed_method, original_method
122
- alias_method original_method, watched_method
123
- end
154
+ def new_registry_from_set(set)
155
+ Registry.new(set.to_a, indexes: indexes, thread_safe: @thread_safe)
156
+ end
157
+
158
+ def validate_index_exists!(idx)
159
+ return if index_exists?(idx)
160
+
161
+ raise IndexNotFound,
162
+ "Index '#{idx}' not found. Available indexes: #{indexes.inspect}. Add it with '.index(:#{idx})'"
163
+ end
164
+
165
+ def single_criteria_search(search_criteria)
166
+ idx, value = search_criteria.first
167
+ validate_index_exists!(idx)
168
+ lookup_index(idx, value)
169
+ end
170
+
171
+ def multi_criteria_search(search_criteria)
172
+ result_set = nil
173
+ search_criteria.each do |idx, value|
174
+ validate_index_exists!(idx)
175
+ current_set = lookup_index(idx, value)
176
+ result_set = result_set ? (result_set & current_set) : current_set
177
+ break if result_set.empty?
124
178
  end
179
+ result_set || Set.new
125
180
  end
126
181
 
127
- def ignore_setter(item, idx)
128
- return if item.frozen?
129
- item.public_methods.select { |m| m.match(/^#{idx}=$/) }.each do |original_method|
130
- watched_method = "__watched_#{original_method}".to_sym
131
- renamed_method = "__unwatched_#{original_method}".to_sym
132
- next unless item.methods.include?(watched_method)
133
- item.singleton_class.class_eval do
134
- alias_method original_method, renamed_method
135
- remove_method(watched_method)
136
- remove_method(renamed_method)
137
- end
182
+ def single_criteria_exists?(search_criteria)
183
+ idx, value = search_criteria.first
184
+ validate_index_exists!(idx)
185
+ lookup_index(idx, value).any?
186
+ end
187
+
188
+ def multi_criteria_exists?(search_criteria)
189
+ result_set = nil
190
+ search_criteria.each do |idx, value|
191
+ validate_index_exists!(idx)
192
+ current_set = lookup_index(idx, value)
193
+ return false if current_set.nil? || current_set.empty?
194
+
195
+ result_set = result_set ? (result_set & current_set) : current_set
196
+ return false if result_set.empty?
138
197
  end
198
+ true
139
199
  end
140
200
 
201
+ # Thread safety wrapper
202
+ def with_thread_safety(&block)
203
+ if @thread_safe
204
+ @mutex.synchronize(&block)
205
+ else
206
+ yield
207
+ end
208
+ end
141
209
  end
metadata CHANGED
@@ -1,29 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: registry
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
  - Dale Stevens
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2019-05-23 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
- name: pry-byebug
13
+ name: benchmark-ips
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
- - - ">="
16
+ - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: '0'
18
+ version: '2.10'
20
19
  type: :development
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
- - - ">="
23
+ - - "~>"
25
24
  - !ruby/object:Gem::Version
26
- version: '0'
25
+ version: '2.10'
27
26
  - !ruby/object:Gem::Dependency
28
27
  name: bundler
29
28
  requirement: !ruby/object:Gem::Requirement
@@ -39,7 +38,7 @@ dependencies:
39
38
  - !ruby/object:Gem::Version
40
39
  version: '0'
41
40
  - !ruby/object:Gem::Dependency
42
- name: rake
41
+ name: pry-byebug
43
42
  requirement: !ruby/object:Gem::Requirement
44
43
  requirements:
45
44
  - - ">="
@@ -52,49 +51,90 @@ dependencies:
52
51
  - - ">="
53
52
  - !ruby/object:Gem::Version
54
53
  version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '13.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '13.0'
55
68
  - !ruby/object:Gem::Dependency
56
69
  name: rspec
57
70
  requirement: !ruby/object:Gem::Requirement
58
71
  requirements:
59
- - - ">="
72
+ - - "~>"
60
73
  - !ruby/object:Gem::Version
61
- version: '0'
74
+ version: '3.12'
62
75
  type: :development
63
76
  prerelease: false
64
77
  version_requirements: !ruby/object:Gem::Requirement
65
78
  requirements:
66
- - - ">="
79
+ - - "~>"
67
80
  - !ruby/object:Gem::Version
68
- version: '0'
81
+ version: '3.12'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rspec_junit_formatter
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.6'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.6'
96
+ - !ruby/object:Gem::Dependency
97
+ name: rubocop
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '1.50'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '1.50'
69
110
  - !ruby/object:Gem::Dependency
70
111
  name: simplecov
71
112
  requirement: !ruby/object:Gem::Requirement
72
113
  requirements:
73
- - - ">="
114
+ - - "~>"
74
115
  - !ruby/object:Gem::Version
75
- version: '0'
116
+ version: '0.22'
76
117
  type: :development
77
118
  prerelease: false
78
119
  version_requirements: !ruby/object:Gem::Requirement
79
120
  requirements:
80
- - - ">="
121
+ - - "~>"
81
122
  - !ruby/object:Gem::Version
82
- version: '0'
123
+ version: '0.22'
83
124
  - !ruby/object:Gem::Dependency
84
- name: rspec_junit_formatter
125
+ name: simplecov-json
85
126
  requirement: !ruby/object:Gem::Requirement
86
127
  requirements:
87
- - - ">="
128
+ - - "~>"
88
129
  - !ruby/object:Gem::Version
89
- version: '0'
130
+ version: '0.2'
90
131
  type: :development
91
132
  prerelease: false
92
133
  version_requirements: !ruby/object:Gem::Requirement
93
134
  requirements:
94
- - - ">="
135
+ - - "~>"
95
136
  - !ruby/object:Gem::Version
96
- version: '0'
97
- description:
137
+ version: '0.2'
98
138
  email:
99
139
  - dale@twilightcoders.net
100
140
  executables: []
@@ -105,12 +145,16 @@ files:
105
145
  - LICENSE
106
146
  - README.md
107
147
  - lib/registry.rb
148
+ - lib/registry/index_store.rb
149
+ - lib/registry/method_watcher.rb
150
+ - lib/registry/query_cache.rb
151
+ - lib/registry/version.rb
108
152
  homepage: https://github.com/TwilightCoders/registry.
109
153
  licenses:
110
154
  - MIT
111
155
  metadata:
112
156
  allowed_push_host: https://rubygems.org
113
- post_install_message:
157
+ rubygems_mfa_required: 'true'
114
158
  rdoc_options: []
115
159
  require_paths:
116
160
  - lib
@@ -119,15 +163,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
119
163
  requirements:
120
164
  - - ">="
121
165
  - !ruby/object:Gem::Version
122
- version: '2.3'
166
+ version: '3.0'
123
167
  required_rubygems_version: !ruby/object:Gem::Requirement
124
168
  requirements:
125
169
  - - ">="
126
170
  - !ruby/object:Gem::Version
127
171
  version: '0'
128
172
  requirements: []
129
- rubygems_version: 3.0.3
130
- signing_key:
173
+ rubygems_version: 4.0.5
131
174
  specification_version: 4
132
175
  summary: Data structure for quick item lookup via indexes.
133
176
  test_files: []