active-query 0.1.1 → 0.1.2

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: cc0875fc4b5ba6101ea1b0198145966d5dde1b46a366e4ec007938add33977fa
4
- data.tar.gz: 2dfd5ebdf7a88d312f3485fd5a8e0c464763f52d7d41c61624c3e5293fcc0228
3
+ metadata.gz: 597183debb31f2b4490361200975eccbeeec9ef03e5d844abfe193e4114284d2
4
+ data.tar.gz: c82fd9b968a2dffcdd83914ea08bdc7b332453322008e41436d3f7ff7221e53e
5
5
  SHA512:
6
- metadata.gz: cb35c0ba08e4ebf370796648920edcd6aeadfc14f50aa3ea4c9d8ec605f53085a09bea9913eedc7bf59f2de057d9e97a50aed5ff4e61cdfca87e96b76de07eec
7
- data.tar.gz: 287d91f5c9291112ba6bf9aa99aabc4f32466ed76995da6a092492250228693ba1b535cd5d7147c601a67fd6e69c5fb24bb9cc415ee3d5b232392e9e7d8d0a8b
6
+ metadata.gz: c8abf99aad689c83d93223dfa5de1e5e7c2ece56b6235ca6594b50f2b5413a03a06e8fb44dbf815137c3217e5eebf18335f52204450b3aab7899e7064a969d65
7
+ data.tar.gz: 8c5165ccf7c2463198e39748fcf47f5c92bc916dd5ce6f6b448e5f682c18b88fdfe05d774e385a0f716cb6bc776f822357705834878f1fe96114b2eed74bdf4e
data/README.md CHANGED
@@ -1,14 +1,16 @@
1
1
  # ActiveQuery
2
2
 
3
- ActiveQuery is a gem that helps you create query objects in a simple way. It provides a DSL to define queries and scopes for your query object, making it easier to organize and reuse complex database queries.
3
+ ActiveQuery is a Ruby gem that helps you create clean, reusable query objects with a simple DSL. It provides type validation, conditional logic, and seamless ActiveRecord integration.
4
4
 
5
5
  ## Features
6
6
 
7
- - **Query Objects**: Create reusable query objects with a clean DSL
8
- - **Type Validation**: Built-in argument type validation
9
- - **Scopes**: Define custom scopes for your queries
10
- - **Operations**: Additional query operations like `gt`, `lt`, `like`, etc.
11
- - **Resolvers**: Support for resolver pattern for complex queries
7
+ - **Clean Query DSL**: Define queries with clear syntax and descriptions
8
+ - **Type Safety**: Built-in argument type validation (String, Integer, Float, Boolean)
9
+ - **Optional & Default Arguments**: Flexible argument handling
10
+ - **Custom Operations**: Extended query operations like `gt`, `lt`, `like`, etc.
11
+ - **Conditional Logic**: Apply scopes conditionally with `if` and `unless`
12
+ - **Resolver Pattern**: Support for complex query logic in separate classes
13
+ - **Custom Scopes**: Define reusable scopes within query objects
12
14
  - **ActiveRecord Integration**: Works seamlessly with ActiveRecord models
13
15
 
14
16
  ## Installation
@@ -25,162 +27,293 @@ And then execute:
25
27
  bundle install
26
28
  ```
27
29
 
28
- Or install it yourself as:
29
-
30
- ```bash
31
- gem install active-query
32
- ```
33
-
34
30
  ## Usage
35
31
 
36
32
  ### Basic Query Object
37
33
 
38
- Create a query object by including `ActiveQuery::Base`:
34
+ Create a query object by including `ActiveQuery::Base` and defining queries with the `query` method:
39
35
 
40
36
  ```ruby
41
37
  class UserQuery
42
38
  include ActiveQuery::Base
43
39
 
44
40
  # The model is automatically inferred from the class name (User)
45
- # Or you can explicitly set it:
46
- # model_name 'User'
41
+ # Or explicitly set it:
42
+ model_name 'User'
47
43
 
48
- query :by_email, 'Find users by email', { email: { type: String } } do |email:|
49
- scope.where(email: email)
50
- end
44
+ # Simple query without arguments
45
+ query :active, 'Returns all active users', -> { scope.where(active: true) }
51
46
 
52
- query :active, 'Find active users' do
53
- scope.where(active: true)
54
- end
47
+ # Query with arguments and type validation
48
+ query :by_email, 'Find users by email address',
49
+ { email: { type: String } },
50
+ -> (email:) { scope.where(email: email) }
55
51
  end
56
52
  ```
57
53
 
58
54
  ### Using Query Objects
59
55
 
60
56
  ```ruby
61
- # Find users by email
62
- users = UserQuery.by_email(email: 'john@example.com')
63
-
64
- # Find active users
57
+ # Execute queries
65
58
  active_users = UserQuery.active
59
+ user = UserQuery.by_email(email: 'john@example.com')
66
60
 
67
- # Chain queries
68
- active_users_with_email = UserQuery.active.by_email(email: 'john@example.com')
61
+ # Chain with ActiveRecord methods
62
+ recent_active_users = UserQuery.active.where('created_at > ?', 1.week.ago)
69
63
  ```
70
64
 
71
- ### Query Arguments with Types
65
+ ### Argument Types and Validation
66
+
67
+ ActiveQuery supports several argument types with automatic validation:
72
68
 
73
69
  ```ruby
74
70
  class ProductQuery
75
71
  include ActiveQuery::Base
76
72
 
77
- query :by_price_range, 'Find products within price range', {
78
- min_price: { type: Float },
79
- max_price: { type: Float, optional: true },
80
- include_tax: { type: ActiveQuery::Base::Boolean, default: false }
81
- } do |min_price:, max_price:, include_tax:|
82
- scope.where('price >= ?', min_price)
83
- .then { |s| max_price ? s.where('price <= ?', max_price) : s }
84
- .then { |s| include_tax ? s.where(tax_included: true) : s }
85
- end
73
+ query :filter_products, 'Filter products by various criteria',
74
+ {
75
+ name: { type: String },
76
+ price: { type: Float },
77
+ quantity: { type: Integer },
78
+ available: { type: ActiveQuery::Base::Boolean }
79
+ },
80
+ -> (name:, price:, quantity:, available:) {
81
+ scope.where(name: name)
82
+ .where(price: price)
83
+ .where(quantity: quantity)
84
+ .where(available: available)
85
+ }
86
86
  end
87
87
 
88
- # Usage
89
- products = ProductQuery.by_price_range(min_price: 10.0, max_price: 100.0, include_tax: true)
90
- ```
88
+ # Usage with type validation
89
+ ProductQuery.filter_products(
90
+ name: 'Widget',
91
+ price: 19.99,
92
+ quantity: 10,
93
+ available: true
94
+ )
91
95
 
92
- ### Custom Scopes
96
+ # This will raise ArgumentError due to type mismatch
97
+ ProductQuery.filter_products(name: 123, price: 'invalid', quantity: true, available: 'yes')
98
+ ```
93
99
 
94
- Define reusable scopes within your query object:
100
+ ### Optional Arguments and Defaults
95
101
 
96
102
  ```ruby
97
103
  class OrderQuery
98
104
  include ActiveQuery::Base
99
- include ActiveQuery::Scopes
100
-
101
- scope :recent, -> { where('created_at > ?', 1.week.ago) }
102
- scope :completed, -> { where(status: 'completed') }
103
105
 
104
- query :recent_completed, 'Find recent completed orders' do
105
- scope.recent.completed
106
- end
106
+ query :search_orders, 'Search orders with optional filters',
107
+ {
108
+ status: { type: String },
109
+ paid: { type: ActiveQuery::Base::Boolean, default: true },
110
+ customer_name: { type: String, optional: true }
111
+ },
112
+ -> (status:, paid:, customer_name:) {
113
+ scope.where(status: status)
114
+ .where(paid: paid)
115
+ .then { |s| customer_name ? s.where('customer_name LIKE ?', "%#{customer_name}%") : s }
116
+ }
107
117
  end
118
+
119
+ # Usage - customer_name is optional, paid defaults to true
120
+ OrderQuery.search_orders(status: 'pending')
121
+ OrderQuery.search_orders(status: 'completed', customer_name: 'John')
122
+ OrderQuery.search_orders(status: 'pending', paid: false, customer_name: 'Jane')
108
123
  ```
109
124
 
110
- ### Operations
125
+ ### Conditional Query Logic
111
126
 
112
- ActiveQuery provides additional query operations:
127
+ Use `if` and `unless` methods for conditional query building:
113
128
 
114
129
  ```ruby
115
130
  class UserQuery
116
131
  include ActiveQuery::Base
117
132
 
118
- query :by_age_range, 'Find users by age range', {
119
- min_age: { type: Integer, optional: true },
120
- max_age: { type: Integer, optional: true }
121
- } do |min_age:, max_age:|
122
- scope.then { |s| min_age ? s.gteq(:age, min_age) : s }
123
- .then { |s| max_age ? s.lteq(:age, max_age) : s }
124
- end
133
+ query :search_users, 'Search users with conditional filters',
134
+ {
135
+ name: { type: String, optional: true },
136
+ active: { type: ActiveQuery::Base::Boolean, optional: true }
137
+ },
138
+ -> (name:, active:) {
139
+ scope.if(name.present?, -> { where('name LIKE ?', "%#{name}%") })
140
+ .if(active == true, -> { where(active: true) })
141
+ }
125
142
 
126
- query :by_name, 'Find users by name pattern', { name: { type: String } } do |name:|
127
- scope.like(:name, name)
128
- end
143
+ query :filter_unless_admin, 'Filter users unless they are admin',
144
+ {
145
+ role: { type: String, optional: true }
146
+ },
147
+ -> (role:) {
148
+ scope.unless(role == 'admin', -> { where.not(role: 'admin') })
149
+ }
129
150
  end
130
151
  ```
131
152
 
132
- Available operations:
153
+ ### Extended Query Operations
154
+
155
+ ActiveQuery provides additional query operations beyond standard ActiveRecord:
156
+
157
+ ```ruby
158
+ class ProductQuery
159
+ include ActiveQuery::Base
160
+
161
+ # Comparison operations
162
+ query :expensive_products, 'Products above price threshold', -> { scope.gt(:price, 100) }
163
+ query :affordable_products, 'Products within budget', -> { scope.lteq(:price, 50) }
164
+
165
+ # Text search operations
166
+ query :search_by_name, 'Search products by name pattern', -> { scope.like(:name, 'Phone') }
167
+ query :products_starting_with, 'Products starting with prefix', -> { scope.start_like(:name, 'iPhone') }
168
+ query :products_ending_with, 'Products ending with suffix', -> { scope.end_like(:name, 'Pro') }
169
+
170
+ # Dynamic filtering
171
+ query :by_price_range, 'Filter by price range',
172
+ { min_price: { type: Float }, max_price: { type: Float } },
173
+ -> (min_price:, max_price:) {
174
+ scope.gteq(:price, min_price)
175
+ .lteq(:price, max_price)
176
+ }
177
+ end
178
+ ```
179
+
180
+ **Available operations:**
133
181
  - `gt(column, value)` - greater than
134
- - `gteq(column, value)` - greater than or equal
135
- - `lt(column, value)` - less than
182
+ - `gteq(column, value)` - greater than or equal
183
+ - `lt(column, value)` - less than
136
184
  - `lteq(column, value)` - less than or equal
137
- - `like(column, value)` - contains pattern
138
- - `start_like(column, value)` - starts with pattern
185
+ - `like(column, value)` - contains pattern (wraps with %)
186
+ - `start_like(column, value)` - starts with pattern
139
187
  - `end_like(column, value)` - ends with pattern
140
188
 
141
- ### Resolvers
189
+ ### Custom Scopes
142
190
 
143
- For complex queries, you can use the resolver pattern:
191
+ Define reusable scopes within your query objects:
144
192
 
145
193
  ```ruby
194
+ class UserQuery
195
+ include ActiveQuery::Base
196
+ include ActiveQuery::Scopes
197
+
198
+ # Define custom scopes
199
+ module Scopes
200
+ include ActiveQuery::Scopes
201
+
202
+ scope :recent, -> { where('created_at > ?', 1.month.ago) }
203
+ scope :by_role, -> (role:) { where(role: role) }
204
+ end
205
+
206
+ # Use scopes in queries
207
+ query :recent_admins, 'Find recent admin users', -> { scope.recent.by_role(role: 'admin') }
208
+
209
+ query :count_recent, 'Count recent users', -> { scope.recent.count }
210
+ end
211
+ ```
212
+
213
+ ### Resolver Pattern
214
+
215
+ For complex query logic, use the resolver pattern to keep your query objects clean:
216
+
217
+ ```ruby
218
+ # Define a resolver
146
219
  class UserStatsResolver < ActiveQuery::Resolver
147
- def resolve(status: nil)
148
- query = scope.joins(:orders)
149
- query = query.where(status: status) if status
150
- query.group(:id).having('COUNT(orders.id) > 5')
220
+ def resolve(min_orders: 5)
221
+ scope.joins(:orders)
222
+ .group('users.id')
223
+ .having('COUNT(orders.id) >= ?', min_orders)
224
+ .select('users.*, COUNT(orders.id) as order_count')
151
225
  end
152
226
  end
153
227
 
228
+ # Use resolver in query object
154
229
  class UserQuery
155
230
  include ActiveQuery::Base
156
231
 
157
- query :with_many_orders, 'Users with many orders', {
158
- status: { type: String, optional: true }
159
- }, resolver: UserStatsResolver
232
+ # Resolver without arguments
233
+ query :high_value_users, 'Users with many orders',
234
+ resolver: UserStatsResolver
235
+
236
+ # Resolver with arguments
237
+ query :users_with_orders, 'Users with minimum order count',
238
+ { min_orders: { type: Integer } },
239
+ resolver: UserStatsResolver
160
240
  end
161
241
 
162
242
  # Usage
163
- users = UserQuery.with_many_orders(status: 'active')
243
+ UserQuery.high_value_users
244
+ UserQuery.users_with_orders(min_orders: 10)
164
245
  ```
165
246
 
166
- ### Conditional Queries
247
+ ### Query Introspection
167
248
 
168
- Use `if` and `unless` for conditional query building:
249
+ Query objects provide metadata about available queries:
169
250
 
170
251
  ```ruby
171
- class ProductQuery
252
+ class UserQuery
172
253
  include ActiveQuery::Base
173
254
 
174
- query :search, 'Search products', {
175
- name: { type: String, optional: true },
176
- category: { type: String, optional: true },
177
- on_sale: { type: ActiveQuery::Base::Boolean, optional: true }
178
- } do |name:, category:, on_sale:|
179
- scope.if(name.present?, -> { where('name ILIKE ?', "%#{name}%") })
180
- .if(category.present?, -> { where(category: category) })
181
- .if(on_sale == true, -> { where('sale_price IS NOT NULL') })
255
+ query :active, 'Find active users', -> { scope.where(active: true) }
256
+ query :by_name, 'Find by name', { name: { type: String } }, -> (name:) { scope.where(name: name) }
257
+ end
258
+
259
+ # Get all available queries
260
+ UserQuery.queries
261
+ # => [
262
+ # { name: :active, description: "Find active users", args_def: {} },
263
+ # { name: :by_name, description: "Find by name", args_def: { name: { type: String } } }
264
+ # ]
265
+ ```
266
+
267
+ ### Error Handling
268
+
269
+ ActiveQuery provides clear error messages for common mistakes:
270
+
271
+ ```ruby
272
+ # Missing required arguments
273
+ UserQuery.by_email
274
+ # => ArgumentError: Params missing: [:email]
275
+
276
+ # Wrong argument type
277
+ UserQuery.by_email(email: 123)
278
+ # => ArgumentError: :email must be of type String
279
+
280
+ # Unknown arguments
281
+ UserQuery.by_email(email: 'test@example.com', invalid_param: 'value')
282
+ # => ArgumentError: Unknown params: [:invalid_param]
283
+
284
+ # Optional and default together (validation error)
285
+ query :invalid_query, 'This will fail',
286
+ { param: { type: String, optional: true, default: 'value' } },
287
+ -> (param:) { scope }
288
+ # => ArgumentError: Optional and Default params can't be present together: [:param]
289
+ ```
290
+
291
+ ### Integration with Rails
292
+
293
+ ActiveQuery works seamlessly with Rails applications:
294
+
295
+ ```ruby
296
+ # app/queries/user_query.rb
297
+ class UserQuery
298
+ include ActiveQuery::Base
299
+
300
+ query :active, 'Active users', -> { scope.where(active: true) }
301
+ query :by_role, 'Users by role', { role: { type: String } }, -> (role:) { scope.where(role: role) }
302
+ end
303
+
304
+ # In controllers
305
+ class UsersController < ApplicationController
306
+ def index
307
+ @users = UserQuery.active
308
+ end
309
+
310
+ def admins
311
+ @admins = UserQuery.by_role(role: 'admin')
182
312
  end
183
313
  end
314
+
315
+ # In views or anywhere else
316
+ <%= UserQuery.active.count %> active users
184
317
  ```
185
318
 
186
319
  ## Requirements
@@ -191,9 +324,9 @@ end
191
324
 
192
325
  ## Development
193
326
 
194
- 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.
327
+ 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.
195
328
 
196
- 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).
329
+ 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`.
197
330
 
198
331
  ## Contributing
199
332
 
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'active_query'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveQuery
4
- VERSION = '0.1.1'
4
+ VERSION = '0.1.2'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active-query
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matias Asis
@@ -151,6 +151,7 @@ files:
151
151
  - README.md
152
152
  - Rakefile
153
153
  - exe/active-query
154
+ - lib/active-query.rb
154
155
  - lib/active_query.rb
155
156
  - lib/active_query/resolver.rb
156
157
  - lib/active_query/version.rb