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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +84 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +246 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +517 -0
- data/Rakefile +12 -0
- data/examples/README.md +41 -0
- data/examples/benchmark.rb +44 -0
- data/lib/generators/mongory/install/install_generator.rb +42 -0
- data/lib/generators/mongory/install/templates/initializer.rb.erb +83 -0
- data/lib/generators/mongory/matcher/matcher_generator.rb +56 -0
- data/lib/generators/mongory/matcher/templates/matcher.rb.erb +92 -0
- data/lib/generators/mongory/matcher/templates/matcher_spec.rb.erb +17 -0
- data/lib/mongory/converters/abstract_converter.rb +122 -0
- data/lib/mongory/converters/condition_converter.rb +74 -0
- data/lib/mongory/converters/data_converter.rb +26 -0
- data/lib/mongory/converters/key_converter.rb +63 -0
- data/lib/mongory/converters/value_converter.rb +47 -0
- data/lib/mongory/converters.rb +7 -0
- data/lib/mongory/matchers/README.md +57 -0
- data/lib/mongory/matchers/abstract_matcher.rb +153 -0
- data/lib/mongory/matchers/abstract_multi_matcher.rb +109 -0
- data/lib/mongory/matchers/abstract_operator_matcher.rb +46 -0
- data/lib/mongory/matchers/and_matcher.rb +65 -0
- data/lib/mongory/matchers/array_record_matcher.rb +88 -0
- data/lib/mongory/matchers/elem_match_matcher.rb +47 -0
- data/lib/mongory/matchers/eq_matcher.rb +37 -0
- data/lib/mongory/matchers/every_matcher.rb +41 -0
- data/lib/mongory/matchers/exists_matcher.rb +48 -0
- data/lib/mongory/matchers/field_matcher.rb +123 -0
- data/lib/mongory/matchers/gt_matcher.rb +29 -0
- data/lib/mongory/matchers/gte_matcher.rb +29 -0
- data/lib/mongory/matchers/hash_condition_matcher.rb +55 -0
- data/lib/mongory/matchers/in_matcher.rb +52 -0
- data/lib/mongory/matchers/literal_matcher.rb +123 -0
- data/lib/mongory/matchers/lt_matcher.rb +29 -0
- data/lib/mongory/matchers/lte_matcher.rb +29 -0
- data/lib/mongory/matchers/ne_matcher.rb +29 -0
- data/lib/mongory/matchers/nin_matcher.rb +51 -0
- data/lib/mongory/matchers/not_matcher.rb +32 -0
- data/lib/mongory/matchers/or_matcher.rb +60 -0
- data/lib/mongory/matchers/present_matcher.rb +52 -0
- data/lib/mongory/matchers/regex_matcher.rb +61 -0
- data/lib/mongory/matchers.rb +176 -0
- data/lib/mongory/mongoid.rb +19 -0
- data/lib/mongory/query_builder.rb +187 -0
- data/lib/mongory/query_matcher.rb +66 -0
- data/lib/mongory/query_operator.rb +28 -0
- data/lib/mongory/rails.rb +15 -0
- data/lib/mongory/utils/debugger.rb +123 -0
- data/lib/mongory/utils/rails_patch.rb +22 -0
- data/lib/mongory/utils/singleton_builder.rb +31 -0
- data/lib/mongory/utils.rb +75 -0
- data/lib/mongory/version.rb +5 -0
- data/lib/mongory-rb.rb +3 -0
- data/lib/mongory.rb +116 -0
- data/mongory.gemspec +40 -0
- data/sig/mongory.rbs +4 -0
- 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
data/examples/README.md
ADDED
@@ -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
|