propel_api 0.3.3 → 0.3.4
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/CHANGELOG.md +6 -0
- data/FILTERING_DOCUMENTATION.md +470 -0
- data/lib/generators/propel_api/templates/lib/propel_dynamic_scope_generator.rb +1 -2
- data/lib/propel_api.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 04fb55f19379b238226c473c68009234156cc6f788d54eb29a89155db938556e
|
4
|
+
data.tar.gz: 6bb69d00b06c6497d0a6fbbec4d4bafc026cd2b1484eaef48e9b31e9bd20564d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 49af6bb0bbfcad1d62fdf4aada990189a1c1e850df87f1cee4b284ece041bf7c3c777907deff2d2df332b7b44a4563dba6b6f4de29c75c3e4c2e97609345b8d5
|
7
|
+
data.tar.gz: 87d70201da1cd3e2630cc1135997dfdb8bcf7c17900a341e4f9337d9accdc446d64dea4a06aeafcf1a7ae6e621daa2366ab5ea7c35e5434f594bd5e2fbb9869a
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [0.3.4] - 2025-09-29
|
11
|
+
|
12
|
+
### Fixed
|
13
|
+
- **Custom `has_scope` definitions**: The dynamic scope generator no longer overrides custom `has_scope` definitions, allowing developers to implement their own filtering logic without interference.
|
14
|
+
- **`has_scope` parameter filtering**: All declared `has_scope` parameters (both dynamic and custom) are now automatically permitted, fixing an issue where they were being filtered out as unsafe.
|
15
|
+
|
10
16
|
### Planned Features
|
11
17
|
- GraphQL adapter support
|
12
18
|
|
@@ -0,0 +1,470 @@
|
|
1
|
+
# Propel Rails Filtering System Documentation
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
The Propel Rails filtering system provides automatic URL query string filtering and scoping using the `has_scope` gem. It supports filtering across multiple data types with various operators, making it easy to build powerful search and filter interfaces.
|
6
|
+
|
7
|
+
## Base URL Structure
|
8
|
+
|
9
|
+
```
|
10
|
+
GET {{base_url}}/api/v1/{resource}
|
11
|
+
```
|
12
|
+
|
13
|
+
## Authentication
|
14
|
+
|
15
|
+
All API requests require authentication via JWT token in the Authorization header:
|
16
|
+
|
17
|
+
```
|
18
|
+
Authorization: Bearer <your_jwt_token>
|
19
|
+
```
|
20
|
+
|
21
|
+
## Data Types and Supported Operators
|
22
|
+
|
23
|
+
### 1. String Fields
|
24
|
+
|
25
|
+
String fields support text-based filtering with multiple operators.
|
26
|
+
|
27
|
+
#### Supported Operators
|
28
|
+
|
29
|
+
| Operator | Description | Example |
|
30
|
+
|----------|-------------|---------|
|
31
|
+
| `_eq` | Exact match | `name_eq=Acme Corporation` |
|
32
|
+
| `_contains` | Contains substring | `title_contains=Project` |
|
33
|
+
| `_starts_with` | Starts with substring | `name_starts_with=Acme` |
|
34
|
+
| `_ends_with` | Ends with substring | `email_ends_with=@company.com` |
|
35
|
+
| `_in` | In list of values | `status_in=active,pending` |
|
36
|
+
|
37
|
+
#### Examples
|
38
|
+
|
39
|
+
```bash
|
40
|
+
# Exact match
|
41
|
+
GET /api/v1/meetings?title_eq=Important Meeting
|
42
|
+
|
43
|
+
# Contains search
|
44
|
+
GET /api/v1/meetings?title_contains=Project
|
45
|
+
|
46
|
+
# Starts with
|
47
|
+
GET /api/v1/organizations?name_starts_with=Acme
|
48
|
+
|
49
|
+
# Ends with
|
50
|
+
GET /api/v1/users?email_ends_with=@company.com
|
51
|
+
|
52
|
+
# Multiple values
|
53
|
+
GET /api/v1/meetings?status_in=active,pending,cancelled
|
54
|
+
```
|
55
|
+
|
56
|
+
### 2. Numeric Fields (Integer, Decimal, Float)
|
57
|
+
|
58
|
+
Numeric fields support mathematical comparisons and range operations.
|
59
|
+
|
60
|
+
#### Supported Operators
|
61
|
+
|
62
|
+
| Operator | Description | Example |
|
63
|
+
|----------|-------------|---------|
|
64
|
+
| `_eq` | Equal to | `max_participants_eq=50` |
|
65
|
+
| `_gt` | Greater than | `max_participants_gt=100` |
|
66
|
+
| `_lt` | Less than | `max_participants_lt=500` |
|
67
|
+
| `_gte` | Greater than or equal | `max_participants_gte=200` |
|
68
|
+
| `_lte` | Less than or equal | `max_participants_lte=400` |
|
69
|
+
| `_min` | Alias for `_gte` | `max_participants_min=100` |
|
70
|
+
| `_max` | Alias for `_lte` | `max_participants_max=500` |
|
71
|
+
| `_range` | Between two values | `max_participants_range=150,350` |
|
72
|
+
| `_in` | In list of values | `room_id_in=1,2,3,4` |
|
73
|
+
|
74
|
+
#### Examples
|
75
|
+
|
76
|
+
```bash
|
77
|
+
# Exact match
|
78
|
+
GET /api/v1/meetings?max_participants_eq=50
|
79
|
+
|
80
|
+
# Greater than
|
81
|
+
GET /api/v1/meetings?max_participants_gt=100
|
82
|
+
|
83
|
+
# Less than
|
84
|
+
GET /api/v1/meetings?max_participants_lt=500
|
85
|
+
|
86
|
+
# Range filtering
|
87
|
+
GET /api/v1/meetings?max_participants_range=150,350
|
88
|
+
|
89
|
+
# Multiple values
|
90
|
+
GET /api/v1/meetings?room_id_in=1,2,3
|
91
|
+
|
92
|
+
# Combined numeric filters
|
93
|
+
GET /api/v1/meetings?max_participants_gte=20&max_participants_lte=100
|
94
|
+
```
|
95
|
+
|
96
|
+
### 3. Boolean Fields
|
97
|
+
|
98
|
+
Boolean fields support true/false filtering with multiple value formats.
|
99
|
+
|
100
|
+
#### Supported Operators
|
101
|
+
|
102
|
+
| Operator | Description | Example |
|
103
|
+
|----------|-------------|---------|
|
104
|
+
| `_eq` | Equal to boolean value | `recording_enabled_eq=true` |
|
105
|
+
|
106
|
+
#### Supported Boolean Values
|
107
|
+
|
108
|
+
| Value | Description |
|
109
|
+
|-------|-------------|
|
110
|
+
| `true`, `false` | Standard boolean |
|
111
|
+
| `1`, `0` | Numeric boolean |
|
112
|
+
| `yes`, `no` | Text boolean |
|
113
|
+
| `on`, `off` | Toggle boolean |
|
114
|
+
|
115
|
+
#### Examples
|
116
|
+
|
117
|
+
```bash
|
118
|
+
# Standard boolean
|
119
|
+
GET /api/v1/meetings?recording_enabled_eq=true
|
120
|
+
GET /api/v1/meetings?ai_research_enabled_eq=false
|
121
|
+
|
122
|
+
# Numeric boolean
|
123
|
+
GET /api/v1/meetings?auto_agenda_eq=1
|
124
|
+
GET /api/v1/meetings?follow_up_enabled_eq=0
|
125
|
+
|
126
|
+
# Text boolean
|
127
|
+
GET /api/v1/meetings?transcription_enabled_eq=yes
|
128
|
+
GET /api/v1/meetings?auto_presentation_eq=no
|
129
|
+
```
|
130
|
+
|
131
|
+
### 4. DateTime Fields
|
132
|
+
|
133
|
+
DateTime fields support temporal filtering with various operators.
|
134
|
+
|
135
|
+
#### Supported Operators
|
136
|
+
|
137
|
+
| Operator | Description | Example |
|
138
|
+
|----------|-------------|---------|
|
139
|
+
| `_before` | Before datetime | `start_time_before=2024-12-31T23:59:59Z` |
|
140
|
+
| `_after` | After datetime | `start_time_after=2024-01-01T00:00:00Z` |
|
141
|
+
| `_year` | Specific year | `start_time_year=2024` |
|
142
|
+
| `_month` | Specific month | `start_time_month=6` |
|
143
|
+
| `_day` | Specific day | `start_time_day=15` |
|
144
|
+
| `_date` | Specific date | `start_time_date=2024-06-15` |
|
145
|
+
|
146
|
+
#### Supported DateTime Formats
|
147
|
+
|
148
|
+
| Format | Example | Description |
|
149
|
+
|--------|---------|-------------|
|
150
|
+
| ISO 8601 | `2024-06-15T14:30:00Z` | Full datetime with timezone |
|
151
|
+
| Date only | `2024-06-15` | Date without time |
|
152
|
+
| Local format | `2024-06-15 14:30:00` | Local datetime format |
|
153
|
+
| Array format | `[2024, 6, 15]` | Year, month, day array |
|
154
|
+
|
155
|
+
#### Examples
|
156
|
+
|
157
|
+
```bash
|
158
|
+
# Before specific datetime
|
159
|
+
GET /api/v1/meetings?start_time_before=2024-12-31T23:59:59Z
|
160
|
+
|
161
|
+
# After specific datetime
|
162
|
+
GET /api/v1/meetings?start_time_after=2024-01-01T00:00:00Z
|
163
|
+
|
164
|
+
# Specific year
|
165
|
+
GET /api/v1/meetings?start_time_year=2024
|
166
|
+
|
167
|
+
# Specific month
|
168
|
+
GET /api/v1/meetings?start_time_month=6
|
169
|
+
|
170
|
+
# Specific day
|
171
|
+
GET /api/v1/meetings?start_time_day=15
|
172
|
+
|
173
|
+
# Specific date
|
174
|
+
GET /api/v1/meetings?start_time_date=2024-06-15
|
175
|
+
|
176
|
+
# Expiration filtering
|
177
|
+
GET /api/v1/rooms?expiration_date_after=2024-12-31T23:59:59Z
|
178
|
+
```
|
179
|
+
|
180
|
+
### 5. Date Fields
|
181
|
+
|
182
|
+
Date fields support date-specific filtering (without time components).
|
183
|
+
|
184
|
+
#### Supported Operators
|
185
|
+
|
186
|
+
Same as DateTime fields, but optimized for date-only operations.
|
187
|
+
|
188
|
+
#### Examples
|
189
|
+
|
190
|
+
```bash
|
191
|
+
# Date range
|
192
|
+
GET /api/v1/events?event_date_after=2024-01-01&event_date_before=2024-12-31
|
193
|
+
|
194
|
+
# Specific year
|
195
|
+
GET /api/v1/events?event_date_year=2024
|
196
|
+
|
197
|
+
# Specific month
|
198
|
+
GET /api/v1/events?event_date_month=6
|
199
|
+
```
|
200
|
+
|
201
|
+
### 6. Time Fields
|
202
|
+
|
203
|
+
Time fields support time-of-day filtering.
|
204
|
+
|
205
|
+
#### Supported Operators
|
206
|
+
|
207
|
+
Same as DateTime fields, but focused on time components.
|
208
|
+
|
209
|
+
#### Examples
|
210
|
+
|
211
|
+
```bash
|
212
|
+
# Time range
|
213
|
+
GET /api/v1/schedules?start_time_after=09:00:00&end_time_before=17:00:00
|
214
|
+
|
215
|
+
# Specific hour
|
216
|
+
GET /api/v1/schedules?start_time_hour=14
|
217
|
+
```
|
218
|
+
|
219
|
+
## Sorting
|
220
|
+
|
221
|
+
All resources support sorting by any filterable field.
|
222
|
+
|
223
|
+
### Sorting Syntax
|
224
|
+
|
225
|
+
| Format | Description | Example |
|
226
|
+
|--------|-------------|---------|
|
227
|
+
| `order_by=field` | Ascending order | `order_by=title` |
|
228
|
+
| `order_by=-field` | Descending order | `order_by=-created_at` |
|
229
|
+
|
230
|
+
### Examples
|
231
|
+
|
232
|
+
```bash
|
233
|
+
# Sort by title ascending
|
234
|
+
GET /api/v1/meetings?order_by=title
|
235
|
+
|
236
|
+
# Sort by start time descending
|
237
|
+
GET /api/v1/meetings?order_by=-start_time
|
238
|
+
|
239
|
+
# Sort by multiple fields (if supported)
|
240
|
+
GET /api/v1/meetings?order_by=status,start_time
|
241
|
+
```
|
242
|
+
|
243
|
+
## Pagination
|
244
|
+
|
245
|
+
All list endpoints support pagination parameters.
|
246
|
+
|
247
|
+
### Pagination Parameters
|
248
|
+
|
249
|
+
| Parameter | Description | Example |
|
250
|
+
|-----------|-------------|---------|
|
251
|
+
| `page` | Page number (1-based) | `page=2` |
|
252
|
+
| `limit` | Records per page | `limit=10` |
|
253
|
+
| `per_page` | Alias for limit | `per_page=25` |
|
254
|
+
|
255
|
+
### Examples
|
256
|
+
|
257
|
+
```bash
|
258
|
+
# First page with 10 records
|
259
|
+
GET /api/v1/meetings?page=1&limit=10
|
260
|
+
|
261
|
+
# Second page with 25 records
|
262
|
+
GET /api/v1/meetings?page=2&per_page=25
|
263
|
+
|
264
|
+
# Large page size
|
265
|
+
GET /api/v1/meetings?limit=100
|
266
|
+
```
|
267
|
+
|
268
|
+
## Complex Query Examples
|
269
|
+
|
270
|
+
### Multi-Parameter Filtering
|
271
|
+
|
272
|
+
```bash
|
273
|
+
# Find large meetings with recording enabled, starting after June 1st
|
274
|
+
GET /api/v1/meetings?max_participants_gte=50&recording_enabled_eq=true&start_time_after=2024-06-01T00:00:00Z
|
275
|
+
|
276
|
+
# Find project meetings in 2024, sorted by start time
|
277
|
+
GET /api/v1/meetings?title_contains=Project&start_time_year=2024&order_by=start_time
|
278
|
+
|
279
|
+
# Find meetings in specific room range with pagination
|
280
|
+
GET /api/v1/meetings?room_id_range=1,5&page=1&limit=20
|
281
|
+
```
|
282
|
+
|
283
|
+
### Organization Filtering
|
284
|
+
|
285
|
+
```bash
|
286
|
+
# Find organizations with names starting with "Acme"
|
287
|
+
GET /api/v1/organizations?name_starts_with=Acme
|
288
|
+
|
289
|
+
# Find organizations with specific names
|
290
|
+
GET /api/v1/organizations?name_in=Acme Corporation,Tech Corp
|
291
|
+
```
|
292
|
+
|
293
|
+
### Action Item Report Filtering
|
294
|
+
|
295
|
+
```bash
|
296
|
+
# Find reports due before end of year with specific status
|
297
|
+
GET /api/v1/action_item_reports?follow_up_date_before=2024-12-31T23:59:59Z&status_in=active,pending
|
298
|
+
|
299
|
+
# Find reports from specific year and month
|
300
|
+
GET /api/v1/action_item_reports?follow_up_date_year=2024&follow_up_date_month=6
|
301
|
+
```
|
302
|
+
|
303
|
+
### Room Availability
|
304
|
+
|
305
|
+
```bash
|
306
|
+
# Find available rooms with sufficient capacity
|
307
|
+
GET /api/v1/rooms?expiration_date_after=2024-12-31T23:59:59Z&max_capacity_gte=50&order_by=max_capacity
|
308
|
+
```
|
309
|
+
|
310
|
+
## Response Format
|
311
|
+
|
312
|
+
All filtered responses follow this structure:
|
313
|
+
|
314
|
+
```json
|
315
|
+
{
|
316
|
+
"data": [
|
317
|
+
{
|
318
|
+
"id": 1,
|
319
|
+
"title": "Meeting Title",
|
320
|
+
"start_time": "2024-06-15T14:30:00Z",
|
321
|
+
"max_participants": 50,
|
322
|
+
"recording_enabled": true,
|
323
|
+
// ... other fields based on facet configuration
|
324
|
+
}
|
325
|
+
],
|
326
|
+
"pagination": {
|
327
|
+
"current_page": 1,
|
328
|
+
"per_page": 10,
|
329
|
+
"total_pages": 5,
|
330
|
+
"total_count": 50,
|
331
|
+
"next_page": 2,
|
332
|
+
"prev_page": null
|
333
|
+
}
|
334
|
+
}
|
335
|
+
```
|
336
|
+
|
337
|
+
## Error Handling
|
338
|
+
|
339
|
+
### Common Error Responses
|
340
|
+
|
341
|
+
#### 400 Bad Request
|
342
|
+
```json
|
343
|
+
{
|
344
|
+
"error": "Invalid filter parameter",
|
345
|
+
"message": "Invalid datetime format for start_time_before",
|
346
|
+
"code": "INVALID_FILTER"
|
347
|
+
}
|
348
|
+
```
|
349
|
+
|
350
|
+
#### 401 Unauthorized
|
351
|
+
```json
|
352
|
+
{
|
353
|
+
"error": "Authentication required",
|
354
|
+
"message": "Valid JWT token must be provided",
|
355
|
+
"code": "MISSING_AUTH_TOKEN"
|
356
|
+
}
|
357
|
+
```
|
358
|
+
|
359
|
+
#### 403 Forbidden
|
360
|
+
```json
|
361
|
+
{
|
362
|
+
"error": "Organization context required",
|
363
|
+
"message": "Valid organization_id must be provided in JWT token",
|
364
|
+
"code": "MISSING_ORGANIZATION_CONTEXT"
|
365
|
+
}
|
366
|
+
```
|
367
|
+
|
368
|
+
## Testing Tips
|
369
|
+
|
370
|
+
### 1. Start Simple
|
371
|
+
Begin with single parameter tests to verify basic functionality.
|
372
|
+
|
373
|
+
### 2. Combine Gradually
|
374
|
+
Add multiple parameters to test complex filtering scenarios.
|
375
|
+
|
376
|
+
### 3. Test Edge Cases
|
377
|
+
- Invalid dates: `start_time_before=invalid-date`
|
378
|
+
- Empty strings: `title_eq=`
|
379
|
+
- Special characters: `title_contains=Project%20Meeting`
|
380
|
+
- Large numbers: `max_participants_gt=999999`
|
381
|
+
|
382
|
+
### 4. Verify Sorting
|
383
|
+
Test both ascending and descending order for each sortable field.
|
384
|
+
|
385
|
+
### 5. Check Pagination
|
386
|
+
Test different page sizes and verify pagination metadata.
|
387
|
+
|
388
|
+
### 6. Test Boolean Variations
|
389
|
+
Try different boolean value formats: `true`, `1`, `yes`, `on`
|
390
|
+
|
391
|
+
## Performance Considerations
|
392
|
+
|
393
|
+
### 1. Use Indexed Fields
|
394
|
+
Filtering on indexed fields (like `id`, `organization_id`) is faster than unindexed fields.
|
395
|
+
|
396
|
+
### 2. Limit Result Sets
|
397
|
+
Use pagination to limit large result sets and improve response times.
|
398
|
+
|
399
|
+
### 3. Combine Filters Efficiently
|
400
|
+
Multiple filters are combined with AND logic, so more specific filters reduce the result set faster.
|
401
|
+
|
402
|
+
### 4. Avoid Wildcard Searches
|
403
|
+
`_contains` operations are slower than exact matches (`_eq`) or prefix matches (`_starts_with`).
|
404
|
+
|
405
|
+
## Security Notes
|
406
|
+
|
407
|
+
### 1. Input Validation
|
408
|
+
All filter parameters are validated against allowed field names and operators.
|
409
|
+
|
410
|
+
### 2. SQL Injection Protection
|
411
|
+
All parameters are properly escaped and parameterized to prevent SQL injection.
|
412
|
+
|
413
|
+
### 3. Multi-tenancy
|
414
|
+
All queries are automatically scoped to the user's organization context.
|
415
|
+
|
416
|
+
### 4. Sensitive Field Filtering
|
417
|
+
Sensitive fields (passwords, tokens, etc.) are automatically excluded from filtering.
|
418
|
+
|
419
|
+
## Troubleshooting
|
420
|
+
|
421
|
+
### Common Issues
|
422
|
+
|
423
|
+
#### No Results Returned
|
424
|
+
- Check if the field name is correct
|
425
|
+
- Verify the operator is supported for that field type
|
426
|
+
- Ensure the value format matches the expected type
|
427
|
+
|
428
|
+
#### Invalid Filter Error
|
429
|
+
- Verify the field name exists on the model
|
430
|
+
- Check that the operator is supported for the field type
|
431
|
+
- Ensure the value can be parsed correctly
|
432
|
+
|
433
|
+
#### Authentication Errors
|
434
|
+
- Verify the JWT token is valid and not expired
|
435
|
+
- Check that the organization context is properly set
|
436
|
+
- Ensure the user has access to the requested resource
|
437
|
+
|
438
|
+
#### Performance Issues
|
439
|
+
- Use more specific filters to reduce result sets
|
440
|
+
- Add database indexes for frequently filtered fields
|
441
|
+
- Consider using pagination for large datasets
|
442
|
+
|
443
|
+
## API Endpoints
|
444
|
+
|
445
|
+
### Available Resources
|
446
|
+
|
447
|
+
| Resource | Endpoint | Description |
|
448
|
+
|----------|----------|-------------|
|
449
|
+
| Meetings | `/api/v1/meetings` | Meeting management |
|
450
|
+
| Organizations | `/api/v1/organizations` | Organization data |
|
451
|
+
| Users | `/api/v1/users` | User management |
|
452
|
+
| Rooms | `/api/v1/rooms` | Room management |
|
453
|
+
| Action Item Reports | `/api/v1/action_item_reports` | Action item tracking |
|
454
|
+
| Meeting Links | `/api/v1/meeting_links` | Meeting link management |
|
455
|
+
| Dossiers | `/api/v1/dossiers` | Document management |
|
456
|
+
|
457
|
+
### Field Reference
|
458
|
+
|
459
|
+
Each resource has different available fields for filtering. Check the individual resource documentation for the complete list of filterable fields.
|
460
|
+
|
461
|
+
## Changelog
|
462
|
+
|
463
|
+
### Version 0.3.2
|
464
|
+
- Added comprehensive datetime filtering support
|
465
|
+
- Implemented database-agnostic datetime parsing
|
466
|
+
- Added boolean field filtering with multiple value formats
|
467
|
+
- Enhanced string filtering with pattern matching
|
468
|
+
- Added range filtering for numeric fields
|
469
|
+
- Implemented multi-tenancy filtering
|
470
|
+
- Added comprehensive error handling and validation
|
@@ -434,8 +434,7 @@ class PropelDynamicScopeGenerator
|
|
434
434
|
end
|
435
435
|
|
436
436
|
def register_has_scope(controller_class, scope_name)
|
437
|
-
return if controller_class.
|
438
|
-
controller_class.has_scope_registered?(scope_name)
|
437
|
+
return if controller_class.scopes_configuration.key?(scope_name)
|
439
438
|
|
440
439
|
controller_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
441
440
|
has_scope :#{scope_name}
|
data/lib/propel_api.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: propel_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Martin, Rafael Pivato, Chi Putera
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-09-
|
11
|
+
date: 2025-09-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -61,6 +61,7 @@ extensions: []
|
|
61
61
|
extra_rdoc_files: []
|
62
62
|
files:
|
63
63
|
- CHANGELOG.md
|
64
|
+
- FILTERING_DOCUMENTATION.md
|
64
65
|
- LICENSE
|
65
66
|
- README.md
|
66
67
|
- Rakefile
|