mongory 0.4.0 → 0.6.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -0
  3. data/README.md +37 -7
  4. data/examples/benchmark-rails.rb +52 -0
  5. data/examples/benchmark.rb +79 -18
  6. data/lib/generators/mongory/matcher/matcher_generator.rb +1 -1
  7. data/lib/mongory/converters/abstract_converter.rb +22 -32
  8. data/lib/mongory/converters/condition_converter.rb +7 -4
  9. data/lib/mongory/converters/data_converter.rb +18 -7
  10. data/lib/mongory/converters/key_converter.rb +43 -19
  11. data/lib/mongory/converters/value_converter.rb +24 -19
  12. data/lib/mongory/matchers/abstract_matcher.rb +94 -32
  13. data/lib/mongory/matchers/abstract_multi_matcher.rb +16 -45
  14. data/lib/mongory/matchers/and_matcher.rb +38 -10
  15. data/lib/mongory/matchers/array_record_matcher.rb +54 -28
  16. data/lib/mongory/matchers/elem_match_matcher.rb +13 -9
  17. data/lib/mongory/matchers/eq_matcher.rb +12 -7
  18. data/lib/mongory/matchers/every_matcher.rb +20 -9
  19. data/lib/mongory/matchers/exists_matcher.rb +15 -14
  20. data/lib/mongory/matchers/field_matcher.rb +58 -38
  21. data/lib/mongory/matchers/gt_matcher.rb +15 -7
  22. data/lib/mongory/matchers/gte_matcher.rb +15 -7
  23. data/lib/mongory/matchers/hash_condition_matcher.rb +47 -25
  24. data/lib/mongory/matchers/in_matcher.rb +12 -10
  25. data/lib/mongory/matchers/literal_matcher.rb +42 -48
  26. data/lib/mongory/matchers/lt_matcher.rb +15 -7
  27. data/lib/mongory/matchers/lte_matcher.rb +15 -7
  28. data/lib/mongory/matchers/ne_matcher.rb +12 -7
  29. data/lib/mongory/matchers/nin_matcher.rb +12 -9
  30. data/lib/mongory/matchers/not_matcher.rb +9 -5
  31. data/lib/mongory/matchers/or_matcher.rb +42 -13
  32. data/lib/mongory/matchers/present_matcher.rb +14 -15
  33. data/lib/mongory/matchers/regex_matcher.rb +37 -22
  34. data/lib/mongory/matchers.rb +0 -1
  35. data/lib/mongory/query_builder.rb +88 -26
  36. data/lib/mongory/query_matcher.rb +39 -12
  37. data/lib/mongory/query_operator.rb +1 -1
  38. data/lib/mongory/utils/context.rb +41 -0
  39. data/lib/mongory/utils/debugger.rb +6 -4
  40. data/lib/mongory/utils.rb +1 -0
  41. data/lib/mongory/version.rb +1 -1
  42. data/lib/mongory.rb +3 -3
  43. data/mongory.gemspec +3 -3
  44. metadata +9 -9
  45. data/lib/mongory/matchers/README.md +0 -57
  46. data/lib/mongory/matchers/abstract_operator_matcher.rb +0 -46
@@ -5,7 +5,8 @@ module Mongory
5
5
  # OrMatcher implements the `$or` logical operator.
6
6
  #
7
7
  # It evaluates an array of subconditions and returns true
8
- # if *any one* of them matches.
8
+ # if *any one* of them matches. For empty conditions, it returns false
9
+ # (using FALSE_PROC).
9
10
  #
10
11
  # Each subcondition is handled by a HashConditionMatcher with conversion disabled,
11
12
  # since the parent matcher already manages data conversion.
@@ -20,24 +21,52 @@ module Mongory
20
21
  # ])
21
22
  # matcher.match?(record) #=> true if either condition matches
22
23
  #
24
+ # @example Empty conditions
25
+ # matcher = OrMatcher.build([])
26
+ # matcher.match?(record) #=> false (uses FALSE_PROC)
27
+ #
23
28
  # @see AbstractMultiMatcher
24
29
  class OrMatcher < AbstractMultiMatcher
25
30
  enable_unwrap!
26
- # Constructs a HashConditionMatcher for each subcondition.
27
- # Conversion is disabled to avoid double-processing.
28
-
29
- # @see HashConditionMatcher
30
- # @param condition [Object] a subcondition to be wrapped
31
- # @return [HashConditionMatcher] a matcher for this condition
32
- def build_sub_matcher(condition)
33
- HashConditionMatcher.build(condition)
31
+
32
+ # Creates a raw Proc that performs the or-matching operation.
33
+ # The Proc combines all submatcher Procs and returns true if any match.
34
+ # For empty conditions, returns FALSE_PROC.
35
+ #
36
+ # @return [Proc] a Proc that performs the or-matching operation
37
+ def raw_proc
38
+ return FALSE_PROC if matchers.empty?
39
+
40
+ combine_procs(*matchers.map(&:to_proc))
41
+ end
42
+
43
+ # Recursively combines multiple matcher procs with OR logic.
44
+ # This method optimizes the combination of multiple matchers by building
45
+ # a balanced tree of OR operations.
46
+ #
47
+ # @param left [Proc] The left matcher proc to combine
48
+ # @param rest [Array<Proc>] The remaining matcher procs to combine
49
+ # @return [Proc] A new proc that combines all matchers with OR logic
50
+ # @example
51
+ # combine_procs(proc1, proc2, proc3)
52
+ # #=> proc { |record| proc1.call(record) || proc2.call(record) || proc3.call(record) }
53
+ def combine_procs(left, *rest)
54
+ return left if rest.empty?
55
+
56
+ right = combine_procs(*rest)
57
+ Proc.new do |record|
58
+ left.call(record) || right.call(record)
59
+ end
34
60
  end
35
61
 
36
- # Uses `:any?` to return true if any submatcher passes.
62
+ # Builds an array of matchers from the subconditions.
63
+ # Each subcondition is wrapped in a HashConditionMatcher.
37
64
  #
38
- # @return [Symbol] the combining operator
39
- def operator
40
- :any?
65
+ # @return [Array<AbstractMatcher>] array of submatchers
66
+ define_instance_cache_method(:matchers) do
67
+ @condition.map do |condition|
68
+ HashConditionMatcher.build(condition, context: @context)
69
+ end
41
70
  end
42
71
 
43
72
  # Ensures the condition is an array of hashes.
@@ -20,22 +20,19 @@ module Mongory
20
20
  # matcher = PresentMatcher.build(false)
21
21
  # matcher.match?(nil) #=> true
22
22
  #
23
- # @see AbstractOperatorMatcher
24
- class PresentMatcher < AbstractOperatorMatcher
25
- # Transforms the record into a boolean presence flag
26
- # before applying comparison.
23
+ # @see AbstractMatcher
24
+ class PresentMatcher < AbstractMatcher
25
+ # Creates a raw Proc that performs the presence check.
26
+ # The Proc checks if the record's presence matches the condition.
27
27
  #
28
- # @param record [Object] the original value
29
- # @return [Boolean] whether the value is present
30
- def preprocess(record)
31
- is_present?(super)
32
- end
28
+ # @return [Proc] A proc that performs presence check
29
+ def raw_proc
30
+ condition = @condition
33
31
 
34
- # Uses Ruby `==` to compare the presence result to the expected boolean.
35
- #
36
- # @return [Symbol] the equality operator
37
- def operator
38
- :==
32
+ Proc.new do |record|
33
+ record = nil if record == KEY_NOT_FOUND
34
+ is_present?(record) == condition
35
+ end
39
36
  end
40
37
 
41
38
  # Ensures that the condition value is a boolean.
@@ -43,7 +40,9 @@ module Mongory
43
40
  # @raise [TypeError] if condition is not true or false
44
41
  # @return [void]
45
42
  def check_validity!
46
- raise TypeError, '$present needs a boolean' unless BOOLEAN_VALUES.include?(@condition)
43
+ return if [true, false].include?(@condition)
44
+
45
+ raise TypeError, '$present needs a boolean'
47
46
  end
48
47
  end
49
48
 
@@ -12,41 +12,56 @@ module Mongory
12
12
  # If a string is provided instead of a Regexp, it will be converted via `Regexp.new(...)`.
13
13
  # This ensures consistent behavior for queries like `:field.regex => "foo"` and `:field.regex => /foo/`.
14
14
  #
15
- # @example
16
- # matcher = RegexMatcher.build('^foo')
15
+ # @example Basic regex matching
16
+ # matcher = RegexMatcher.build(/^foo/)
17
17
  # matcher.match?('foobar') #=> true
18
18
  # matcher.match?('barfoo') #=> false
19
19
  #
20
- # @example Match with explicit regex
21
- # RegexMatcher.build(/admin/i).match?("ADMIN") # => true
20
+ # @example Case-insensitive matching
21
+ # matcher = RegexMatcher.build(/admin/i)
22
+ # matcher.match?("ADMIN") #=> true
22
23
  #
23
- # @example Match via LiteralMatcher fallback
24
- # LiteralMatcher.new(/admin/i).match("ADMIN") # => true
24
+ # @example String pattern
25
+ # matcher = RegexMatcher.build("^foo")
26
+ # matcher.match?("foobar") #=> true
25
27
  #
26
- # @see LiteralMatcher
27
- # @see Mongory::Matchers::AbstractOperatorMatcher
28
- class RegexMatcher < AbstractOperatorMatcher
29
- # Uses `:match?` as the operator to invoke on the record string.
28
+ # @example Non-string input
29
+ # matcher = RegexMatcher.build(/\d+/)
30
+ # matcher.match?(123) #=> false (not a string)
31
+ #
32
+ # @see AbstractMatcher
33
+ class RegexMatcher < AbstractMatcher
34
+ # Initializes the matcher with a regex pattern.
35
+ # Converts string patterns to Regexp objects.
30
36
  #
31
- # @return [Symbol] the match? method symbol
32
- def operator
33
- :match?
37
+ # @param condition [String, Regexp] the regex pattern to match against
38
+ # @param context [Context] the query context
39
+ # @raise [TypeError] if condition is not a string or Regexp
40
+ def initialize(condition, context: Context.new)
41
+ super
42
+ @condition = Regexp.new(condition) if condition.is_a?(String)
34
43
  end
35
44
 
36
- # Ensures the record is a string before applying regex.
37
- # If not, coerces to empty string to ensure match fails safely.
45
+ # Creates a raw Proc that performs the regex matching operation.
46
+ # The Proc checks if the record is a string that matches the pattern.
47
+ # Returns false for non-string inputs or if the match fails.
38
48
  #
39
- # @param record [Object] the raw input
40
- # @return [String] a safe string to match against
41
- def preprocess(record)
42
- return '' unless record.is_a?(String)
49
+ # @return [Proc] a Proc that performs regex matching
50
+ def raw_proc
51
+ condition = @condition
52
+
53
+ Proc.new do |record|
54
+ next false unless record.is_a?(String)
43
55
 
44
- record
56
+ record.match?(condition)
57
+ rescue StandardError
58
+ false
59
+ end
45
60
  end
46
61
 
47
- # Ensures the condition is a Regexp (strings are converted during initialization).
62
+ # Ensures the condition is a valid regex pattern (Regexp or String).
48
63
  #
49
- # @raise [TypeError] if condition is not a string
64
+ # @raise [TypeError] if condition is not a string or Regexp
50
65
  # @return [void]
51
66
  def check_validity!
52
67
  return if @condition.is_a?(Regexp)
@@ -153,7 +153,6 @@ end
153
153
 
154
154
  require_relative 'matchers/abstract_matcher'
155
155
  require_relative 'matchers/abstract_multi_matcher'
156
- require_relative 'matchers/abstract_operator_matcher'
157
156
  require_relative 'matchers/literal_matcher'
158
157
  require_relative 'matchers/hash_condition_matcher'
159
158
  require_relative 'matchers/and_matcher'
@@ -10,12 +10,19 @@ module Mongory
10
10
  #
11
11
  # Internally it compiles all conditions and invokes `QueryMatcher`.
12
12
  #
13
- # @example
13
+ # @example Basic query
14
14
  # records.mongory
15
15
  # .where(:age.gte => 18)
16
16
  # .or({ :name => /J/ }, { :name.eq => 'Bob' })
17
17
  # .limit(2)
18
18
  # .to_a
19
+ #
20
+ # @example Complex query
21
+ # records.mongory
22
+ # .where(:status => 'active')
23
+ # .not(:age.lt => 18)
24
+ # .any_of({ :role => 'admin' }, { :role => 'moderator' })
25
+ # .pluck(:name, :email)
19
26
  class QueryBuilder
20
27
  include ::Enumerable
21
28
  include Utils
@@ -23,44 +30,68 @@ module Mongory
23
30
  # Initializes a new query builder with the given record set.
24
31
  #
25
32
  # @param records [Enumerable] the collection to query against
26
- def initialize(records)
33
+ def initialize(records, context: Utils::Context.new)
27
34
  @records = records
35
+ @context = context
28
36
  set_matcher
29
37
  end
30
38
 
31
39
  # Iterates through all records that match the current matcher.
40
+ # Uses the standard matcher implementation.
32
41
  #
33
- # @yieldparam record [Object]
34
- # @return [Enumerator]
42
+ # @yieldparam record [Object] each matching record
43
+ # @return [Enumerator] if no block given
44
+ # @return [void] if block given
35
45
  def each
36
46
  return to_enum(:each) unless block_given?
37
47
 
38
48
  @matcher.prepare_query
39
49
  @records.each do |record|
50
+ @context.current_record = record
40
51
  yield record if @matcher.match?(record)
41
52
  end
42
53
  end
43
54
 
55
+ # Iterates through all records that match the current matcher.
56
+ # Uses a compiled Proc for faster matching.
57
+ #
58
+ # @yieldparam record [Object] each matching record
59
+ # @return [Enumerator] if no block given
60
+ # @return [void] if block given
61
+ def fast
62
+ return to_enum(:fast) unless block_given?
63
+
64
+ @context.need_convert = false
65
+ @matcher.prepare_query
66
+ matcher_block = @matcher.to_proc
67
+ @records.each do |record|
68
+ yield record if matcher_block.call(record)
69
+ end
70
+ end
71
+
44
72
  # Adds a condition to filter records using the given condition.
73
+ # This is an alias for `and`.
45
74
  #
46
- # @param condition [Hash]
75
+ # @param condition [Hash] the condition to add
47
76
  # @return [QueryBuilder] a new builder instance
48
77
  def where(condition)
49
78
  self.and(condition)
50
79
  end
51
80
 
52
81
  # Adds a negated condition to the current query.
82
+ # Wraps the condition in a `$not` operator.
53
83
  #
54
- # @param condition [Hash]
55
- # @return [QueryBuilder]
84
+ # @param condition [Hash] the condition to negate
85
+ # @return [QueryBuilder] a new builder instance
56
86
  def not(condition)
57
87
  self.and('$not' => condition)
58
88
  end
59
89
 
60
90
  # Adds one or more conditions combined with `$and`.
91
+ # All conditions must match for a record to be included.
61
92
  #
62
- # @param conditions [Array<Hash>]
63
- # @return [QueryBuilder]
93
+ # @param conditions [Array<Hash>] the conditions to add
94
+ # @return [QueryBuilder] a new builder instance
64
95
  def and(*conditions)
65
96
  dup_instance_exec do
66
97
  add_conditions('$and', conditions)
@@ -68,9 +99,10 @@ module Mongory
68
99
  end
69
100
 
70
101
  # Adds one or more conditions combined with `$or`.
102
+ # Any condition can match for a record to be included.
71
103
  #
72
- # @param conditions [Array<Hash>]
73
- # @return [QueryBuilder]
104
+ # @param conditions [Array<Hash>] the conditions to add
105
+ # @return [QueryBuilder] a new builder instance
74
106
  def or(*conditions)
75
107
  operator = '$or'
76
108
  dup_instance_exec do
@@ -85,24 +117,34 @@ module Mongory
85
117
  # Adds a `$or` query combined inside an `$and` block.
86
118
  # This is a semantic alias for `.and('$or' => [...])`
87
119
  #
88
- # @param conditions [Array<Hash>]
89
- # @return [QueryBuilder]
120
+ # @param conditions [Array<Hash>] the conditions to add
121
+ # @return [QueryBuilder] a new builder instance
90
122
  def any_of(*conditions)
91
123
  self.and('$or' => conditions)
92
124
  end
93
125
 
126
+ # Adds an `$in` condition to the query.
127
+ # Matches records where the field value is in the given array.
128
+ #
129
+ # @param condition [Hash] the field and values to match
130
+ # @return [QueryBuilder] a new builder instance
94
131
  def in(condition)
95
132
  self.and(wrap_values_with_key(condition, '$in'))
96
133
  end
97
134
 
135
+ # Adds a `$nin` condition to the query.
136
+ # Matches records where the field value is not in the given array.
137
+ #
138
+ # @param condition [Hash] the field and values to exclude
139
+ # @return [QueryBuilder] a new builder instance
98
140
  def nin(condition)
99
141
  self.and(wrap_values_with_key(condition, '$nin'))
100
142
  end
101
143
 
102
144
  # Limits the number of records returned by the query.
103
145
  #
104
- # @param count [Integer]
105
- # @return [QueryBuilder]
146
+ # @param count [Integer] the maximum number of records to return
147
+ # @return [QueryBuilder] a new builder instance
106
148
  def limit(count)
107
149
  dup_instance_exec do
108
150
  @records = take(count)
@@ -111,9 +153,10 @@ module Mongory
111
153
 
112
154
  # Extracts selected fields from matching records.
113
155
  #
114
- # @param field [Symbol, String]
115
- # @param fields [Array<Symbol, String>]
116
- # @return [Array<Object>, Array<Array<Object>>]
156
+ # @param field [Symbol, String] the first field to extract
157
+ # @param fields [Array<Symbol, String>] additional fields to extract
158
+ # @return [Array<Object>] array of single field values if one field given
159
+ # @return [Array<Array<Object>>] array of field value arrays if multiple fields given
117
160
  def pluck(field, *fields)
118
161
  if fields.empty?
119
162
  map { |record| record[field] }
@@ -126,11 +169,24 @@ module Mongory
126
169
  # Returns the raw parsed condition for this query.
127
170
  #
128
171
  # @return [Hash] the raw compiled condition
129
- def condition
172
+ def raw_condition
130
173
  @matcher.condition
131
174
  end
132
175
 
133
- alias_method :selector, :condition
176
+ # Creates a new query builder with additional context configuration.
177
+ #
178
+ # @param addon_context [Hash] Additional context configuration to merge
179
+ # @return [QueryBuilder] A new query builder instance with merged context
180
+ # @note Creates a new query builder with the current matcher's condition and merged context
181
+ # @example
182
+ # query.with_context(need_convert: false) #=> Returns a new query builder with conversion disabled
183
+ def with_context(addon_context = {})
184
+ dup_instance_exec do
185
+ @context = @context.dup
186
+ @context.config.merge!(addon_context)
187
+ set_matcher(@matcher.condition)
188
+ end
189
+ end
134
190
 
135
191
  # Prints the internal matcher tree structure for the current query.
136
192
  # Will output a human-readable visual tree of matchers.
@@ -148,8 +204,7 @@ module Mongory
148
204
  # @private
149
205
  # Duplicates the query and executes the block in context.
150
206
  #
151
- # @yieldparam dup [QueryBuilder]
152
- # @return [QueryBuilder]
207
+ # @return [QueryBuilder] the modified duplicate
153
208
  def dup_instance_exec(&block)
154
209
  dup.tap do |obj|
155
210
  obj.instance_exec(&block)
@@ -160,17 +215,18 @@ module Mongory
160
215
  # Builds the internal matcher tree from a condition hash.
161
216
  # Used to eagerly parse conditions to improve inspect/debug visibility.
162
217
  #
163
- # @param condition [Hash]
218
+ # @param condition [Hash] the condition to build the matcher from
164
219
  # @return [void]
165
220
  def set_matcher(condition = {})
166
- @matcher = QueryMatcher.new(condition)
221
+ @matcher = QueryMatcher.new(condition, context: @context)
167
222
  end
168
223
 
169
224
  # @private
170
225
  # Merges additional conditions into the matcher.
171
226
  #
172
- # @param key [String, Symbol]
173
- # @param conditions [Array<Hash>]
227
+ # @param key [String, Symbol] the operator key (e.g. '$and', '$or')
228
+ # @param conditions [Array<Hash>] the conditions to add
229
+ # @return [void]
174
230
  def add_conditions(key, conditions)
175
231
  condition_dup = @matcher.condition.dup
176
232
  condition_dup[key] ||= []
@@ -178,6 +234,12 @@ module Mongory
178
234
  set_matcher(condition_dup)
179
235
  end
180
236
 
237
+ # @private
238
+ # Wraps values in a condition hash with a given operator key.
239
+ #
240
+ # @param condition [Hash] the condition to transform
241
+ # @param key [String] the operator key to wrap with
242
+ # @return [Hash] the transformed condition
181
243
  def wrap_values_with_key(condition, key)
182
244
  condition.transform_values do |sub_condition|
183
245
  { key => sub_condition }
@@ -11,31 +11,58 @@ module Mongory
11
11
  # Typically used internally by `QueryBuilder`.
12
12
  #
13
13
  # Conversion via Mongory.data_converter is applied to the record
14
+ # before matching to ensure consistent data types.
14
15
  #
15
- # @example
16
- # matcher = QueryMatcher.build({ :age.gte => 18 })
17
- # matcher.match?(record)
16
+ # @example Basic matching
17
+ # matcher = QueryMatcher.new({ :age.gte => 18 })
18
+ # matcher.match?(record) # => true/false
19
+ #
20
+ # @example Complex condition
21
+ # matcher = QueryMatcher.new({
22
+ # :age.gte => 18,
23
+ # :$or => [
24
+ # { :name => /J/ },
25
+ # { :name.eq => 'Bob' }
26
+ # ]
27
+ # })
18
28
  #
19
29
  # @see Matchers::LiteralMatcher
20
30
  # @see Converters::ConditionConverter
21
31
  class QueryMatcher < Matchers::LiteralMatcher
32
+ # Initializes a new query matcher with the given condition.
33
+ # The condition is converted using Mongory.condition_converter
34
+ # before being passed to the parent matcher.
35
+ #
22
36
  # @param condition [Hash<Symbol, Object>] a query condition using operator-style symbol keys,
23
37
  # e.g. { :age.gt => 18 }, which will be parsed by `Mongory.condition_converter`.
24
- def initialize(condition)
25
- super(Mongory.condition_converter.convert(condition))
38
+ # @param context [Context] The query context containing configuration and current record
39
+ # @option context [Hash] :config The query configuration
40
+ # @option context [Object] :current_record The current record being processed
41
+ # @option context [Boolean] :need_convert Whether the record needs to be converted
42
+ def initialize(condition, context: Utils::Context.new)
43
+ super(Mongory.condition_converter.convert(condition), context: context)
26
44
  end
27
45
 
28
- # Matches the given record against the condition.
46
+ # Returns a Proc that can be used for fast matching.
47
+ # The Proc converts the record using Mongory.data_converter
48
+ # and delegates to the superclass's raw_proc.
29
49
  #
30
- # @param record [Object] the raw input record (e.g., Hash or model object) to be matched.
31
- # It will be converted internally using `Mongory.data_converter`.
32
- # @return [Boolean] whether the record satisfies the condition
33
- def match(record)
34
- super(Mongory.data_converter.convert(record))
50
+ # @return [Proc] A proc that performs query matching with context awareness
51
+ # @note The proc includes error handling and context-based record conversion
52
+ def raw_proc
53
+ super_proc = super
54
+ need_convert = @context.need_convert
55
+ data_converter = Mongory.data_converter
56
+
57
+ Proc.new do |record|
58
+ record = data_converter.convert(record) if need_convert
59
+ super_proc.call(record)
60
+ rescue StandardError
61
+ false
62
+ end
35
63
  end
36
64
 
37
65
  # Renders the full matcher tree for the current query.
38
- #
39
66
  # This method is intended to be the public entry point for rendering
40
67
  # the matcher tree. It does not accept any arguments and internally
41
68
  # handles rendering via the configured pretty-print logic.
@@ -22,7 +22,7 @@ module Mongory
22
22
  # @param other [Object] the value to match against
23
23
  # @return [Hash] converted query condition
24
24
  def __expr_part__(other, *)
25
- Converters::KeyConverter.instance.convert(@name, @operator => other)
25
+ { @name => { @operator => other } }
26
26
  end
27
27
  end
28
28
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongory
4
+ module Utils
5
+ # Context is a utility class that provides a stable but mutatable
6
+ # shared context for the Mongory query builder. It holds the configuration
7
+ # and the current record being matcher tree processed.
8
+ #
9
+ # @example
10
+ # context = Mongory::Utils::Context.new(config)
11
+ # context.current_record = record
12
+ # context.config = new_config
13
+ #
14
+ # @attr [Config] config The configuration object for the context
15
+ # @attr [Record] current_record The current record being processed in the matcher tree
16
+ # @attr [Boolean] need_convert Whether the record needs to be converted before matching
17
+ class Context
18
+ attr_accessor :config, :current_record, :need_convert
19
+
20
+ # Initializes a new Context instance with the given configuration.
21
+ #
22
+ # @param config [Config] The configuration object for the context.
23
+ # @return [Context] A new Context instance.
24
+ def initialize(config = {})
25
+ @config = config
26
+ @current_record = nil
27
+ @need_convert = true
28
+ end
29
+
30
+ # Creates a duplicate of the context with its own configuration.
31
+ #
32
+ # @return [Context] A new context instance with duplicated configuration
33
+ # @note The new context shares the same configuration object but has its own state
34
+ def dup
35
+ new_context = super
36
+ new_context.config = @config.dup
37
+ new_context
38
+ end
39
+ end
40
+ end
41
+ end
@@ -32,16 +32,18 @@ module Mongory
32
32
  @trace_entries = []
33
33
  end
34
34
 
35
- # Enables debug mode by aliasing `match?` to `debug_match`.
35
+ # Enables debug mode by aliasing `to_proc` to `debug_proc`.
36
36
  # @return [void]
37
+ # @note Changes the behavior of to_proc to use debug_proc instead of cached_proc
37
38
  def enable
38
- Matchers::AbstractMatcher.alias_method :match?, :debug_match
39
+ Matchers::AbstractMatcher.alias_method :to_proc, :debug_proc
39
40
  end
40
41
 
41
- # Disables debug mode by restoring `match?` to `regular_match`.
42
+ # Disables debug mode by restoring `to_proc` to `cached_proc`.
42
43
  # @return [void]
44
+ # @note Restores the original behavior of to_proc
43
45
  def disable
44
- Matchers::AbstractMatcher.alias_method :match?, :regular_match
46
+ Matchers::AbstractMatcher.alias_method :to_proc, :cached_proc
45
47
  end
46
48
 
47
49
  # Wraps a matcher evaluation block with indentation control.
data/lib/mongory/utils.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require 'date'
4
4
  require_relative 'utils/singleton_builder'
5
5
  require_relative 'utils/debugger'
6
+ require_relative 'utils/context'
6
7
 
7
8
  module Mongory
8
9
  # Utility helpers shared across Mongory internals.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mongory
4
- VERSION = '0.4.0'
4
+ VERSION = '0.6.0'
5
5
  end
data/lib/mongory.rb CHANGED
@@ -58,21 +58,21 @@ module Mongory
58
58
  #
59
59
  # @return [Converters::DataConverter]
60
60
  def self.data_converter
61
- Converters::DataConverter.instance
61
+ @data_converter ||= Converters::DataConverter.instance
62
62
  end
63
63
 
64
64
  # Returns the condition converter instance.
65
65
  #
66
66
  # @return [Converters::ConditionConverter]
67
67
  def self.condition_converter
68
- Converters::ConditionConverter.instance
68
+ @condition_converter ||= Converters::ConditionConverter.instance
69
69
  end
70
70
 
71
71
  # Returns the debugger instance.
72
72
  #
73
73
  # @return [Utils::Debugger]
74
74
  def self.debugger
75
- Utils::Debugger.instance
75
+ @debugger ||= Utils::Debugger.instance
76
76
  end
77
77
 
78
78
  # Builds a new query over the given record set.
data/mongory.gemspec CHANGED
@@ -10,15 +10,15 @@ Gem::Specification.new do |spec|
10
10
 
11
11
  spec.summary = 'MongoDB-like in-memory query DSL for Ruby'
12
12
  spec.description = 'A Mongo-like in-memory query DSL for Ruby'
13
- spec.homepage = 'https://koten0224.github.io/mongory-rb/'
13
+ spec.homepage = 'https://mongoryhq.github.io/mongory-rb/'
14
14
  spec.license = 'MIT'
15
15
  spec.required_ruby_version = '>= 2.6.0'
16
16
 
17
17
  spec.metadata['allowed_push_host'] = 'https://rubygems.org'
18
18
  spec.metadata['rubygems_mfa_required'] = 'true'
19
19
  spec.metadata['homepage_uri'] = spec.homepage
20
- spec.metadata['source_code_uri'] = 'https://github.com/koten0224/mongory-rb'
21
- spec.metadata['changelog_uri'] = 'https://github.com/koten0224/mongory-rb/blob/main/CHANGELOG.md'
20
+ spec.metadata['source_code_uri'] = 'https://github.com/mongoryhq/mongory-rb'
21
+ spec.metadata['changelog_uri'] = 'https://github.com/mongoryhq/mongory-rb/blob/main/CHANGELOG.md'
22
22
 
23
23
  # Specify which files should be added to the gem when it is released.
24
24
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.