mongory 0.4.0 → 0.6.1

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -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 +9 -19
  9. data/lib/mongory/converters/converted.rb +81 -0
  10. data/lib/mongory/converters/data_converter.rb +18 -7
  11. data/lib/mongory/converters/key_converter.rb +43 -19
  12. data/lib/mongory/converters/value_converter.rb +24 -19
  13. data/lib/mongory/converters.rb +1 -0
  14. data/lib/mongory/matchers/abstract_matcher.rb +94 -32
  15. data/lib/mongory/matchers/abstract_multi_matcher.rb +16 -45
  16. data/lib/mongory/matchers/and_matcher.rb +38 -10
  17. data/lib/mongory/matchers/array_record_matcher.rb +54 -28
  18. data/lib/mongory/matchers/elem_match_matcher.rb +13 -9
  19. data/lib/mongory/matchers/eq_matcher.rb +12 -7
  20. data/lib/mongory/matchers/every_matcher.rb +20 -9
  21. data/lib/mongory/matchers/exists_matcher.rb +15 -14
  22. data/lib/mongory/matchers/field_matcher.rb +58 -38
  23. data/lib/mongory/matchers/gt_matcher.rb +15 -7
  24. data/lib/mongory/matchers/gte_matcher.rb +15 -7
  25. data/lib/mongory/matchers/hash_condition_matcher.rb +54 -26
  26. data/lib/mongory/matchers/in_matcher.rb +20 -13
  27. data/lib/mongory/matchers/literal_matcher.rb +42 -48
  28. data/lib/mongory/matchers/lt_matcher.rb +15 -7
  29. data/lib/mongory/matchers/lte_matcher.rb +15 -7
  30. data/lib/mongory/matchers/ne_matcher.rb +12 -7
  31. data/lib/mongory/matchers/nin_matcher.rb +20 -12
  32. data/lib/mongory/matchers/not_matcher.rb +9 -5
  33. data/lib/mongory/matchers/or_matcher.rb +42 -13
  34. data/lib/mongory/matchers/present_matcher.rb +14 -15
  35. data/lib/mongory/matchers/regex_matcher.rb +37 -22
  36. data/lib/mongory/matchers/size_matcher.rb +50 -0
  37. data/lib/mongory/matchers.rb +1 -1
  38. data/lib/mongory/query_builder.rb +89 -27
  39. data/lib/mongory/query_matcher.rb +40 -13
  40. data/lib/mongory/query_operator.rb +1 -1
  41. data/lib/mongory/utils/context.rb +41 -0
  42. data/lib/mongory/utils/debugger.rb +6 -4
  43. data/lib/mongory/utils.rb +1 -0
  44. data/lib/mongory/version.rb +1 -1
  45. data/lib/mongory.rb +3 -3
  46. data/mongory.gemspec +3 -3
  47. metadata +11 -9
  48. data/lib/mongory/matchers/README.md +0 -57
  49. data/lib/mongory/matchers/abstract_operator_matcher.rb +0 -46
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5eea64a674029abe74c693aeb3c1ded8b35101e7b88f0f435f51a22ae6f254b7
4
- data.tar.gz: 96d9c62bf532ba3c8d55d38b6f99667e62fe0143fe196428446da2a23aa678c4
3
+ metadata.gz: f2e272e3c249c11059ecf96ad8eb0659d5e0fb0157050e7d69b6e272e16f874a
4
+ data.tar.gz: 84316343470d975672edbed9e590661bf555af5aa6ceb862d4e850efac0f5266
5
5
  SHA512:
6
- metadata.gz: 0b102c1aebfd8f7117c17cdf791cff88d0c413a3eb66e62968f8cfd72ab261ce5a06c7d7eaf0d7d7f352abc961ecb5aecd96cd8089a5107677fb9fd3c666beb3
7
- data.tar.gz: 9cf4b13d971b4ec78b8fc7682c20964aef5b334b88b0d1c09fbd70a7a0b266556ac850c9f71bdf915f723ce414a82313e44d6876a5cd76d1856e1d0f6761c867
6
+ metadata.gz: f0709d9abd1a769c111cc27b73a25f1d79b2af931de122fad4538259b97eccd5a8bb26336fab68d8affbba830ffa9a8af3bf07323f17051f588c633938a43b75
7
+ data.tar.gz: 292d65f888d36af1d22a9e4d7973ee8995c055c37abc837b1fdca161f5d4b37ed285c253da7a6019172848cb89d35e76e5ce0fa7b88725272ecdc46718d23968
data/CHANGELOG.md CHANGED
@@ -1,4 +1,55 @@
1
1
  # Changelog
2
+ ## [0.6.1] - 2025-04-24
3
+
4
+ ### Major Changes
5
+ - Add size matcher
6
+ - $in and $nin supports range condition
7
+ - Query matcher inherits from Hash condition matcher to accept hash condition only
8
+ - Introduced Converted to mark converted data and prevent double convert
9
+
10
+ ## [0.6.0] - 2025-04-23
11
+
12
+ ### Major Changes
13
+ - Refactored matcher system to use Proc-based implementation
14
+ - Introduced Context system for better state management
15
+ - Optimized empty condition handling with TRUE_PROC and FALSE_PROC
16
+
17
+ ### Breaking Changes
18
+ - Removed `match` method in favor of `raw_proc`
19
+ - Changed converter behavior to be executed within Proc
20
+ - Modified fallback behavior in converters
21
+
22
+ ### Features
23
+ - Added Context system for better configuration management
24
+ - Improved performance with Proc caching
25
+ - Enhanced error handling in matchers
26
+ - Better support for complex query conditions
27
+
28
+ ### Performance
29
+ - Optimized empty condition handling
30
+ - Reduced memory usage with Proc caching
31
+ - Improved execution speed with Proc-based implementation
32
+
33
+ ### Internal
34
+ - Refactored matcher system architecture
35
+ - Improved code organization and maintainability
36
+ - Enhanced test coverage
37
+
38
+ ## [0.5.0] - 2025-04-22
39
+
40
+ ### Added
41
+ - Added fast mode implementation for optimized query performance
42
+ - Added Proc-based matching for efficient record filtering
43
+ - Added error handling in fast mode execution
44
+
45
+ ### Changed
46
+ - Optimized query execution with compiled Proc objects
47
+ - Improved memory efficiency in record matching
48
+ - Enhanced error handling in query execution
49
+
50
+ ### Fixed
51
+ - Fixed potential performance bottlenecks in record matching
52
+ - Fixed error handling in query execution
2
53
 
3
54
  ## [0.4.0] - 2025-04-20
4
55
 
data/README.md CHANGED
@@ -245,6 +245,22 @@ Internally, the query is compiled into a matcher tree using the `QueryMatcher` a
245
245
  | `nin` | Checks if a value is not in a set | `nin(age: [18, 19, 20])` |
246
246
  | `limit` | Limits the number of records returned. This method executes immediately and affects subsequent conditions. | `limit(2)` |
247
247
  | `pluck` | Extracts selected fields from matching records | `pluck(:name)` |
248
+ | `with_context` | Sets a custom context for the query. Useful for controlling data conversion and sharing configuration across matchers. | `with_context(merchant: merchant)` |
249
+
250
+ #### Context Configuration
251
+
252
+ The `with_context` method allows you to customize the query execution environment:
253
+
254
+ ```ruby
255
+ # Share configuration across matchers
256
+ records.mongory
257
+ .with_context(custom_option: true)
258
+ .where(:status => 'active')
259
+ .where(:age.gte => 18)
260
+ ```
261
+
262
+ This will share a mutatable, but stable context object to all matchers in matcher tree.
263
+ To get your custom option, using `@context.config` in your custom matcher.
248
264
 
249
265
  ### Debugging Queries
250
266
 
@@ -313,30 +329,37 @@ The debug output includes:
313
329
  1. **Memory Usage**
314
330
  - Mongory operates entirely in memory
315
331
  - Consider your data size and memory constraints
332
+ - Proc-based implementation reduces memory usage
333
+ - Context system provides better memory management
316
334
 
317
335
  2. **Query Optimization**
318
336
  - Complex conditions are evaluated in sequence
319
337
  - Use `explain` to analyze query performance
338
+ - Empty conditions are optimized with cached Procs
339
+ - Context system allows fine-grained control over conversion
320
340
 
321
341
  3. **Benchmarks**
322
342
  ```ruby
323
343
  # Simple query (1000 records)
324
- records.mongory.where(:age.gte => 18) # ~2.5ms
344
+ records.mongory.where(:age.gte => 18) # ~0.8ms
325
345
 
326
346
  # Complex query (1000 records)
327
- records.mongory.where(:$or => [{:age.gte => 18}, {:status => 'active'}]) # ~3.2ms
347
+ records.mongory.where(:$or => [{:age.gte => 18}, {:status => 'active'}]) # ~0.9ms
328
348
 
329
349
  # Simple query (10000 records)
330
- records.mongory.where(:age.gte => 18) # ~24.5ms
350
+ records.mongory.where(:age.gte => 18) # ~6.5ms
331
351
 
332
352
  # Complex query (10000 records)
333
- records.mongory.where(:$or => [{:age.gte => 18}, {:status => 'active'}]) # ~31.5ms
353
+ records.mongory.where(:$or => [{:age.gte => 18}, {:status => 'active'}]) # ~7.5ms
334
354
 
335
355
  # Simple query (100000 records)
336
- records.mongory.where(:age.gte => 18) # ~242.5ms
356
+ records.mongory.where(:age.gte => 18) # ~64.7ms
337
357
 
338
358
  # Complex query (100000 records)
339
- records.mongory.where(:$or => [{:age.gte => 18}, {:status => 'active'}]) # ~323.0ms
359
+ records.mongory.where(:$or => [{:age.gte => 18}, {:status => 'active'}]) # ~75.0ms
360
+
361
+ # Complex query with fast mode (100000 records)
362
+ records.mongory.where(:$or => [{:age.gte => 18}, {:status => 'active'}]).fast # ~42.8ms, about 3x of plain ruby
340
363
  ```
341
364
 
342
365
  Note: Performance varies based on:
@@ -435,6 +458,9 @@ end
435
458
  # Use limit to restrict result set
436
459
  records.mongory.limit(100).where(:age.gte => 18)
437
460
 
461
+ # Use fast mode for better performance
462
+ records.mongory.where(:age.gte => 18).fast
463
+
438
464
  # Use explain to analyze complex queries
439
465
  query = records.mongory.where(:$or => [...])
440
466
  query.explain
@@ -457,10 +483,14 @@ end
457
483
  1. **Data Size**
458
484
  - Suitable for small to medium datasets
459
485
  - Large datasets may impact performance
486
+ - Proc-based implementation helps with memory usage
487
+ - Context system provides better resource management
460
488
 
461
489
  2. **Query Complexity**
462
490
  - Complex queries may affect performance
463
491
  - Not all MongoDB operators are supported
492
+ - Proc-based implementation improves complex query performance
493
+ - Context system allows better control over query execution
464
494
 
465
495
  3. **Memory Usage**
466
496
  - All operations are performed in memory
@@ -510,7 +540,7 @@ Please ensure your code adheres to the project's style guide and that all tests
510
540
 
511
541
  ## Code of Conduct
512
542
 
513
- Everyone interacting in the Mongory-rb project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [code of conduct](https://github.com/koten0224/mongory-rb/blob/main/CODE_OF_CONDUCT.md).
543
+ Everyone interacting in the Mongory-rb project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [code of conduct](https://github.com/mongoryhq/mongory-rb/blob/main/CODE_OF_CONDUCT.md).
514
544
 
515
545
  ## License
516
546
 
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/all'
4
+ require 'benchmark'
5
+
6
+ puts 'Ruby String#empty?'
7
+ result = Benchmark.measure do
8
+ 1_000_000.times do
9
+ ''.empty?
10
+ end
11
+ end
12
+ puts result
13
+
14
+ puts 'Rails String#blank?'
15
+ result = Benchmark.measure do
16
+ 1_000_000.times do
17
+ ''.blank?
18
+ end
19
+ end
20
+ puts result
21
+
22
+ puts 'Ruby Array#empty?'
23
+ result = Benchmark.measure do
24
+ 1_000_000.times do
25
+ [].empty?
26
+ end
27
+ end
28
+ puts result
29
+
30
+ puts 'Rails Array#blank?'
31
+ result = Benchmark.measure do
32
+ 1_000_000.times do
33
+ [].blank?
34
+ end
35
+ end
36
+ puts result
37
+
38
+ puts 'Ruby Hash#empty?'
39
+ result = Benchmark.measure do
40
+ 1_000_000.times do
41
+ {}.empty?
42
+ end
43
+ end
44
+ puts result
45
+
46
+ puts 'Rails Hash#blank?'
47
+ result = Benchmark.measure do
48
+ 1_000_000.times do
49
+ {}.blank?
50
+ end
51
+ end
52
+ puts result
@@ -7,8 +7,23 @@ require 'benchmark'
7
7
  Mongory.register(Array)
8
8
  Mongory.enable_symbol_snippets!
9
9
 
10
+ def gc_handler
11
+ # GC.disable
12
+ before = GC.stat
13
+ yield
14
+ after = GC.stat
15
+ # GC.enable
16
+
17
+ created = after[:total_allocated_objects] - before[:total_allocated_objects]
18
+ freed = after[:total_freed_objects] - before[:total_freed_objects]
19
+ alive = after[:heap_live_slots] - before[:heap_live_slots]
20
+
21
+ puts "Created: #{created}" # 分配了幾個 object
22
+ puts "Freed: #{freed}" # 中途 GC 掃掉幾個(若 GC 有觸發)
23
+ puts "Net alive: #{alive}" # 最後還活著的物件數
24
+ end
10
25
  # Test with different data sizes
11
- [1000, 10_000, 100_000].each do |size|
26
+ [20, 1000, 10_000, 100_000].each do |size|
12
27
  puts "\nTesting with #{size} records:"
13
28
 
14
29
  # Generate test data
@@ -19,26 +34,72 @@ Mongory.enable_symbol_snippets!
19
34
  }
20
35
  end
21
36
 
22
- # Simple query test
23
- puts "\nSimple query (#{size} records):"
24
- 5.times do
25
- result = Benchmark.measure do
26
- records.mongory.where(:age.gte => 18).to_a
37
+ # Simple query (Plain Ruby) test
38
+ puts "\nSimple query (Plain Ruby) (#{size} records):"
39
+ gc_handler do
40
+ 5.times do
41
+ result = Benchmark.measure do
42
+ records.select { |r| r.key?('age') && r['age'] >= 18 }
43
+ end
44
+ puts result
45
+ end
46
+ end
47
+
48
+ # Simple query (Mongory) test
49
+ puts "\nSimple query (Mongory) (#{size} records):"
50
+ gc_handler do
51
+ 5.times do
52
+ result = Benchmark.measure do
53
+ records.mongory.where(:age.gte => 18).to_a
54
+ end
55
+ puts result
56
+ end
57
+ end
58
+
59
+ # Complex query (Plain Ruby) test
60
+ puts "\nComplex query (Plain Ruby) (#{size} records):"
61
+ gc_handler do
62
+ 5.times do
63
+ result = Benchmark.measure do
64
+ records.select do |r|
65
+ next false unless r.key?('age') && r.key?('status')
66
+
67
+ r['age'] >= 18 || r['status'] == 'active'
68
+ end
69
+ end
70
+ puts result
71
+ end
72
+ end
73
+
74
+ # Complex query (Mongory) test
75
+ puts "\nComplex query (Mongory) (#{size} records):"
76
+ gc_handler do
77
+ 5.times do
78
+ result = Benchmark.measure do
79
+ records.mongory.where(
80
+ :$or => [
81
+ { :age.gte => 18 },
82
+ { status: 'active' }
83
+ ]
84
+ ).to_a
85
+ end
86
+ puts result
27
87
  end
28
- puts result
29
88
  end
30
89
 
31
- # Complex query test
32
- puts "\nComplex query (#{size} records):"
33
- 5.times do
34
- result = Benchmark.measure do
35
- records.mongory.where(
36
- :$or => [
37
- { :age.gte => 18 },
38
- { status: 'active' }
39
- ]
40
- ).to_a
90
+ # Complex query (Mongory) test
91
+ puts "\nComplex query (Mongory) fast mode (#{size} records):"
92
+ gc_handler do
93
+ 5.times do
94
+ result = Benchmark.measure do
95
+ records.mongory.where(
96
+ :$or => [
97
+ { :age.gte => 18 },
98
+ { status: 'active' }
99
+ ]
100
+ ).fast.to_a
101
+ end
102
+ puts result
41
103
  end
42
- puts result
43
104
  end
44
105
  end
@@ -14,7 +14,7 @@ module Mongory
14
14
  # # spec/mongory/matchers/class_in_matcher_spec.rb
15
15
  # # config/initializers/mongory.rb (if not exists)
16
16
  #
17
- # @see Mongory::Matchers::AbstractOperatorMatcher
17
+ # @see Mongory::Matchers::AbstractMatcher
18
18
  class MatcherGenerator < Rails::Generators::NamedBase
19
19
  source_root File.expand_path('templates', __dir__)
20
20
 
@@ -33,8 +33,7 @@ module Mongory
33
33
  def initialize
34
34
  super(self.class.to_s)
35
35
  @registries = []
36
- @fallback = Proc.new { |*| self }
37
- execute_once_only!(:default_registrations)
36
+ @convert_strategy_map = {}.compare_by_identity
38
37
  end
39
38
 
40
39
  # Applies the registered conversion to the given target object.
@@ -43,27 +42,32 @@ module Mongory
43
42
  # @param other [Object] optional secondary value
44
43
  # @return [Object] converted result
45
44
  def convert(target, other = NOTHING)
46
- @registries.each do |registry|
47
- next unless target.is_a?(registry.klass)
45
+ convert_strategy = @convert_strategy_map[target.class] ||= find_strategy(target)
48
46
 
49
- return exec_convert(target, other, &registry.exec)
50
- end
47
+ return fallback(target, other) if convert_strategy == NOTHING
48
+ return target.instance_exec(&convert_strategy) if other == NOTHING
49
+
50
+ target.instance_exec(other, &convert_strategy)
51
+ end
51
52
 
52
- exec_convert(target, other, &@fallback)
53
+ def fallback(target, _)
54
+ target
53
55
  end
54
56
 
55
- # Internal dispatch logic to apply a matching converter.
57
+ # Finds the appropriate conversion strategy for the target object.
58
+ # Searches through registered rules and returns the first matching one,
59
+ # or the fallback strategy if no match is found.
56
60
  #
57
- # @param target [Object] the object to match
58
- # @param other [Object] optional extra data
59
- # @yield fallback block if no converter is found
60
- # @return [Object]
61
- def exec_convert(target, other, &block)
62
- if other == NOTHING
63
- target.instance_exec(&block)
64
- else
65
- target.instance_exec(other, &block)
61
+ # @param target [Object] the object to find a strategy for
62
+ # @return [Proc] the conversion strategy to use
63
+ def find_strategy(target)
64
+ @registries.each do |registry|
65
+ next unless target.is_a?(registry.klass)
66
+
67
+ return registry.exec
66
68
  end
69
+
70
+ NOTHING
67
71
  end
68
72
 
69
73
  # Opens a configuration block to register more converters.
@@ -98,25 +102,11 @@ module Mongory
98
102
  register(klass) { |*args, &bl| send(converter, *args, &bl) }
99
103
  elsif block.is_a?(Proc)
100
104
  @registries.unshift(Registry.new(klass, block))
105
+ @convert_strategy_map[klass] = block
101
106
  else
102
107
  raise 'Support Symbol and block only.'
103
108
  end
104
109
  end
105
-
106
- # Executes the given method only once by undefining it after execution.
107
- #
108
- # @param method_sym [Symbol] method name to execute once
109
- # @return [void]
110
- def execute_once_only!(method_sym)
111
- send(method_sym)
112
- singleton_class.undef_method(method_sym)
113
- end
114
-
115
- # Defines default class-to-converter registrations.
116
- # Should be overridden by subclasses.
117
- #
118
- # @return [void]
119
- def default_registrations; end
120
110
  end
121
111
  end
122
112
  end
@@ -16,36 +16,27 @@ module Mongory
16
16
  #
17
17
  class ConditionConverter < AbstractConverter
18
18
  # Converts a flat condition hash into a nested structure.
19
+ # Applies both key and value conversion, and merges overlapping keys.
19
20
  #
20
- # @param condition [Hash]
21
+ # @param condition [Hash] the flat condition hash to convert
21
22
  # @return [Hash] the transformed nested condition
22
23
  def convert(condition)
23
- result = {}
24
+ return condition if condition.is_a?(Converted)
25
+
26
+ result = Converted::Hash.new
24
27
  condition.each_pair do |k, v|
25
28
  converted_value = value_converter.convert(v)
26
29
  converted_pair = key_converter.convert(k, converted_value)
27
- result.merge!(converted_pair, &deep_merge_block)
30
+ result.deep_merge!(converted_pair)
28
31
  end
29
- result
30
- end
31
32
 
32
- # Provides a block that merges values for overlapping keys in a deep way.
33
- #
34
- # @return [Proc]
35
- def deep_merge_block
36
- @deep_merge_block ||= Proc.new do |_, a, b|
37
- if a.is_a?(Hash) && b.is_a?(Hash)
38
- a.merge(b, &deep_merge_block)
39
- else
40
- b
41
- end
42
- end
33
+ result
43
34
  end
44
35
 
45
36
  # @note Singleton instance, not configurable after initialization
46
37
  # Returns the key converter used to transform condition keys.
47
38
  #
48
- # @return [AbstractConverter]
39
+ # @return [AbstractConverter] the key converter instance
49
40
  def key_converter
50
41
  KeyConverter.instance
51
42
  end
@@ -62,13 +53,12 @@ module Mongory
62
53
  #
63
54
  # @return [void]
64
55
  def freeze
65
- deep_merge_block
66
56
  super
67
57
  key_converter.freeze
68
58
  value_converter.freeze
69
59
  end
70
60
 
71
- undef_method :register, :exec_convert
61
+ undef_method :register
72
62
  end
73
63
  end
74
64
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongory
4
+ module Converters
5
+ # Converted is a module that provides conversion structures marked as
6
+ # Converted. It is used to convert various types of data into a
7
+ # standardized form for MongoDB queries. The module includes
8
+ # classes for converting hashes and arrays into nested structures.
9
+ # It is used internally by the ConditionConverter and ValueConverter
10
+ # to handle the conversion of complex data types into a format
11
+ # suitable for MongoDB queries.
12
+ module Converted
13
+ def instance_convert(other)
14
+ return other if other.is_a?(Converted)
15
+
16
+ case other
17
+ when Hash
18
+ Converted::Hash.new(other)
19
+ when Array
20
+ Converted::Array.new(other)
21
+ else
22
+ other
23
+ end
24
+ end
25
+
26
+ # Converts a flat condition hash into a nested structure.
27
+ # Applies value conversion to each element.
28
+ # This is used for conditions that are hashes of values.
29
+ # It is used internally by the ConditionConverter and ValueConverter
30
+ # to handle the conversion of complex data types into a format
31
+ # suitable for MongoDB queries.
32
+ class Hash < ::Hash
33
+ include Converted
34
+
35
+ def initialize(other = {})
36
+ super()
37
+ other.each_pair do |k, v|
38
+ self[k] = instance_convert(v)
39
+ end
40
+ end
41
+
42
+ def deep_merge(other)
43
+ dup.deep_merge!(Hash.new(other))
44
+ end
45
+
46
+ def deep_merge!(other)
47
+ _deep_merge!(self, Hash.new(other))
48
+ end
49
+
50
+ private
51
+
52
+ def _deep_merge!(left, right)
53
+ left.merge!(right) do |_, a, b|
54
+ if a.is_a?(::Hash) && b.is_a?(::Hash)
55
+ _deep_merge!(a.dup, b)
56
+ else
57
+ b
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ # Converts a flat condition array into a nested structure.
64
+ # Applies value conversion to each element.
65
+ # This is used for conditions that are arrays of values.
66
+ # It is used internally by the ConditionConverter and ValueConverter
67
+ # to handle the conversion of complex data types into a format
68
+ # suitable for MongoDB queries.
69
+ class Array < ::Array
70
+ include Converted
71
+
72
+ def initialize(other)
73
+ super()
74
+ other.each do |v|
75
+ self << instance_convert(v)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -13,13 +13,24 @@ module Mongory
13
13
  # DataConverter.instance.convert(:status) #=> "status"
14
14
  #
15
15
  class DataConverter < AbstractConverter
16
- def default_registrations
17
- register(Symbol, :to_s)
18
- register(Date, :to_s)
19
- register(Time, :iso8601)
20
- register(DateTime, :iso8601)
21
- register(String, :itself)
22
- register(Integer, :itself)
16
+ alias_method :super_convert, :convert
17
+
18
+ # Converts a value into its standardized form based on its type.
19
+ # Handles common primitive types with predefined conversion rules.
20
+ #
21
+ # @param target [Object] the value to convert
22
+ # @return [Object] the converted value
23
+ def convert(target)
24
+ case target
25
+ when String, Integer, Hash, Array
26
+ target
27
+ when Symbol, Date
28
+ target.to_s
29
+ when Time, DateTime
30
+ target.iso8601
31
+ else
32
+ super_convert(target)
33
+ end
23
34
  end
24
35
  end
25
36
  end