active-query 0.1.2 → 0.2.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 +4 -4
- data/Appraisals +19 -0
- data/CHANGELOG.md +22 -1
- data/README.md +152 -31
- data/active-query.gemspec +46 -0
- data/gemfiles/rails_7_0.gemfile +9 -0
- data/gemfiles/rails_7_1.gemfile +9 -0
- data/gemfiles/rails_8_0.gemfile +9 -0
- data/lib/active_query/type_registry.rb +56 -0
- data/lib/active_query/types/base.rb +15 -0
- data/lib/active_query/types/boolean.rb +24 -0
- data/lib/active_query/types/float.rb +17 -0
- data/lib/active_query/types/integer.rb +17 -0
- data/lib/active_query/types/string.rb +15 -0
- data/lib/active_query/version.rb +1 -1
- data/lib/active_query.rb +15 -14
- metadata +41 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5636b341a804f0c3b2fb42b02b57ac502655c70c1cdc37c7e955004012375181
|
|
4
|
+
data.tar.gz: 4e48fbc0f6877a05d963aca4aa04bac4cf2691111cc9857381428b3ff9998f22
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b8c12b1ebe7b7317af9e2201d3cc6a6edc8cda475a2967e175f2e14605f273d87e1fe514045c3597396c7b5291ef83d6470212d41048af0c3a7ed7179b48df39
|
|
7
|
+
data.tar.gz: ed6ba6312dcf12f8f83594ce2d0557466d495a7c901e351eff1490a641a8afcb9dda96f6885ae935e722ffca3738285b03cf2339380fc1a0c5edf451c8330b51
|
data/Appraisals
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
appraise 'rails-7.0' do
|
|
4
|
+
gem 'activerecord', '~> 7.0.0'
|
|
5
|
+
gem 'activesupport', '~> 7.0.0'
|
|
6
|
+
gem 'sqlite3', '~> 1.4'
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
appraise 'rails-7.1' do
|
|
10
|
+
gem 'activerecord', '~> 7.1.0'
|
|
11
|
+
gem 'activesupport', '~> 7.1.0'
|
|
12
|
+
gem 'sqlite3', '~> 1.4'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
appraise 'rails-8.0' do
|
|
16
|
+
gem 'activerecord', '~> 8.0.0'
|
|
17
|
+
gem 'activesupport', '~> 8.0.0'
|
|
18
|
+
gem 'sqlite3', '~> 2.1'
|
|
19
|
+
end
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] - 2026-03-11
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `ActiveQuery::TypeRegistry` for extensible type validation and coercion — register custom types via `TypeRegistry.register` with a `type_class:`, custom validator lambda, or coercer lambda
|
|
14
|
+
- Built-in type classes (`Types::String`, `Types::Integer`, `Types::Float`, `Types::Boolean`) with default coercion support
|
|
15
|
+
- Per-argument `coerce:` option in argument definitions, taking priority over registry-level coercion
|
|
16
|
+
- `TypeRegistry.unregister` to disable built-in coercion and fall back to strict `is_a?` validation
|
|
17
|
+
- Integer coercion restricted to base-10 strings to prevent unexpected hex/octal conversions
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- Replace `instance_of?` with `is_a?` in type validation so subclass instances (e.g. `ActiveSupport::SafeBuffer`) pass `String` type checks
|
|
21
|
+
|
|
22
|
+
## [0.1.3] - 2024-12-19
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- Support for Rails 8.0
|
|
26
|
+
- Compatibility with SQLite3 version 2.1+ required by Rails 8
|
|
27
|
+
- Updated CI matrix to test against Rails 8.0
|
|
28
|
+
|
|
10
29
|
## [0.0.1] - 2024-12-19
|
|
11
30
|
|
|
12
31
|
### Added
|
|
@@ -31,5 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
31
50
|
- **Resolvers**: Support complex query logic with resolver classes
|
|
32
51
|
- **Conditional Logic**: Apply scopes conditionally with `if`/`unless`
|
|
33
52
|
|
|
34
|
-
[Unreleased]: https://github.com/matiasasis/active-query/compare/v0.0
|
|
53
|
+
[Unreleased]: https://github.com/matiasasis/active-query/compare/v0.2.0...HEAD
|
|
54
|
+
[0.2.0]: https://github.com/matiasasis/active-query/compare/v0.1.3...v0.2.0
|
|
55
|
+
[0.1.3]: https://github.com/matiasasis/active-query/compare/v0.0.1...v0.1.3
|
|
35
56
|
[0.0.1]: https://github.com/matiasasis/active-query/releases/tag/v0.0.1
|
data/README.md
CHANGED
|
@@ -36,14 +36,14 @@ Create a query object by including `ActiveQuery::Base` and defining queries with
|
|
|
36
36
|
```ruby
|
|
37
37
|
class UserQuery
|
|
38
38
|
include ActiveQuery::Base
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
# The model is automatically inferred from the class name (User)
|
|
41
41
|
# Or explicitly set it:
|
|
42
42
|
model_name 'User'
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
# Simple query without arguments
|
|
45
45
|
query :active, 'Returns all active users', -> { scope.where(active: true) }
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
# Query with arguments and type validation
|
|
48
48
|
query :by_email, 'Find users by email address',
|
|
49
49
|
{ email: { type: String } },
|
|
@@ -69,13 +69,13 @@ ActiveQuery supports several argument types with automatic validation:
|
|
|
69
69
|
```ruby
|
|
70
70
|
class ProductQuery
|
|
71
71
|
include ActiveQuery::Base
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
query :filter_products, 'Filter products by various criteria',
|
|
74
74
|
{
|
|
75
75
|
name: { type: String },
|
|
76
76
|
price: { type: Float },
|
|
77
77
|
quantity: { type: Integer },
|
|
78
|
-
available: { type:
|
|
78
|
+
available: { type: Boolean }
|
|
79
79
|
},
|
|
80
80
|
-> (name:, price:, quantity:, available:) {
|
|
81
81
|
scope.where(name: name)
|
|
@@ -97,22 +97,84 @@ ProductQuery.filter_products(
|
|
|
97
97
|
ProductQuery.filter_products(name: 123, price: 'invalid', quantity: true, available: 'yes')
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
+
### Custom Types & Coercion
|
|
101
|
+
|
|
102
|
+
ActiveQuery includes built-in type coercion for `String`, `Integer`, `Float`, and `Boolean`. When a value doesn't match the expected type, ActiveQuery will attempt to coerce it before validation:
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
# These all work — coercion converts the values automatically
|
|
106
|
+
ProductQuery.filter_products(name: :widget, price: '19.99', quantity: '10', available: 'true')
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Built-in coercion behavior:**
|
|
110
|
+
|
|
111
|
+
| Type | Coerces from | Example |
|
|
112
|
+
|------|-------------|---------|
|
|
113
|
+
| `String` | Any (via `.to_s`) | `42` → `"42"` |
|
|
114
|
+
| `Integer` | Numeric strings | `"42"` → `42` |
|
|
115
|
+
| `Float` | Numeric strings, integers | `"1.5"` → `1.5`, `42` → `42.0` |
|
|
116
|
+
| `Boolean` | `"true"`, `"1"`, `1`, `"false"`, `"0"`, `0` | `"true"` → `true` |
|
|
117
|
+
|
|
118
|
+
**Registering a custom type class:**
|
|
119
|
+
|
|
120
|
+
Create a class that inherits from `ActiveQuery::Types::Base` and implements `.valid?` and `.coerce`:
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
# config/initializers/active_query.rb
|
|
124
|
+
class UuidType < ActiveQuery::Types::Base
|
|
125
|
+
UUID_REGEX = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i
|
|
126
|
+
|
|
127
|
+
def self.valid?(value)
|
|
128
|
+
value.is_a?(String) && value.match?(UUID_REGEX)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def self.coerce(value)
|
|
132
|
+
value.to_s.downcase.strip
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
ActiveQuery::TypeRegistry.register(UuidType, type_class: UuidType)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Disabling built-in coercion:**
|
|
140
|
+
|
|
141
|
+
If you prefer strict type validation without coercion, unregister the built-in type:
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
# config/initializers/active_query.rb
|
|
145
|
+
ActiveQuery::TypeRegistry.unregister(Integer) # now "42" won't auto-convert to 42
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
After unregistering, the type falls back to `is_a?` validation with no coercion.
|
|
149
|
+
|
|
150
|
+
**Per-argument coercion:**
|
|
151
|
+
|
|
152
|
+
You can also define coercion on individual arguments using the `coerce:` option:
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
query :by_number, 'Find by number',
|
|
156
|
+
{ number: { type: Integer, coerce: ->(v) { v.to_i } } },
|
|
157
|
+
-> (number:) { scope.where(number: number) }
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Per-argument `coerce:` takes priority over the global TypeRegistry coercion.
|
|
161
|
+
|
|
100
162
|
### Optional Arguments and Defaults
|
|
101
163
|
|
|
102
164
|
```ruby
|
|
103
165
|
class OrderQuery
|
|
104
166
|
include ActiveQuery::Base
|
|
105
|
-
|
|
167
|
+
|
|
106
168
|
query :search_orders, 'Search orders with optional filters',
|
|
107
169
|
{
|
|
108
170
|
status: { type: String },
|
|
109
|
-
paid: { type:
|
|
171
|
+
paid: { type: Boolean, default: true },
|
|
110
172
|
customer_name: { type: String, optional: true }
|
|
111
173
|
},
|
|
112
174
|
-> (status:, paid:, customer_name:) {
|
|
113
175
|
scope.where(status: status)
|
|
114
176
|
.where(paid: paid)
|
|
115
|
-
.
|
|
177
|
+
.if(customer_name, -> { where('customer_name LIKE ?', "%#{customer_name}%") })
|
|
116
178
|
}
|
|
117
179
|
end
|
|
118
180
|
|
|
@@ -129,17 +191,17 @@ Use `if` and `unless` methods for conditional query building:
|
|
|
129
191
|
```ruby
|
|
130
192
|
class UserQuery
|
|
131
193
|
include ActiveQuery::Base
|
|
132
|
-
|
|
194
|
+
|
|
133
195
|
query :search_users, 'Search users with conditional filters',
|
|
134
196
|
{
|
|
135
197
|
name: { type: String, optional: true },
|
|
136
|
-
active: { type:
|
|
198
|
+
active: { type: Boolean, optional: true }
|
|
137
199
|
},
|
|
138
200
|
-> (name:, active:) {
|
|
139
201
|
scope.if(name.present?, -> { where('name LIKE ?', "%#{name}%") })
|
|
140
202
|
.if(active == true, -> { where(active: true) })
|
|
141
203
|
}
|
|
142
|
-
|
|
204
|
+
|
|
143
205
|
query :filter_unless_admin, 'Filter users unless they are admin',
|
|
144
206
|
{
|
|
145
207
|
role: { type: String, optional: true }
|
|
@@ -157,16 +219,16 @@ ActiveQuery provides additional query operations beyond standard ActiveRecord:
|
|
|
157
219
|
```ruby
|
|
158
220
|
class ProductQuery
|
|
159
221
|
include ActiveQuery::Base
|
|
160
|
-
|
|
222
|
+
|
|
161
223
|
# Comparison operations
|
|
162
224
|
query :expensive_products, 'Products above price threshold', -> { scope.gt(:price, 100) }
|
|
163
225
|
query :affordable_products, 'Products within budget', -> { scope.lteq(:price, 50) }
|
|
164
|
-
|
|
165
|
-
# Text search operations
|
|
226
|
+
|
|
227
|
+
# Text search operations
|
|
166
228
|
query :search_by_name, 'Search products by name pattern', -> { scope.like(:name, 'Phone') }
|
|
167
229
|
query :products_starting_with, 'Products starting with prefix', -> { scope.start_like(:name, 'iPhone') }
|
|
168
230
|
query :products_ending_with, 'Products ending with suffix', -> { scope.end_like(:name, 'Pro') }
|
|
169
|
-
|
|
231
|
+
|
|
170
232
|
# Dynamic filtering
|
|
171
233
|
query :by_price_range, 'Filter by price range',
|
|
172
234
|
{ min_price: { type: Float }, max_price: { type: Float } },
|
|
@@ -179,11 +241,11 @@ end
|
|
|
179
241
|
|
|
180
242
|
**Available operations:**
|
|
181
243
|
- `gt(column, value)` - greater than
|
|
182
|
-
- `gteq(column, value)` - greater than or equal
|
|
244
|
+
- `gteq(column, value)` - greater than or equal
|
|
183
245
|
- `lt(column, value)` - less than
|
|
184
246
|
- `lteq(column, value)` - less than or equal
|
|
185
247
|
- `like(column, value)` - contains pattern (wraps with %)
|
|
186
|
-
- `start_like(column, value)` - starts with pattern
|
|
248
|
+
- `start_like(column, value)` - starts with pattern
|
|
187
249
|
- `end_like(column, value)` - ends with pattern
|
|
188
250
|
|
|
189
251
|
### Custom Scopes
|
|
@@ -194,18 +256,18 @@ Define reusable scopes within your query objects:
|
|
|
194
256
|
class UserQuery
|
|
195
257
|
include ActiveQuery::Base
|
|
196
258
|
include ActiveQuery::Scopes
|
|
197
|
-
|
|
259
|
+
|
|
198
260
|
# Define custom scopes
|
|
199
261
|
module Scopes
|
|
200
262
|
include ActiveQuery::Scopes
|
|
201
|
-
|
|
263
|
+
|
|
202
264
|
scope :recent, -> { where('created_at > ?', 1.month.ago) }
|
|
203
265
|
scope :by_role, -> (role:) { where(role: role) }
|
|
204
266
|
end
|
|
205
|
-
|
|
267
|
+
|
|
206
268
|
# Use scopes in queries
|
|
207
269
|
query :recent_admins, 'Find recent admin users', -> { scope.recent.by_role(role: 'admin') }
|
|
208
|
-
|
|
270
|
+
|
|
209
271
|
query :count_recent, 'Count recent users', -> { scope.recent.count }
|
|
210
272
|
end
|
|
211
273
|
```
|
|
@@ -228,11 +290,11 @@ end
|
|
|
228
290
|
# Use resolver in query object
|
|
229
291
|
class UserQuery
|
|
230
292
|
include ActiveQuery::Base
|
|
231
|
-
|
|
293
|
+
|
|
232
294
|
# Resolver without arguments
|
|
233
|
-
query :high_value_users, 'Users with many orders',
|
|
295
|
+
query :high_value_users, 'Users with many orders',
|
|
234
296
|
resolver: UserStatsResolver
|
|
235
|
-
|
|
297
|
+
|
|
236
298
|
# Resolver with arguments
|
|
237
299
|
query :users_with_orders, 'Users with minimum order count',
|
|
238
300
|
{ min_orders: { type: Integer } },
|
|
@@ -251,7 +313,7 @@ Query objects provide metadata about available queries:
|
|
|
251
313
|
```ruby
|
|
252
314
|
class UserQuery
|
|
253
315
|
include ActiveQuery::Base
|
|
254
|
-
|
|
316
|
+
|
|
255
317
|
query :active, 'Find active users', -> { scope.where(active: true) }
|
|
256
318
|
query :by_name, 'Find by name', { name: { type: String } }, -> (name:) { scope.where(name: name) }
|
|
257
319
|
end
|
|
@@ -296,7 +358,7 @@ ActiveQuery works seamlessly with Rails applications:
|
|
|
296
358
|
# app/queries/user_query.rb
|
|
297
359
|
class UserQuery
|
|
298
360
|
include ActiveQuery::Base
|
|
299
|
-
|
|
361
|
+
|
|
300
362
|
query :active, 'Active users', -> { scope.where(active: true) }
|
|
301
363
|
query :by_role, 'Users by role', { role: { type: String } }, -> (role:) { scope.where(role: role) }
|
|
302
364
|
end
|
|
@@ -306,7 +368,7 @@ class UsersController < ApplicationController
|
|
|
306
368
|
def index
|
|
307
369
|
@users = UserQuery.active
|
|
308
370
|
end
|
|
309
|
-
|
|
371
|
+
|
|
310
372
|
def admins
|
|
311
373
|
@admins = UserQuery.by_role(role: 'admin')
|
|
312
374
|
end
|
|
@@ -318,9 +380,23 @@ end
|
|
|
318
380
|
|
|
319
381
|
## Requirements
|
|
320
382
|
|
|
321
|
-
- Ruby >= 2.
|
|
322
|
-
- ActiveRecord >= 6.1
|
|
323
|
-
- ActiveSupport >= 6.1
|
|
383
|
+
- Ruby >= 3.2.0
|
|
384
|
+
- ActiveRecord >= 6.1, < 9.0
|
|
385
|
+
- ActiveSupport >= 6.1, < 9.0
|
|
386
|
+
|
|
387
|
+
### Rails 8 Support
|
|
388
|
+
|
|
389
|
+
ActiveQuery fully supports Rails 8.0! Note that Rails 8 requires SQLite3 version 2.1 or higher:
|
|
390
|
+
|
|
391
|
+
```ruby
|
|
392
|
+
# For Rails 8 applications
|
|
393
|
+
gem 'sqlite3', '~> 2.1'
|
|
394
|
+
|
|
395
|
+
# For Rails 7 and below
|
|
396
|
+
gem 'sqlite3', '~> 1.4'
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
See [RAILS_8_MIGRATION.md](RAILS_8_MIGRATION.md) for detailed migration instructions.
|
|
324
400
|
|
|
325
401
|
## Development
|
|
326
402
|
|
|
@@ -328,6 +404,51 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
|
328
404
|
|
|
329
405
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`.
|
|
330
406
|
|
|
407
|
+
### Testing Against Multiple Rails Versions
|
|
408
|
+
|
|
409
|
+
This gem is tested against Rails 7.0, 7.1, and 8.0 to ensure compatibility across versions.
|
|
410
|
+
|
|
411
|
+
#### Running Tests Locally
|
|
412
|
+
|
|
413
|
+
To test against all supported Rails versions locally:
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
# Test against all Rails versions
|
|
417
|
+
./bin/test_all_rails
|
|
418
|
+
|
|
419
|
+
# Test against a specific Rails version
|
|
420
|
+
BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle exec rake spec
|
|
421
|
+
BUNDLE_GEMFILE=gemfiles/rails_7_1.gemfile bundle exec rake spec
|
|
422
|
+
BUNDLE_GEMFILE=gemfiles/rails_8_0.gemfile bundle exec rake spec
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
#### Using Appraisal (Alternative Method)
|
|
426
|
+
|
|
427
|
+
If you prefer using the `appraisal` gem:
|
|
428
|
+
|
|
429
|
+
```bash
|
|
430
|
+
# Install appraisal gemfiles
|
|
431
|
+
bundle exec appraisal install
|
|
432
|
+
|
|
433
|
+
# Run tests against all Rails versions
|
|
434
|
+
bundle exec appraisal rake spec
|
|
435
|
+
|
|
436
|
+
# Run tests against specific Rails version
|
|
437
|
+
bundle exec appraisal rails-7-0 rake spec
|
|
438
|
+
bundle exec appraisal rails-7-1 rake spec
|
|
439
|
+
bundle exec appraisal rails-8-0 rake spec
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
#### Continuous Integration
|
|
443
|
+
|
|
444
|
+
The gem uses GitHub Actions to automatically test against multiple Ruby and Rails versions in a matrix configuration. Each push and pull request triggers tests across:
|
|
445
|
+
|
|
446
|
+
- Ruby versions: 3.2, 3.3
|
|
447
|
+
- Rails versions: 7.0, 7.1, 8.0
|
|
448
|
+
- Appropriate SQLite3 versions for each Rails version (1.4 for Rails 7.x, 2.1+ for Rails 8.0)
|
|
449
|
+
|
|
450
|
+
See `.github/workflows/main.yml` for the complete CI configuration.
|
|
451
|
+
|
|
331
452
|
## Contributing
|
|
332
453
|
|
|
333
454
|
Bug reports and pull requests are welcome on GitHub at https://github.com/matiasasis/active-query. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/matiasasis/active-query/blob/master/CODE_OF_CONDUCT.md).
|
|
@@ -338,4 +459,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
|
338
459
|
|
|
339
460
|
## Code of Conduct
|
|
340
461
|
|
|
341
|
-
Everyone interacting in the ActiveQuery project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/matiasasis/active-query/blob/master/CODE_OF_CONDUCT.md).
|
|
462
|
+
Everyone interacting in the ActiveQuery project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/matiasasis/active-query/blob/master/CODE_OF_CONDUCT.md).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/active_query/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "active-query"
|
|
7
|
+
spec.version = ActiveQuery::VERSION
|
|
8
|
+
spec.authors = ["Matias Asis"]
|
|
9
|
+
spec.email = ["matiasis.90@gmail.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "ActiveQuery is a gem that helps you to create query objects in a simple way."
|
|
12
|
+
spec.description = "ActiveQuery is a gem that helps you to create query objects in a simple way. It provides a DSL to define queries and scopes for your query object."
|
|
13
|
+
spec.homepage = "https://github.com/matiasasis/active-query"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = ">= 3.2.0"
|
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
17
|
+
spec.metadata["source_code_uri"] = "https://github.com/matiasasis/active-query"
|
|
18
|
+
spec.metadata["bug_tracker_uri"] = "https://github.com/matiasasis/active-query/issues"
|
|
19
|
+
spec.metadata["changelog_uri"] = "https://github.com/matiasasis/active-query/blob/master/CHANGELOG.md"
|
|
20
|
+
|
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
|
22
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
23
|
+
spec.files = Dir.chdir(__dir__) do
|
|
24
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
25
|
+
(File.expand_path(f) == __FILE__) ||
|
|
26
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile]) ||
|
|
27
|
+
f.match?(/\.gem\z/)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
spec.bindir = "exe"
|
|
31
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
32
|
+
spec.require_paths = ['lib']
|
|
33
|
+
|
|
34
|
+
# Dependencies
|
|
35
|
+
spec.add_dependency 'activerecord', '>= 6.1', '< 9.0'
|
|
36
|
+
spec.add_dependency 'activesupport', '>= 6.1', '< 9.0'
|
|
37
|
+
|
|
38
|
+
# Dev dependencies
|
|
39
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
|
40
|
+
spec.add_development_dependency 'rspec', '~> 3.9.0'
|
|
41
|
+
spec.add_development_dependency 'simplecov', '~> 0.17.1'
|
|
42
|
+
spec.add_development_dependency 'sqlite3', '>= 1.5.1', '< 3.0'
|
|
43
|
+
spec.add_development_dependency 'database_cleaner-active_record', '~> 2.2.0'
|
|
44
|
+
spec.add_development_dependency 'byebug', '~> 11.0'
|
|
45
|
+
spec.add_development_dependency 'appraisal', '~> 2.4'
|
|
46
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'types/base'
|
|
4
|
+
require_relative 'types/string'
|
|
5
|
+
require_relative 'types/integer'
|
|
6
|
+
require_relative 'types/float'
|
|
7
|
+
require_relative 'types/boolean'
|
|
8
|
+
|
|
9
|
+
module ActiveQuery
|
|
10
|
+
module TypeRegistry
|
|
11
|
+
@validators = {}
|
|
12
|
+
@coercers = {}
|
|
13
|
+
@type_classes = {}
|
|
14
|
+
|
|
15
|
+
def self.register(type, validator: nil, coerce: nil, type_class: nil)
|
|
16
|
+
@validators[type] = validator if validator
|
|
17
|
+
@coercers[type] = coerce if coerce
|
|
18
|
+
@type_classes[type] = type_class if type_class
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.unregister(type)
|
|
22
|
+
@validators.delete(type)
|
|
23
|
+
@coercers.delete(type)
|
|
24
|
+
@type_classes.delete(type)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.valid?(type, value)
|
|
28
|
+
if @validators.key?(type)
|
|
29
|
+
@validators[type].call(value)
|
|
30
|
+
elsif @type_classes.key?(type)
|
|
31
|
+
@type_classes[type].valid?(value)
|
|
32
|
+
else
|
|
33
|
+
value.is_a?(type)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.coerce(type, value)
|
|
38
|
+
if @coercers.key?(type)
|
|
39
|
+
@coercers[type].call(value)
|
|
40
|
+
elsif @type_classes.key?(type)
|
|
41
|
+
@type_classes[type].coerce(value)
|
|
42
|
+
else
|
|
43
|
+
value
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.coercer?(type)
|
|
48
|
+
@coercers.key?(type) || @type_classes.key?(type)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
register(String, type_class: Types::String)
|
|
52
|
+
register(Integer, type_class: Types::Integer)
|
|
53
|
+
register(Float, type_class: Types::Float)
|
|
54
|
+
register(Types::Boolean, type_class: Types::Boolean)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveQuery
|
|
4
|
+
module Types
|
|
5
|
+
class Boolean < Base
|
|
6
|
+
def self.to_s = 'Boolean'
|
|
7
|
+
def self.inspect = 'Boolean'
|
|
8
|
+
|
|
9
|
+
TRUTHY = [true, 'true', '1', 1].freeze
|
|
10
|
+
FALSY = [false, 'false', '0', 0].freeze
|
|
11
|
+
|
|
12
|
+
def self.valid?(value)
|
|
13
|
+
value == true || value == false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.coerce(value)
|
|
17
|
+
return true if TRUTHY.include?(value)
|
|
18
|
+
return false if FALSY.include?(value)
|
|
19
|
+
|
|
20
|
+
value
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveQuery
|
|
4
|
+
module Types
|
|
5
|
+
class Float < Base
|
|
6
|
+
def self.valid?(value)
|
|
7
|
+
value.is_a?(::Float)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.coerce(value)
|
|
11
|
+
::Kernel.Float(value)
|
|
12
|
+
rescue ArgumentError, TypeError
|
|
13
|
+
value
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveQuery
|
|
4
|
+
module Types
|
|
5
|
+
class Integer < Base
|
|
6
|
+
def self.valid?(value)
|
|
7
|
+
value.is_a?(::Integer)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.coerce(value)
|
|
11
|
+
::Kernel.Integer(value, 10)
|
|
12
|
+
rescue ArgumentError, TypeError
|
|
13
|
+
value
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/active_query/version.rb
CHANGED
data/lib/active_query.rb
CHANGED
|
@@ -5,13 +5,14 @@ require 'active_support'
|
|
|
5
5
|
require 'active_support/concern'
|
|
6
6
|
require_relative 'active_query/version'
|
|
7
7
|
require_relative 'active_query/resolver'
|
|
8
|
+
require_relative 'active_query/type_registry'
|
|
8
9
|
require_relative 'active_record_relation_extensions'
|
|
9
10
|
|
|
10
11
|
module ActiveQuery
|
|
11
12
|
module Base
|
|
12
13
|
extend ::ActiveSupport::Concern
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
Boolean = ActiveQuery::Types::Boolean
|
|
15
16
|
|
|
16
17
|
included do
|
|
17
18
|
infer_model
|
|
@@ -84,7 +85,7 @@ module ActiveQuery
|
|
|
84
85
|
def query_with_arguments(name, description, args_def, lambda)
|
|
85
86
|
register_query(name, description, args_def)
|
|
86
87
|
|
|
87
|
-
define_singleton_method(name) do |given_args|
|
|
88
|
+
define_singleton_method(name) do |given_args = {}|
|
|
88
89
|
given_args = validate_args(name, given_args, args_def)
|
|
89
90
|
lambda.call(**given_args)
|
|
90
91
|
end
|
|
@@ -139,20 +140,20 @@ module ActiveQuery
|
|
|
139
140
|
extra_params = given_args.keys - args_def.keys
|
|
140
141
|
raise ArgumentError, "Unknown params: #{extra_params}" unless extra_params.empty?
|
|
141
142
|
|
|
142
|
-
given_args.each do |
|
|
143
|
-
given_arg_config = args_def[
|
|
143
|
+
given_args.each do |given_arg_name, given_arg_value|
|
|
144
|
+
given_arg_config = args_def[given_arg_name]
|
|
144
145
|
given_arg_type = given_arg_config[:type]
|
|
145
|
-
given_arg_name = given_arg.first
|
|
146
|
-
given_arg_value = given_arg.second
|
|
147
146
|
|
|
148
|
-
if
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
147
|
+
if given_arg_config[:coerce]
|
|
148
|
+
given_args[given_arg_name] = given_arg_config[:coerce].call(given_arg_value)
|
|
149
|
+
given_arg_value = given_args[given_arg_name]
|
|
150
|
+
elsif ActiveQuery::TypeRegistry.coercer?(given_arg_type)
|
|
151
|
+
given_args[given_arg_name] = ActiveQuery::TypeRegistry.coerce(given_arg_type, given_arg_value)
|
|
152
|
+
given_arg_value = given_args[given_arg_name]
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
unless ActiveQuery::TypeRegistry.valid?(given_arg_type, given_arg_value)
|
|
156
|
+
raise ArgumentError, ":#{given_arg_name} must be of type #{given_arg_type}"
|
|
156
157
|
end
|
|
157
158
|
end
|
|
158
159
|
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active-query
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matias Asis
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: activerecord
|
|
@@ -19,7 +18,7 @@ dependencies:
|
|
|
19
18
|
version: '6.1'
|
|
20
19
|
- - "<"
|
|
21
20
|
- !ruby/object:Gem::Version
|
|
22
|
-
version: '
|
|
21
|
+
version: '9.0'
|
|
23
22
|
type: :runtime
|
|
24
23
|
prerelease: false
|
|
25
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -29,7 +28,7 @@ dependencies:
|
|
|
29
28
|
version: '6.1'
|
|
30
29
|
- - "<"
|
|
31
30
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '
|
|
31
|
+
version: '9.0'
|
|
33
32
|
- !ruby/object:Gem::Dependency
|
|
34
33
|
name: activesupport
|
|
35
34
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -39,7 +38,7 @@ dependencies:
|
|
|
39
38
|
version: '6.1'
|
|
40
39
|
- - "<"
|
|
41
40
|
- !ruby/object:Gem::Version
|
|
42
|
-
version: '
|
|
41
|
+
version: '9.0'
|
|
43
42
|
type: :runtime
|
|
44
43
|
prerelease: false
|
|
45
44
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -49,7 +48,7 @@ dependencies:
|
|
|
49
48
|
version: '6.1'
|
|
50
49
|
- - "<"
|
|
51
50
|
- !ruby/object:Gem::Version
|
|
52
|
-
version: '
|
|
51
|
+
version: '9.0'
|
|
53
52
|
- !ruby/object:Gem::Dependency
|
|
54
53
|
name: rake
|
|
55
54
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -96,16 +95,22 @@ dependencies:
|
|
|
96
95
|
name: sqlite3
|
|
97
96
|
requirement: !ruby/object:Gem::Requirement
|
|
98
97
|
requirements:
|
|
99
|
-
- - "
|
|
98
|
+
- - ">="
|
|
100
99
|
- !ruby/object:Gem::Version
|
|
101
100
|
version: 1.5.1
|
|
101
|
+
- - "<"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '3.0'
|
|
102
104
|
type: :development
|
|
103
105
|
prerelease: false
|
|
104
106
|
version_requirements: !ruby/object:Gem::Requirement
|
|
105
107
|
requirements:
|
|
106
|
-
- - "
|
|
108
|
+
- - ">="
|
|
107
109
|
- !ruby/object:Gem::Version
|
|
108
110
|
version: 1.5.1
|
|
111
|
+
- - "<"
|
|
112
|
+
- !ruby/object:Gem::Version
|
|
113
|
+
version: '3.0'
|
|
109
114
|
- !ruby/object:Gem::Dependency
|
|
110
115
|
name: database_cleaner-active_record
|
|
111
116
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -134,6 +139,20 @@ dependencies:
|
|
|
134
139
|
- - "~>"
|
|
135
140
|
- !ruby/object:Gem::Version
|
|
136
141
|
version: '11.0'
|
|
142
|
+
- !ruby/object:Gem::Dependency
|
|
143
|
+
name: appraisal
|
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
|
145
|
+
requirements:
|
|
146
|
+
- - "~>"
|
|
147
|
+
- !ruby/object:Gem::Version
|
|
148
|
+
version: '2.4'
|
|
149
|
+
type: :development
|
|
150
|
+
prerelease: false
|
|
151
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
152
|
+
requirements:
|
|
153
|
+
- - "~>"
|
|
154
|
+
- !ruby/object:Gem::Version
|
|
155
|
+
version: '2.4'
|
|
137
156
|
description: ActiveQuery is a gem that helps you to create query objects in a simple
|
|
138
157
|
way. It provides a DSL to define queries and scopes for your query object.
|
|
139
158
|
email:
|
|
@@ -145,15 +164,26 @@ extra_rdoc_files: []
|
|
|
145
164
|
files:
|
|
146
165
|
- ".byebug_history"
|
|
147
166
|
- ".rspec"
|
|
167
|
+
- Appraisals
|
|
148
168
|
- CHANGELOG.md
|
|
149
169
|
- CODE_OF_CONDUCT.md
|
|
150
170
|
- LICENSE.txt
|
|
151
171
|
- README.md
|
|
152
172
|
- Rakefile
|
|
173
|
+
- active-query.gemspec
|
|
153
174
|
- exe/active-query
|
|
175
|
+
- gemfiles/rails_7_0.gemfile
|
|
176
|
+
- gemfiles/rails_7_1.gemfile
|
|
177
|
+
- gemfiles/rails_8_0.gemfile
|
|
154
178
|
- lib/active-query.rb
|
|
155
179
|
- lib/active_query.rb
|
|
156
180
|
- lib/active_query/resolver.rb
|
|
181
|
+
- lib/active_query/type_registry.rb
|
|
182
|
+
- lib/active_query/types/base.rb
|
|
183
|
+
- lib/active_query/types/boolean.rb
|
|
184
|
+
- lib/active_query/types/float.rb
|
|
185
|
+
- lib/active_query/types/integer.rb
|
|
186
|
+
- lib/active_query/types/string.rb
|
|
157
187
|
- lib/active_query/version.rb
|
|
158
188
|
- lib/active_record_relation_extensions.rb
|
|
159
189
|
homepage: https://github.com/matiasasis/active-query
|
|
@@ -165,7 +195,6 @@ metadata:
|
|
|
165
195
|
bug_tracker_uri: https://github.com/matiasasis/active-query/issues
|
|
166
196
|
changelog_uri: https://github.com/matiasasis/active-query/blob/master/CHANGELOG.md
|
|
167
197
|
rubygems_mfa_required: 'true'
|
|
168
|
-
post_install_message:
|
|
169
198
|
rdoc_options: []
|
|
170
199
|
require_paths:
|
|
171
200
|
- lib
|
|
@@ -173,15 +202,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
173
202
|
requirements:
|
|
174
203
|
- - ">="
|
|
175
204
|
- !ruby/object:Gem::Version
|
|
176
|
-
version: 2.
|
|
205
|
+
version: 3.2.0
|
|
177
206
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
178
207
|
requirements:
|
|
179
208
|
- - ">="
|
|
180
209
|
- !ruby/object:Gem::Version
|
|
181
210
|
version: '0'
|
|
182
211
|
requirements: []
|
|
183
|
-
rubygems_version:
|
|
184
|
-
signing_key:
|
|
212
|
+
rubygems_version: 4.0.2
|
|
185
213
|
specification_version: 4
|
|
186
214
|
summary: ActiveQuery is a gem that helps you to create query objects in a simple way.
|
|
187
215
|
test_files: []
|