mongory 0.7.2-arm64-darwin-23
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 +88 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +364 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +488 -0
- data/Rakefile +107 -0
- data/SUBMODULE_INTEGRATION.md +325 -0
- data/docs/advanced_usage.md +40 -0
- data/docs/clang_bridge.md +69 -0
- data/docs/field_names.md +30 -0
- data/docs/migration.md +30 -0
- data/docs/performance.md +61 -0
- data/examples/README.md +41 -0
- data/examples/benchmark-rails.rb +52 -0
- data/examples/benchmark.rb +184 -0
- data/ext/mongory_ext/extconf.rb +91 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/array.h +122 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/config.h +161 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/error.h +79 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/memory_pool.h +95 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/table.h +127 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/value.h +175 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core/matchers/matcher.h +76 -0
- data/ext/mongory_ext/mongory-core/include/mongory-core.h +12 -0
- data/ext/mongory_ext/mongory-core/src/foundations/array.c +287 -0
- data/ext/mongory_ext/mongory-core/src/foundations/array_private.h +19 -0
- data/ext/mongory_ext/mongory-core/src/foundations/config.c +270 -0
- data/ext/mongory_ext/mongory-core/src/foundations/config_private.h +48 -0
- data/ext/mongory_ext/mongory-core/src/foundations/error.c +38 -0
- data/ext/mongory_ext/mongory-core/src/foundations/memory_pool.c +298 -0
- data/ext/mongory_ext/mongory-core/src/foundations/string_buffer.c +65 -0
- data/ext/mongory_ext/mongory-core/src/foundations/string_buffer.h +49 -0
- data/ext/mongory_ext/mongory-core/src/foundations/table.c +498 -0
- data/ext/mongory_ext/mongory-core/src/foundations/utils.c +210 -0
- data/ext/mongory_ext/mongory-core/src/foundations/utils.h +70 -0
- data/ext/mongory_ext/mongory-core/src/foundations/value.c +500 -0
- data/ext/mongory_ext/mongory-core/src/matchers/array_record_matcher.c +164 -0
- data/ext/mongory_ext/mongory-core/src/matchers/array_record_matcher.h +47 -0
- data/ext/mongory_ext/mongory-core/src/matchers/base_matcher.c +122 -0
- data/ext/mongory_ext/mongory-core/src/matchers/base_matcher.h +100 -0
- data/ext/mongory_ext/mongory-core/src/matchers/compare_matcher.c +217 -0
- data/ext/mongory_ext/mongory-core/src/matchers/compare_matcher.h +83 -0
- data/ext/mongory_ext/mongory-core/src/matchers/composite_matcher.c +573 -0
- data/ext/mongory_ext/mongory-core/src/matchers/composite_matcher.h +125 -0
- data/ext/mongory_ext/mongory-core/src/matchers/existance_matcher.c +147 -0
- data/ext/mongory_ext/mongory-core/src/matchers/existance_matcher.h +48 -0
- data/ext/mongory_ext/mongory-core/src/matchers/external_matcher.c +124 -0
- data/ext/mongory_ext/mongory-core/src/matchers/external_matcher.h +46 -0
- data/ext/mongory_ext/mongory-core/src/matchers/inclusion_matcher.c +126 -0
- data/ext/mongory_ext/mongory-core/src/matchers/inclusion_matcher.h +46 -0
- data/ext/mongory_ext/mongory-core/src/matchers/literal_matcher.c +314 -0
- data/ext/mongory_ext/mongory-core/src/matchers/literal_matcher.h +97 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher.c +252 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_explainable.c +79 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_explainable.h +23 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_traversable.c +60 -0
- data/ext/mongory_ext/mongory-core/src/matchers/matcher_traversable.h +23 -0
- data/ext/mongory_ext/mongory_ext.c +683 -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/c_query_builder.rb +44 -0
- data/lib/mongory/converters/abstract_converter.rb +111 -0
- data/lib/mongory/converters/condition_converter.rb +64 -0
- data/lib/mongory/converters/converted.rb +81 -0
- data/lib/mongory/converters/data_converter.rb +37 -0
- data/lib/mongory/converters/key_converter.rb +87 -0
- data/lib/mongory/converters/value_converter.rb +52 -0
- data/lib/mongory/converters.rb +8 -0
- data/lib/mongory/matchers/abstract_matcher.rb +219 -0
- data/lib/mongory/matchers/abstract_multi_matcher.rb +124 -0
- data/lib/mongory/matchers/and_matcher.rb +72 -0
- data/lib/mongory/matchers/array_record_matcher.rb +93 -0
- data/lib/mongory/matchers/elem_match_matcher.rb +55 -0
- data/lib/mongory/matchers/eq_matcher.rb +46 -0
- data/lib/mongory/matchers/every_matcher.rb +56 -0
- data/lib/mongory/matchers/exists_matcher.rb +53 -0
- data/lib/mongory/matchers/field_matcher.rb +147 -0
- data/lib/mongory/matchers/gt_matcher.rb +41 -0
- data/lib/mongory/matchers/gte_matcher.rb +41 -0
- data/lib/mongory/matchers/hash_condition_matcher.rb +62 -0
- data/lib/mongory/matchers/in_matcher.rb +68 -0
- data/lib/mongory/matchers/literal_matcher.rb +121 -0
- data/lib/mongory/matchers/lt_matcher.rb +41 -0
- data/lib/mongory/matchers/lte_matcher.rb +41 -0
- data/lib/mongory/matchers/ne_matcher.rb +38 -0
- data/lib/mongory/matchers/nin_matcher.rb +68 -0
- data/lib/mongory/matchers/not_matcher.rb +40 -0
- data/lib/mongory/matchers/or_matcher.rb +68 -0
- data/lib/mongory/matchers/present_matcher.rb +55 -0
- data/lib/mongory/matchers/regex_matcher.rb +80 -0
- data/lib/mongory/matchers/size_matcher.rb +54 -0
- data/lib/mongory/matchers.rb +176 -0
- data/lib/mongory/mongoid.rb +19 -0
- data/lib/mongory/query_builder.rb +257 -0
- data/lib/mongory/query_matcher.rb +93 -0
- data/lib/mongory/query_operator.rb +28 -0
- data/lib/mongory/rails.rb +15 -0
- data/lib/mongory/utils/context.rb +48 -0
- data/lib/mongory/utils/debugger.rb +125 -0
- data/lib/mongory/utils/rails_patch.rb +22 -0
- data/lib/mongory/utils/singleton_builder.rb +31 -0
- data/lib/mongory/utils.rb +76 -0
- data/lib/mongory/version.rb +5 -0
- data/lib/mongory.rb +123 -0
- data/lib/mongory_ext.bundle +0 -0
- data/mongory.gemspec +50 -0
- data/scripts/build_with_core.sh +292 -0
- data/sig/mongory.rbs +4 -0
- metadata +164 -0
data/README.md
ADDED
@@ -0,0 +1,488 @@
|
|
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
|
+
## Table of Contents
|
8
|
+
|
9
|
+
- Overview & Positioning
|
10
|
+
- [Positioning](#positioning)
|
11
|
+
- Getting Started
|
12
|
+
- [Requirements](#requirements)
|
13
|
+
- [Installation & Quick Start](#installation--quick-start)
|
14
|
+
- [Integration with MongoDB](#integration-with-mongodb)
|
15
|
+
- Usage & Concepts
|
16
|
+
- [Creating Custom Matchers](#creating-custom-matchers)
|
17
|
+
- [Core Concepts & API Reference](#core-concepts--api-reference)
|
18
|
+
- [Handling Dots in Field Names](docs/field_names.md)
|
19
|
+
- [Advanced Usage](docs/advanced_usage.md)
|
20
|
+
- [Debugging](#debugging)
|
21
|
+
- [Clang Bridge (C Extension)](docs/clang_bridge.md)
|
22
|
+
- Performance
|
23
|
+
- [Performance & Benchmarks](docs/performance.md)
|
24
|
+
- [Supported Operators](#supported-operators)
|
25
|
+
- Guides
|
26
|
+
- [Best Practices](#best-practices)
|
27
|
+
- [Limitations](#limitations)
|
28
|
+
- [FAQ](#faq)
|
29
|
+
- [Troubleshooting](#troubleshooting)
|
30
|
+
- [Migration Guide](docs/migration.md)
|
31
|
+
- Project
|
32
|
+
- [Contributing](#contributing)
|
33
|
+
- [Code of Conduct](#code-of-conduct)
|
34
|
+
- [License](#license)
|
35
|
+
|
36
|
+
## Requirements
|
37
|
+
|
38
|
+
- Ruby >= 2.6.0
|
39
|
+
- No external database required
|
40
|
+
|
41
|
+
## Installation & Quick Start
|
42
|
+
|
43
|
+
### Installation
|
44
|
+
Install manually:
|
45
|
+
```bash
|
46
|
+
gem install mongory
|
47
|
+
```
|
48
|
+
|
49
|
+
Or add to your Gemfile:
|
50
|
+
```ruby
|
51
|
+
gem 'mongory'
|
52
|
+
```
|
53
|
+
|
54
|
+
#### Before installing: install build tools
|
55
|
+
|
56
|
+
Mongory ships with an optional native extension. Before installing the gem, make sure your system has a C build toolchain (gcc/clang and make). Install the toolchain with the following commands for your platform:
|
57
|
+
|
58
|
+
- Debian/Ubuntu (including ruby:*-slim base images)
|
59
|
+
```bash
|
60
|
+
apt-get update && apt-get install -y build-essential
|
61
|
+
```
|
62
|
+
|
63
|
+
- Alpine
|
64
|
+
```bash
|
65
|
+
apk add --no-cache build-base
|
66
|
+
```
|
67
|
+
|
68
|
+
- CentOS/RHEL
|
69
|
+
```bash
|
70
|
+
yum groupinstall -y "Development Tools"
|
71
|
+
```
|
72
|
+
|
73
|
+
- Fedora
|
74
|
+
```bash
|
75
|
+
dnf groupinstall -y "Development Tools"
|
76
|
+
```
|
77
|
+
|
78
|
+
- Amazon Linux
|
79
|
+
```bash
|
80
|
+
yum groupinstall -y "Development Tools"
|
81
|
+
```
|
82
|
+
|
83
|
+
- macOS
|
84
|
+
```bash
|
85
|
+
xcode-select --install
|
86
|
+
```
|
87
|
+
|
88
|
+
#### Rails Generator
|
89
|
+
|
90
|
+
You can install a starter configuration with:
|
91
|
+
|
92
|
+
```bash
|
93
|
+
rails g mongory:install
|
94
|
+
```
|
95
|
+
|
96
|
+
This will generate `config/initializers/mongory.rb` and set up:
|
97
|
+
- Optional symbol operator snippets (e.g. `:age.gt => 18`)
|
98
|
+
- Class registration (e.g. `Array`, `ActiveRecord::Relation`, etc.)
|
99
|
+
- Custom value/key converters for your ORM
|
100
|
+
|
101
|
+
### Basic Usage
|
102
|
+
```ruby
|
103
|
+
records = [
|
104
|
+
{ 'name' => 'Jack', 'age' => 18, 'gender' => 'M' },
|
105
|
+
{ 'name' => 'Jill', 'age' => 15, 'gender' => 'F' },
|
106
|
+
{ 'name' => 'Bob', 'age' => 21, 'gender' => 'M' },
|
107
|
+
{ 'name' => 'Mary', 'age' => 18, 'gender' => 'F' }
|
108
|
+
]
|
109
|
+
|
110
|
+
# Basic query with conditions
|
111
|
+
result = records.mongory
|
112
|
+
.where(:age.gte => 18)
|
113
|
+
.or({ :name => /J/ }, { :name.eq => 'Bob' })
|
114
|
+
|
115
|
+
# Using limit to restrict results
|
116
|
+
# Note: limit executes immediately and affects subsequent conditions
|
117
|
+
limited = records.mongory
|
118
|
+
.limit(2) # Only process first 2 records
|
119
|
+
.where(:age.gte => 18) # Conditions apply to limited set
|
120
|
+
```
|
121
|
+
|
122
|
+
### C Extension (Optional but Recommended)
|
123
|
+
|
124
|
+
Mongory-rb includes an optional high-performance C extension powered by [mongory-core](https://github.com/mongoryhq/mongory-core):
|
125
|
+
|
126
|
+
**System Dependencies:**
|
127
|
+
- C99-compatible compiler (gcc/clang)
|
128
|
+
- CMake >= 3.12 (optional; only needed if you want to build `mongory-core` standalone or run its native tests)
|
129
|
+
|
130
|
+
**Installation:**
|
131
|
+
```bash
|
132
|
+
# macOS
|
133
|
+
brew install cmake
|
134
|
+
|
135
|
+
# Ubuntu/Debian
|
136
|
+
sudo apt install cmake build-essential
|
137
|
+
|
138
|
+
# CentOS/RHEL
|
139
|
+
sudo yum install cmake gcc make
|
140
|
+
```
|
141
|
+
|
142
|
+
The C extension provides significant performance improvements for large datasets. If not available, Mongory-rb automatically falls back to pure Ruby implementation.
|
143
|
+
|
144
|
+
Note: The Ruby C extension is built via Ruby's `mkmf` (see `ext/mongory_ext/extconf.rb`) and compiles `mongory-core` sources directly. You do not need CMake for normal gem installation.
|
145
|
+
|
146
|
+
## Positioning
|
147
|
+
|
148
|
+
Mongory is designed to serve two types of users:
|
149
|
+
|
150
|
+
1. For MongoDB users:
|
151
|
+
- Seamless integration with familiar query syntax
|
152
|
+
- Extends query capabilities for non-indexed fields
|
153
|
+
- No additional learning cost
|
154
|
+
|
155
|
+
2. For non-MongoDB users:
|
156
|
+
- Initial learning cost for MongoDB-style syntax
|
157
|
+
- Long-term benefits:
|
158
|
+
- Improved code readability
|
159
|
+
- Better development efficiency
|
160
|
+
- Lower maintenance costs
|
161
|
+
- Ideal for teams valuing code quality and maintainability
|
162
|
+
|
163
|
+
### Integration with MongoDB
|
164
|
+
|
165
|
+
Mongory is designed to complement MongoDB, not replace it. Here's how to use them together:
|
166
|
+
|
167
|
+
1. Use MongoDB for:
|
168
|
+
- Queries with indexes
|
169
|
+
- Persistent data operations
|
170
|
+
- Large-scale data processing
|
171
|
+
|
172
|
+
2. Use Mongory for:
|
173
|
+
- Queries without indexes
|
174
|
+
- Complex in-memory calculations
|
175
|
+
- Temporary data filtering needs
|
176
|
+
|
177
|
+
Example:
|
178
|
+
```ruby
|
179
|
+
# First use MongoDB for indexed queries
|
180
|
+
users = User.where(status: 'active') # Uses MongoDB index
|
181
|
+
|
182
|
+
# Then use Mongory for non-indexed fields
|
183
|
+
active_users = users.mongory
|
184
|
+
.where(:last_login.gte => 1.week.ago) # No index on last_login
|
185
|
+
.where(:tags.elem_match => { :name => 'ruby' }) # Complex array query
|
186
|
+
```
|
187
|
+
|
188
|
+
### Creating Custom Matchers
|
189
|
+
#### Using the Generator
|
190
|
+
|
191
|
+
You can generate a new matcher using:
|
192
|
+
|
193
|
+
```bash
|
194
|
+
rails g mongory:matcher class_in
|
195
|
+
```
|
196
|
+
|
197
|
+
This will:
|
198
|
+
1. Create a new matcher file at `lib/mongory/matchers/class_in_matcher.rb`
|
199
|
+
2. Create a spec file at `spec/mongory/matchers/class_in_matcher_spec.rb`
|
200
|
+
3. Update `config/initializers/mongory.rb` to require the new matcher
|
201
|
+
|
202
|
+
The generated matcher will:
|
203
|
+
- Be named `ClassInMatcher`
|
204
|
+
- Register the operator as `$classIn`
|
205
|
+
- Be available as `:class_in` in queries
|
206
|
+
|
207
|
+
Example usage of the generated matcher:
|
208
|
+
```ruby
|
209
|
+
records.mongory.where(:value.class_in => [Integer, String])
|
210
|
+
```
|
211
|
+
|
212
|
+
#### Manual Creation
|
213
|
+
|
214
|
+
If you prefer to create matchers manually, here's an example:
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
class ClassInMatcher < Mongory::Matchers::AbstractMatcher
|
218
|
+
def match(subject)
|
219
|
+
@condition.any? { |klass| subject.is_a?(klass) }
|
220
|
+
end
|
221
|
+
|
222
|
+
def check_validity!
|
223
|
+
raise TypeError, '$classIn needs an array.' unless @condition.is_a?(Array)
|
224
|
+
@condition.each do |klass|
|
225
|
+
raise TypeError, '$classIn needs an array of class.' unless klass.is_a?(Class)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
Mongory::Matchers.register(:class_in, '$classIn', ClassInMatcher)
|
231
|
+
|
232
|
+
[{a: 1}].mongory.where(:a.class_in => [Integer]).first
|
233
|
+
# => { a: 1 }
|
234
|
+
```
|
235
|
+
|
236
|
+
You can define any matcher behavior and attach it to a `$operator` of your choice.
|
237
|
+
Matchers can be composed, validated, and traced just like built-in ones.
|
238
|
+
|
239
|
+
## Core Concepts & API Reference
|
240
|
+
#### Registering Models
|
241
|
+
|
242
|
+
To allow calling `.mongory` on collections, use `register`:
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
Mongory.register(Array)
|
246
|
+
Mongory.register(ActiveRecord::Relation)
|
247
|
+
User.where(status: 'active').mongory.where(:age.gte => 18, :name.regex => "^S.+")
|
248
|
+
```
|
249
|
+
|
250
|
+
This injects a `.mongory` method via an internal extension module.
|
251
|
+
|
252
|
+
Internally, the query is compiled into a matcher tree using the `QueryMatcher` and `ConditionConverter`.
|
253
|
+
|
254
|
+
| Method | Description | Example |
|
255
|
+
|--------|-------------|---------|
|
256
|
+
| `where` | Adds a condition to filter records | `where(age: { :$gte => 18 })` |
|
257
|
+
| `not` | Adds a negated condition | `not(age: { :$lt => 18 })` |
|
258
|
+
| `and` | Combines conditions with `$and` | `and({ age: { :$gte => 18 } }, { name: /J/ })` |
|
259
|
+
| `or` | Combines conditions with `$or` | `or({ age: { :$gte => 18 } }, { name: /J/ })` |
|
260
|
+
| `any_of` | Combines conditions with `$or` inside an `$and` block | `any_of({ age: { :$gte => 18 } }, { name: /J/ })` |
|
261
|
+
| `in` | Checks if a value is in a set | `in(age: [18, 19, 20])` |
|
262
|
+
| `nin` | Checks if a value is not in a set | `nin(age: [18, 19, 20])` |
|
263
|
+
| `limit` | Limits the number of records returned. This method executes immediately and affects subsequent conditions. | `limit(2)` |
|
264
|
+
| `pluck` | Extracts selected fields from matching records | `pluck(:name)` |
|
265
|
+
| `with_context` | Sets a custom context for the query. Useful for controlling data conversion and sharing configuration across matchers. | `with_context(merchant: merchant)` |
|
266
|
+
|
267
|
+
#### Context Configuration
|
268
|
+
|
269
|
+
The `with_context` method allows you to customize the query execution environment:
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
# Share configuration across matchers
|
273
|
+
records.mongory
|
274
|
+
.with_context(custom_option: true)
|
275
|
+
.where(:status => 'active')
|
276
|
+
.where(:age.gte => 18)
|
277
|
+
```
|
278
|
+
|
279
|
+
This will share a mutatable, but stable context object to all matchers in matcher tree.
|
280
|
+
To get your custom option, using `@context.config` in your custom matcher.
|
281
|
+
|
282
|
+
## Debugging
|
283
|
+
|
284
|
+
You can use `explain` to visualize the matcher tree structure:
|
285
|
+
```ruby
|
286
|
+
records = [
|
287
|
+
{ name: 'John', age: 25, status: 'active' },
|
288
|
+
{ name: 'Jane', age: 30, status: 'inactive' }
|
289
|
+
]
|
290
|
+
|
291
|
+
query = records.mongory
|
292
|
+
.where(:age.gte => 18)
|
293
|
+
.any_of(
|
294
|
+
{ :status => 'active' },
|
295
|
+
{ :name.regex => /^J/ }
|
296
|
+
)
|
297
|
+
|
298
|
+
query.explain
|
299
|
+
```
|
300
|
+
Output:
|
301
|
+
```
|
302
|
+
And: {"age"=>{"$gte"=>18}, "$or"=>[{"status"=>"active"}, {"name"=>{"$regex"=>/^J/}}]}
|
303
|
+
├─ Field: "age" to match: {"$gte"=>18}
|
304
|
+
│ └─ Gte: 18
|
305
|
+
└─ Or: [{"status"=>"active"}, {"name"=>{"$regex"=>/^J/}}]
|
306
|
+
├─ Field: "status" to match: "active"
|
307
|
+
│ └─ Eq: "active"
|
308
|
+
└─ Field: "name" to match: {"$regex"=>/^J/}
|
309
|
+
└─ Regex: /^J/
|
310
|
+
```
|
311
|
+
|
312
|
+
This helps you understand how your query is being processed and can be useful for debugging complex conditions.
|
313
|
+
|
314
|
+
Or use the debugger for detailed matching process:
|
315
|
+
```ruby
|
316
|
+
# Enable debugging
|
317
|
+
Mongory.debugger.enable
|
318
|
+
|
319
|
+
# Execute your query
|
320
|
+
query = Mongory.build_query(users).where(age: { :$gt => 18 })
|
321
|
+
query.each do |user|
|
322
|
+
puts user
|
323
|
+
end
|
324
|
+
|
325
|
+
# Display the debug trace
|
326
|
+
Mongory.debugger.display
|
327
|
+
```
|
328
|
+
|
329
|
+
The debug output will show detailed matching process with full class names:
|
330
|
+
```
|
331
|
+
QueryMatcher Matched, condition: {"age"=>{"$gt"=>18}}, record: {"age"=>25}
|
332
|
+
AndMatcher Matched, condition: {"age"=>{"$gt"=>18}}, record: {"age"=>25}
|
333
|
+
FieldMatcher Matched, condition: {"$gt"=>18}, field: "age", record: {"age"=>25}
|
334
|
+
GtMatcher Matched, condition: 18, record: 25
|
335
|
+
```
|
336
|
+
|
337
|
+
The debug output includes:
|
338
|
+
- The matcher tree structure with full class names
|
339
|
+
- Each matcher's condition and record value
|
340
|
+
- Color-coded results (green for matched, red for mismatched, purple for errors)
|
341
|
+
- Field names highlighted in gray background
|
342
|
+
- Detailed matching process for each record
|
343
|
+
|
344
|
+
### Supported Operators
|
345
|
+
|
346
|
+
| Category | Operators |
|
347
|
+
|--------------|-------------------------------------|
|
348
|
+
| Comparison | `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte` |
|
349
|
+
| Set | `$in`, `$nin` |
|
350
|
+
| Boolean | `$and`, `$or`, `$not` |
|
351
|
+
| Pattern | `$regex` |
|
352
|
+
| Presence | `$exists`, `$present` |
|
353
|
+
| Nested Match | `$elemMatch`, `$every` |
|
354
|
+
|
355
|
+
Note: Some operators are Mongory-specific and not available in MongoDB:
|
356
|
+
- `$present`: Checks if a field is considered "present" (not nil, not empty, not KEY_NOT_FOUND)
|
357
|
+
- Similar to `$exists` but evaluates truthiness of the value
|
358
|
+
- Example: `where(:name.present => true)`
|
359
|
+
- `$every`: Checks if all elements in an array match the given condition
|
360
|
+
- Similar to `$elemMatch` but requires all elements to match
|
361
|
+
- At least one element in an array, or returns false
|
362
|
+
- Example: `where(:tags.every => { :priority.gt => 5 })`
|
363
|
+
|
364
|
+
Example:
|
365
|
+
```ruby
|
366
|
+
# $present: Check if field is present (not nil, not empty)
|
367
|
+
records.mongory.where(:name.present => true) # name is present
|
368
|
+
records.mongory.where(:name.present => false) # name is not present
|
369
|
+
|
370
|
+
# $every: Check if all array elements match condition
|
371
|
+
records.mongory.where(:tags.every => { :priority.gt => 5 }) # all tags have priority > 5
|
372
|
+
```
|
373
|
+
|
374
|
+
## FAQ
|
375
|
+
|
376
|
+
### Q: How does Mongory compare to MongoDB?
|
377
|
+
A: Mongory provides similar query syntax but operates entirely in memory. It's ideal for:
|
378
|
+
- Small to medium datasets
|
379
|
+
- Complex in-memory filtering
|
380
|
+
- Testing MongoDB-like queries without a database
|
381
|
+
|
382
|
+
### Q: Can I use Mongory with large datasets?
|
383
|
+
A: Yes, but consider:
|
384
|
+
- Memory usage
|
385
|
+
- Query complexity
|
386
|
+
- Caching strategies
|
387
|
+
- Using `limit` early in the chain
|
388
|
+
|
389
|
+
### Q: How do I handle errors?
|
390
|
+
```ruby
|
391
|
+
begin
|
392
|
+
result = records.mongory.where(invalid: :condition)
|
393
|
+
rescue Mongory::Error => e
|
394
|
+
# Handle error
|
395
|
+
end
|
396
|
+
```
|
397
|
+
|
398
|
+
## Troubleshooting
|
399
|
+
|
400
|
+
1. **Debugging Queries**
|
401
|
+
```ruby
|
402
|
+
Mongory.debugger.enable
|
403
|
+
records.mongory.where(:age => 18).to_a
|
404
|
+
Mongory.debugger.display
|
405
|
+
Mongory.debugger.disable
|
406
|
+
```
|
407
|
+
|
408
|
+
2. **Common Issues**
|
409
|
+
- Symbol snippets not working? Call `Mongory.enable_symbol_snippets!`
|
410
|
+
- Complex queries slow? Use `explain` to analyze
|
411
|
+
- Memory issues? Consider pagination or streaming
|
412
|
+
|
413
|
+
## Best Practices
|
414
|
+
|
415
|
+
1. **Query Composition**
|
416
|
+
```ruby
|
417
|
+
# Good: Use method chaining
|
418
|
+
records.mongory
|
419
|
+
.where(:age.gte => 18)
|
420
|
+
.where(:status => 'active')
|
421
|
+
.limit(10)
|
422
|
+
|
423
|
+
# Bad: Avoid redundant query creation
|
424
|
+
query = records.mongory.where(:age.gte => 18)
|
425
|
+
query = query.where(:status => 'active') # Unnecessary
|
426
|
+
```
|
427
|
+
|
428
|
+
2. **Performance Tips**
|
429
|
+
```ruby
|
430
|
+
# Use limit to restrict result set
|
431
|
+
records.mongory.limit(100).where(:age.gte => 18)
|
432
|
+
|
433
|
+
# Use fast mode for better performance
|
434
|
+
records.mongory.where(:age.gte => 18).fast
|
435
|
+
|
436
|
+
# Use explain to analyze complex queries
|
437
|
+
query = records.mongory.where(:$or => [...])
|
438
|
+
query.explain
|
439
|
+
```
|
440
|
+
|
441
|
+
3. **Code Organization**
|
442
|
+
```ruby
|
443
|
+
# Encapsulate common queries as methods
|
444
|
+
class User
|
445
|
+
def active_adults
|
446
|
+
friends.mongory
|
447
|
+
.where(:age.gte => 18)
|
448
|
+
.where(:status => 'active')
|
449
|
+
end
|
450
|
+
end
|
451
|
+
```
|
452
|
+
|
453
|
+
## Limitations
|
454
|
+
|
455
|
+
1. **Data Size**
|
456
|
+
- Suitable for small to medium datasets
|
457
|
+
- Large datasets may impact performance
|
458
|
+
- Proc-based implementation helps with memory usage
|
459
|
+
- Context system provides better resource management
|
460
|
+
|
461
|
+
2. **Query Complexity**
|
462
|
+
- Complex queries may affect performance
|
463
|
+
- Not all MongoDB operators are supported
|
464
|
+
- Proc-based implementation improves complex query performance
|
465
|
+
- Context system allows better control over query execution
|
466
|
+
|
467
|
+
3. **Memory Usage**
|
468
|
+
- All operations are performed in memory
|
469
|
+
- Consider memory constraints
|
470
|
+
|
471
|
+
## Contributing
|
472
|
+
|
473
|
+
Contributions are welcome! Here's how you can help:
|
474
|
+
|
475
|
+
1. **Fork the repository**.
|
476
|
+
2. **Create a new branch** for each significant change.
|
477
|
+
3. **Write tests** for your changes.
|
478
|
+
4. **Send a pull request**.
|
479
|
+
|
480
|
+
Please ensure your code adheres to the project's style guide and that all tests pass before submitting.
|
481
|
+
|
482
|
+
## Code of Conduct
|
483
|
+
|
484
|
+
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).
|
485
|
+
|
486
|
+
## License
|
487
|
+
|
488
|
+
MIT. See LICENSE file.
|
data/Rakefile
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rake/extensiontask'
|
4
|
+
require 'bundler/gem_tasks'
|
5
|
+
begin
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
8
|
+
|
9
|
+
require 'rubocop/rake_task'
|
10
|
+
RuboCop::RakeTask.new
|
11
|
+
rescue LoadError
|
12
|
+
# When rspec or rubocop is not installed in the CI cross-build environment, the definition of the test task is skipped
|
13
|
+
end
|
14
|
+
|
15
|
+
# Add support for rake-compiler if available
|
16
|
+
begin
|
17
|
+
ENV['RUBY_CC_VERSION'] ||= '2.6.0:2.7.0:3.0.0:3.1.0:3.2.0:3.3.0'
|
18
|
+
|
19
|
+
spec = Gem::Specification.load('mongory.gemspec')
|
20
|
+
|
21
|
+
Rake::ExtensionTask.new('mongory_ext', spec) do |ext|
|
22
|
+
ext.lib_dir = 'lib'
|
23
|
+
ext.ext_dir = 'ext/mongory_ext'
|
24
|
+
ext.source_pattern = '*.c'
|
25
|
+
ext.gem_spec = spec
|
26
|
+
ext.cross_compile = true
|
27
|
+
ext.cross_platform = [
|
28
|
+
'x86_64-linux',
|
29
|
+
'aarch64-linux',
|
30
|
+
'x86_64-darwin',
|
31
|
+
'arm64-darwin',
|
32
|
+
# 'arm64-mingw-ucrt', # TODO: add this when we have a mingw-ucrt rake-compiler-dock image
|
33
|
+
'x64-mingw32',
|
34
|
+
'x64-mingw-ucrt',
|
35
|
+
'x86_64-linux-musl',
|
36
|
+
'aarch64-linux-musl'
|
37
|
+
]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Add tasks for building with submodule
|
41
|
+
namespace :submodule do
|
42
|
+
desc 'Initialize/update the mongory-core submodule'
|
43
|
+
task :init do
|
44
|
+
sh 'git submodule update --init --recursive'
|
45
|
+
end
|
46
|
+
|
47
|
+
desc 'Update the mongory-core submodule to latest'
|
48
|
+
task :update do
|
49
|
+
sh 'git submodule update --remote'
|
50
|
+
end
|
51
|
+
|
52
|
+
desc 'Build mongory-core submodule'
|
53
|
+
task :build do
|
54
|
+
core_dir = 'ext/mongory_ext/mongory-core'
|
55
|
+
if Dir.exist?(core_dir)
|
56
|
+
Dir.chdir(core_dir) do
|
57
|
+
if File.exist?('build.sh')
|
58
|
+
sh 'chmod +x build.sh && ./build.sh'
|
59
|
+
else
|
60
|
+
sh 'mkdir -p build && cd build && cmake .. && make'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
else
|
64
|
+
puts 'mongory-core submodule not found. Run rake submodule:init first.'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
desc 'Build the project (without standalone mongory-core build)'
|
70
|
+
task build_all: ['submodule:init', :compile]
|
71
|
+
|
72
|
+
desc 'Clean all build artifacts including submodule'
|
73
|
+
task clean_all: :clean do
|
74
|
+
sh 'rm -rf ext/mongory_ext/mongory-core/build' if Dir.exist?('ext/mongory_ext/mongory-core/build')
|
75
|
+
end
|
76
|
+
rescue LoadError
|
77
|
+
puts 'rake-compiler not available. Install it with: gem install rake-compiler'
|
78
|
+
|
79
|
+
# Fallback tasks without rake-compiler
|
80
|
+
desc 'Build the C extension manually'
|
81
|
+
task :compile do
|
82
|
+
Dir.chdir('ext/mongory_ext') do
|
83
|
+
sh 'ruby extconf.rb && make'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
desc 'Clean the C extension manually'
|
88
|
+
task :clean do
|
89
|
+
Dir.chdir('ext/mongory_ext') do
|
90
|
+
sh 'make clean' if File.exist?('Makefile')
|
91
|
+
sh 'rm -f Makefile *.o foundations/*.o matchers/*.o mongory_ext.so'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Custom build task using our build script
|
97
|
+
desc 'Build using the custom build script'
|
98
|
+
task :build_with_script do
|
99
|
+
sh 'scripts/build_with_core.sh'
|
100
|
+
end
|
101
|
+
|
102
|
+
desc 'Build in debug mode'
|
103
|
+
task :build_debug do
|
104
|
+
sh 'scripts/build_with_core.sh --debug'
|
105
|
+
end
|
106
|
+
|
107
|
+
task default: %i(spec rubocop)
|