jsonapi_responses 1.0.0 → 1.0.1

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: '0699dc3667f281c640290126057003d615a2925420486b95bf4c6b6ca0498493'
4
- data.tar.gz: 2ad39e4993c900b4d675fcba5cf45065d7b467a34f06c6e99d7e6a246dc87010
3
+ metadata.gz: 9a729df17b8e8b391b9813a8a5c33234f9ff41893b52e4e25709a0df6e424e67
4
+ data.tar.gz: 533245b43a98e243764f278a5a0290ca7e6d6d35307a068393e2db0492432f9f
5
5
  SHA512:
6
- metadata.gz: c9272215b1f4238ab65d30e961a928d1d20307287e1d3b82a9dbbe79f0f08c63567973e3b38cde93777064f20c8d0d977541910cf2b3433604eef0891a662e3b
7
- data.tar.gz: 6b2f625abcbe757534d222f16a1edcbbdd24f1947f5071a6f26c7ad1b2745fe188c51f54d69aaf777124e8af3eacb33a8769a9bc701c3538cf95623fbdc013f7
6
+ metadata.gz: 4abb25e296611f19b14379bb81c463d83210028ed4115453f100afa493d817234ea754e3f19211f7008bb4ab35347e4ee02516e26c44644260864aa68fb6cdba
7
+ data.tar.gz: 3a8e7416b1017a718b505e041287a75512ec3aec5d73ed60e132c16d1c204a688cf135004c30ebb45c79df731c4be6f22d6fe76f2f3cf3e916c9ed10380129da
data/CHANGELOG.md CHANGED
@@ -7,11 +7,13 @@
7
7
  This is a major release with significant architectural improvements and breaking changes.
8
8
 
9
9
  ### Breaking Changes
10
+
10
11
  - **Renamed `BaseSerializer`** → `ApplicationSerializer` (follows Rails convention)
11
12
  - **Renamed `BaseResponder`** → `ApplicationResponder` (follows Rails convention like ApplicationPolicy)
12
13
  - Responders now follow **Pundit-style pattern**: one responder class per controller with multiple action methods
13
14
 
14
15
  ### Added
16
+
15
17
  - **Pundit-Style Responders**: One responder class per controller (e.g., `AcademyResponder`, `CourseResponder`)
16
18
  - **Action-based method calls**: Use `render_with(@records, responder: AcademyResponder, action: :featured)`
17
19
  - **ApplicationResponder base class**: With common helpers like `render_collection_with_meta`, `base_meta`, `filters_applied`
@@ -43,7 +45,7 @@ class AcademyResponder < ApplicationResponder
43
45
  def featured # ← Method instead of render
44
46
  # ...
45
47
  end
46
-
48
+
47
49
  def popular
48
50
  # ...
49
51
  end
@@ -63,6 +65,7 @@ render_with(@academies, responder: AcademyResponder, action: :featured)
63
65
  #### 3. Consolidate Responders
64
66
 
65
67
  Instead of one file per action:
68
+
66
69
  ```
67
70
  # Before
68
71
  app/responders/academy_featured_responder.rb
@@ -74,7 +77,8 @@ app/responders/academy_responder.rb # All methods inside
74
77
  ```
75
78
 
76
79
  ### Improved
77
- - Better alignment with Rails naming conventions (Application* prefix)
80
+
81
+ - Better alignment with Rails naming conventions (Application\* prefix)
78
82
  - Reduced file proliferation (3-4 responder files instead of 20+)
79
83
  - Easier to maintain and understand (Pundit-style pattern)
80
84
  - Better code organization and discoverability
@@ -82,18 +86,18 @@ app/responders/academy_responder.rb # All methods inside
82
86
  ## [0.3.0] - 2025-10-07
83
87
 
84
88
  ### Added
89
+
85
90
  - **Custom Responders System**: New architecture for handling complex custom actions
86
91
  - Added `JsonapiResponses::Responder` base class for creating dedicated responder objects
87
92
  - Responders encapsulate response logic, keeping controllers clean and promoting reusability
88
93
  - Full access to controller context, serialization helpers, and request params
89
-
90
94
  - **Responder Base Class Features**:
91
95
  - `serialize_collection` and `serialize_item` helpers for data serialization
92
96
  - `render_json` helper for consistent JSON rendering
93
97
  - `collection?` and `single_item?` type checking utilities
94
98
  - Access to `params`, `current_user`, `controller`, `record`, `serializer_class`, and `context`
95
-
96
99
  - **Enhanced render_with**:
100
+
97
101
  - New `responder:` parameter to use custom responder classes
98
102
  - New `serializer:` parameter to override default serializer detection
99
103
  - Example: `render_with(@records, responder: FeaturedResponder, serializer: CustomSerializer)`
@@ -104,17 +108,20 @@ app/responders/academy_responder.rb # All methods inside
104
108
  - `CategorizedResponder` - For grouped/categorized responses
105
109
 
106
110
  ### Changed
111
+
107
112
  - Updated `lib/jsonapi_responses.rb` to require `responder.rb`
108
113
  - Enhanced `Respondable` module to support responder classes
109
114
  - Improved documentation with comprehensive Responder guide
110
115
 
111
116
  ### Documentation
117
+
112
118
  - Added "Custom Responders" section to README with complete examples
113
119
  - Added comparison table: when to use each approach (mapping vs methods vs responders)
114
120
  - Added `RESPONDERS_FEATURE.md` with complete implementation guide
115
121
  - Documented Responder API and best practices
116
122
 
117
123
  ### Benefits
124
+
118
125
  - **Separation of Concerns**: Response logic separated from controllers
119
126
  - **Reusability**: Same responder can be used across multiple controllers
120
127
  - **Testability**: Test response logic independently from controllers
@@ -123,31 +130,35 @@ app/responders/academy_responder.rb # All methods inside
123
130
  ## [0.2.0] - 2025-10-06
124
131
 
125
132
  ### Added
133
+
126
134
  - **Custom Actions Support**: Support for custom actions beyond standard CRUD (index, show, create, update, destroy)
127
135
  - **Explicit Action Mapping**: `map_response_action` and `map_response_actions` for mapping custom actions to existing responses
128
136
  - **Metaprogramming Support**: Dynamic method generation for response handlers
129
137
  - `define_response_for`: Define custom response methods using blocks
130
138
  - `define_responses_for`: Define same behavior for multiple actions in batch
131
139
  - `define_crud_responses`: Intelligent CRUD responses with dynamic context
132
- - `generate_rest_responses`: Auto-generate namespaced REST responses (public_, admin_, etc.)
140
+ - `generate_rest_responses`: Auto-generate namespaced REST responses (public*, admin*, etc.)
133
141
  - **Dynamic Context**: Support for lambdas/procs in context generation with `instance_eval`
134
142
  - **Method Introspection**: `response_definitions` class attribute for debugging generated methods
135
143
  - **Enhanced Error Messages**: Detailed error responses with suggestions when actions are not supported
136
144
 
137
145
  ### Changed
146
+
138
147
  - **Breaking**: Removed automatic fallback behavior for action mapping (by design - explicit is better than implicit)
139
148
  - **Improved**: `render_invalid_action` now provides detailed error information with actionable suggestions
140
149
  - **Enhanced**: Better error handling with specific guidance on how to implement missing methods
141
150
 
142
151
  ### Technical Details
152
+
143
153
  - Added `class_attribute :response_definitions` for storing method definitions
144
154
  - Enhanced `render_with` method resolution to support custom and mapped actions
145
155
  - All generated methods are properly marked as private
146
156
  - Full backward compatibility maintained for existing CRUD operations
147
157
 
148
158
  ### Documentation
159
+
149
160
  - Added comprehensive documentation in CUSTOM_ACTIONS.md
150
- - Added metaprogramming guide in METAPROGRAMMING.md
161
+ - Added metaprogramming guide in METAPROGRAMMING.md
151
162
  - Added practical implementation example in PRACTICAL_EXAMPLE.md
152
163
  - Updated README with new functionality overview
153
164
 
data/README.md CHANGED
@@ -147,6 +147,7 @@ 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
@@ -165,14 +166,15 @@ Beyond the standard CRUD actions (index, show, create, update, destroy), you can
165
166
  ### Basic Usage
166
167
 
167
168
  **Option 1: Map to existing actions**
169
+
168
170
  ```ruby
169
171
  class Api::V1::CoursesController < ApplicationController
170
172
  include JsonapiResponses::Respondable
171
-
173
+
172
174
  # Map custom actions to existing response methods
173
175
  map_response_action :public_index, to: :index
174
176
  map_response_action :public_show, to: :show
175
-
177
+
176
178
  def public_index
177
179
  # Your logic here
178
180
  render_with(@courses) # Will use respond_for_index
@@ -181,17 +183,18 @@ end
181
183
  ```
182
184
 
183
185
  **Option 2: Define custom response methods**
186
+
184
187
  ```ruby
185
188
  class Api::V1::CoursesController < ApplicationController
186
189
  include JsonapiResponses::Respondable
187
-
190
+
188
191
  def dashboard_stats
189
192
  # Your logic here
190
193
  render_with(@stats)
191
194
  end
192
-
195
+
193
196
  private
194
-
197
+
195
198
  def respond_for_dashboard_stats(record, serializer_class, context)
196
199
  render json: {
197
200
  data: record,
@@ -202,17 +205,18 @@ end
202
205
  ```
203
206
 
204
207
  **Option 3: Metaprogramming (Recommended for complex scenarios)**
208
+
205
209
  ```ruby
206
210
  class Api::V1::CoursesController < ApplicationController
207
211
  include JsonapiResponses::Respondable
208
-
212
+
209
213
  # Generate methods automatically
210
214
  generate_rest_responses(
211
215
  namespace: 'public',
212
216
  actions: [:index, :show],
213
217
  context: { access_level: 'public' }
214
218
  )
215
-
219
+
216
220
  # Define similar responses in batch
217
221
  define_responses_for [:export_csv, :export_pdf] do |record, serializer_class, context|
218
222
  format = action_name.to_s.split('_').last
@@ -253,26 +257,26 @@ app/responders/
253
257
  # app/responders/application_responder.rb
254
258
  class ApplicationResponder < JsonapiResponses::Responder
255
259
  protected
256
-
260
+
257
261
  def render_collection_with_meta(type: nil, additional_meta: {})
258
262
  render_json({
259
263
  data: serialize_collection(record),
260
264
  meta: base_meta.merge({ type: type }.compact).merge(additional_meta)
261
265
  })
262
266
  end
263
-
267
+
264
268
  def base_meta
265
269
  {
266
270
  timestamp: Time.current.iso8601,
267
271
  count: record_count
268
272
  }.compact
269
273
  end
270
-
274
+
271
275
  def record_count
272
276
  return nil unless collection?
273
277
  record.respond_to?(:count) ? record.count : record.size
274
278
  end
275
-
279
+
276
280
  def filters_applied
277
281
  filter_keys = [:category_id, :level, :status]
278
282
  filters = {}
@@ -287,7 +291,7 @@ end
287
291
  ```ruby
288
292
  # app/responders/academy_responder.rb
289
293
  class AcademyResponder < ApplicationResponder
290
-
294
+
291
295
  # GET /api/v1/academies/featured
292
296
  def featured
293
297
  if params[:category_id].present?
@@ -296,7 +300,7 @@ class AcademyResponder < ApplicationResponder
296
300
  render_all_featured
297
301
  end
298
302
  end
299
-
303
+
300
304
  # GET /api/v1/academies/popular
301
305
  def popular
302
306
  render_collection_with_meta(
@@ -307,7 +311,7 @@ class AcademyResponder < ApplicationResponder
307
311
  }
308
312
  )
309
313
  end
310
-
314
+
311
315
  # GET /api/v1/academies/recommended
312
316
  def recommended
313
317
  render_collection_with_meta(
@@ -318,9 +322,9 @@ class AcademyResponder < ApplicationResponder
318
322
  }
319
323
  )
320
324
  end
321
-
325
+
322
326
  private
323
-
327
+
324
328
  def render_filtered_featured
325
329
  render_json({
326
330
  data: serialize_collection(record),
@@ -330,7 +334,7 @@ class AcademyResponder < ApplicationResponder
330
334
  }
331
335
  })
332
336
  end
333
-
337
+
334
338
  def render_all_featured
335
339
  render_collection_with_meta(type: 'featured')
336
340
  end
@@ -347,12 +351,12 @@ class Api::V1::AcademiesController < ApplicationController
347
351
  @academies = load_featured_academies
348
352
  render_with(@academies, responder: AcademyResponder, action: :featured)
349
353
  end
350
-
354
+
351
355
  def popular
352
356
  @academies = Academy.popular.limit(20)
353
357
  render_with(@academies, responder: AcademyResponder, action: :popular)
354
358
  end
355
-
359
+
356
360
  def recommended
357
361
  @academies = Academy.recommended_for(current_user)
358
362
  render_with(@academies, responder: AcademyResponder, action: :recommended)
@@ -363,12 +367,14 @@ end
363
367
  ### Benefits of This Pattern
364
368
 
365
369
  **Like Pundit Policies:**
370
+
366
371
  - ✅ One file per controller (not per action)
367
372
  - ✅ All related logic in one place
368
373
  - ✅ Easy to find and maintain
369
374
  - ✅ Shared helpers in base class
370
375
 
371
376
  **Example Structure:**
377
+
372
378
  ```
373
379
  AcademiesController → AcademyResponder (featured, popular, recommended)
374
380
  CoursesController → CourseResponder (featured, search, progress)
@@ -384,18 +390,18 @@ class MyCustomResponder < JsonapiResponses::Responder
384
390
  def render
385
391
  # Access to controller instance
386
392
  controller.current_user
387
-
393
+
388
394
  # Access to params
389
395
  params[:filter]
390
-
396
+
391
397
  # Serialize data
392
398
  serialize_collection(record) # For collections
393
399
  serialize_item(record) # For single items
394
-
400
+
395
401
  # Check record type
396
402
  collection? # true if record is a collection
397
403
  single_item? # true if record is a single item
398
-
404
+
399
405
  # Render JSON
400
406
  render_json({ data: [], meta: {} })
401
407
  end
@@ -419,14 +425,14 @@ class CategorizedResponder < JsonapiResponses::Responder
419
425
  private
420
426
 
421
427
  def structured_data?
422
- record.is_a?(Array) &&
423
- record.first.is_a?(Hash) &&
428
+ record.is_a?(Array) &&
429
+ record.first.is_a?(Hash) &&
424
430
  record.first.key?(:category)
425
431
  end
426
432
 
427
433
  def group_by_category
428
434
  categories = {}
429
-
435
+
430
436
  serialize_collection(record).each do |item|
431
437
  category_id = item.dig(:category, :id) || 'uncategorized'
432
438
  categories[category_id] ||= {
@@ -435,7 +441,7 @@ class CategorizedResponder < JsonapiResponses::Responder
435
441
  }
436
442
  categories[category_id][:items] << item
437
443
  end
438
-
444
+
439
445
  categories.values.map do |group|
440
446
  group.merge(count: group[:items].size)
441
447
  end
@@ -445,12 +451,12 @@ end
445
451
 
446
452
  ### When to Use Each Approach
447
453
 
448
- | Approach | Best For | Complexity |
449
- |----------|----------|------------|
450
- | `map_response_action` | Simple actions similar to existing ones | Low |
451
- | `respond_for_*` methods | 1-2 custom actions with simple logic | Medium |
452
- | Custom Responders | 3+ custom actions or complex response logic | High |
453
- | Metaprogramming | Batch generation of similar actions | High |
454
+ | Approach | Best For | Complexity |
455
+ | ----------------------- | ------------------------------------------- | ---------- |
456
+ | `map_response_action` | Simple actions similar to existing ones | Low |
457
+ | `respond_for_*` methods | 1-2 custom actions with simple logic | Medium |
458
+ | Custom Responders | 3+ custom actions or complex response logic | High |
459
+ | Metaprogramming | Batch generation of similar actions | High |
454
460
 
455
461
  ### Mixing Approaches
456
462
 
@@ -459,23 +465,23 @@ You can combine different approaches in the same controller:
459
465
  ```ruby
460
466
  class Api::V1::ProductsController < ApplicationController
461
467
  include JsonapiResponses::Respondable
462
-
468
+
463
469
  # Map simple actions
464
470
  map_response_action :public_index, to: :index
465
-
471
+
466
472
  # Use responder for complex actions
467
473
  def featured
468
474
  @products = Product.featured
469
475
  render_with(@products, responder: FeaturedResponder)
470
476
  end
471
-
477
+
472
478
  # Use custom method for one-off logic
473
479
  def statistics
474
480
  render_with(@stats)
475
481
  end
476
-
482
+
477
483
  private
478
-
484
+
479
485
  def respond_for_statistics(record, serializer_class, context)
480
486
  render json: { stats: record, generated_at: Time.current }
481
487
  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
@@ -0,0 +1,105 @@
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
+ end
105
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JsonapiResponses
4
- VERSION = '1.0.0'.freeze
4
+ VERSION = '1.0.1'.freeze
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonapi_responses
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oscar Ortega
@@ -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