jsonapi_responses 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +56 -6
- data/README.md +192 -38
- data/app/responders/application_responder.rb +99 -0
- data/app/serializers/application_serializer.rb +88 -0
- data/lib/jsonapi_responses/respondable.rb +31 -1
- data/lib/jsonapi_responses/responder.rb +149 -0
- data/lib/jsonapi_responses/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ab50f413d2756f7f1f6b1faae376ebaf041a60fef7df43b29c037cfdfff295c2
|
|
4
|
+
data.tar.gz: a3921894b3f3c0a3565e4be39667c4c9f643b9837c35c1a1e8c9c1ac491c477d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0bfebe0f3f65b8125776d5161b7c26ac0d673ae979e8ca84ebca91422ef895812260e857ee43404c5a684eeec2e816258397229d25f172b3b4875d311cd6b979
|
|
7
|
+
data.tar.gz: 83a0979577818624cabe42b395c12df5c1ea594768a855469ecd9de0673911bc6b6843731020f21931b81da35f8eb83582d9d99f826f19d13cfc52381d3e9955
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.1.0] - 2025-11-21
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Automatic Pagination Support**: `respond_for_index` now auto-detects Kaminari/WillPaginate pagination and includes meta automatically
|
|
8
|
+
- **Pagination Helpers in Responder**: Added `paginated?`, `pagination_meta`, and `render_collection_with_meta` methods
|
|
9
|
+
- **Smart Meta Handling**: Automatically merges pagination meta with context meta if both are present
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
- Auto-detection of paginated collections (checks for `current_page`, `total_pages`, `total_count` methods)
|
|
14
|
+
- Automatic inclusion of pagination metadata: `current_page`, `total_pages`, `total_count`, `per_page`
|
|
15
|
+
- Backward compatible - works with manual `meta` in context or auto-detects pagination
|
|
16
|
+
- Works with both Kaminari and WillPaginate gems
|
|
17
|
+
|
|
18
|
+
### Example Usage
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
# Controller - Automatic pagination meta
|
|
22
|
+
def index
|
|
23
|
+
@academies = Academy.page(params[:page]).per(15)
|
|
24
|
+
render_with(@academies) # Auto-includes pagination meta!
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Manual meta still works
|
|
28
|
+
def index
|
|
29
|
+
@academies = Academy.page(params[:page]).per(15)
|
|
30
|
+
render_with(@academies, context: { meta: { custom: 'data' } })
|
|
31
|
+
# Merges pagination + custom meta
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Custom responder with pagination helper
|
|
35
|
+
class MyResponder < JsonapiResponses::Responder
|
|
36
|
+
def render
|
|
37
|
+
render_collection_with_meta(record, { custom: 'meta' })
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
3
42
|
## [1.0.0] - 2025-10-07
|
|
4
43
|
|
|
5
44
|
### 🎉 Major Release - Breaking Changes
|
|
@@ -7,11 +46,13 @@
|
|
|
7
46
|
This is a major release with significant architectural improvements and breaking changes.
|
|
8
47
|
|
|
9
48
|
### Breaking Changes
|
|
49
|
+
|
|
10
50
|
- **Renamed `BaseSerializer`** → `ApplicationSerializer` (follows Rails convention)
|
|
11
51
|
- **Renamed `BaseResponder`** → `ApplicationResponder` (follows Rails convention like ApplicationPolicy)
|
|
12
52
|
- Responders now follow **Pundit-style pattern**: one responder class per controller with multiple action methods
|
|
13
53
|
|
|
14
54
|
### Added
|
|
55
|
+
|
|
15
56
|
- **Pundit-Style Responders**: One responder class per controller (e.g., `AcademyResponder`, `CourseResponder`)
|
|
16
57
|
- **Action-based method calls**: Use `render_with(@records, responder: AcademyResponder, action: :featured)`
|
|
17
58
|
- **ApplicationResponder base class**: With common helpers like `render_collection_with_meta`, `base_meta`, `filters_applied`
|
|
@@ -43,7 +84,7 @@ class AcademyResponder < ApplicationResponder
|
|
|
43
84
|
def featured # ← Method instead of render
|
|
44
85
|
# ...
|
|
45
86
|
end
|
|
46
|
-
|
|
87
|
+
|
|
47
88
|
def popular
|
|
48
89
|
# ...
|
|
49
90
|
end
|
|
@@ -63,6 +104,7 @@ render_with(@academies, responder: AcademyResponder, action: :featured)
|
|
|
63
104
|
#### 3. Consolidate Responders
|
|
64
105
|
|
|
65
106
|
Instead of one file per action:
|
|
107
|
+
|
|
66
108
|
```
|
|
67
109
|
# Before
|
|
68
110
|
app/responders/academy_featured_responder.rb
|
|
@@ -74,7 +116,8 @@ app/responders/academy_responder.rb # All methods inside
|
|
|
74
116
|
```
|
|
75
117
|
|
|
76
118
|
### Improved
|
|
77
|
-
|
|
119
|
+
|
|
120
|
+
- Better alignment with Rails naming conventions (Application\* prefix)
|
|
78
121
|
- Reduced file proliferation (3-4 responder files instead of 20+)
|
|
79
122
|
- Easier to maintain and understand (Pundit-style pattern)
|
|
80
123
|
- Better code organization and discoverability
|
|
@@ -82,18 +125,18 @@ app/responders/academy_responder.rb # All methods inside
|
|
|
82
125
|
## [0.3.0] - 2025-10-07
|
|
83
126
|
|
|
84
127
|
### Added
|
|
128
|
+
|
|
85
129
|
- **Custom Responders System**: New architecture for handling complex custom actions
|
|
86
130
|
- Added `JsonapiResponses::Responder` base class for creating dedicated responder objects
|
|
87
131
|
- Responders encapsulate response logic, keeping controllers clean and promoting reusability
|
|
88
132
|
- Full access to controller context, serialization helpers, and request params
|
|
89
|
-
|
|
90
133
|
- **Responder Base Class Features**:
|
|
91
134
|
- `serialize_collection` and `serialize_item` helpers for data serialization
|
|
92
135
|
- `render_json` helper for consistent JSON rendering
|
|
93
136
|
- `collection?` and `single_item?` type checking utilities
|
|
94
137
|
- Access to `params`, `current_user`, `controller`, `record`, `serializer_class`, and `context`
|
|
95
|
-
|
|
96
138
|
- **Enhanced render_with**:
|
|
139
|
+
|
|
97
140
|
- New `responder:` parameter to use custom responder classes
|
|
98
141
|
- New `serializer:` parameter to override default serializer detection
|
|
99
142
|
- Example: `render_with(@records, responder: FeaturedResponder, serializer: CustomSerializer)`
|
|
@@ -104,17 +147,20 @@ app/responders/academy_responder.rb # All methods inside
|
|
|
104
147
|
- `CategorizedResponder` - For grouped/categorized responses
|
|
105
148
|
|
|
106
149
|
### Changed
|
|
150
|
+
|
|
107
151
|
- Updated `lib/jsonapi_responses.rb` to require `responder.rb`
|
|
108
152
|
- Enhanced `Respondable` module to support responder classes
|
|
109
153
|
- Improved documentation with comprehensive Responder guide
|
|
110
154
|
|
|
111
155
|
### Documentation
|
|
156
|
+
|
|
112
157
|
- Added "Custom Responders" section to README with complete examples
|
|
113
158
|
- Added comparison table: when to use each approach (mapping vs methods vs responders)
|
|
114
159
|
- Added `RESPONDERS_FEATURE.md` with complete implementation guide
|
|
115
160
|
- Documented Responder API and best practices
|
|
116
161
|
|
|
117
162
|
### Benefits
|
|
163
|
+
|
|
118
164
|
- **Separation of Concerns**: Response logic separated from controllers
|
|
119
165
|
- **Reusability**: Same responder can be used across multiple controllers
|
|
120
166
|
- **Testability**: Test response logic independently from controllers
|
|
@@ -123,31 +169,35 @@ app/responders/academy_responder.rb # All methods inside
|
|
|
123
169
|
## [0.2.0] - 2025-10-06
|
|
124
170
|
|
|
125
171
|
### Added
|
|
172
|
+
|
|
126
173
|
- **Custom Actions Support**: Support for custom actions beyond standard CRUD (index, show, create, update, destroy)
|
|
127
174
|
- **Explicit Action Mapping**: `map_response_action` and `map_response_actions` for mapping custom actions to existing responses
|
|
128
175
|
- **Metaprogramming Support**: Dynamic method generation for response handlers
|
|
129
176
|
- `define_response_for`: Define custom response methods using blocks
|
|
130
177
|
- `define_responses_for`: Define same behavior for multiple actions in batch
|
|
131
178
|
- `define_crud_responses`: Intelligent CRUD responses with dynamic context
|
|
132
|
-
- `generate_rest_responses`: Auto-generate namespaced REST responses (
|
|
179
|
+
- `generate_rest_responses`: Auto-generate namespaced REST responses (public*, admin*, etc.)
|
|
133
180
|
- **Dynamic Context**: Support for lambdas/procs in context generation with `instance_eval`
|
|
134
181
|
- **Method Introspection**: `response_definitions` class attribute for debugging generated methods
|
|
135
182
|
- **Enhanced Error Messages**: Detailed error responses with suggestions when actions are not supported
|
|
136
183
|
|
|
137
184
|
### Changed
|
|
185
|
+
|
|
138
186
|
- **Breaking**: Removed automatic fallback behavior for action mapping (by design - explicit is better than implicit)
|
|
139
187
|
- **Improved**: `render_invalid_action` now provides detailed error information with actionable suggestions
|
|
140
188
|
- **Enhanced**: Better error handling with specific guidance on how to implement missing methods
|
|
141
189
|
|
|
142
190
|
### Technical Details
|
|
191
|
+
|
|
143
192
|
- Added `class_attribute :response_definitions` for storing method definitions
|
|
144
193
|
- Enhanced `render_with` method resolution to support custom and mapped actions
|
|
145
194
|
- All generated methods are properly marked as private
|
|
146
195
|
- Full backward compatibility maintained for existing CRUD operations
|
|
147
196
|
|
|
148
197
|
### Documentation
|
|
198
|
+
|
|
149
199
|
- Added comprehensive documentation in CUSTOM_ACTIONS.md
|
|
150
|
-
- Added metaprogramming guide in METAPROGRAMMING.md
|
|
200
|
+
- Added metaprogramming guide in METAPROGRAMMING.md
|
|
151
201
|
- Added practical implementation example in PRACTICAL_EXAMPLE.md
|
|
152
202
|
- Updated README with new functionality overview
|
|
153
203
|
|
data/README.md
CHANGED
|
@@ -147,11 +147,160 @@ GET /api/v1/digital_products?view=minimal # Returns minimal response
|
|
|
147
147
|
### Performance Benefits
|
|
148
148
|
|
|
149
149
|
By allowing the frontend to request only the needed data, you can:
|
|
150
|
+
|
|
150
151
|
- Reduce response payload size
|
|
151
152
|
- Improve API performance
|
|
152
153
|
- Avoid creating multiple endpoints for different data requirements
|
|
153
154
|
- Optimize database queries based on the requested view
|
|
154
155
|
|
|
156
|
+
## Automatic Pagination Support
|
|
157
|
+
|
|
158
|
+
**New in v1.1.0:** JsonapiResponses now automatically detects and handles paginated collections using Kaminari, making pagination effortless.
|
|
159
|
+
|
|
160
|
+
### Basic Usage
|
|
161
|
+
|
|
162
|
+
When you paginate your records with Kaminari, pagination metadata is automatically included in the response:
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
class Api::V1::AcademiesController < ApplicationController
|
|
166
|
+
include JsonapiResponses::Respondable
|
|
167
|
+
|
|
168
|
+
def index
|
|
169
|
+
academies = Academy.page(params[:page]).per(15)
|
|
170
|
+
|
|
171
|
+
# That's it! Pagination is automatic
|
|
172
|
+
render_with(academies)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Response:**
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"data": [
|
|
182
|
+
{ "id": 1, "name": "Academy 1", ... },
|
|
183
|
+
{ "id": 2, "name": "Academy 2", ... }
|
|
184
|
+
],
|
|
185
|
+
"meta": {
|
|
186
|
+
"current_page": 1,
|
|
187
|
+
"total_pages": 5,
|
|
188
|
+
"total_count": 73,
|
|
189
|
+
"per_page": 15
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### How It Works
|
|
195
|
+
|
|
196
|
+
JsonapiResponses automatically detects if your collection responds to Kaminari's pagination methods:
|
|
197
|
+
- `current_page`
|
|
198
|
+
- `total_pages`
|
|
199
|
+
- `total_count`
|
|
200
|
+
|
|
201
|
+
If these methods exist, pagination metadata is automatically included in the response.
|
|
202
|
+
|
|
203
|
+
### With Custom Views
|
|
204
|
+
|
|
205
|
+
Pagination works seamlessly with different view formats:
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
def index
|
|
209
|
+
academies = Academy
|
|
210
|
+
.includes(:owner, :courses)
|
|
211
|
+
.page(params[:page])
|
|
212
|
+
.per(params[:per_page] || 15)
|
|
213
|
+
|
|
214
|
+
# Supports view parameter and automatic pagination
|
|
215
|
+
render_with(academies)
|
|
216
|
+
end
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Request:**
|
|
220
|
+
```
|
|
221
|
+
GET /api/v1/academies?page=2&per_page=20&view=summary
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Response:**
|
|
225
|
+
```json
|
|
226
|
+
{
|
|
227
|
+
"data": [
|
|
228
|
+
{ "id": 21, "name": "Academy 21", ... }
|
|
229
|
+
],
|
|
230
|
+
"meta": {
|
|
231
|
+
"current_page": 2,
|
|
232
|
+
"total_pages": 4,
|
|
233
|
+
"total_count": 73,
|
|
234
|
+
"per_page": 20
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Requirements
|
|
240
|
+
|
|
241
|
+
- **Kaminari gem** must be installed and configured
|
|
242
|
+
- Your collection must be paginated with `.page()` method
|
|
243
|
+
|
|
244
|
+
### Custom Pagination Metadata
|
|
245
|
+
|
|
246
|
+
You can add additional metadata alongside automatic pagination:
|
|
247
|
+
|
|
248
|
+
```ruby
|
|
249
|
+
def index
|
|
250
|
+
academies = Academy.page(params[:page]).per(15)
|
|
251
|
+
|
|
252
|
+
render_with(
|
|
253
|
+
academies,
|
|
254
|
+
context: { view: view },
|
|
255
|
+
meta: {
|
|
256
|
+
fetched_at: Time.current,
|
|
257
|
+
filters_applied: params[:search].present?
|
|
258
|
+
}
|
|
259
|
+
)
|
|
260
|
+
end
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**Response:**
|
|
264
|
+
```json
|
|
265
|
+
{
|
|
266
|
+
"data": [...],
|
|
267
|
+
"meta": {
|
|
268
|
+
"current_page": 1,
|
|
269
|
+
"total_pages": 5,
|
|
270
|
+
"total_count": 73,
|
|
271
|
+
"per_page": 15,
|
|
272
|
+
"fetched_at": "2024-01-15T10:30:00Z",
|
|
273
|
+
"filters_applied": true
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Using Pagination Helpers
|
|
279
|
+
|
|
280
|
+
For custom responders, use the built-in pagination helpers:
|
|
281
|
+
|
|
282
|
+
```ruby
|
|
283
|
+
class AcademyResponder < JsonapiResponses::Responder
|
|
284
|
+
def respond_for_index
|
|
285
|
+
if paginated?(record)
|
|
286
|
+
render json: {
|
|
287
|
+
data: serialize_collection(record, serializer_class, context),
|
|
288
|
+
meta: pagination_meta(record, context)
|
|
289
|
+
}
|
|
290
|
+
else
|
|
291
|
+
render json: {
|
|
292
|
+
data: serialize_collection(record, serializer_class, context)
|
|
293
|
+
}
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Available helpers:
|
|
300
|
+
- `paginated?(record)` - Check if record supports pagination
|
|
301
|
+
- `pagination_meta(record, context)` - Extract pagination metadata hash
|
|
302
|
+
- `render_collection_with_meta(record, serializer_class, context)` - Render with automatic pagination
|
|
303
|
+
|
|
155
304
|
## Development
|
|
156
305
|
|
|
157
306
|
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.
|
|
@@ -165,14 +314,15 @@ Beyond the standard CRUD actions (index, show, create, update, destroy), you can
|
|
|
165
314
|
### Basic Usage
|
|
166
315
|
|
|
167
316
|
**Option 1: Map to existing actions**
|
|
317
|
+
|
|
168
318
|
```ruby
|
|
169
319
|
class Api::V1::CoursesController < ApplicationController
|
|
170
320
|
include JsonapiResponses::Respondable
|
|
171
|
-
|
|
321
|
+
|
|
172
322
|
# Map custom actions to existing response methods
|
|
173
323
|
map_response_action :public_index, to: :index
|
|
174
324
|
map_response_action :public_show, to: :show
|
|
175
|
-
|
|
325
|
+
|
|
176
326
|
def public_index
|
|
177
327
|
# Your logic here
|
|
178
328
|
render_with(@courses) # Will use respond_for_index
|
|
@@ -181,17 +331,18 @@ end
|
|
|
181
331
|
```
|
|
182
332
|
|
|
183
333
|
**Option 2: Define custom response methods**
|
|
334
|
+
|
|
184
335
|
```ruby
|
|
185
336
|
class Api::V1::CoursesController < ApplicationController
|
|
186
337
|
include JsonapiResponses::Respondable
|
|
187
|
-
|
|
338
|
+
|
|
188
339
|
def dashboard_stats
|
|
189
340
|
# Your logic here
|
|
190
341
|
render_with(@stats)
|
|
191
342
|
end
|
|
192
|
-
|
|
343
|
+
|
|
193
344
|
private
|
|
194
|
-
|
|
345
|
+
|
|
195
346
|
def respond_for_dashboard_stats(record, serializer_class, context)
|
|
196
347
|
render json: {
|
|
197
348
|
data: record,
|
|
@@ -202,17 +353,18 @@ end
|
|
|
202
353
|
```
|
|
203
354
|
|
|
204
355
|
**Option 3: Metaprogramming (Recommended for complex scenarios)**
|
|
356
|
+
|
|
205
357
|
```ruby
|
|
206
358
|
class Api::V1::CoursesController < ApplicationController
|
|
207
359
|
include JsonapiResponses::Respondable
|
|
208
|
-
|
|
360
|
+
|
|
209
361
|
# Generate methods automatically
|
|
210
362
|
generate_rest_responses(
|
|
211
363
|
namespace: 'public',
|
|
212
364
|
actions: [:index, :show],
|
|
213
365
|
context: { access_level: 'public' }
|
|
214
366
|
)
|
|
215
|
-
|
|
367
|
+
|
|
216
368
|
# Define similar responses in batch
|
|
217
369
|
define_responses_for [:export_csv, :export_pdf] do |record, serializer_class, context|
|
|
218
370
|
format = action_name.to_s.split('_').last
|
|
@@ -253,26 +405,26 @@ app/responders/
|
|
|
253
405
|
# app/responders/application_responder.rb
|
|
254
406
|
class ApplicationResponder < JsonapiResponses::Responder
|
|
255
407
|
protected
|
|
256
|
-
|
|
408
|
+
|
|
257
409
|
def render_collection_with_meta(type: nil, additional_meta: {})
|
|
258
410
|
render_json({
|
|
259
411
|
data: serialize_collection(record),
|
|
260
412
|
meta: base_meta.merge({ type: type }.compact).merge(additional_meta)
|
|
261
413
|
})
|
|
262
414
|
end
|
|
263
|
-
|
|
415
|
+
|
|
264
416
|
def base_meta
|
|
265
417
|
{
|
|
266
418
|
timestamp: Time.current.iso8601,
|
|
267
419
|
count: record_count
|
|
268
420
|
}.compact
|
|
269
421
|
end
|
|
270
|
-
|
|
422
|
+
|
|
271
423
|
def record_count
|
|
272
424
|
return nil unless collection?
|
|
273
425
|
record.respond_to?(:count) ? record.count : record.size
|
|
274
426
|
end
|
|
275
|
-
|
|
427
|
+
|
|
276
428
|
def filters_applied
|
|
277
429
|
filter_keys = [:category_id, :level, :status]
|
|
278
430
|
filters = {}
|
|
@@ -287,7 +439,7 @@ end
|
|
|
287
439
|
```ruby
|
|
288
440
|
# app/responders/academy_responder.rb
|
|
289
441
|
class AcademyResponder < ApplicationResponder
|
|
290
|
-
|
|
442
|
+
|
|
291
443
|
# GET /api/v1/academies/featured
|
|
292
444
|
def featured
|
|
293
445
|
if params[:category_id].present?
|
|
@@ -296,7 +448,7 @@ class AcademyResponder < ApplicationResponder
|
|
|
296
448
|
render_all_featured
|
|
297
449
|
end
|
|
298
450
|
end
|
|
299
|
-
|
|
451
|
+
|
|
300
452
|
# GET /api/v1/academies/popular
|
|
301
453
|
def popular
|
|
302
454
|
render_collection_with_meta(
|
|
@@ -307,7 +459,7 @@ class AcademyResponder < ApplicationResponder
|
|
|
307
459
|
}
|
|
308
460
|
)
|
|
309
461
|
end
|
|
310
|
-
|
|
462
|
+
|
|
311
463
|
# GET /api/v1/academies/recommended
|
|
312
464
|
def recommended
|
|
313
465
|
render_collection_with_meta(
|
|
@@ -318,9 +470,9 @@ class AcademyResponder < ApplicationResponder
|
|
|
318
470
|
}
|
|
319
471
|
)
|
|
320
472
|
end
|
|
321
|
-
|
|
473
|
+
|
|
322
474
|
private
|
|
323
|
-
|
|
475
|
+
|
|
324
476
|
def render_filtered_featured
|
|
325
477
|
render_json({
|
|
326
478
|
data: serialize_collection(record),
|
|
@@ -330,7 +482,7 @@ class AcademyResponder < ApplicationResponder
|
|
|
330
482
|
}
|
|
331
483
|
})
|
|
332
484
|
end
|
|
333
|
-
|
|
485
|
+
|
|
334
486
|
def render_all_featured
|
|
335
487
|
render_collection_with_meta(type: 'featured')
|
|
336
488
|
end
|
|
@@ -347,12 +499,12 @@ class Api::V1::AcademiesController < ApplicationController
|
|
|
347
499
|
@academies = load_featured_academies
|
|
348
500
|
render_with(@academies, responder: AcademyResponder, action: :featured)
|
|
349
501
|
end
|
|
350
|
-
|
|
502
|
+
|
|
351
503
|
def popular
|
|
352
504
|
@academies = Academy.popular.limit(20)
|
|
353
505
|
render_with(@academies, responder: AcademyResponder, action: :popular)
|
|
354
506
|
end
|
|
355
|
-
|
|
507
|
+
|
|
356
508
|
def recommended
|
|
357
509
|
@academies = Academy.recommended_for(current_user)
|
|
358
510
|
render_with(@academies, responder: AcademyResponder, action: :recommended)
|
|
@@ -363,12 +515,14 @@ end
|
|
|
363
515
|
### Benefits of This Pattern
|
|
364
516
|
|
|
365
517
|
**Like Pundit Policies:**
|
|
518
|
+
|
|
366
519
|
- ✅ One file per controller (not per action)
|
|
367
520
|
- ✅ All related logic in one place
|
|
368
521
|
- ✅ Easy to find and maintain
|
|
369
522
|
- ✅ Shared helpers in base class
|
|
370
523
|
|
|
371
524
|
**Example Structure:**
|
|
525
|
+
|
|
372
526
|
```
|
|
373
527
|
AcademiesController → AcademyResponder (featured, popular, recommended)
|
|
374
528
|
CoursesController → CourseResponder (featured, search, progress)
|
|
@@ -384,18 +538,18 @@ class MyCustomResponder < JsonapiResponses::Responder
|
|
|
384
538
|
def render
|
|
385
539
|
# Access to controller instance
|
|
386
540
|
controller.current_user
|
|
387
|
-
|
|
541
|
+
|
|
388
542
|
# Access to params
|
|
389
543
|
params[:filter]
|
|
390
|
-
|
|
544
|
+
|
|
391
545
|
# Serialize data
|
|
392
546
|
serialize_collection(record) # For collections
|
|
393
547
|
serialize_item(record) # For single items
|
|
394
|
-
|
|
548
|
+
|
|
395
549
|
# Check record type
|
|
396
550
|
collection? # true if record is a collection
|
|
397
551
|
single_item? # true if record is a single item
|
|
398
|
-
|
|
552
|
+
|
|
399
553
|
# Render JSON
|
|
400
554
|
render_json({ data: [], meta: {} })
|
|
401
555
|
end
|
|
@@ -419,14 +573,14 @@ class CategorizedResponder < JsonapiResponses::Responder
|
|
|
419
573
|
private
|
|
420
574
|
|
|
421
575
|
def structured_data?
|
|
422
|
-
record.is_a?(Array) &&
|
|
423
|
-
record.first.is_a?(Hash) &&
|
|
576
|
+
record.is_a?(Array) &&
|
|
577
|
+
record.first.is_a?(Hash) &&
|
|
424
578
|
record.first.key?(:category)
|
|
425
579
|
end
|
|
426
580
|
|
|
427
581
|
def group_by_category
|
|
428
582
|
categories = {}
|
|
429
|
-
|
|
583
|
+
|
|
430
584
|
serialize_collection(record).each do |item|
|
|
431
585
|
category_id = item.dig(:category, :id) || 'uncategorized'
|
|
432
586
|
categories[category_id] ||= {
|
|
@@ -435,7 +589,7 @@ class CategorizedResponder < JsonapiResponses::Responder
|
|
|
435
589
|
}
|
|
436
590
|
categories[category_id][:items] << item
|
|
437
591
|
end
|
|
438
|
-
|
|
592
|
+
|
|
439
593
|
categories.values.map do |group|
|
|
440
594
|
group.merge(count: group[:items].size)
|
|
441
595
|
end
|
|
@@ -445,12 +599,12 @@ end
|
|
|
445
599
|
|
|
446
600
|
### When to Use Each Approach
|
|
447
601
|
|
|
448
|
-
| Approach
|
|
449
|
-
|
|
450
|
-
| `map_response_action`
|
|
451
|
-
| `respond_for_*` methods | 1-2 custom actions with simple logic
|
|
452
|
-
| Custom Responders
|
|
453
|
-
| Metaprogramming
|
|
602
|
+
| Approach | Best For | Complexity |
|
|
603
|
+
| ----------------------- | ------------------------------------------- | ---------- |
|
|
604
|
+
| `map_response_action` | Simple actions similar to existing ones | Low |
|
|
605
|
+
| `respond_for_*` methods | 1-2 custom actions with simple logic | Medium |
|
|
606
|
+
| Custom Responders | 3+ custom actions or complex response logic | High |
|
|
607
|
+
| Metaprogramming | Batch generation of similar actions | High |
|
|
454
608
|
|
|
455
609
|
### Mixing Approaches
|
|
456
610
|
|
|
@@ -459,23 +613,23 @@ You can combine different approaches in the same controller:
|
|
|
459
613
|
```ruby
|
|
460
614
|
class Api::V1::ProductsController < ApplicationController
|
|
461
615
|
include JsonapiResponses::Respondable
|
|
462
|
-
|
|
616
|
+
|
|
463
617
|
# Map simple actions
|
|
464
618
|
map_response_action :public_index, to: :index
|
|
465
|
-
|
|
619
|
+
|
|
466
620
|
# Use responder for complex actions
|
|
467
621
|
def featured
|
|
468
622
|
@products = Product.featured
|
|
469
623
|
render_with(@products, responder: FeaturedResponder)
|
|
470
624
|
end
|
|
471
|
-
|
|
625
|
+
|
|
472
626
|
# Use custom method for one-off logic
|
|
473
627
|
def statistics
|
|
474
628
|
render_with(@stats)
|
|
475
629
|
end
|
|
476
|
-
|
|
630
|
+
|
|
477
631
|
private
|
|
478
|
-
|
|
632
|
+
|
|
479
633
|
def respond_for_statistics(record, serializer_class, context)
|
|
480
634
|
render json: { stats: record, generated_at: Time.current }
|
|
481
635
|
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# ApplicationResponder - Base class for all responders
|
|
2
|
+
#
|
|
3
|
+
# This follows Rails convention (like ApplicationRecord, ApplicationController)
|
|
4
|
+
# and Pundit pattern (like ApplicationPolicy).
|
|
5
|
+
#
|
|
6
|
+
# Create one responder per controller with multiple action methods inside.
|
|
7
|
+
#
|
|
8
|
+
# @example Creating a resource responder
|
|
9
|
+
# class ProductResponder < ApplicationResponder
|
|
10
|
+
# # GET /products/featured
|
|
11
|
+
# def featured
|
|
12
|
+
# render_collection_with_meta(
|
|
13
|
+
# type: 'featured',
|
|
14
|
+
# additional_meta: { category: params[:category_id] }
|
|
15
|
+
# )
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# # GET /products/popular
|
|
19
|
+
# def popular
|
|
20
|
+
# render_collection_with_meta(
|
|
21
|
+
# type: 'popular',
|
|
22
|
+
# additional_meta: { period: params[:period] || 'month' }
|
|
23
|
+
# )
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# @example Using in controller
|
|
28
|
+
# class ProductsController < ApplicationController
|
|
29
|
+
# def featured
|
|
30
|
+
# @products = Product.featured
|
|
31
|
+
# render_with(@products, responder: ProductResponder, action: :featured)
|
|
32
|
+
# end
|
|
33
|
+
# end
|
|
34
|
+
class ApplicationResponder < JsonapiResponses::Responder
|
|
35
|
+
# Common helper methods available to all responders
|
|
36
|
+
|
|
37
|
+
protected
|
|
38
|
+
|
|
39
|
+
# Render a standard collection with metadata
|
|
40
|
+
# @param type [String, Symbol] Type of collection (e.g., 'featured', 'popular')
|
|
41
|
+
# @param additional_meta [Hash] Additional metadata to include
|
|
42
|
+
def render_collection_with_meta(type: nil, additional_meta: {})
|
|
43
|
+
render_json({
|
|
44
|
+
data: serialize_collection(record),
|
|
45
|
+
meta: base_meta.merge({ type: type }.compact).merge(additional_meta)
|
|
46
|
+
})
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Render a single item with metadata
|
|
50
|
+
# @param additional_meta [Hash] Additional metadata to include
|
|
51
|
+
def render_item_with_meta(additional_meta: {})
|
|
52
|
+
render_json({
|
|
53
|
+
data: serialize_item(record),
|
|
54
|
+
meta: base_meta.merge(additional_meta)
|
|
55
|
+
})
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Render grouped/categorized data
|
|
59
|
+
# @param groups [Array, Hash] Pre-structured grouped data
|
|
60
|
+
def render_grouped_data(groups)
|
|
61
|
+
render_json(groups)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Base metadata common to all responses
|
|
65
|
+
# @return [Hash] Base metadata including timestamp and count
|
|
66
|
+
def base_meta
|
|
67
|
+
{
|
|
68
|
+
timestamp: Time.current.iso8601,
|
|
69
|
+
count: record_count
|
|
70
|
+
}.compact
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Get the count of records
|
|
74
|
+
# @return [Integer, nil] Count if collection, nil otherwise
|
|
75
|
+
def record_count
|
|
76
|
+
return nil unless collection?
|
|
77
|
+
record.respond_to?(:count) ? record.count : record.size
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Check if a parameter is present
|
|
81
|
+
# @param key [Symbol, String] Parameter key
|
|
82
|
+
# @return [Boolean]
|
|
83
|
+
def param_present?(key)
|
|
84
|
+
params[key].present?
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Get filters applied from params
|
|
88
|
+
# @param filter_keys [Array<Symbol>] Keys to check for filters
|
|
89
|
+
# @return [Hash, nil] Hash of applied filters or nil if none
|
|
90
|
+
def filters_applied(filter_keys = [:category_id, :level, :status, :sort_by, :limit])
|
|
91
|
+
filters = {}
|
|
92
|
+
|
|
93
|
+
filter_keys.each do |key|
|
|
94
|
+
filters[key] = params[key] if params[key].present?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
filters.empty? ? nil : filters
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# ApplicationSerializer - Base class for all serializers
|
|
2
|
+
#
|
|
3
|
+
# This follows Rails convention (like ApplicationRecord, ApplicationController).
|
|
4
|
+
# All your model serializers should inherit from this class.
|
|
5
|
+
#
|
|
6
|
+
# @example Creating a model serializer
|
|
7
|
+
# class ProductSerializer < ApplicationSerializer
|
|
8
|
+
# def serializable_hash
|
|
9
|
+
# case context[:view]
|
|
10
|
+
# when :summary
|
|
11
|
+
# summary_hash
|
|
12
|
+
# when :minimal
|
|
13
|
+
# minimal_hash
|
|
14
|
+
# else
|
|
15
|
+
# full_hash
|
|
16
|
+
# end
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# private
|
|
20
|
+
#
|
|
21
|
+
# def full_hash
|
|
22
|
+
# {
|
|
23
|
+
# id: resource.id,
|
|
24
|
+
# name: resource.name,
|
|
25
|
+
# description: resource.description,
|
|
26
|
+
# price: resource.price,
|
|
27
|
+
# created_at: resource.created_at
|
|
28
|
+
# }
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# def summary_hash
|
|
32
|
+
# {
|
|
33
|
+
# id: resource.id,
|
|
34
|
+
# name: resource.name,
|
|
35
|
+
# price: resource.price
|
|
36
|
+
# }
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# def minimal_hash
|
|
40
|
+
# {
|
|
41
|
+
# id: resource.id,
|
|
42
|
+
# name: resource.name
|
|
43
|
+
# }
|
|
44
|
+
# end
|
|
45
|
+
# end
|
|
46
|
+
class ApplicationSerializer
|
|
47
|
+
attr_reader :resource, :context
|
|
48
|
+
|
|
49
|
+
# Initialize serializer with resource and optional context
|
|
50
|
+
# @param resource [Object] The object to serialize
|
|
51
|
+
# @param context [Hash] Additional context (e.g., current_user, view type)
|
|
52
|
+
def initialize(resource, context = {})
|
|
53
|
+
@resource = resource
|
|
54
|
+
@context = context
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Override this method in your serializers
|
|
58
|
+
# @return [Hash] Serialized representation of the resource
|
|
59
|
+
def serializable_hash
|
|
60
|
+
raise NotImplementedError, "#{self.class.name} must implement #serializable_hash"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Access to current_user from context
|
|
64
|
+
# @return [User, nil]
|
|
65
|
+
def current_user
|
|
66
|
+
@context[:current_user]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Access to view type from context
|
|
70
|
+
# @return [Symbol, nil] e.g., :summary, :minimal, :full
|
|
71
|
+
def view
|
|
72
|
+
@context[:view]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Helper to serialize associations
|
|
76
|
+
# @param association [Object, Array] Association to serialize
|
|
77
|
+
# @param serializer_class [Class] Serializer class to use
|
|
78
|
+
# @return [Hash, Array<Hash>]
|
|
79
|
+
def serialize_association(association, serializer_class)
|
|
80
|
+
return nil if association.nil?
|
|
81
|
+
|
|
82
|
+
if association.respond_to?(:map)
|
|
83
|
+
association.map { |item| serializer_class.new(item, context).serializable_hash }
|
|
84
|
+
else
|
|
85
|
+
serializer_class.new(association, context).serializable_hash
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -212,7 +212,17 @@ module JsonapiResponses
|
|
|
212
212
|
end
|
|
213
213
|
|
|
214
214
|
def respond_for_index(record, serializer_class, context)
|
|
215
|
-
|
|
215
|
+
response = { data: serialize_collection(record, serializer_class, context) }
|
|
216
|
+
|
|
217
|
+
# Auto-detect pagination and add meta if available
|
|
218
|
+
if paginated?(record)
|
|
219
|
+
response[:meta] = pagination_meta(record, context)
|
|
220
|
+
elsif context[:meta]
|
|
221
|
+
# Allow manual meta from context
|
|
222
|
+
response[:meta] = context[:meta]
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
render json: response
|
|
216
226
|
end
|
|
217
227
|
|
|
218
228
|
def respond_for_show(record, serializer_class, context)
|
|
@@ -259,5 +269,25 @@ module JsonapiResponses
|
|
|
259
269
|
]
|
|
260
270
|
}, status: :bad_request
|
|
261
271
|
end
|
|
272
|
+
|
|
273
|
+
# Check if record is paginated (Kaminari or WillPaginate support)
|
|
274
|
+
def paginated?(record)
|
|
275
|
+
record.respond_to?(:current_page) &&
|
|
276
|
+
record.respond_to?(:total_pages) &&
|
|
277
|
+
record.respond_to?(:total_count)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Extract pagination metadata from paginated record
|
|
281
|
+
def pagination_meta(record, context = {})
|
|
282
|
+
base_meta = {
|
|
283
|
+
current_page: record.current_page,
|
|
284
|
+
total_pages: record.total_pages,
|
|
285
|
+
total_count: record.total_count,
|
|
286
|
+
per_page: record.try(:limit_value) || record.try(:per_page) || context[:per_page]
|
|
287
|
+
}.compact
|
|
288
|
+
|
|
289
|
+
# Merge with any additional meta from context
|
|
290
|
+
context[:meta] ? base_meta.merge(context[:meta]) : base_meta
|
|
291
|
+
end
|
|
262
292
|
end
|
|
263
293
|
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
module JsonapiResponses
|
|
2
|
+
# Base class for custom responders
|
|
3
|
+
# Responders encapsulate response logic for custom actions,
|
|
4
|
+
# keeping controllers clean and promoting reusability.
|
|
5
|
+
#
|
|
6
|
+
# @example Basic usage
|
|
7
|
+
# class FeaturedResponder < JsonapiResponses::Responder
|
|
8
|
+
# def render
|
|
9
|
+
# controller.render json: {
|
|
10
|
+
# data: serialize_collection(record),
|
|
11
|
+
# meta: {
|
|
12
|
+
# type: 'featured',
|
|
13
|
+
# count: record.count
|
|
14
|
+
# }
|
|
15
|
+
# }
|
|
16
|
+
# end
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example Using in controller
|
|
20
|
+
# def featured
|
|
21
|
+
# @academies = Academy.featured
|
|
22
|
+
# render_with(@academies, responder: FeaturedResponder)
|
|
23
|
+
# end
|
|
24
|
+
class Responder
|
|
25
|
+
attr_reader :controller, :record, :serializer_class, :context
|
|
26
|
+
|
|
27
|
+
# @param controller [ActionController::Base] The controller instance
|
|
28
|
+
# @param record [Object, Array, ActiveRecord::Relation] The record(s) to serialize
|
|
29
|
+
# @param serializer_class [Class] The serializer class to use
|
|
30
|
+
# @param context [Hash] Additional context for serialization
|
|
31
|
+
def initialize(controller, record, serializer_class, context = {})
|
|
32
|
+
@controller = controller
|
|
33
|
+
@record = record
|
|
34
|
+
@serializer_class = serializer_class
|
|
35
|
+
@context = context
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Render the response. Must be implemented by subclasses.
|
|
39
|
+
# @raise [NotImplementedError] if not implemented in subclass
|
|
40
|
+
def render
|
|
41
|
+
raise NotImplementedError, "#{self.class.name} must implement #render method"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
protected
|
|
45
|
+
|
|
46
|
+
# Serialize a collection of records
|
|
47
|
+
# @param records [Array, ActiveRecord::Relation] Records to serialize
|
|
48
|
+
# @param custom_serializer [Class, nil] Optional custom serializer
|
|
49
|
+
# @param custom_context [Hash, nil] Optional custom context
|
|
50
|
+
# @return [Array<Hash>] Serialized collection
|
|
51
|
+
def serialize_collection(records = nil, custom_serializer = nil, custom_context = nil)
|
|
52
|
+
records ||= record
|
|
53
|
+
serializer = custom_serializer || serializer_class
|
|
54
|
+
ctx = custom_context || context
|
|
55
|
+
|
|
56
|
+
controller.send(:serialize_collection, records, serializer, ctx)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Serialize a single record
|
|
60
|
+
# @param item [Object] Record to serialize
|
|
61
|
+
# @param custom_serializer [Class, nil] Optional custom serializer
|
|
62
|
+
# @param custom_context [Hash, nil] Optional custom context
|
|
63
|
+
# @return [Hash] Serialized record
|
|
64
|
+
def serialize_item(item = nil, custom_serializer = nil, custom_context = nil)
|
|
65
|
+
item ||= record
|
|
66
|
+
serializer = custom_serializer || serializer_class
|
|
67
|
+
ctx = custom_context || context
|
|
68
|
+
|
|
69
|
+
controller.send(:serialize_item, item, serializer, ctx)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Access to params from the controller
|
|
73
|
+
# @return [ActionController::Parameters]
|
|
74
|
+
def params
|
|
75
|
+
controller.params
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Access to current_user from the controller (if available)
|
|
79
|
+
# @return [Object, nil]
|
|
80
|
+
def current_user
|
|
81
|
+
controller.respond_to?(:current_user, true) ? controller.send(:current_user) : nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Helper to render JSON directly through the controller
|
|
85
|
+
# @param data [Hash] Data to render
|
|
86
|
+
# @param options [Hash] Additional render options (status, etc.)
|
|
87
|
+
def render_json(data, options = {})
|
|
88
|
+
controller.render({ json: data }.merge(options))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Helper to check if record is a collection
|
|
92
|
+
# @return [Boolean]
|
|
93
|
+
def collection?
|
|
94
|
+
record.is_a?(Array) ||
|
|
95
|
+
record.is_a?(ActiveRecord::Relation) ||
|
|
96
|
+
(record.respond_to?(:to_a) && !record.is_a?(Hash))
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Helper to check if record is a single item
|
|
100
|
+
# @return [Boolean]
|
|
101
|
+
def single_item?
|
|
102
|
+
!collection?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Check if record is paginated (Kaminari or WillPaginate support)
|
|
106
|
+
# @return [Boolean]
|
|
107
|
+
def paginated?
|
|
108
|
+
record.respond_to?(:current_page) &&
|
|
109
|
+
record.respond_to?(:total_pages) &&
|
|
110
|
+
record.respond_to?(:total_count)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Extract pagination metadata from paginated record
|
|
114
|
+
# @return [Hash, nil] Pagination metadata or nil if not paginated
|
|
115
|
+
def pagination_meta
|
|
116
|
+
return nil unless paginated?
|
|
117
|
+
|
|
118
|
+
{
|
|
119
|
+
current_page: record.current_page,
|
|
120
|
+
total_pages: record.total_pages,
|
|
121
|
+
total_count: record.total_count,
|
|
122
|
+
per_page: record.try(:limit_value) || record.try(:per_page)
|
|
123
|
+
}.compact
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Render collection with automatic pagination support
|
|
127
|
+
# @param records [Array, ActiveRecord::Relation] Records to render
|
|
128
|
+
# @param additional_meta [Hash] Additional metadata to include
|
|
129
|
+
def render_collection_with_meta(records = nil, additional_meta = {})
|
|
130
|
+
records ||= record
|
|
131
|
+
response = { data: serialize_collection(records) }
|
|
132
|
+
|
|
133
|
+
# Auto-detect pagination
|
|
134
|
+
if records.respond_to?(:current_page)
|
|
135
|
+
meta = {
|
|
136
|
+
current_page: records.current_page,
|
|
137
|
+
total_pages: records.total_pages,
|
|
138
|
+
total_count: records.total_count,
|
|
139
|
+
per_page: records.try(:limit_value) || records.try(:per_page)
|
|
140
|
+
}.compact
|
|
141
|
+
response[:meta] = meta.merge(additional_meta)
|
|
142
|
+
elsif additional_meta.any?
|
|
143
|
+
response[:meta] = additional_meta
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
render_json(response)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jsonapi_responses
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Oscar Ortega
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-11-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: JsonapiResponses simplifies API response handling by allowing multiple
|
|
14
14
|
response formats from a single endpoint, improving performance and reducing endpoint
|
|
@@ -28,11 +28,14 @@ files:
|
|
|
28
28
|
- README.md
|
|
29
29
|
- Rakefile
|
|
30
30
|
- app/models/item.rb
|
|
31
|
+
- app/responders/application_responder.rb
|
|
32
|
+
- app/serializers/application_serializer.rb
|
|
31
33
|
- app/serializers/item_serializer.rb
|
|
32
34
|
- config/database.yml
|
|
33
35
|
- lib/jsonapi_responses.rb
|
|
34
36
|
- lib/jsonapi_responses/engine.rb
|
|
35
37
|
- lib/jsonapi_responses/respondable.rb
|
|
38
|
+
- lib/jsonapi_responses/responder.rb
|
|
36
39
|
- lib/jsonapi_responses/serializable.rb
|
|
37
40
|
- lib/jsonapi_responses/user_context_provider.rb
|
|
38
41
|
- lib/jsonapi_responses/version.rb
|