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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 597183debb31f2b4490361200975eccbeeec9ef03e5d844abfe193e4114284d2
4
- data.tar.gz: c82fd9b968a2dffcdd83914ea08bdc7b332453322008e41436d3f7ff7221e53e
3
+ metadata.gz: 5636b341a804f0c3b2fb42b02b57ac502655c70c1cdc37c7e955004012375181
4
+ data.tar.gz: 4e48fbc0f6877a05d963aca4aa04bac4cf2691111cc9857381428b3ff9998f22
5
5
  SHA512:
6
- metadata.gz: c8abf99aad689c83d93223dfa5de1e5e7c2ece56b6235ca6594b50f2b5413a03a06e8fb44dbf815137c3217e5eebf18335f52204450b3aab7899e7064a969d65
7
- data.tar.gz: 8c5165ccf7c2463198e39748fcf47f5c92bc916dd5ce6f6b448e5f682c18b88fdfe05d774e385a0f716cb6bc776f822357705834878f1fe96114b2eed74bdf4e
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.1...HEAD
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: ActiveQuery::Base::Boolean }
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: ActiveQuery::Base::Boolean, default: true },
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
- .then { |s| customer_name ? s.where('customer_name LIKE ?', "%#{customer_name}%") : s }
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: ActiveQuery::Base::Boolean, optional: true }
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.6.0
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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec path: ".."
6
+
7
+ gem "activerecord", "~> 7.0.0"
8
+ gem "activesupport", "~> 7.0.0"
9
+ gem "sqlite3", "~> 1.4"
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec path: ".."
6
+
7
+ gem "activerecord", "~> 7.1.0"
8
+ gem "activesupport", "~> 7.1.0"
9
+ gem "sqlite3", "~> 1.4"
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec path: ".."
6
+
7
+ gem "activerecord", "~> 8.0.0"
8
+ gem "activesupport", "~> 8.0.0"
9
+ gem "sqlite3", "~> 2.1"
@@ -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,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveQuery
4
+ module Types
5
+ class Base
6
+ def self.valid?(_value)
7
+ raise NotImplementedError, "#{name} must implement .valid?"
8
+ end
9
+
10
+ def self.coerce(value)
11
+ value
12
+ end
13
+ end
14
+ end
15
+ 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
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveQuery
4
+ module Types
5
+ class String < Base
6
+ def self.valid?(value)
7
+ value.is_a?(::String)
8
+ end
9
+
10
+ def self.coerce(value)
11
+ value.to_s
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveQuery
4
- VERSION = '0.1.2'
4
+ VERSION = '0.2.0'
5
5
  end
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
- class Boolean; end
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 |given_arg|
143
- given_arg_config = args_def[given_arg.first]
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 given_arg_type == ActiveQuery::Base::Boolean
149
- unless given_arg_value == true || given_arg_value == false
150
- raise ArgumentError, ":#{given_arg_name} must be of type Boolean"
151
- end
152
- else
153
- unless given_arg_value.instance_of?(given_arg_type)
154
- raise ArgumentError, ":#{given_arg_name} must be of type #{given_arg_type}"
155
- end
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.1.2
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: 2025-08-06 00:00:00.000000000 Z
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: '8.0'
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: '8.0'
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: '8.0'
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: '8.0'
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.6.0
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: 3.4.15
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: []