mongory 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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +84 -0
  4. data/.yardopts +7 -0
  5. data/CHANGELOG.md +246 -0
  6. data/CODE_OF_CONDUCT.md +84 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +517 -0
  9. data/Rakefile +12 -0
  10. data/examples/README.md +41 -0
  11. data/examples/benchmark.rb +44 -0
  12. data/lib/generators/mongory/install/install_generator.rb +42 -0
  13. data/lib/generators/mongory/install/templates/initializer.rb.erb +83 -0
  14. data/lib/generators/mongory/matcher/matcher_generator.rb +56 -0
  15. data/lib/generators/mongory/matcher/templates/matcher.rb.erb +92 -0
  16. data/lib/generators/mongory/matcher/templates/matcher_spec.rb.erb +17 -0
  17. data/lib/mongory/converters/abstract_converter.rb +122 -0
  18. data/lib/mongory/converters/condition_converter.rb +74 -0
  19. data/lib/mongory/converters/data_converter.rb +26 -0
  20. data/lib/mongory/converters/key_converter.rb +63 -0
  21. data/lib/mongory/converters/value_converter.rb +47 -0
  22. data/lib/mongory/converters.rb +7 -0
  23. data/lib/mongory/matchers/README.md +57 -0
  24. data/lib/mongory/matchers/abstract_matcher.rb +153 -0
  25. data/lib/mongory/matchers/abstract_multi_matcher.rb +109 -0
  26. data/lib/mongory/matchers/abstract_operator_matcher.rb +46 -0
  27. data/lib/mongory/matchers/and_matcher.rb +65 -0
  28. data/lib/mongory/matchers/array_record_matcher.rb +88 -0
  29. data/lib/mongory/matchers/elem_match_matcher.rb +47 -0
  30. data/lib/mongory/matchers/eq_matcher.rb +37 -0
  31. data/lib/mongory/matchers/every_matcher.rb +41 -0
  32. data/lib/mongory/matchers/exists_matcher.rb +48 -0
  33. data/lib/mongory/matchers/field_matcher.rb +123 -0
  34. data/lib/mongory/matchers/gt_matcher.rb +29 -0
  35. data/lib/mongory/matchers/gte_matcher.rb +29 -0
  36. data/lib/mongory/matchers/hash_condition_matcher.rb +55 -0
  37. data/lib/mongory/matchers/in_matcher.rb +52 -0
  38. data/lib/mongory/matchers/literal_matcher.rb +123 -0
  39. data/lib/mongory/matchers/lt_matcher.rb +29 -0
  40. data/lib/mongory/matchers/lte_matcher.rb +29 -0
  41. data/lib/mongory/matchers/ne_matcher.rb +29 -0
  42. data/lib/mongory/matchers/nin_matcher.rb +51 -0
  43. data/lib/mongory/matchers/not_matcher.rb +32 -0
  44. data/lib/mongory/matchers/or_matcher.rb +60 -0
  45. data/lib/mongory/matchers/present_matcher.rb +52 -0
  46. data/lib/mongory/matchers/regex_matcher.rb +61 -0
  47. data/lib/mongory/matchers.rb +176 -0
  48. data/lib/mongory/mongoid.rb +19 -0
  49. data/lib/mongory/query_builder.rb +187 -0
  50. data/lib/mongory/query_matcher.rb +66 -0
  51. data/lib/mongory/query_operator.rb +28 -0
  52. data/lib/mongory/rails.rb +15 -0
  53. data/lib/mongory/utils/debugger.rb +123 -0
  54. data/lib/mongory/utils/rails_patch.rb +22 -0
  55. data/lib/mongory/utils/singleton_builder.rb +31 -0
  56. data/lib/mongory/utils.rb +75 -0
  57. data/lib/mongory/version.rb +5 -0
  58. data/lib/mongory-rb.rb +3 -0
  59. data/lib/mongory.rb +116 -0
  60. data/mongory.gemspec +40 -0
  61. data/sig/mongory.rbs +4 -0
  62. metadata +108 -0
data/README.md ADDED
@@ -0,0 +1,517 @@
1
+ # Mongory-rb
2
+
3
+ A Mongo-like in-memory query DSL for Ruby.
4
+
5
+ Mongory lets you filter and query in-memory collections using syntax and semantics similar to MongoDB. It is designed for expressive chaining, symbolic operators, and composable matchers.
6
+
7
+ ## Positioning
8
+
9
+ Mongory is designed to serve two types of users:
10
+
11
+ 1. For MongoDB users:
12
+ - Seamless integration with familiar query syntax
13
+ - Extends query capabilities for non-indexed fields
14
+ - No additional learning cost
15
+
16
+ 2. For non-MongoDB users:
17
+ - Initial learning cost for MongoDB-style syntax
18
+ - Long-term benefits:
19
+ - Improved code readability
20
+ - Better development efficiency
21
+ - Lower maintenance costs
22
+ - Ideal for teams valuing code quality and maintainability
23
+
24
+ ## Requirements
25
+
26
+ - Ruby >= 2.6.0
27
+ - No external database required
28
+
29
+ ## Quick Start
30
+
31
+ ### Installation
32
+ #### Rails Generator
33
+
34
+ You can install a starter configuration with:
35
+
36
+ ```bash
37
+ rails g mongory:install
38
+ ```
39
+
40
+ This will generate `config/initializers/mongory.rb` and set up:
41
+ - Optional symbol operator snippets (e.g. `:age.gt => 18`)
42
+ - Class registration (e.g. `Array`, `ActiveRecord::Relation`, etc.)
43
+ - Custom value/key converters for your ORM
44
+
45
+ Or install manually:
46
+ ```bash
47
+ gem install mongory
48
+ ```
49
+
50
+ Or add to your Gemfile:
51
+ ```ruby
52
+ gem 'mongory'
53
+ ```
54
+
55
+ ### Basic Usage
56
+ ```ruby
57
+ records = [
58
+ { 'name' => 'Jack', 'age' => 18, 'gender' => 'M' },
59
+ { 'name' => 'Jill', 'age' => 15, 'gender' => 'F' },
60
+ { 'name' => 'Bob', 'age' => 21, 'gender' => 'M' },
61
+ { 'name' => 'Mary', 'age' => 18, 'gender' => 'F' }
62
+ ]
63
+
64
+ # Basic query with conditions
65
+ result = records.mongory
66
+ .where(:age.gte => 18)
67
+ .or({ :name => /J/ }, { :name.eq => 'Bob' })
68
+
69
+ # Using limit to restrict results
70
+ # Note: limit executes immediately and affects subsequent conditions
71
+ limited = records.mongory
72
+ .limit(2) # Only process first 2 records
73
+ .where(:age.gte => 18) # Conditions apply to limited set
74
+ ```
75
+
76
+ ### Integration with MongoDB
77
+
78
+ Mongory is designed to complement MongoDB, not replace it. Here's how to use them together:
79
+
80
+ 1. Use MongoDB for:
81
+ - Queries with indexes
82
+ - Persistent data operations
83
+ - Large-scale data processing
84
+
85
+ 2. Use Mongory for:
86
+ - Queries without indexes
87
+ - Complex in-memory calculations
88
+ - Temporary data filtering needs
89
+
90
+ Example:
91
+ ```ruby
92
+ # First use MongoDB for indexed queries
93
+ users = User.where(status: 'active') # Uses MongoDB index
94
+
95
+ # Then use Mongory for non-indexed fields
96
+ active_users = users.mongory
97
+ .where(:last_login.gte => 1.week.ago) # No index on last_login
98
+ .where(:tags.elem_match => { :name => 'ruby' }) # Complex array query
99
+ ```
100
+
101
+ ### Creating Custom Matchers
102
+ #### Using the Generator
103
+
104
+ You can generate a new matcher using:
105
+
106
+ ```bash
107
+ rails g mongory:matcher class_in
108
+ ```
109
+
110
+ This will:
111
+ 1. Create a new matcher file at `lib/mongory/matchers/class_in_matcher.rb`
112
+ 2. Create a spec file at `spec/mongory/matchers/class_in_matcher_spec.rb`
113
+ 3. Update `config/initializers/mongory.rb` to require the new matcher
114
+
115
+ The generated matcher will:
116
+ - Be named `ClassInMatcher`
117
+ - Register the operator as `$classIn`
118
+ - Be available as `:class_in` in queries
119
+
120
+ Example usage of the generated matcher:
121
+ ```ruby
122
+ records.mongory.where(:value.class_in => [Integer, String])
123
+ ```
124
+
125
+ #### Manual Creation
126
+
127
+ If you prefer to create matchers manually, here's an example:
128
+
129
+ ```ruby
130
+ class ClassInMatcher < Mongory::Matchers::AbstractMatcher
131
+ def match(subject)
132
+ @condition.any? { |klass| subject.is_a?(klass) }
133
+ end
134
+
135
+ def check_validity!
136
+ raise TypeError, '$classIn needs an array.' unless @condition.is_a?(Array)
137
+ @condition.each do |klass|
138
+ raise TypeError, '$classIn needs an array of class.' unless klass.is_a?(Class)
139
+ end
140
+ end
141
+ end
142
+
143
+ Mongory::Matchers.register(:class_in, '$classIn', ClassInMatcher)
144
+
145
+ [{a: 1}].mongory.where(:a.class_in => [Integer]).first
146
+ # => { a: 1 }
147
+ ```
148
+
149
+ You can define any matcher behavior and attach it to a `$operator` of your choice.
150
+ Matchers can be composed, validated, and traced just like built-in ones.
151
+
152
+ ### Handling Dots in Field Names
153
+
154
+ Mongory supports field names containing dots, which require escaping:
155
+
156
+ ```ruby
157
+ # Sample data
158
+ records = [
159
+ { "user.name" => "John", "age" => 25 }, # Field name contains a dot
160
+ { "user" => { "name" => "Bob" }, "age" => 30 } # Nested field
161
+ ]
162
+
163
+ # Field name contains a dot
164
+ records.mongory.where("user\\.name" => "John") # Two backslashes needed with double quotes
165
+ # => [{ "user.name" => "John", "age" => 25 }]
166
+
167
+ # or
168
+ records.mongory.where('user\.name' => "John") # One backslash needed with single quotes
169
+ # => [{ "user.name" => "John", "age" => 25 }]
170
+
171
+ # Nested field (no escaping needed)
172
+ records.mongory.where("user.name" => "Bob")
173
+ # => [{ "user" => { "name" => "Bob" }, "age" => 30 }]
174
+ ```
175
+
176
+ Note:
177
+ - With double quotes, backslashes need to be escaped (`\\`)
178
+ - With single quotes, backslashes don't need to be escaped (`\`)
179
+ - This behavior is consistent with MongoDB's query syntax
180
+ - The escaped dot pattern (`\.`) matches fields where the dot is part of the field name
181
+ - The unescaped dot pattern (`.`) matches nested fields in the document structure
182
+
183
+ ### Advanced Usage
184
+
185
+ #### Complex Queries
186
+ ```ruby
187
+ # Nested conditions
188
+ users.mongory
189
+ .where(
190
+ :age.gte => 18,
191
+ :$or => [
192
+ { :status => 'active' },
193
+ { :status => 'pending', :created_at.gte => 1.week.ago }
194
+ ]
195
+ )
196
+
197
+ # Using any_of for nested OR conditions
198
+ users.mongory
199
+ .where(:age.gte => 18)
200
+ .any_of(
201
+ { :status => 'active' },
202
+ { :status => 'pending', :created_at.gte => 1.week.ago }
203
+ )
204
+
205
+ # Array operations
206
+ posts.mongory
207
+ .where(:tags.elem_match => { :name => 'ruby', :priority.gt => 5 })
208
+ .where(:comments.every => { :approved => true })
209
+ ```
210
+
211
+ #### Integration with ActiveRecord
212
+ ```ruby
213
+ class User < ActiveRecord::Base
214
+ def active_friends
215
+ friends.mongory
216
+ .where(:status => 'active')
217
+ .where(:last_seen.gte => 1.day.ago)
218
+ end
219
+ end
220
+ ```
221
+
222
+ ### Query API Reference
223
+ #### Registering Models
224
+
225
+ To allow calling `.mongory` on collections, use `register`:
226
+
227
+ ```ruby
228
+ Mongory.register(Array)
229
+ Mongory.register(ActiveRecord::Relation)
230
+ User.where(status: 'active').mongory.where(:age.gte => 18, :name.regex => "^S.+")
231
+ ```
232
+
233
+ This injects a `.mongory` method via an internal extension module.
234
+
235
+ Internally, the query is compiled into a matcher tree using the `QueryMatcher` and `ConditionConverter`.
236
+
237
+ | Method | Description | Example |
238
+ |--------|-------------|---------|
239
+ | `where` | Adds a condition to filter records | `where(age: { :$gte => 18 })` |
240
+ | `not` | Adds a negated condition | `not(age: { :$lt => 18 })` |
241
+ | `and` | Combines conditions with `$and` | `and({ age: { :$gte => 18 } }, { name: /J/ })` |
242
+ | `or` | Combines conditions with `$or` | `or({ age: { :$gte => 18 } }, { name: /J/ })` |
243
+ | `any_of` | Combines conditions with `$or` inside an `$and` block | `any_of({ age: { :$gte => 18 } }, { name: /J/ })` |
244
+ | `in` | Checks if a value is in a set | `in(age: [18, 19, 20])` |
245
+ | `nin` | Checks if a value is not in a set | `nin(age: [18, 19, 20])` |
246
+ | `limit` | Limits the number of records returned. This method executes immediately and affects subsequent conditions. | `limit(2)` |
247
+ | `pluck` | Extracts selected fields from matching records | `pluck(:name)` |
248
+
249
+ ### Debugging Queries
250
+
251
+ You can use `explain` to visualize the matcher tree structure:
252
+ ```ruby
253
+ records = [
254
+ { name: 'John', age: 25, status: 'active' },
255
+ { name: 'Jane', age: 30, status: 'inactive' }
256
+ ]
257
+
258
+ query = records.mongory
259
+ .where(:age.gte => 18)
260
+ .any_of(
261
+ { :status => 'active' },
262
+ { :name.regex => /^J/ }
263
+ )
264
+
265
+ query.explain
266
+ ```
267
+ Output:
268
+ ```
269
+ And: {"age"=>{"$gte"=>18}, "$or"=>[{"status"=>"active"}, {"name"=>{"$regex"=>/^J/}}]}
270
+ ├─ Field: "age" to match: {"$gte"=>18}
271
+ │ └─ Gte: 18
272
+ └─ Or: [{"status"=>"active"}, {"name"=>{"$regex"=>/^J/}}]
273
+ ├─ Field: "status" to match: "active"
274
+ │ └─ Eq: "active"
275
+ └─ Field: "name" to match: {"$regex"=>/^J/}
276
+ └─ Regex: /^J/
277
+ ```
278
+
279
+ This helps you understand how your query is being processed and can be useful for debugging complex conditions.
280
+
281
+ Or use the debugger for detailed matching process:
282
+ ```ruby
283
+ # Enable debugging
284
+ Mongory.debugger.enable
285
+
286
+ # Execute your query
287
+ query = Mongory.build_query(users).where(age: { :$gt => 18 })
288
+ query.each do |user|
289
+ puts user
290
+ end
291
+
292
+ # Display the debug trace
293
+ Mongory.debugger.display
294
+ ```
295
+
296
+ The debug output will show detailed matching process with full class names:
297
+ ```
298
+ QueryMatcher Matched, condition: {"age"=>{"$gt"=>18}}, record: {"age"=>25}
299
+ AndMatcher Matched, condition: {"age"=>{"$gt"=>18}}, record: {"age"=>25}
300
+ FieldMatcher Matched, condition: {"$gt"=>18}, field: "age", record: {"age"=>25}
301
+ GtMatcher Matched, condition: 18, record: 25
302
+ ```
303
+
304
+ The debug output includes:
305
+ - The matcher tree structure with full class names
306
+ - Each matcher's condition and record value
307
+ - Color-coded results (green for matched, red for mismatched, purple for errors)
308
+ - Field names highlighted in gray background
309
+ - Detailed matching process for each record
310
+
311
+ ### Performance Considerations
312
+
313
+ 1. **Memory Usage**
314
+ - Mongory operates entirely in memory
315
+ - Consider your data size and memory constraints
316
+
317
+ 2. **Query Optimization**
318
+ - Complex conditions are evaluated in sequence
319
+ - Use `explain` to analyze query performance
320
+
321
+ 3. **Benchmarks**
322
+ ```ruby
323
+ # Simple query (1000 records)
324
+ records.mongory.where(:age.gte => 18) # ~2.5ms
325
+
326
+ # Complex query (1000 records)
327
+ records.mongory.where(:$or => [{:age.gte => 18}, {:status => 'active'}]) # ~3.2ms
328
+
329
+ # Simple query (10000 records)
330
+ records.mongory.where(:age.gte => 18) # ~24.5ms
331
+
332
+ # Complex query (10000 records)
333
+ records.mongory.where(:$or => [{:age.gte => 18}, {:status => 'active'}]) # ~31.5ms
334
+
335
+ # Simple query (100000 records)
336
+ records.mongory.where(:age.gte => 18) # ~242.5ms
337
+
338
+ # Complex query (100000 records)
339
+ records.mongory.where(:$or => [{:age.gte => 18}, {:status => 'active'}]) # ~323.0ms
340
+ ```
341
+
342
+ Note: Performance varies based on:
343
+ - Data size
344
+ - Query complexity
345
+ - Hardware specifications
346
+ - Ruby version
347
+
348
+ Test in your environment to determine if performance meets your needs.
349
+
350
+ ### Supported Operators
351
+
352
+ | Category | Operators |
353
+ |--------------|-------------------------------------|
354
+ | Comparison | `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte` |
355
+ | Set | `$in`, `$nin` |
356
+ | Boolean | `$and`, `$or`, `$not` |
357
+ | Pattern | `$regex` |
358
+ | Presence | `$exists`, `$present` |
359
+ | Nested Match | `$elemMatch`, `$every` |
360
+
361
+ Note: Some operators are Mongory-specific and not available in MongoDB:
362
+ - `$present`: Checks if a field is considered "present" (not nil, not empty, not KEY_NOT_FOUND)
363
+ - Similar to `$exists` but evaluates truthiness of the value
364
+ - Example: `where(:name.present => true)`
365
+ - `$every`: Checks if all elements in an array match the given condition
366
+ - Similar to `$elemMatch` but requires all elements to match
367
+ - Example: `where(:tags.every => { :priority.gt => 5 })`
368
+
369
+ Example:
370
+ ```ruby
371
+ # $present: Check if field is present (not nil, not empty)
372
+ records.mongory.where(:name.present => true) # name is present
373
+ records.mongory.where(:name.present => false) # name is not present
374
+
375
+ # $every: Check if all array elements match condition
376
+ records.mongory.where(:tags.every => { :priority.gt => 5 }) # all tags have priority > 5
377
+ ```
378
+
379
+ ## FAQ
380
+
381
+ ### Q: How does Mongory compare to MongoDB?
382
+ A: Mongory provides similar query syntax but operates entirely in memory. It's ideal for:
383
+ - Small to medium datasets
384
+ - Complex in-memory filtering
385
+ - Testing MongoDB-like queries without a database
386
+
387
+ ### Q: Can I use Mongory with large datasets?
388
+ A: Yes, but consider:
389
+ - Memory usage
390
+ - Query complexity
391
+ - Caching strategies
392
+ - Using `limit` early in the chain
393
+
394
+ ### Q: How do I handle errors?
395
+ ```ruby
396
+ begin
397
+ result = records.mongory.where(invalid: :condition)
398
+ rescue Mongory::Error => e
399
+ # Handle error
400
+ end
401
+ ```
402
+
403
+ ## Troubleshooting
404
+
405
+ 1. **Debugging Queries**
406
+ ```ruby
407
+ Mongory.debugger.enable
408
+ records.mongory.where(:age => 18).to_a
409
+ Mongory.debugger.display
410
+ Mongory.debugger.disable
411
+ ```
412
+
413
+ 2. **Common Issues**
414
+ - Symbol snippets not working? Call `Mongory.enable_symbol_snippets!`
415
+ - Complex queries slow? Use `explain` to analyze
416
+ - Memory issues? Consider pagination or streaming
417
+
418
+ ## Best Practices
419
+
420
+ 1. **Query Composition**
421
+ ```ruby
422
+ # Good: Use method chaining
423
+ records.mongory
424
+ .where(:age.gte => 18)
425
+ .where(:status => 'active')
426
+ .limit(10)
427
+
428
+ # Bad: Avoid redundant query creation
429
+ query = records.mongory.where(:age.gte => 18)
430
+ query = query.where(:status => 'active') # Unnecessary
431
+ ```
432
+
433
+ 2. **Performance Tips**
434
+ ```ruby
435
+ # Use limit to restrict result set
436
+ records.mongory.limit(100).where(:age.gte => 18)
437
+
438
+ # Use explain to analyze complex queries
439
+ query = records.mongory.where(:$or => [...])
440
+ query.explain
441
+ ```
442
+
443
+ 3. **Code Organization**
444
+ ```ruby
445
+ # Encapsulate common queries as methods
446
+ class User
447
+ def active_adults
448
+ friends.mongory
449
+ .where(:age.gte => 18)
450
+ .where(:status => 'active')
451
+ end
452
+ end
453
+ ```
454
+
455
+ ## Limitations
456
+
457
+ 1. **Data Size**
458
+ - Suitable for small to medium datasets
459
+ - Large datasets may impact performance
460
+
461
+ 2. **Query Complexity**
462
+ - Complex queries may affect performance
463
+ - Not all MongoDB operators are supported
464
+
465
+ 3. **Memory Usage**
466
+ - All operations are performed in memory
467
+ - Consider memory constraints
468
+
469
+ ## Migration Guide
470
+
471
+ 1. **From Array#select**
472
+ ```ruby
473
+ # Before
474
+ records.select { |r| r['age'] >= 18 && r['status'] == 'active' }
475
+
476
+ # After
477
+ records.mongory.where(:age.gte => 18, status: 'active')
478
+ ```
479
+
480
+ 2. **From ActiveRecord**
481
+ ```ruby
482
+ # Before
483
+ indexed_query.where("age >= ? AND status = ?", 18, 'active')
484
+
485
+ # After
486
+ indexed_query.mongory.where(:age.gte => 18, status: 'active')
487
+ ```
488
+
489
+ 3. **From MongoDB**
490
+ ```ruby
491
+ # Before (MongoDB)
492
+ users.where(:age.gte => 18, status: 'active')
493
+
494
+ # After (Mongory)
495
+ users.mongory.where(:age.gte => 18, status: 'active')
496
+
497
+ # Just the same.
498
+ ```
499
+
500
+ ## Contributing
501
+
502
+ Contributions are welcome! Here's how you can help:
503
+
504
+ 1. **Fork the repository**.
505
+ 2. **Create a new branch** for each significant change.
506
+ 3. **Write tests** for your changes.
507
+ 4. **Send a pull request**.
508
+
509
+ Please ensure your code adheres to the project's style guide and that all tests pass before submitting.
510
+
511
+ ## Code of Conduct
512
+
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).
514
+
515
+ ## License
516
+
517
+ MIT. See LICENSE file.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i(spec rubocop)
@@ -0,0 +1,41 @@
1
+ # Examples
2
+
3
+ This directory contains example scripts demonstrating Mongory's features and capabilities.
4
+
5
+ ## Performance Benchmark
6
+
7
+ `benchmark.rb` demonstrates Mongory's performance characteristics with different data sizes and query complexities.
8
+
9
+ ### Usage
10
+
11
+ ```bash
12
+ ruby examples/benchmark.rb
13
+ ```
14
+
15
+ ### What it tests
16
+
17
+ 1. Simple queries with different data sizes:
18
+ - 1000 records
19
+ - 10000 records
20
+ - 100000 records
21
+
22
+ 2. Complex queries with different data sizes:
23
+ - OR conditions
24
+ - Nested conditions
25
+
26
+ ### Output
27
+
28
+ The script outputs execution times for each test case, helping you understand:
29
+ - How query complexity affects performance
30
+ - How data size impacts execution time
31
+ - The relative performance of different query types
32
+
33
+ ### Note
34
+
35
+ Results may vary based on:
36
+ - Hardware specifications
37
+ - Ruby version
38
+ - System load
39
+ - Other factors
40
+
41
+ Run the benchmark in your environment to get accurate performance data for your use case.
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/mongory'
4
+ require 'benchmark'
5
+
6
+ # Register Array class
7
+ Mongory.register(Array)
8
+ Mongory.enable_symbol_snippets!
9
+
10
+ # Test with different data sizes
11
+ [1000, 10_000, 100_000].each do |size|
12
+ puts "\nTesting with #{size} records:"
13
+
14
+ # Generate test data
15
+ records = (1..size).map do |_|
16
+ {
17
+ 'age' => rand(1..100),
18
+ 'status' => ['active', 'inactive'].sample
19
+ }
20
+ end
21
+
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
27
+ end
28
+ puts result
29
+ end
30
+
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
41
+ end
42
+ puts result
43
+ end
44
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler'
4
+
5
+ module Mongory
6
+ module Generators
7
+ # Generates a Mongory initializer file with suggested configuration
8
+ # based on detected ORMs (ActiveRecord, Mongoid, Sequel).
9
+ #
10
+ # This is intended to be used via:
11
+ # rails generate mongory:install
12
+ #
13
+ # @example
14
+ # # Will generate config/initializers/mongory.rb with appropriate snippets
15
+ # rails g mongory:install
16
+ class InstallGenerator < Rails::Generators::Base
17
+ source_root File.expand_path('templates', __dir__)
18
+
19
+ # Generates the Mongory initializer under `config/initializers/mongory.rb`.
20
+ # Dynamically injects converter and registration config based on detected ORMs.
21
+ #
22
+ # @return [void]
23
+ def create_initializer_file
24
+ @use_ar = gem_used?('activerecord')
25
+ @use_mongoid = gem_used?('mongoid')
26
+ @use_sequel = gem_used?('sequel')
27
+
28
+ template 'initializer.rb.erb', 'config/initializers/mongory.rb'
29
+ end
30
+
31
+ private
32
+
33
+ # Checks whether a specific gem is listed in the locked dependencies.
34
+ #
35
+ # @param gem_name [String] the name of the gem to check
36
+ # @return [Boolean] true if the gem is present in the lockfile
37
+ def gem_used?(gem_name)
38
+ Bundler.locked_gems.dependencies.key?(gem_name)
39
+ end
40
+ end
41
+ end
42
+ end