mongory 0.6.3 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/CHANGELOG.md +46 -0
  4. data/README.md +83 -176
  5. data/Rakefile +77 -0
  6. data/SUBMODULE_INTEGRATION.md +325 -0
  7. data/docs/advanced_usage.md +40 -0
  8. data/docs/clang_bridge.md +69 -0
  9. data/docs/field_names.md +30 -0
  10. data/docs/migration.md +30 -0
  11. data/docs/performance.md +61 -0
  12. data/examples/benchmark.rb +98 -19
  13. data/ext/mongory_ext/extconf.rb +91 -0
  14. data/ext/mongory_ext/mongory-core/LICENSE +3 -0
  15. data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/array.h +105 -0
  16. data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/config.h +206 -0
  17. data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/error.h +82 -0
  18. data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/memory_pool.h +95 -0
  19. data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/table.h +108 -0
  20. data/ext/mongory_ext/mongory-core/include/mongory-core/foundations/value.h +175 -0
  21. data/ext/mongory_ext/mongory-core/include/mongory-core/matchers/matcher.h +76 -0
  22. data/ext/mongory_ext/mongory-core/include/mongory-core.h +12 -0
  23. data/ext/mongory_ext/mongory-core/src/foundations/array.c +246 -0
  24. data/ext/mongory_ext/mongory-core/src/foundations/array_private.h +18 -0
  25. data/ext/mongory_ext/mongory-core/src/foundations/config.c +406 -0
  26. data/ext/mongory_ext/mongory-core/src/foundations/config_private.h +30 -0
  27. data/ext/mongory_ext/mongory-core/src/foundations/error.c +30 -0
  28. data/ext/mongory_ext/mongory-core/src/foundations/memory_pool.c +298 -0
  29. data/ext/mongory_ext/mongory-core/src/foundations/string_buffer.c +65 -0
  30. data/ext/mongory_ext/mongory-core/src/foundations/string_buffer.h +49 -0
  31. data/ext/mongory_ext/mongory-core/src/foundations/table.c +458 -0
  32. data/ext/mongory_ext/mongory-core/src/foundations/value.c +459 -0
  33. data/ext/mongory_ext/mongory-core/src/matchers/array_record_matcher.c +309 -0
  34. data/ext/mongory_ext/mongory-core/src/matchers/array_record_matcher.h +47 -0
  35. data/ext/mongory_ext/mongory-core/src/matchers/base_matcher.c +161 -0
  36. data/ext/mongory_ext/mongory-core/src/matchers/base_matcher.h +115 -0
  37. data/ext/mongory_ext/mongory-core/src/matchers/compare_matcher.c +210 -0
  38. data/ext/mongory_ext/mongory-core/src/matchers/compare_matcher.h +83 -0
  39. data/ext/mongory_ext/mongory-core/src/matchers/composite_matcher.c +539 -0
  40. data/ext/mongory_ext/mongory-core/src/matchers/composite_matcher.h +125 -0
  41. data/ext/mongory_ext/mongory-core/src/matchers/existance_matcher.c +144 -0
  42. data/ext/mongory_ext/mongory-core/src/matchers/existance_matcher.h +48 -0
  43. data/ext/mongory_ext/mongory-core/src/matchers/external_matcher.c +121 -0
  44. data/ext/mongory_ext/mongory-core/src/matchers/external_matcher.h +46 -0
  45. data/ext/mongory_ext/mongory-core/src/matchers/inclusion_matcher.c +199 -0
  46. data/ext/mongory_ext/mongory-core/src/matchers/inclusion_matcher.h +46 -0
  47. data/ext/mongory_ext/mongory-core/src/matchers/literal_matcher.c +334 -0
  48. data/ext/mongory_ext/mongory-core/src/matchers/literal_matcher.h +97 -0
  49. data/ext/mongory_ext/mongory-core/src/matchers/matcher.c +198 -0
  50. data/ext/mongory_ext/mongory-core/src/matchers/matcher_explainable.c +107 -0
  51. data/ext/mongory_ext/mongory-core/src/matchers/matcher_explainable.h +50 -0
  52. data/ext/mongory_ext/mongory-core/src/matchers/matcher_traversable.c +55 -0
  53. data/ext/mongory_ext/mongory-core/src/matchers/matcher_traversable.h +23 -0
  54. data/ext/mongory_ext/mongory_ext.c +635 -0
  55. data/lib/mongory/c_query_builder.rb +44 -0
  56. data/lib/mongory/query_builder.rb +8 -0
  57. data/lib/mongory/utils/context.rb +7 -0
  58. data/lib/mongory/version.rb +1 -1
  59. data/lib/mongory.rb +7 -0
  60. data/mongory.gemspec +10 -4
  61. data/scripts/build_with_core.sh +292 -0
  62. metadata +69 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 78a3d73e3c078b41aeeeb727445229e4edce85ff089de2d7412c9e64bfc613c0
4
- data.tar.gz: d7a9191a78a75b00f7a3ef7aa5d0b8e5ed8e8cb53e6a839e0873548da341849f
3
+ metadata.gz: 2a7ec315565b227d1051af4673da4a5e9eab1c91bb3a578b0bef378d7b829a38
4
+ data.tar.gz: 8d85c987c23b6f4066f8ed3fd752b5ba829e830e75eb3859eb7765bcf999278b
5
5
  SHA512:
6
- metadata.gz: 6cc1387d67ccf0559a5f12d2140b2f0349a16b8461ae3e6992a3ba199a1824b178236d91cd8fe31bdbd73a85d056074efac32440ef8827bb31e9aebb6fd544bf
7
- data.tar.gz: 8f34cab007b5c692b48634686aa2931036b3f928da72004512b7e0de744dbe009656cac108f9f2d6d3d59b8d6d7e997b1b1b3f3a5865998bc83168c2dce60ea0
6
+ metadata.gz: 894bf40d954782890b1793b78f2710f370c964b4fec5cd9e3b453dc9d57bcf102081dee5ea1023e3c59dabe5f37ff09ccb2dde2d5585cbd7c0c6e79b6c115904
7
+ data.tar.gz: afdc73456ddfec6bce3e326bc1a948293c0e6a844c358cb06130b20678ea9c7c53dc06d2e0e43cfb03cf067a4f3a696938243a5c6af4715efd1a813276300a2e
data/.rubocop.yml CHANGED
@@ -5,6 +5,8 @@ AllCops:
5
5
  - 'vendor/**/*'
6
6
  - 'node_modules/**/*'
7
7
  - 'tmp/**/*'
8
+ - 'examples/**/*'
9
+ - 'ext/**/*'
8
10
  TargetRubyVersion: 2.6
9
11
 
10
12
  Style/StringLiterals:
data/CHANGELOG.md CHANGED
@@ -1,4 +1,50 @@
1
1
  # Changelog
2
+ ## [0.7.1] - 2025-08-17
3
+ ### Fixes
4
+ - Linter error
5
+ - CMatcher get crash if print trace when not enable trace yet
6
+
7
+ ## [0.7.0] - 2025-08-17
8
+
9
+ ### Major Changes
10
+ - Introduced Clang bridge for the C extension, wiring Ruby DSL to `mongory-core` via `Mongory::CMatcher` and `CQueryBuilder`.
11
+
12
+ ### Features
13
+ - New `Mongory::CMatcher` with `match?`, `explain`, `trace`, `enable_trace`, `disable_trace`, and `print_trace`.
14
+ - New `QueryBuilder#c` to switch a Ruby query into the C fast path seamlessly.
15
+ - Regex bridging uses Ruby's `Regexp` under the hood for compatibility.
16
+ - Custom matcher bridge: core can delegate to Ruby matchers (build/match/lookup) when needed.
17
+ - Context bridge: shares `Utils::Context` between Ruby and C during matching.
18
+
19
+ ### Performance
20
+ - Significant speedups for large datasets when using `CMatcher`/`CQueryBuilder`.
21
+
22
+ ### Build & Tooling
23
+ - `ext/mongory_ext/extconf.rb` compiles `mongory-core` sources directly and normalizes GCC/Clang flags.
24
+ - Added explicit submodule build rules to avoid copying/linking extra artifacts.
25
+
26
+ ### Docs
27
+ - Updated README to document Clang bridge usage, availability checks, and examples.
28
+
29
+ ## [0.6.3] - 2025-05-27
30
+
31
+ ### Major Changes
32
+ - Introduced `AbstractMultiMatcher` to unify logic for compound matchers (`$and`, `$or`, `$nor`)
33
+ - Matchers are now sorted by `priority` to improve performance (early exit in `$and`, etc.)
34
+
35
+ ### Features
36
+ - Each matcher can define its own `priority` to participate in execution optimization
37
+ - `$in` and `$nin` matchers now normalize `Range` conditions into consistent matcher structures
38
+
39
+ ### Fixes
40
+ - Explicitly use `::Hash` and `::Array` in `converted.rb` to avoid namespace conflicts
41
+
42
+ ### Tests
43
+ - Added edge case specs for `$in` and `$nin` with `nil` and empty values
44
+
45
+ ### Chores
46
+ - Ignore `.vscode/` in `.gitignore`
47
+
2
48
  ## [0.6.1] - 2025-04-24
3
49
 
4
50
  ### Major Changes
data/README.md CHANGED
@@ -4,31 +4,53 @@ A Mongo-like in-memory query DSL for Ruby.
4
4
 
5
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
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
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)
23
35
 
24
36
  ## Requirements
25
37
 
26
38
  - Ruby >= 2.6.0
27
39
  - No external database required
28
40
 
29
- ## Quick Start
41
+ ## Installation & Quick Start
30
42
 
31
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
+
32
54
  #### Rails Generator
33
55
 
34
56
  You can install a starter configuration with:
@@ -42,16 +64,6 @@ This will generate `config/initializers/mongory.rb` and set up:
42
64
  - Class registration (e.g. `Array`, `ActiveRecord::Relation`, etc.)
43
65
  - Custom value/key converters for your ORM
44
66
 
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
67
  ### Basic Usage
56
68
  ```ruby
57
69
  records = [
@@ -73,6 +85,47 @@ limited = records.mongory
73
85
  .where(:age.gte => 18) # Conditions apply to limited set
74
86
  ```
75
87
 
88
+ ### C Extension (Optional but Recommended)
89
+
90
+ Mongory-rb includes an optional high-performance C extension powered by [mongory-core](https://github.com/mongoryhq/mongory-core):
91
+
92
+ **System Dependencies:**
93
+ - C99-compatible compiler (gcc/clang)
94
+ - CMake >= 3.12 (optional; only needed if you want to build `mongory-core` standalone or run its native tests)
95
+
96
+ **Installation:**
97
+ ```bash
98
+ # macOS
99
+ brew install cmake
100
+
101
+ # Ubuntu/Debian
102
+ sudo apt install cmake build-essential
103
+
104
+ # CentOS/RHEL
105
+ sudo yum install cmake gcc make
106
+ ```
107
+
108
+ The C extension provides significant performance improvements for large datasets. If not available, Mongory-rb automatically falls back to pure Ruby implementation.
109
+
110
+ 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.
111
+
112
+ ## Positioning
113
+
114
+ Mongory is designed to serve two types of users:
115
+
116
+ 1. For MongoDB users:
117
+ - Seamless integration with familiar query syntax
118
+ - Extends query capabilities for non-indexed fields
119
+ - No additional learning cost
120
+
121
+ 2. For non-MongoDB users:
122
+ - Initial learning cost for MongoDB-style syntax
123
+ - Long-term benefits:
124
+ - Improved code readability
125
+ - Better development efficiency
126
+ - Lower maintenance costs
127
+ - Ideal for teams valuing code quality and maintainability
128
+
76
129
  ### Integration with MongoDB
77
130
 
78
131
  Mongory is designed to complement MongoDB, not replace it. Here's how to use them together:
@@ -149,77 +202,7 @@ Mongory::Matchers.register(:class_in, '$classIn', ClassInMatcher)
149
202
  You can define any matcher behavior and attach it to a `$operator` of your choice.
150
203
  Matchers can be composed, validated, and traced just like built-in ones.
151
204
 
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
205
+ ## Core Concepts & API Reference
223
206
  #### Registering Models
224
207
 
225
208
  To allow calling `.mongory` on collections, use `register`:
@@ -262,7 +245,7 @@ records.mongory
262
245
  This will share a mutatable, but stable context object to all matchers in matcher tree.
263
246
  To get your custom option, using `@context.config` in your custom matcher.
264
247
 
265
- ### Debugging Queries
248
+ ## Debugging
266
249
 
267
250
  You can use `explain` to visualize the matcher tree structure:
268
251
  ```ruby
@@ -324,52 +307,6 @@ The debug output includes:
324
307
  - Field names highlighted in gray background
325
308
  - Detailed matching process for each record
326
309
 
327
- ### Performance Considerations
328
-
329
- 1. **Memory Usage**
330
- - Mongory operates entirely in memory
331
- - Consider your data size and memory constraints
332
- - Proc-based implementation reduces memory usage
333
- - Context system provides better memory management
334
-
335
- 2. **Query Optimization**
336
- - Complex conditions are evaluated in sequence
337
- - Use `explain` to analyze query performance
338
- - Empty conditions are optimized with cached Procs
339
- - Context system allows fine-grained control over conversion
340
-
341
- 3. **Benchmarks**
342
- ```ruby
343
- # Simple query (1000 records)
344
- records.mongory.where(:age.gte => 18) # ~0.8ms
345
-
346
- # Complex query (1000 records)
347
- records.mongory.where(:$or => [{:age.gte => 18}, {:status => 'active'}]) # ~0.9ms
348
-
349
- # Simple query (10000 records)
350
- records.mongory.where(:age.gte => 18) # ~6.5ms
351
-
352
- # Complex query (10000 records)
353
- records.mongory.where(:$or => [{:age.gte => 18}, {:status => 'active'}]) # ~7.5ms
354
-
355
- # Simple query (100000 records)
356
- records.mongory.where(:age.gte => 18) # ~64.7ms
357
-
358
- # Complex query (100000 records)
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
363
- ```
364
-
365
- Note: Performance varies based on:
366
- - Data size
367
- - Query complexity
368
- - Hardware specifications
369
- - Ruby version
370
-
371
- Test in your environment to determine if performance meets your needs.
372
-
373
310
  ### Supported Operators
374
311
 
375
312
  | Category | Operators |
@@ -387,6 +324,7 @@ Note: Some operators are Mongory-specific and not available in MongoDB:
387
324
  - Example: `where(:name.present => true)`
388
325
  - `$every`: Checks if all elements in an array match the given condition
389
326
  - Similar to `$elemMatch` but requires all elements to match
327
+ - At least one element in an array, or returns false
390
328
  - Example: `where(:tags.every => { :priority.gt => 5 })`
391
329
 
392
330
  Example:
@@ -496,37 +434,6 @@ end
496
434
  - All operations are performed in memory
497
435
  - Consider memory constraints
498
436
 
499
- ## Migration Guide
500
-
501
- 1. **From Array#select**
502
- ```ruby
503
- # Before
504
- records.select { |r| r['age'] >= 18 && r['status'] == 'active' }
505
-
506
- # After
507
- records.mongory.where(:age.gte => 18, status: 'active')
508
- ```
509
-
510
- 2. **From ActiveRecord**
511
- ```ruby
512
- # Before
513
- indexed_query.where("age >= ? AND status = ?", 18, 'active')
514
-
515
- # After
516
- indexed_query.mongory.where(:age.gte => 18, status: 'active')
517
- ```
518
-
519
- 3. **From MongoDB**
520
- ```ruby
521
- # Before (MongoDB)
522
- users.where(:age.gte => 18, status: 'active')
523
-
524
- # After (Mongory)
525
- users.mongory.where(:age.gte => 18, status: 'active')
526
-
527
- # Just the same.
528
- ```
529
-
530
437
  ## Contributing
531
438
 
532
439
  Contributions are welcome! Here's how you can help:
data/Rakefile CHANGED
@@ -9,4 +9,81 @@ require 'rubocop/rake_task'
9
9
 
10
10
  RuboCop::RakeTask.new
11
11
 
12
+ # Add support for rake-compiler if available
13
+ begin
14
+ require 'rake/extensiontask'
15
+
16
+ Rake::ExtensionTask.new('mongory_ext') do |ext|
17
+ ext.lib_dir = 'ext/mongory_ext'
18
+ ext.ext_dir = 'ext/mongory_ext'
19
+ ext.source_pattern = '*.c'
20
+ end
21
+
22
+ # Add tasks for building with submodule
23
+ namespace :submodule do
24
+ desc 'Initialize/update the mongory-core submodule'
25
+ task :init do
26
+ sh 'git submodule update --init --recursive'
27
+ end
28
+
29
+ desc 'Update the mongory-core submodule to latest'
30
+ task :update do
31
+ sh 'git submodule update --remote'
32
+ end
33
+
34
+ desc 'Build mongory-core submodule'
35
+ task :build do
36
+ core_dir = 'ext/mongory_ext/mongory-core'
37
+ if Dir.exist?(core_dir)
38
+ Dir.chdir(core_dir) do
39
+ if File.exist?('build.sh')
40
+ sh 'chmod +x build.sh && ./build.sh'
41
+ else
42
+ sh 'mkdir -p build && cd build && cmake .. && make'
43
+ end
44
+ end
45
+ else
46
+ puts 'mongory-core submodule not found. Run rake submodule:init first.'
47
+ end
48
+ end
49
+ end
50
+
51
+ desc 'Build the project (without standalone mongory-core build)'
52
+ task build_all: ['submodule:init', :compile]
53
+
54
+ desc 'Clean all build artifacts including submodule'
55
+ task clean_all: :clean do
56
+ sh 'rm -rf ext/mongory_ext/mongory-core/build' if Dir.exist?('ext/mongory_ext/mongory-core/build')
57
+ end
58
+ rescue LoadError
59
+ puts 'rake-compiler not available. Install it with: gem install rake-compiler'
60
+
61
+ # Fallback tasks without rake-compiler
62
+ desc 'Build the C extension manually'
63
+ task :compile do
64
+ Dir.chdir('ext/mongory_ext') do
65
+ sh 'ruby extconf.rb && make'
66
+ end
67
+ end
68
+
69
+ desc 'Clean the C extension manually'
70
+ task :clean do
71
+ Dir.chdir('ext/mongory_ext') do
72
+ sh 'make clean' if File.exist?('Makefile')
73
+ sh 'rm -f Makefile *.o foundations/*.o matchers/*.o mongory_ext.so'
74
+ end
75
+ end
76
+ end
77
+
78
+ # Custom build task using our build script
79
+ desc 'Build using the custom build script'
80
+ task :build_with_script do
81
+ sh 'scripts/build_with_core.sh'
82
+ end
83
+
84
+ desc 'Build in debug mode'
85
+ task :build_debug do
86
+ sh 'scripts/build_with_core.sh --debug'
87
+ end
88
+
12
89
  task default: %i(spec rubocop)