respondo 0.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,94 +1,152 @@
1
- # frozen_string_literal: true
1
+ # # frozen_string_literal: true
2
2
 
3
- module Respondo
4
- # Extracts pagination metadata from Kaminari or Pagy collection objects.
5
- #
6
- # Returned hash shape (always the same regardless of pagination library):
7
- # {
8
- # current_page: Integer,
9
- # per_page: Integer,
10
- # total_pages: Integer,
11
- # total_count: Integer,
12
- # next_page: Integer | nil,
13
- # prev_page: Integer | nil
14
- # }
15
- module Pagination
16
- module_function
3
+ # module Respondo
4
+ # # Extracts pagination metadata from Kaminari, Pagy, or WillPaginate collections.
5
+ # #
6
+ # # --- How pagination works in Respondo ---
7
+ # #
8
+ # # Respondo does NOT paginate data for you. Pagination is always performed by
9
+ # # your chosen library (Kaminari, Pagy, or WillPaginate) in your controller
10
+ # # BEFORE you call render_success / render_ok.
11
+ # #
12
+ # # Respondo's role is purely to DETECT that the collection is paginated and
13
+ # # EXTRACT the metadata so it appears in the `meta.pagination` block.
14
+ # #
15
+ # # --- Usage patterns by library ---
16
+ # #
17
+ # # Kaminari (pagination lives on the collection itself):
18
+ # # @users = User.page(params[:page]).per(params[:per_page] || 10)
19
+ # # render_ok(data: @users) # ← just pass the collection; Respondo detects Kaminari
20
+ # #
21
+ # # WillPaginate (same — pagination lives on the collection):
22
+ # # @users = User.paginate(page: params[:page], per_page: 10)
23
+ # # render_ok(data: @users) # ← same pattern
24
+ # #
25
+ # # Pagy (metadata lives on a SEPARATE Pagy object, not the collection):
26
+ # # @pagy, @users = pagy(User.all, items: 10)
27
+ # # render_ok(data: @users, pagy: @pagy) # ← pass the Pagy object explicitly
28
+ # #
29
+ # # Alternatively, if you decorate your collection with pagy_metadata:
30
+ # # render_ok(data: @pagy) # ← pass the Pagy object as data (unusual)
31
+ # #
32
+ # # --- Returned hash shape (same regardless of library) ---
33
+ # # {
34
+ # # current_page: Integer,
35
+ # # per_page: Integer,
36
+ # # total_pages: Integer,
37
+ # # total_count: Integer,
38
+ # # next_page: Integer | nil,
39
+ # # prev_page: Integer | nil
40
+ # # }
41
+ # #
42
+ # # --- Disabling pagination meta ---
43
+ # # Pass pagination: false to any render_* helper to suppress the block entirely:
44
+ # # render_ok(data: @users, pagination: false)
45
+ # #
46
+ # module Pagination
47
+ # module_function
17
48
 
18
- # @param collection [Object] any object — returns nil if not a paginated collection
19
- # @return [Hash, nil]
20
- def extract(collection)
21
- return nil if collection.nil?
49
+ # # @param collection [Object] any object — returns nil if not a paginated collection
50
+ # # @return [Hash, nil]
51
+ # def extract(collection)
52
+ # return nil if collection.nil?
22
53
 
23
- if pagy?(collection)
24
- from_pagy(collection)
25
- elsif kaminari?(collection)
26
- from_kaminari(collection)
27
- elsif will_paginate?(collection)
28
- from_will_paginate(collection)
29
- else
30
- nil
31
- end
32
- end
54
+ # if pagy?(collection)
55
+ # from_pagy(collection)
56
+ # elsif kaminari?(collection)
57
+ # from_kaminari(collection)
58
+ # elsif will_paginate?(collection)
59
+ # from_will_paginate(collection)
60
+ # else
61
+ # nil
62
+ # end
63
+ # end
33
64
 
34
- private
65
+ # private
35
66
 
36
- module_function
67
+ # module_function
37
68
 
38
- # ----- Pagy ---------------------------------------------------------------
39
- # Pagy stores metadata on a separate Pagy object, not the collection itself.
40
- # We support both: passing the Pagy object directly, or a collection that
41
- # has been decorated with pagy metadata via pagy_metadata.
42
- def pagy?(object)
43
- defined?(Pagy) && object.is_a?(Pagy)
44
- end
69
+ # # -------------------------------------------------------------------------
70
+ # # Pagy
71
+ # # -------------------------------------------------------------------------
72
+ # # Pagy stores metadata on a SEPARATE Pagy object, not the collection.
73
+ # # You must pass it explicitly via the `pagy:` keyword in render_success/render_ok.
74
+ # #
75
+ # # Example in controller:
76
+ # # @pagy, @records = pagy(User.all, items: 10)
77
+ # # render_ok(data: @records, pagy: @pagy)
78
+ # #
79
+ # def pagy?(object)
80
+ # defined?(Pagy) && object.is_a?(Pagy)
81
+ # end
45
82
 
46
- def from_pagy(pagy)
47
- {
48
- current_page: pagy.page,
49
- per_page: pagy.items,
50
- total_pages: pagy.pages,
51
- total_count: pagy.count,
52
- next_page: pagy.next,
53
- prev_page: pagy.prev
54
- }
55
- end
83
+ # def from_pagy(pagy)
84
+ # {
85
+ # current_page: pagy.page,
86
+ # per_page: pagy.items,
87
+ # total_pages: pagy.pages,
88
+ # total_count: pagy.count,
89
+ # next_page: pagy.next,
90
+ # prev_page: pagy.prev
91
+ # }
92
+ # end
56
93
 
57
- # ----- Kaminari -----------------------------------------------------------
58
- def kaminari?(object)
59
- object.respond_to?(:current_page) &&
60
- object.respond_to?(:total_pages) &&
61
- object.respond_to?(:limit_value)
62
- end
94
+ # # -------------------------------------------------------------------------
95
+ # # Kaminari
96
+ # # -------------------------------------------------------------------------
97
+ # # Kaminari attaches pagination directly to the ActiveRecord relation.
98
+ # # No extra argument needed — just pass the collection.
99
+ # #
100
+ # # Example in controller:
101
+ # # @records = User.page(params[:page]).per(10)
102
+ # # render_ok(data: @records)
103
+ # #
104
+ # def kaminari?(object)
105
+ # object.respond_to?(:current_page) &&
106
+ # object.respond_to?(:total_pages) &&
107
+ # object.respond_to?(:limit_value)
108
+ # end
63
109
 
64
- def from_kaminari(collection)
65
- {
66
- current_page: collection.current_page,
67
- per_page: collection.limit_value,
68
- total_pages: collection.total_pages,
69
- total_count: collection.total_count,
70
- next_page: collection.next_page,
71
- prev_page: collection.prev_page
72
- }
73
- end
110
+ # def from_kaminari(collection)
111
+ # {
112
+ # current_page: collection.current_page,
113
+ # per_page: collection.limit_value,
114
+ # total_pages: collection.total_pages,
115
+ # total_count: collection.total_count,
116
+ # next_page: collection.next_page,
117
+ # prev_page: collection.prev_page
118
+ # }
119
+ # end
74
120
 
75
- # ----- WillPaginate -------------------------------------------------------
76
- def will_paginate?(object)
77
- object.respond_to?(:current_page) &&
78
- object.respond_to?(:total_pages) &&
79
- object.respond_to?(:per_page) &&
80
- !object.respond_to?(:limit_value) # distinguishes from Kaminari
81
- end
121
+ # # -------------------------------------------------------------------------
122
+ # # WillPaginate
123
+ # # -------------------------------------------------------------------------
124
+ # # WillPaginate also attaches pagination to the collection.
125
+ # # No extra argument needed — just pass the collection.
126
+ # #
127
+ # # Example in controller:
128
+ # # @records = User.paginate(page: params[:page], per_page: 10)
129
+ # # render_ok(data: @records)
130
+ # #
131
+ # # We distinguish WillPaginate from Kaminari by the absence of #limit_value.
132
+ # #
133
+ # def will_paginate?(object)
134
+ # object.respond_to?(:current_page) &&
135
+ # object.respond_to?(:total_pages) &&
136
+ # object.respond_to?(:per_page) &&
137
+ # !object.respond_to?(:limit_value)
138
+ # end
139
+
140
+ # def from_will_paginate(collection)
141
+ # {
142
+ # current_page: collection.current_page,
143
+ # per_page: collection.per_page,
144
+ # total_pages: collection.total_pages,
145
+ # total_count: collection.total_entries,
146
+ # next_page: collection.next_page,
147
+ # prev_page: collection.previous_page
148
+ # }
149
+ # end
150
+ # end
151
+ # end
82
152
 
83
- def from_will_paginate(collection)
84
- {
85
- current_page: collection.current_page,
86
- per_page: collection.per_page,
87
- total_pages: collection.total_pages,
88
- total_count: collection.total_entries,
89
- next_page: collection.next_page,
90
- prev_page: collection.previous_page
91
- }
92
- end
93
- end
94
- end
@@ -17,24 +17,24 @@ module Respondo
17
17
  # { timestamp: ISO8601 String }
18
18
  # Plus pagination keys when pagination: true and the data is a paginated collection.
19
19
  # Plus request_id when config.include_request_id is true.
20
+ # Plus pagination when a pagination hash is supplied by the caller.
20
21
  class ResponseBuilder
21
22
  # @param success [Boolean]
22
23
  # @param data [Object] anything — serialized automatically
23
24
  # @param message [String]
24
25
  # @param meta [Hash] caller-supplied extra meta (merged in)
25
26
  # @param errors [Hash] field-level errors (for 422 responses)
26
- # @param pagy [Pagy] optional Pagy object for pagination meta
27
- # @param pagination [Boolean] true = include pagination meta (default),
28
- # false = always suppress pagination meta
27
+ # @param pagination [Hash, nil] plain pagination hash supplied by the caller, e.g.
28
+ # { current_page: 1, per_page: 25, total_pages: 4,
29
+ # total_count: 100, next_page: 2, prev_page: nil }
29
30
  # @param request [Object] ActionDispatch::Request (for request_id)
30
31
  def initialize(success:, data: nil, message: nil, meta: {}, errors: nil,
31
- pagy: nil, pagination: true, request: nil)
32
+ pagination: nil, request: nil)
32
33
  @success = success
33
34
  @raw_data = data
34
35
  @message = message
35
36
  @extra_meta = meta || {}
36
37
  @errors = errors
37
- @pagy = pagy
38
38
  @pagination = pagination
39
39
  @request = request
40
40
  end
@@ -69,27 +69,56 @@ module Respondo
69
69
  end
70
70
 
71
71
  def build_meta
72
- meta = { timestamp: current_timestamp }
73
-
74
- # Only extract pagination when caller has not explicitly disabled it
75
- if @pagination
76
- pagination = if @pagy
77
- Pagination.extract(@pagy)
78
- else
79
- Pagination.extract(@raw_data)
80
- end
81
- meta[:pagination] = pagination if pagination
82
- end
72
+ meta = {}
83
73
 
84
- # Request ID (Rails only, opt-in via config)
74
+ # 1. Request ID first (opt-in, always authoritative)
85
75
  if Respondo.config.include_request_id && @request&.respond_to?(:request_id)
86
76
  meta[:request_id] = @request.request_id
87
77
  end
88
78
 
89
- # Merge any caller-supplied meta last (allows overriding)
90
- meta.merge(@extra_meta)
79
+ # 2. Timestamp second
80
+ meta[:timestamp] = current_timestamp
81
+
82
+ # 3. Global defaults in the middle (lowest priority)
83
+ meta.merge!(Respondo.config.default_meta)
84
+
85
+ # 4. Caller-supplied meta (overrides defaults)
86
+ meta.merge!(@extra_meta)
87
+
88
+ # 5. Pagination — plain hash from the caller, placed last before code/status
89
+ meta[:pagination] = @pagination if @pagination.is_a?(Hash) && !@pagination.empty?
90
+
91
+ # 6. Re-pin code and status to the very end
92
+ code = meta.delete(:code)
93
+ status = meta.delete(:status)
94
+ meta[:code] = code if code
95
+ meta[:status] = status if status
96
+
97
+ meta
91
98
  end
92
99
 
100
+ # def build_meta
101
+ # meta = { timestamp: current_timestamp }
102
+
103
+ # # Only extract pagination when caller has not explicitly disabled it
104
+ # if @pagination
105
+ # pagination = if @pagy
106
+ # Pagination.extract(@pagy)
107
+ # else
108
+ # Pagination.extract(@raw_data)
109
+ # end
110
+ # meta[:pagination] = pagination if pagination
111
+ # end
112
+
113
+ # # Request ID (Rails only, opt-in via config)
114
+ # if Respondo.config.include_request_id && @request&.respond_to?(:request_id)
115
+ # meta[:request_id] = @request.request_id
116
+ # end
117
+
118
+ # # Merge any caller-supplied meta last (allows overriding)
119
+ # meta.merge(@extra_meta)
120
+ # end
121
+
93
122
  def current_timestamp
94
123
  if defined?(Time.current)
95
124
  Time.current.iso8601
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Respondo
4
- VERSION = "0.1.0"
4
+ VERSION = "2.0.0"
5
5
  end
data/lib/respondo.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require_relative "respondo/version"
4
4
  require_relative "respondo/configuration"
5
5
  require_relative "respondo/serializer"
6
- require_relative "respondo/pagination"
6
+ # require_relative "respondo/pagination"
7
7
  require_relative "respondo/response_builder"
8
8
  require_relative "respondo/controller_helpers"
9
9
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: respondo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - shailendra Kumar
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-31 00:00:00.000000000 Z
11
+ date: 2026-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec