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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +51 -0
- data/README.md +37 -7
- data/examples/benchmark-rails.rb +52 -0
- data/examples/benchmark.rb +79 -18
- data/lib/generators/mongory/matcher/matcher_generator.rb +1 -1
- data/lib/mongory/converters/abstract_converter.rb +22 -32
- data/lib/mongory/converters/condition_converter.rb +9 -19
- data/lib/mongory/converters/converted.rb +81 -0
- data/lib/mongory/converters/data_converter.rb +18 -7
- data/lib/mongory/converters/key_converter.rb +43 -19
- data/lib/mongory/converters/value_converter.rb +24 -19
- data/lib/mongory/converters.rb +1 -0
- data/lib/mongory/matchers/abstract_matcher.rb +94 -32
- data/lib/mongory/matchers/abstract_multi_matcher.rb +16 -45
- data/lib/mongory/matchers/and_matcher.rb +38 -10
- data/lib/mongory/matchers/array_record_matcher.rb +54 -28
- data/lib/mongory/matchers/elem_match_matcher.rb +13 -9
- data/lib/mongory/matchers/eq_matcher.rb +12 -7
- data/lib/mongory/matchers/every_matcher.rb +20 -9
- data/lib/mongory/matchers/exists_matcher.rb +15 -14
- data/lib/mongory/matchers/field_matcher.rb +58 -38
- data/lib/mongory/matchers/gt_matcher.rb +15 -7
- data/lib/mongory/matchers/gte_matcher.rb +15 -7
- data/lib/mongory/matchers/hash_condition_matcher.rb +54 -26
- data/lib/mongory/matchers/in_matcher.rb +20 -13
- data/lib/mongory/matchers/literal_matcher.rb +42 -48
- data/lib/mongory/matchers/lt_matcher.rb +15 -7
- data/lib/mongory/matchers/lte_matcher.rb +15 -7
- data/lib/mongory/matchers/ne_matcher.rb +12 -7
- data/lib/mongory/matchers/nin_matcher.rb +20 -12
- data/lib/mongory/matchers/not_matcher.rb +9 -5
- data/lib/mongory/matchers/or_matcher.rb +42 -13
- data/lib/mongory/matchers/present_matcher.rb +14 -15
- data/lib/mongory/matchers/regex_matcher.rb +37 -22
- data/lib/mongory/matchers/size_matcher.rb +50 -0
- data/lib/mongory/matchers.rb +1 -1
- data/lib/mongory/query_builder.rb +89 -27
- data/lib/mongory/query_matcher.rb +40 -13
- data/lib/mongory/query_operator.rb +1 -1
- data/lib/mongory/utils/context.rb +41 -0
- data/lib/mongory/utils/debugger.rb +6 -4
- data/lib/mongory/utils.rb +1 -0
- data/lib/mongory/version.rb +1 -1
- data/lib/mongory.rb +3 -3
- data/mongory.gemspec +3 -3
- metadata +11 -9
- data/lib/mongory/matchers/README.md +0 -57
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2e272e3c249c11059ecf96ad8eb0659d5e0fb0157050e7d69b6e272e16f874a
|
4
|
+
data.tar.gz: 84316343470d975672edbed9e590661bf555af5aa6ceb862d4e850efac0f5266
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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) # ~
|
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'}]) # ~
|
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) # ~
|
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'}]) # ~
|
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) # ~
|
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'}]) # ~
|
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/
|
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
|
data/examples/benchmark.rb
CHANGED
@@ -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
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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::
|
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
|
-
@
|
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
|
-
@
|
47
|
-
next unless target.is_a?(registry.klass)
|
45
|
+
convert_strategy = @convert_strategy_map[target.class] ||= find_strategy(target)
|
48
46
|
|
49
|
-
|
50
|
-
|
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
|
-
|
53
|
+
def fallback(target, _)
|
54
|
+
target
|
53
55
|
end
|
54
56
|
|
55
|
-
#
|
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
|
58
|
-
# @
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
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.
|
30
|
+
result.deep_merge!(converted_pair)
|
28
31
|
end
|
29
|
-
result
|
30
|
-
end
|
31
32
|
|
32
|
-
|
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
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|