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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +179 -0
- data/README.md +419 -188
- data/lib/respondo/configuration.rb +6 -0
- data/lib/respondo/controller_helpers.rb +272 -67
- data/lib/respondo/pagination.rb +141 -83
- data/lib/respondo/response_builder.rb +48 -19
- data/lib/respondo/version.rb +1 -1
- data/lib/respondo.rb +1 -1
- metadata +2 -2
data/lib/respondo/pagination.rb
CHANGED
|
@@ -1,94 +1,152 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
1
|
+
# # frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module Respondo
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
65
|
+
# private
|
|
35
66
|
|
|
36
|
-
|
|
67
|
+
# module_function
|
|
37
68
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
27
|
-
#
|
|
28
|
-
#
|
|
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
|
-
|
|
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 = {
|
|
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 (
|
|
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
|
-
#
|
|
90
|
-
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
|
data/lib/respondo/version.rb
CHANGED
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.
|
|
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-
|
|
11
|
+
date: 2026-04-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rspec
|