actionquery 0.0.0 → 1.0.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: 2782e3bfd4aacfe9003f0717357e1f9cd3a3302fee6f9d4cc4c8f69be14af1e1
4
- data.tar.gz: 1711a432737d6cabcc250cfe91cc8329f10f4813796928d72bbcd1f896c0f762
3
+ metadata.gz: 3bd844fec818667d207722f7397c1a8778cb11eb205fa046ee4b39e66c0d556f
4
+ data.tar.gz: 321043d8240fa89717cdb73c834832146b399e71e8fdbec8fd442030727946ed
5
5
  SHA512:
6
- metadata.gz: f89ca3b711cc35082d0b0cc2c1a857fb0a4af356e09f4addabf2193222e115f33adb3326bf1519e33974e336a568c71a9680014cb104a346334e3ccadcf21a09
7
- data.tar.gz: 3ab5a4ff51cd66a1d8c3a3ac4d24047150f924038c6065401746b3c94496910723eac9f9b2a2a4bd942f3581f243c371cdd0346b7fc45200bbb58664aa7f2391
6
+ metadata.gz: 61109f5a2d0aede2847a6858bd1b7d639705b592d8782f7c3e35553fcd0beba205d3c2b7b95cddcd316e8c83b8bbd324e4cf1b9382dfbdd2e025f15a6d9b781b
7
+ data.tar.gz: 0ce3a0122c7496a310424db8ad7dad75066d30cf3741b48ecd3ffc356f84e0c92280c30b281f8861d53c56a69e48adadd82366cc787937449bf77d431bed5246
data/.mise.toml ADDED
@@ -0,0 +1,2 @@
1
+ [tools]
2
+ ruby = "4.0.7"
data/.rubocop.yml CHANGED
@@ -1,13 +1,27 @@
1
+ require:
2
+ - standard
3
+
4
+ inherit_gem:
5
+ standard: config/base.yml
6
+
1
7
  AllCops:
2
- TargetRubyVersion: 2.6
8
+ TargetRubyVersion: 3.1
9
+ SuggestExtensions: false
10
+ NewCops: enable
11
+ Exclude:
12
+ - "gemfiles/**/*"
13
+ - "vendor/**/*"
3
14
 
4
- Style/StringLiterals:
5
- Enabled: true
6
- EnforcedStyle: double_quotes
15
+ Style/ClassAndModuleChildren:
16
+ EnforcedStyle: compact
7
17
 
8
- Style/StringLiteralsInInterpolation:
9
- Enabled: true
10
- EnforcedStyle: double_quotes
18
+ # Trailing commas make multiline diffs smaller when entries are added or reordered.
19
+ Style/TrailingCommaInArrayLiteral:
20
+ EnforcedStyleForMultiline: comma
21
+ Style/TrailingCommaInHashLiteral:
22
+ EnforcedStyleForMultiline: comma
23
+ Style/TrailingCommaInArguments:
24
+ EnforcedStyleForMultiline: comma
11
25
 
12
- Layout/LineLength:
13
- Max: 120
26
+ Layout/AccessModifierIndentation:
27
+ EnforcedStyle: outdent
data/.standard.yml ADDED
@@ -0,0 +1,10 @@
1
+ # Standard's built-in styles conflict with the project conventions below.
2
+ # RuboCop inherits Standard's full ruleset and enforces these overrides.
3
+ ruby_version: 3.1
4
+ ignore:
5
+ - "**/*":
6
+ - Layout/AccessModifierIndentation
7
+ - Layout/CommentIndentation
8
+ - Style/TrailingCommaInArguments
9
+ - Style/TrailingCommaInArrayLiteral
10
+ - Style/TrailingCommaInHashLiteral
data/Appraisals ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ %w[7.2 8.0 8.1].each do |rails_version|
4
+ appraise "rails-#{rails_version}" do
5
+ gem "rails", "~> #{rails_version}.0"
6
+ gem "rake", "~> 13.0"
7
+ gem "rspec", "~> 3.13"
8
+ gem "sqlite3", "~> 2.8"
9
+ end
10
+ end
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
+ # Changelog
2
+
1
3
  ## [Unreleased]
2
4
 
3
- ## [0.1.0] - 2022-07-13
5
+ ## [1.0.0] - 2026-09-21
6
+
7
+ ### Added
4
8
 
5
- - Initial release
9
+ - Relation-backed `ActionQuery::Base` query objects.
10
+ - Model inference, explicit model/relation sources, and relation wrapping.
11
+ - Conditional scopes, pagination, distinct-column values, and SQL unions.
12
+ - Type-aware `ActionQuery::SearchQuery` filters and custom filter/sort DSLs.
13
+ - Rails 7.2, 8.0, and 8.1 compatibility coverage.
data/Gemfile CHANGED
@@ -5,8 +5,9 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in actionquery.gemspec
6
6
  gemspec
7
7
 
8
+ gem "appraisal", "~> 2.5"
9
+ gem "rails", "~> 8.1.0"
8
10
  gem "rake", "~> 13.0"
9
-
10
- gem "rspec", "~> 3.0"
11
-
12
- gem "rubocop", "~> 1.21"
11
+ gem "rspec", "~> 3.13"
12
+ gem "sqlite3", "~> 2.8"
13
+ gem "standard", "~> 1.56", require: false
data/Gemfile.lock CHANGED
@@ -1,57 +1,281 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- actionquery (0.0.0)
4
+ actionquery (1.0.0)
5
+ activerecord (>= 7.2)
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
8
9
  specs:
9
- ast (2.4.2)
10
- diff-lcs (1.5.0)
11
- json (2.6.2)
12
- parallel (1.22.1)
13
- parser (3.1.2.0)
10
+ action_text-trix (2.1.19)
11
+ railties
12
+ actioncable (8.1.3.1)
13
+ actionpack (= 8.1.3.1)
14
+ activesupport (= 8.1.3.1)
15
+ nio4r (~> 2.0)
16
+ websocket-driver (>= 0.6.1)
17
+ zeitwerk (~> 2.6)
18
+ actionmailbox (8.1.3.1)
19
+ actionpack (= 8.1.3.1)
20
+ activejob (= 8.1.3.1)
21
+ activerecord (= 8.1.3.1)
22
+ activestorage (= 8.1.3.1)
23
+ activesupport (= 8.1.3.1)
24
+ mail (>= 2.8.0)
25
+ actionmailer (8.1.3.1)
26
+ actionpack (= 8.1.3.1)
27
+ actionview (= 8.1.3.1)
28
+ activejob (= 8.1.3.1)
29
+ activesupport (= 8.1.3.1)
30
+ mail (>= 2.8.0)
31
+ rails-dom-testing (~> 2.2)
32
+ actionpack (8.1.3.1)
33
+ actionview (= 8.1.3.1)
34
+ activesupport (= 8.1.3.1)
35
+ nokogiri (>= 1.8.5)
36
+ rack (>= 2.2.4)
37
+ rack-session (>= 1.0.1)
38
+ rack-test (>= 0.6.3)
39
+ rails-dom-testing (~> 2.2)
40
+ rails-html-sanitizer (~> 1.6)
41
+ useragent (~> 0.16)
42
+ actiontext (8.1.3.1)
43
+ action_text-trix (~> 2.1.15)
44
+ actionpack (= 8.1.3.1)
45
+ activerecord (= 8.1.3.1)
46
+ activestorage (= 8.1.3.1)
47
+ activesupport (= 8.1.3.1)
48
+ globalid (>= 0.6.0)
49
+ nokogiri (>= 1.8.5)
50
+ actionview (8.1.3.1)
51
+ activesupport (= 8.1.3.1)
52
+ builder (~> 3.1)
53
+ erubi (~> 1.11)
54
+ rails-dom-testing (~> 2.2)
55
+ rails-html-sanitizer (~> 1.6)
56
+ activejob (8.1.3.1)
57
+ activesupport (= 8.1.3.1)
58
+ globalid (>= 0.3.6)
59
+ activemodel (8.1.3.1)
60
+ activesupport (= 8.1.3.1)
61
+ activerecord (8.1.3.1)
62
+ activemodel (= 8.1.3.1)
63
+ activesupport (= 8.1.3.1)
64
+ timeout (>= 0.4.0)
65
+ activestorage (8.1.3.1)
66
+ actionpack (= 8.1.3.1)
67
+ activejob (= 8.1.3.1)
68
+ activerecord (= 8.1.3.1)
69
+ activesupport (= 8.1.3.1)
70
+ marcel (~> 1.0)
71
+ activesupport (8.1.3.1)
72
+ base64
73
+ bigdecimal
74
+ concurrent-ruby (~> 1.0, >= 1.3.1)
75
+ connection_pool (>= 2.2.5)
76
+ drb
77
+ i18n (>= 1.6, < 2)
78
+ json
79
+ logger (>= 1.4.2)
80
+ minitest (>= 5.1)
81
+ securerandom (>= 0.3)
82
+ tzinfo (~> 2.0, >= 2.0.5)
83
+ uri (>= 0.13.1)
84
+ appraisal (2.5.0)
85
+ bundler
86
+ rake
87
+ thor (>= 0.14.0)
88
+ ast (2.4.3)
89
+ base64 (0.3.0)
90
+ bigdecimal (4.1.3)
91
+ builder (3.3.0)
92
+ concurrent-ruby (1.3.8)
93
+ connection_pool (3.0.2)
94
+ crass (1.0.7)
95
+ date (3.5.1)
96
+ diff-lcs (1.6.2)
97
+ drb (2.2.3)
98
+ erb (6.0.7)
99
+ erubi (1.13.1)
100
+ globalid (1.4.0)
101
+ activesupport (>= 6.1)
102
+ i18n (1.15.2)
103
+ concurrent-ruby (~> 1.0)
104
+ io-console (0.9.4)
105
+ irb (1.18.0)
106
+ pp (>= 0.6.0)
107
+ prism (>= 1.3.0)
108
+ rdoc (>= 4.0.0)
109
+ reline (>= 0.4.2)
110
+ json (2.21.2)
111
+ language_server-protocol (3.17.0.6)
112
+ lint_roller (1.1.0)
113
+ logger (1.7.0)
114
+ loofah (2.25.2)
115
+ crass (~> 1.0.2)
116
+ nokogiri (>= 1.12.0)
117
+ mail (2.9.1)
118
+ logger
119
+ mini_mime (>= 0.1.1)
120
+ net-imap
121
+ net-pop
122
+ net-smtp
123
+ marcel (1.2.1)
124
+ mini_mime (1.1.5)
125
+ minitest (6.0.6)
126
+ drb (~> 2.0)
127
+ prism (~> 1.5)
128
+ net-imap (0.6.7)
129
+ date
130
+ net-protocol
131
+ net-pop (0.1.2)
132
+ net-protocol
133
+ net-protocol (0.4.0)
134
+ timeout
135
+ net-smtp (0.5.1)
136
+ net-protocol
137
+ nio4r (2.7.5)
138
+ nokogiri (1.19.4-arm64-darwin)
139
+ racc (~> 1.4)
140
+ nokogiri (1.19.4-x86_64-linux-gnu)
141
+ racc (~> 1.4)
142
+ parallel (2.2.0)
143
+ parser (3.3.12.0)
14
144
  ast (~> 2.4.1)
145
+ racc
146
+ pp (0.6.4)
147
+ prettyprint
148
+ prettyprint (0.2.0)
149
+ prism (1.9.0)
150
+ racc (1.8.1)
151
+ rack (3.2.7)
152
+ rack-session (2.1.2)
153
+ base64 (>= 0.1.0)
154
+ rack (>= 3.0.0)
155
+ rack-test (2.2.0)
156
+ rack (>= 1.3)
157
+ rackup (2.3.1)
158
+ rack (>= 3)
159
+ rails (8.1.3.1)
160
+ actioncable (= 8.1.3.1)
161
+ actionmailbox (= 8.1.3.1)
162
+ actionmailer (= 8.1.3.1)
163
+ actionpack (= 8.1.3.1)
164
+ actiontext (= 8.1.3.1)
165
+ actionview (= 8.1.3.1)
166
+ activejob (= 8.1.3.1)
167
+ activemodel (= 8.1.3.1)
168
+ activerecord (= 8.1.3.1)
169
+ activestorage (= 8.1.3.1)
170
+ activesupport (= 8.1.3.1)
171
+ bundler (>= 1.15.0)
172
+ railties (= 8.1.3.1)
173
+ rails-dom-testing (2.3.0)
174
+ activesupport (>= 5.0.0)
175
+ minitest
176
+ nokogiri (>= 1.6)
177
+ rails-html-sanitizer (1.7.1)
178
+ loofah (~> 2.25, >= 2.25.2)
179
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
180
+ railties (8.1.3.1)
181
+ actionpack (= 8.1.3.1)
182
+ activesupport (= 8.1.3.1)
183
+ irb (~> 1.13)
184
+ rackup (>= 1.0.0)
185
+ rake (>= 12.2)
186
+ thor (~> 1.0, >= 1.2.2)
187
+ tsort (>= 0.2)
188
+ zeitwerk (~> 2.6)
15
189
  rainbow (3.1.1)
16
- rake (13.0.6)
17
- regexp_parser (2.5.0)
18
- rexml (3.2.5)
19
- rspec (3.11.0)
20
- rspec-core (~> 3.11.0)
21
- rspec-expectations (~> 3.11.0)
22
- rspec-mocks (~> 3.11.0)
23
- rspec-core (3.11.0)
24
- rspec-support (~> 3.11.0)
25
- rspec-expectations (3.11.0)
190
+ rake (13.4.2)
191
+ rbs (4.2.0)
192
+ logger
193
+ prism (>= 1.6.0)
194
+ tsort
195
+ rdoc (8.0.0)
196
+ erb
197
+ prism (>= 1.6.0)
198
+ rbs (>= 4.0.0)
199
+ tsort
200
+ regexp_parser (2.12.0)
201
+ reline (0.7.0)
202
+ io-console (~> 0.5)
203
+ rspec (3.13.2)
204
+ rspec-core (~> 3.13.0)
205
+ rspec-expectations (~> 3.13.0)
206
+ rspec-mocks (~> 3.13.0)
207
+ rspec-core (3.13.6)
208
+ rspec-support (~> 3.13.0)
209
+ rspec-expectations (3.13.5)
26
210
  diff-lcs (>= 1.2.0, < 2.0)
27
- rspec-support (~> 3.11.0)
28
- rspec-mocks (3.11.1)
211
+ rspec-support (~> 3.13.0)
212
+ rspec-mocks (3.13.8)
29
213
  diff-lcs (>= 1.2.0, < 2.0)
30
- rspec-support (~> 3.11.0)
31
- rspec-support (3.11.0)
32
- rubocop (1.31.2)
214
+ rspec-support (~> 3.13.0)
215
+ rspec-support (3.13.7)
216
+ rubocop (1.88.2)
33
217
  json (~> 2.3)
34
- parallel (~> 1.10)
35
- parser (>= 3.1.0.0)
218
+ language_server-protocol (~> 3.17.0.2)
219
+ lint_roller (~> 1.1.0)
220
+ parallel (>= 1.10)
221
+ parser (>= 3.3.0.2)
36
222
  rainbow (>= 2.2.2, < 4.0)
37
- regexp_parser (>= 1.8, < 3.0)
38
- rexml (>= 3.2.5, < 4.0)
39
- rubocop-ast (>= 1.18.0, < 2.0)
223
+ regexp_parser (>= 2.9.3, < 3.0)
224
+ rubocop-ast (>= 1.49.0, < 2.0)
40
225
  ruby-progressbar (~> 1.7)
41
- unicode-display_width (>= 1.4.0, < 3.0)
42
- rubocop-ast (1.19.1)
43
- parser (>= 3.1.1.0)
44
- ruby-progressbar (1.11.0)
45
- unicode-display_width (2.2.0)
226
+ unicode-display_width (>= 2.4.0, < 4.0)
227
+ rubocop-ast (1.50.0)
228
+ parser (>= 3.3.7.2)
229
+ prism (~> 1.7)
230
+ rubocop-performance (1.26.1)
231
+ lint_roller (~> 1.1)
232
+ rubocop (>= 1.75.0, < 2.0)
233
+ rubocop-ast (>= 1.47.1, < 2.0)
234
+ ruby-progressbar (1.13.0)
235
+ securerandom (0.4.1)
236
+ sqlite3 (2.9.6-arm64-darwin)
237
+ sqlite3 (2.9.6-x86_64-linux-gnu)
238
+ standard (1.56.0)
239
+ language_server-protocol (~> 3.17.0.2)
240
+ lint_roller (~> 1.0)
241
+ rubocop (~> 1.88.0)
242
+ standard-custom (~> 1.0.0)
243
+ standard-performance (~> 1.8)
244
+ standard-custom (1.0.2)
245
+ lint_roller (~> 1.0)
246
+ rubocop (~> 1.50)
247
+ standard-performance (1.9.0)
248
+ lint_roller (~> 1.1)
249
+ rubocop-performance (~> 1.26.0)
250
+ thor (1.5.0)
251
+ timeout (0.6.1)
252
+ tsort (0.2.0)
253
+ tzinfo (2.0.6)
254
+ concurrent-ruby (~> 1.0)
255
+ unicode-display_width (3.3.0)
256
+ unicode-emoji (~> 4.3)
257
+ unicode-emoji (4.3.0)
258
+ uri (1.1.1)
259
+ useragent (0.16.11)
260
+ websocket-driver (0.8.2)
261
+ base64
262
+ websocket-extensions (>= 0.1.0)
263
+ websocket-extensions (0.1.5)
264
+ zeitwerk (2.8.3)
46
265
 
47
266
  PLATFORMS
48
267
  arm64-darwin-21
268
+ arm64-darwin-23
269
+ x86_64-linux
49
270
 
50
271
  DEPENDENCIES
51
272
  actionquery!
273
+ appraisal (~> 2.5)
274
+ rails (~> 8.1.0)
52
275
  rake (~> 13.0)
53
- rspec (~> 3.0)
54
- rubocop (~> 1.21)
276
+ rspec (~> 3.13)
277
+ sqlite3 (~> 2.8)
278
+ standard (~> 1.56)
55
279
 
56
280
  BUNDLED WITH
57
- 2.3.17
281
+ 2.6.9
data/README.md CHANGED
@@ -1,37 +1,256 @@
1
- # Actionquery
1
+ # ActionQuery
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/actionquery`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ [![Gem Version](https://badge.fury.io/rb/actionquery.svg)](https://badge.fury.io/rb/actionquery)
4
+ [![CI](https://github.com/ElMassimo/actionquery/actions/workflows/main.yml/badge.svg)](https://github.com/ElMassimo/actionquery/actions/workflows/main.yml)
5
+ [![Ruby Style](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/standardrb/standard)
6
+ [![Ruby](https://img.shields.io/badge/Ruby-%3E%3D%203.1-CC342D.svg)](https://www.ruby-lang.org/)
7
+ [![Rails](https://img.shields.io/badge/Rails-%3E%3D%207.2-D30001.svg)](https://rubyonrails.org/)
8
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE.txt)
4
9
 
5
- TODO: Delete this and the text above, and describe your gem
10
+ ActionQuery lets you define query objects with ordinary Ruby methods while retaining the full behavior of `ActiveRecord::Relation`.
11
+
12
+ It follows the spirit of [Queryable](https://github.com/ElMassimo/queryable), but is designed specifically for modern Active Record. Instead of wrapping a relation and maintaining lists of delegated and chainable methods, an ActionQuery **is** a relation.
13
+
14
+ ## Query methods
15
+
16
+ Define reusable query behavior as instance methods:
17
+
18
+ ```ruby
19
+ class CustomersQuery < ActionQuery::Base
20
+ def active
21
+ where(status: "active")
22
+ end
23
+
24
+ def recently_logged_in
25
+ where(logged_in_at: 1.week.ago..)
26
+ end
27
+
28
+ def favourite_brand(product, brand)
29
+ where(favourite_product: product, favourite_brand: brand)
30
+ end
31
+ end
32
+
33
+ CustomersQuery.new
34
+ .active
35
+ .recently_logged_in
36
+ .favourite_brand(:beer, "Miller")
37
+ ```
38
+
39
+ Relation methods such as `where`, `joins`, `includes`, `order`, `limit`, calculations, and record loading work normally. Chained relations retain the query class and its methods—there is no `scope`, `chain`, or `delegate_and_chain` declaration.
40
+
41
+ ## Model scopes and delegation
42
+
43
+ Model scopes and public model query methods are available from a query object. They execute inside the current relation scope:
44
+
45
+ ```ruby
46
+ class Customer < ApplicationRecord
47
+ scope :verified, -> { where(verified: true) }
48
+ end
49
+
50
+ CustomersQuery.new
51
+ .where(shop: current_shop)
52
+ .verified
53
+ .recently_logged_in
54
+ ```
55
+
56
+ ActionQuery also delegates the model metadata, query-cache, logging, and connection methods required by Active Record internals, including `adapter_class`, `connection_pool`, `primary_key`, `lease_connection`, and `with_connection`.
57
+
58
+ ## Advantages
59
+
60
+ - Query objects use plain Ruby methods, inheritance, and modules.
61
+ - Queries compose with model scopes and standard Active Record relations.
62
+ - Chaining does not depend on maintaining a list of Relation methods.
63
+ - Query behavior stays out of models without introducing a repository abstraction.
64
+ - Search filters and sorting strategies can be inherited and extended safely.
65
+ - Compatibility is tested against every supported Rails minor line.
6
66
 
7
67
  ## Installation
8
68
 
9
- Install the gem and add to the application's Gemfile by executing:
69
+ Add ActionQuery to your bundle:
70
+
71
+ ```ruby
72
+ gem "actionquery"
73
+ ```
74
+
75
+ Then run:
76
+
77
+ ```sh
78
+ bundle install
79
+ ```
80
+
81
+ ActionQuery requires Ruby 3.1 or newer and Active Record 7.2 or newer. It depends on `activerecord`, not the full `rails` gem.
82
+
83
+ ## Basic usage
84
+
85
+ ### Model inference
86
+
87
+ ActionQuery removes `Query` or `SearchQuery`, singularizes the remainder, and resolves the model:
88
+
89
+ ```ruby
90
+ class CustomersQuery < ActionQuery::Base
91
+ end
92
+
93
+ CustomersQuery.new.model # => Customer
94
+ ```
95
+
96
+ Namespaced models are preferred, with a top-level fallback:
97
+
98
+ ```ruby
99
+ class Admin::CustomersQuery < ActionQuery::Base
100
+ end
101
+
102
+ Admin::CustomersQuery.new.model
103
+ # => Admin::Customer when defined, otherwise Customer
104
+ ```
105
+
106
+ ### Explicit query sources
107
+
108
+ Use `query_from` when naming inference is not appropriate. It accepts a model or an existing relation:
109
+
110
+ ```ruby
111
+ class RecentCustomersQuery < ActionQuery::Base
112
+ query_from Customer.where(created_at: 30.days.ago..).order(created_at: :desc)
113
+ end
114
+ ```
115
+
116
+ A relation source retains its clauses while keeping `Customer` as the Active Record model. The source relation is not mutated.
117
+
118
+ ### Wrapping a relation
10
119
 
11
- $ bundle add actionquery
120
+ Use `.wrap` to convert an existing relation into a particular query class:
12
121
 
13
- If bundler is not being used to manage dependencies, install the gem by executing:
122
+ ```ruby
123
+ RecentCustomersQuery.wrap(shop.customers.where(status: "active"))
124
+ ```
14
125
 
15
- $ gem install actionquery
126
+ ## Search queries
16
127
 
17
- ## Usage
128
+ `ActionQuery::SearchQuery` adds conventions for common search forms:
18
129
 
19
- TODO: Write usage instructions here
130
+ ```ruby
131
+ class CustomersSearchQuery < ActionQuery::SearchQuery
132
+ end
133
+
134
+ CustomersSearchQuery.new.search(
135
+ name: "miller",
136
+ active: true,
137
+ status: :invited
138
+ )
139
+ ```
140
+
141
+ Default filtering is based on the column type:
142
+
143
+ | Column type | Behavior |
144
+ | --- | --- |
145
+ | string, text | Escaped, case-insensitive partial match |
146
+ | integer, decimal, boolean, date | Exact Active Record condition |
147
+ | enum | Exact enum match |
148
+ | datetime + `Date` | Entire calendar day |
149
+ | datetime + `Time` or `DateTime` | Exact value |
150
+
151
+ Nil conditions and `sort` metadata are ignored. Unsupported or unknown fields raise `NotImplementedError` instead of silently producing an unintended query.
152
+
153
+ ### Custom filters
154
+
155
+ Define a filter with a callable executed in the query context:
156
+
157
+ ```ruby
158
+ class CustomersSearchQuery < ActionQuery::SearchQuery
159
+ filter :minimum_age, ->(age) { where(age: age..) }
160
+ filter :email, :eq
161
+ filter :older_than, :gt
162
+ end
163
+ ```
164
+
165
+ `:eq` uses Active Record hash conditions, so it supports a scalar or an array. Other symbols must name an `Arel::Attributes::Attribute` operator.
166
+
167
+ Use `matches_any_of` for escaped partial matching across columns:
168
+
169
+ ```ruby
170
+ class CustomersSearchQuery < ActionQuery::SearchQuery
171
+ filter :general, ->(value) {
172
+ matches_any_of(%i[first_name last_name email], value)
173
+ }
174
+ end
175
+ ```
176
+
177
+ Filter definitions are inherited. Adding a filter to a child query does not modify its parent or sibling classes.
178
+
179
+ ### Sorting
180
+
181
+ `apply_sorting` accepts any object responding to `strategy` and `direction`:
182
+
183
+ ```ruby
184
+ SortOption = Struct.new(:strategy, :direction, keyword_init: true)
185
+ option = SortOption.new(strategy: :created_at, direction: :desc)
186
+
187
+ CustomersSearchQuery.new.apply_sorting(option)
188
+ ```
189
+
190
+ Unconfigured strategies use Active Record's hash-based `order`. Define a custom strategy for computed or joined values:
191
+
192
+ ```ruby
193
+ class CustomersSearchQuery < ActionQuery::SearchQuery
194
+ sort_by :seniority, ->(direction) { order(hired_at: direction) }
195
+ end
196
+ ```
197
+
198
+ Active Record validates default sort directions. Custom strategies are responsible for safely handling any SQL fragments they introduce.
199
+
200
+ ## Query helpers
201
+
202
+ `ActionQuery::Base` includes a small set of composable helpers:
203
+
204
+ ```ruby
205
+ query.any_of(Customer.arel_table[:role].eq("admin"), Customer.arel_table[:owner].eq(true))
206
+ query.scope_if(include_archived) { |relation| relation.unscope(where: :archived_at) }
207
+ query.paginate(page_number: 2, page_size: 25)
208
+ query.unsorted
209
+ query.union(Customer.where(invited: true))
210
+ ```
211
+
212
+ `distinct` keeps Active Record's normal behavior unless given a column symbol:
213
+
214
+ ```ruby
215
+ query.distinct # a SELECT DISTINCT relation
216
+ query.distinct(false) # a relation without SELECT DISTINCT
217
+ query.distinct(:country_code) # an eager array of unique values
218
+ ```
219
+
220
+ Search queries also provide `with_ids(ids)` and `all_attributes` for primary-key filtering and resetting a previous `select`.
221
+
222
+ ## Compatibility notes
223
+
224
+ Subclassing `ActiveRecord::Relation` provides native composition but intentionally couples ActionQuery to Relation initialization and delegation behavior. The suite exercises real database queries on Rails 7.2, 8.0, and 8.1, including connection methods and query-cache semantics.
225
+
226
+ See [ADR-001](docs/decisions/001-subclass-active-record-relation.md) for the rationale and alternatives considered.
20
227
 
21
228
  ## Development
22
229
 
23
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
230
+ The repository uses [mise](https://mise.jdx.dev/) for Ruby and Bundler for dependencies. Local development targets Ruby 4 and Rails 8.1:
24
231
 
25
- 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`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
232
+ ```sh
233
+ mise install
234
+ bin/setup
235
+ bundle exec rake
236
+ bundle exec standardrb
237
+ bundle exec rubocop
238
+ ```
26
239
 
27
- ## Contributing
240
+ The RuboCop configuration inherits Standard Ruby and adds trailing commas for multiline constructs plus outdented access modifiers.
28
241
 
29
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/actionquery. 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/[USERNAME]/actionquery/blob/main/CODE_OF_CONDUCT.md).
242
+ Run an individual Rails appraisal with:
30
243
 
31
- ## License
244
+ ```sh
245
+ bundle exec appraisal rails-7.2 rspec
246
+ bundle exec appraisal rails-8.0 rspec
247
+ bundle exec appraisal rails-8.1 rspec
248
+ ```
249
+
250
+ ## Contributing
32
251
 
33
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
252
+ Bug reports and pull requests are welcome at https://github.com/ElMassimo/actionquery. Behavioral changes should include regression tests against real Active Record relations.
34
253
 
35
- ## Code of Conduct
254
+ ## License
36
255
 
37
- Everyone interacting in the Actionquery project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/actionquery/blob/main/CODE_OF_CONDUCT.md).
256
+ ActionQuery is available under the [MIT License](LICENSE.txt).
data/Rakefile CHANGED
@@ -9,4 +9,9 @@ require "rubocop/rake_task"
9
9
 
10
10
  RuboCop::RakeTask.new
11
11
 
12
- task default: %i[spec rubocop]
12
+ desc "Run Standard Ruby"
13
+ task :standard do
14
+ sh "bundle exec standardrb"
15
+ end
16
+
17
+ task default: %i[spec standard rubocop]
data/actionquery.gemspec CHANGED
@@ -4,16 +4,17 @@ require_relative "lib/actionquery/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "actionquery"
7
- spec.version = Actionquery::VERSION
7
+ spec.version = ActionQuery::VERSION
8
8
  spec.authors = ["Maximo Mussini"]
9
9
  spec.email = ["maximomussini@gmail.com"]
10
10
 
11
11
  spec.summary = "Keep your scopes and queries flexible by using Ruby"
12
- spec.description = "ActionQuery is a module that encapsulates query building so you don\'t have to tuck scopes inside your models."
12
+ spec.description = "ActionQuery encapsulates query building in relation-backed objects instead of model scopes."
13
13
  spec.homepage = "https://github.com/ElMassimo/actionquery"
14
14
  spec.license = "MIT"
15
- spec.required_ruby_version = ">= 2.6.0"
15
+ spec.required_ruby_version = ">= 3.1.0"
16
16
 
17
+ spec.metadata["rubygems_mfa_required"] = "true"
17
18
  spec.metadata["homepage_uri"] = spec.homepage
18
19
  spec.metadata["source_code_uri"] = "https://github.com/ElMassimo/actionquery/tree/main"
19
20
  spec.metadata["changelog_uri"] = "https://github.com/ElMassimo/actionquery/blob/main/CHANGELOG.md"
@@ -29,9 +30,5 @@ Gem::Specification.new do |spec|
29
30
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
31
  spec.require_paths = ["lib"]
31
32
 
32
- # Uncomment to register a new dependency of your gem
33
- # spec.add_dependency "example-gem", "~> 1.0"
34
-
35
- # For more information and examples about making a new gem, check out our
36
- # guide at: https://bundler.io/guides/creating_gem.html
33
+ spec.add_dependency "activerecord", ">= 7.2"
37
34
  end
@@ -0,0 +1,41 @@
1
+ # ADR-001: Subclass ActiveRecord::Relation
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Date
8
+
9
+ 2026-09-21
10
+
11
+ ## Context
12
+
13
+ Queryable-style query objects usually wrap a relation and delegate selected methods. That requires maintaining lists of chainable and terminal methods, and a wrapper can stop behaving like a native relation when Active Record adds APIs.
14
+
15
+ ActionQuery is intended specifically for Active Record. Its query objects need to compose with scopes, association queries, calculations, connection management, and third-party instrumentation as relations rather than relation-like wrappers.
16
+
17
+ ## Decision
18
+
19
+ `ActionQuery::Base` subclasses `ActiveRecord::Relation`. Query classes add behavior with ordinary instance methods, and Relation cloning preserves the query subclass through normal chains.
20
+
21
+ A configured relation source is decomposed into its model, table, predicate builder, and relation values. The relation itself is never installed as Active Record's model.
22
+
23
+ ActionQuery explicitly delegates the model and connection methods used by supported Relation versions. A real-database test suite runs against the minimum Rails version and each currently supported minor line.
24
+
25
+ ## Alternatives considered
26
+
27
+ ### Wrap and delegate an internal relation
28
+
29
+ This is the approach used by Queryable. It has a smaller dependency on Relation initialization internals, but it requires classifying Active Record methods as chainable or terminal and can return wrapper-breaking values when that classification becomes stale.
30
+
31
+ ### Extend each relation with a module
32
+
33
+ `relation.extending(Module)` avoids subclass construction but makes named query inheritance, model inference, and class-level filter configuration less direct. It also changes the API from constructing a query object to decorating every source relation.
34
+
35
+ ## Consequences
36
+
37
+ - Query objects pass `is_a?(ActiveRecord::Relation)` and work with native relation APIs.
38
+ - Relation methods naturally retain custom query methods after chaining.
39
+ - ActionQuery is intentionally coupled to supported Active Record Relation internals.
40
+ - New Rails minor versions must be added to CI before being claimed as supported.
41
+ - Connection, query-cache, scope delegation, and relation-source initialization need dedicated regression tests.
@@ -0,0 +1,12 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal", "~> 2.5"
6
+ gem "rails", "~> 7.2.0"
7
+ gem "rake", "~> 13.0"
8
+ gem "rspec", "~> 3.13"
9
+ gem "sqlite3", "~> 2.8"
10
+ gem "standard", "~> 1.56", require: false
11
+
12
+ gemspec path: "../"
@@ -0,0 +1,12 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal", "~> 2.5"
6
+ gem "rails", "~> 8.0.0"
7
+ gem "rake", "~> 13.0"
8
+ gem "rspec", "~> 3.13"
9
+ gem "sqlite3", "~> 2.8"
10
+ gem "standard", "~> 1.56", require: false
11
+
12
+ gemspec path: "../"
@@ -0,0 +1,12 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal", "~> 2.5"
6
+ gem "rails", "~> 8.1.0"
7
+ gem "rake", "~> 13.0"
8
+ gem "rspec", "~> 3.13"
9
+ gem "sqlite3", "~> 2.8"
10
+ gem "standard", "~> 1.56", require: false
11
+
12
+ gemspec path: "../"
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ # An Active Record relation that can be extended with ordinary instance methods.
4
+ class ActionQuery::Base < ActiveRecord::Relation
5
+ class << self
6
+ # Sets the model or relation used when constructing this query.
7
+ def query_from(source)
8
+ @query_source = source
9
+ end
10
+
11
+ # Wraps an existing relation in this query class.
12
+ def wrap(relation)
13
+ new.merge(relation)
14
+ end
15
+
16
+ # Returns the Active Record model queried by this class.
17
+ def model_class
18
+ source = query_source
19
+ source.is_a?(ActiveRecord::Relation) ? source.model : source
20
+ end
21
+
22
+ private
23
+
24
+ def query_source
25
+ return @query_source if instance_variable_defined?(:@query_source)
26
+
27
+ @query_source = inherited_query_source || infer_model_class
28
+ rescue NameError
29
+ message = "A model could not be inferred for #{name || self}. Follow the naming convention " \
30
+ "or specify one with `query_from`."
31
+ raise NotImplementedError, message
32
+ end
33
+
34
+ def inherited_query_source
35
+ return unless superclass.respond_to?(:query_source, true)
36
+
37
+ superclass.send(:query_source)
38
+ end
39
+
40
+ def infer_model_class
41
+ model_name = name.sub(/(Search)?Query\z/, "").singularize
42
+ model_name.safe_constantize || model_name.demodulize.constantize
43
+ end
44
+ end
45
+
46
+ query_from false
47
+
48
+ delegate :name, :model_class, to: :class
49
+ delegate :attribute_types, :columns_hash, :logger, :sanitize_sql_like, :uncached, to: :model_class
50
+ delegate :adapter_class, :connection, :connection_pool, :lease_connection, :primary_key, :with_connection,
51
+ to: :model_class
52
+
53
+ def initialize
54
+ source = self.class.send(:query_source)
55
+ raise NotImplementedError, abstract_query_message unless source
56
+
57
+ model, options = initialization_attributes(source)
58
+ super(model, **options)
59
+ end
60
+
61
+ # Applies an OR expression composed from the supplied Arel conditions.
62
+ def any_of(*conditions)
63
+ return self if conditions.empty?
64
+
65
+ first_condition, *remaining_conditions = conditions
66
+ where(remaining_conditions.reduce(first_condition, &:or))
67
+ end
68
+
69
+ def paginate(page_number:, page_size:)
70
+ return self unless page_size
71
+
72
+ offset((page_number - 1) * page_size).limit(page_size)
73
+ end
74
+
75
+ def scope_if(condition)
76
+ condition ? yield(self) : self
77
+ end
78
+
79
+ def unsorted
80
+ except(:order)
81
+ end
82
+
83
+ # With a column, returns its unique values. Otherwise retains Active Record behavior.
84
+ # The boolean default intentionally matches ActiveRecord::Relation#distinct.
85
+ # rubocop:disable Style/OptionalBooleanParameter
86
+ def distinct(column = true)
87
+ return super unless column.is_a?(Symbol)
88
+
89
+ super().select(column).pluck(column).flatten.uniq
90
+ end
91
+ # rubocop:enable Style/OptionalBooleanParameter
92
+
93
+ def union(relation)
94
+ unscope(:where).from(arel.union(relation.arel).as(table.name))
95
+ end
96
+
97
+ protected
98
+
99
+ def column_type(column_name)
100
+ name = column_name.to_s
101
+ return :enum if model_class.respond_to?(:defined_enums) && model_class.defined_enums.key?(name)
102
+
103
+ attribute_types[name]&.type
104
+ end
105
+
106
+ # rubocop:disable Naming/PredicatePrefix
107
+ def has_column?(column_name)
108
+ columns_hash.key?(column_name.to_s)
109
+ end
110
+ # rubocop:enable Naming/PredicatePrefix
111
+
112
+ private
113
+
114
+ def respond_to_missing?(method_name, include_private = false)
115
+ model_class.respond_to?(method_name, include_private) || super
116
+ end
117
+
118
+ def method_missing(method_name, ...)
119
+ return super unless model_class.respond_to?(method_name)
120
+
121
+ scoping { model_class.public_send(method_name, ...) }
122
+ end
123
+
124
+ def abstract_query_message
125
+ "#{self.class.name || self.class} is an abstract query, create a subclass instead."
126
+ end
127
+
128
+ def initialization_attributes(source)
129
+ return [source, {}] unless source.is_a?(ActiveRecord::Relation)
130
+
131
+ options = {
132
+ table: source.table,
133
+ predicate_builder: source.predicate_builder,
134
+ values: source.values.dup,
135
+ }
136
+ [source.model, options]
137
+ end
138
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ActionQuery::Error < StandardError; end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A query with conventions for filtering and sorting search results.
4
+ class ActionQuery::SearchQuery < ActionQuery::Base
5
+ class << self
6
+ def filter(field, processor)
7
+ field = field.to_sym
8
+ search_filters[field] = build_filter_processor(field, processor)
9
+ end
10
+
11
+ def sort_by(field, processor)
12
+ sort_options[field.to_sym] = processor
13
+ end
14
+
15
+ def search_filters
16
+ @search_filters ||= inherited_configuration(:search_filters)
17
+ end
18
+
19
+ def sort_options
20
+ @sort_options ||= inherited_configuration(:sort_options)
21
+ end
22
+
23
+ private
24
+
25
+ def build_filter_processor(field, processor)
26
+ return processor unless processor.is_a?(Symbol)
27
+
28
+ unless Arel::Attributes::Attribute.method_defined?(processor)
29
+ raise ArgumentError, "Unknown operator for an Arel attribute: #{processor}"
30
+ end
31
+
32
+ return ->(value) { where(field => value) } if processor == :eq
33
+
34
+ ->(value) { where(table[field].public_send(processor, value)) }
35
+ end
36
+
37
+ def inherited_configuration(name)
38
+ return {} unless superclass.respond_to?(name)
39
+
40
+ superclass.public_send(name).dup
41
+ end
42
+ end
43
+
44
+ query_from false
45
+
46
+ def search(**conditions)
47
+ apply_search_filters(conditions)
48
+ end
49
+
50
+ def apply_search_filters(conditions)
51
+ conditions.reduce(self) do |query, (field, value)|
52
+ query.send(:apply_search_filter, field, value)
53
+ end
54
+ end
55
+
56
+ def apply_sorting(sort_option)
57
+ return self unless sort_option
58
+
59
+ field = sort_option.strategy
60
+ direction = sort_option.direction
61
+ processor = self.class.sort_options[field.to_sym]
62
+
63
+ processor ? instance_exec(direction, &processor) : order(field => direction)
64
+ end
65
+
66
+ def matches_any_of(fields, value)
67
+ any_of(*fields.map { |field| matches(field, value) })
68
+ end
69
+
70
+ def with_ids(ids)
71
+ where(primary_key => ids)
72
+ end
73
+
74
+ def all_attributes
75
+ reselect(table[Arel.star])
76
+ end
77
+
78
+ protected
79
+
80
+ def matches(field, value)
81
+ attribute = field.respond_to?(:matches) ? field : table[field]
82
+ attribute.matches("%#{sanitize_sql_like(value.to_s)}%", "\\")
83
+ end
84
+
85
+ def apply_search_filter(field, value)
86
+ return self if field.to_s == "sort" || value.nil?
87
+
88
+ processor = self.class.search_filters[field.to_sym]
89
+ return instance_exec(value, &processor) if processor
90
+
91
+ apply_automatic_filter(field, value)
92
+ end
93
+
94
+ private
95
+
96
+ def apply_automatic_filter(field, value)
97
+ case column_type(field)
98
+ when :string, :text
99
+ where(matches(field, value))
100
+ when :datetime
101
+ where(field => datetime_filter_value(value))
102
+ when :boolean, :date, :decimal, :integer, :enum
103
+ where(field => value)
104
+ else
105
+ raise_unsupported_filter(field)
106
+ end
107
+ end
108
+
109
+ def datetime_filter_value(value)
110
+ return value unless value.is_a?(Date) && !value.is_a?(DateTime)
111
+
112
+ value.all_day
113
+ end
114
+
115
+ def raise_unsupported_filter(field)
116
+ message = "Please implement `search` in #{self.class} to handle conditions on #{field.inspect}, " \
117
+ "or define a handler using `filter :#{field}`."
118
+ raise NotImplementedError, message
119
+ end
120
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Actionquery
4
- VERSION = "0.0.0"
3
+ # Relation-backed query objects for Active Record.
4
+ module ActionQuery
5
5
  end
6
+
7
+ ActionQuery::VERSION = "1.0.0"
data/lib/actionquery.rb CHANGED
@@ -1,8 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "actionquery/version"
3
+ require "active_record"
4
+ require "active_support/core_ext/string/inflections"
4
5
 
5
- module Actionquery
6
- class Error < StandardError; end
7
- # Your code goes here...
6
+ # Relation-backed query objects for Active Record.
7
+ module ActionQuery
8
8
  end
9
+
10
+ require_relative "actionquery/error"
11
+ require_relative "actionquery/version"
12
+ require_relative "actionquery/base"
13
+ require_relative "actionquery/search_query"
data/sig/actionquery.rbs CHANGED
@@ -1,4 +1,35 @@
1
- module Actionquery
1
+ module ActionQuery
2
2
  VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
3
+
4
+ class Error < StandardError
5
+ end
6
+
7
+ class Base < ActiveRecord::Relation
8
+ def self.query_from: (untyped source) -> untyped
9
+ def self.wrap: (ActiveRecord::Relation relation) -> Base
10
+ def self.model_class: () -> singleton(ActiveRecord::Base)
11
+
12
+ def initialize: () -> void
13
+ def model_class: () -> singleton(ActiveRecord::Base)
14
+ def any_of: (*untyped conditions) -> Base
15
+ def paginate: (page_number: Integer, page_size: Integer?) -> Base
16
+ def scope_if: [T] (untyped condition) { (Base) -> T } -> (T | Base)
17
+ def unsorted: () -> Base
18
+ def distinct: (?untyped column) -> untyped
19
+ def union: (ActiveRecord::Relation relation) -> Base
20
+ end
21
+
22
+ class SearchQuery < Base
23
+ def self.filter: (String | Symbol field, untyped processor) -> untyped
24
+ def self.sort_by: (String | Symbol field, untyped processor) -> untyped
25
+ def self.search_filters: () -> Hash[Symbol, untyped]
26
+ def self.sort_options: () -> Hash[Symbol, untyped]
27
+
28
+ def search: (**untyped conditions) -> SearchQuery
29
+ def apply_search_filters: (Hash[untyped, untyped] conditions) -> SearchQuery
30
+ def apply_sorting: (untyped sort_option) -> SearchQuery
31
+ def matches_any_of: (Array[untyped] fields, untyped value) -> SearchQuery
32
+ def with_ids: (untyped ids) -> SearchQuery
33
+ def all_attributes: () -> SearchQuery
34
+ end
4
35
  end
metadata CHANGED
@@ -1,25 +1,41 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actionquery
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maximo Mussini
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2022-07-14 00:00:00.000000000 Z
12
- dependencies: []
13
- description: ActionQuery is a module that encapsulates query building so you don't
14
- have to tuck scopes inside your models.
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activerecord
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.2'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.2'
26
+ description: ActionQuery encapsulates query building in relation-backed objects instead
27
+ of model scopes.
15
28
  email:
16
29
  - maximomussini@gmail.com
17
30
  executables: []
18
31
  extensions: []
19
32
  extra_rdoc_files: []
20
33
  files:
34
+ - ".mise.toml"
21
35
  - ".rspec"
22
36
  - ".rubocop.yml"
37
+ - ".standard.yml"
38
+ - Appraisals
23
39
  - CHANGELOG.md
24
40
  - CODE_OF_CONDUCT.md
25
41
  - Gemfile
@@ -28,17 +44,24 @@ files:
28
44
  - README.md
29
45
  - Rakefile
30
46
  - actionquery.gemspec
47
+ - docs/decisions/001-subclass-active-record-relation.md
48
+ - gemfiles/rails_7.2.gemfile
49
+ - gemfiles/rails_8.0.gemfile
50
+ - gemfiles/rails_8.1.gemfile
31
51
  - lib/actionquery.rb
52
+ - lib/actionquery/base.rb
53
+ - lib/actionquery/error.rb
54
+ - lib/actionquery/search_query.rb
32
55
  - lib/actionquery/version.rb
33
56
  - sig/actionquery.rbs
34
57
  homepage: https://github.com/ElMassimo/actionquery
35
58
  licenses:
36
59
  - MIT
37
60
  metadata:
61
+ rubygems_mfa_required: 'true'
38
62
  homepage_uri: https://github.com/ElMassimo/actionquery
39
63
  source_code_uri: https://github.com/ElMassimo/actionquery/tree/main
40
64
  changelog_uri: https://github.com/ElMassimo/actionquery/blob/main/CHANGELOG.md
41
- post_install_message:
42
65
  rdoc_options: []
43
66
  require_paths:
44
67
  - lib
@@ -46,15 +69,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
46
69
  requirements:
47
70
  - - ">="
48
71
  - !ruby/object:Gem::Version
49
- version: 2.6.0
72
+ version: 3.1.0
50
73
  required_rubygems_version: !ruby/object:Gem::Requirement
51
74
  requirements:
52
75
  - - ">="
53
76
  - !ruby/object:Gem::Version
54
77
  version: '0'
55
78
  requirements: []
56
- rubygems_version: 3.2.32
57
- signing_key:
79
+ rubygems_version: 4.0.20
58
80
  specification_version: 4
59
81
  summary: Keep your scopes and queries flexible by using Ruby
60
82
  test_files: []