respondo 2.0.0 → 2.1.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 +4 -4
- data/CHANGELOG.md +127 -0
- data/README.md +272 -31
- data/lib/generators/respondo/install/install_generator.rb +350 -0
- data/lib/respondo/controller_helpers.rb +1 -1
- data/lib/respondo/response_builder.rb +0 -22
- data/lib/respondo/version.rb +1 -1
- data/respondo.gemspec +13 -4
- metadata +25 -6
- data/lib/respondo/pagination.rb +0 -152
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module Respondo
|
|
6
|
+
module Generators
|
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
|
8
|
+
desc "Interactive setup — creates config/initializers/respondo.rb with your preferences."
|
|
9
|
+
|
|
10
|
+
# We bypass Thor's `say` entirely for all display output and use
|
|
11
|
+
# $stdout.puts / print directly. This prevents Thor from re-echoing
|
|
12
|
+
# buffered output and causing duplicate lines in the terminal.
|
|
13
|
+
|
|
14
|
+
def run_interactive_setup
|
|
15
|
+
# out LOGO
|
|
16
|
+
out logo_with_version
|
|
17
|
+
out divider
|
|
18
|
+
out line(" This wizard will generate config/initializers/respondo.rb")
|
|
19
|
+
out line(" tailored to your project — no need to read the full README.")
|
|
20
|
+
out blank
|
|
21
|
+
out line(yellow(" All settings can be changed later by editing the initializer."))
|
|
22
|
+
out divider
|
|
23
|
+
out blank
|
|
24
|
+
|
|
25
|
+
unless confirm(" Ready to configure Respondo? (y/n) ")
|
|
26
|
+
out blank
|
|
27
|
+
out line(yellow(" Skipped. Run this again any time:"))
|
|
28
|
+
out line(" rails generate respondo:install")
|
|
29
|
+
out blank
|
|
30
|
+
return
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
@cfg = {}
|
|
34
|
+
|
|
35
|
+
step_project_info
|
|
36
|
+
step_messages
|
|
37
|
+
step_request_id
|
|
38
|
+
step_camelize
|
|
39
|
+
step_default_meta
|
|
40
|
+
step_serializer
|
|
41
|
+
|
|
42
|
+
print_summary
|
|
43
|
+
write_initializer
|
|
44
|
+
print_done
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
# =========================================================================
|
|
50
|
+
# Steps
|
|
51
|
+
# =========================================================================
|
|
52
|
+
|
|
53
|
+
def step_project_info
|
|
54
|
+
out section("Project Info")
|
|
55
|
+
out line(" Project / app name")
|
|
56
|
+
out line(yellow(" (Used as a comment header in the initializer)"))
|
|
57
|
+
@cfg[:project_name] = prompt_default(Rails.application.class.module_parent_name)
|
|
58
|
+
|
|
59
|
+
out blank
|
|
60
|
+
out line(" API version")
|
|
61
|
+
out line(yellow(" (e.g. v1 — added to every response meta block)"))
|
|
62
|
+
@cfg[:api_version] = prompt_default("v1")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def step_messages
|
|
66
|
+
out section("Response Messages")
|
|
67
|
+
out line(" Fallback messages used when you don't pass message: explicitly.")
|
|
68
|
+
out blank
|
|
69
|
+
|
|
70
|
+
out line(" Default success message")
|
|
71
|
+
@cfg[:default_success_message] = prompt_default("Success")
|
|
72
|
+
|
|
73
|
+
out blank
|
|
74
|
+
out line(" Default error message")
|
|
75
|
+
@cfg[:default_error_message] = prompt_default("An error occurred")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def step_request_id
|
|
79
|
+
out section("Request ID")
|
|
80
|
+
out line(" When enabled, Rails request.request_id is included in every")
|
|
81
|
+
out line(" response meta block — useful for log correlation and debugging.")
|
|
82
|
+
out blank
|
|
83
|
+
@cfg[:include_request_id] = confirm(" Include request_id in every response? (y/n) ")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def step_camelize
|
|
87
|
+
out section("Key Format")
|
|
88
|
+
out line(" When enabled, all JSON keys are camelCased:")
|
|
89
|
+
out line(yellow(' { "createdAt": "...", "userId": 1 }'))
|
|
90
|
+
out line(" Recommended for Flutter, React, and JavaScript clients.")
|
|
91
|
+
out blank
|
|
92
|
+
@cfg[:camelize_keys] = confirm(" Camelize all response keys? (y/n) ")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def step_default_meta
|
|
96
|
+
out section("Global Meta Fields")
|
|
97
|
+
out line(" Static key=value pairs merged into the meta block of EVERY response.")
|
|
98
|
+
out line(" Example: platform=mobile environment=production")
|
|
99
|
+
out blank
|
|
100
|
+
out line(yellow(" Note: api_version from above is already included automatically."))
|
|
101
|
+
out blank
|
|
102
|
+
|
|
103
|
+
@cfg[:default_meta] = {}
|
|
104
|
+
return unless confirm(" Add extra global meta fields? (y/n) ")
|
|
105
|
+
|
|
106
|
+
out blank
|
|
107
|
+
out line(" Enter key=value one at a time. Blank line to finish.")
|
|
108
|
+
out blank
|
|
109
|
+
|
|
110
|
+
loop do
|
|
111
|
+
$stdout.print cyan(" key=value › ")
|
|
112
|
+
raw = $stdin.gets.to_s.strip
|
|
113
|
+
break if raw.empty?
|
|
114
|
+
|
|
115
|
+
unless raw.include?("=")
|
|
116
|
+
out line(yellow(" Use key=value format (e.g. platform=mobile). Skipping."))
|
|
117
|
+
next
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
key, value = raw.split("=", 2)
|
|
121
|
+
|
|
122
|
+
next out line(yellow(" Use key=value format.")) if key.nil? || key.strip.empty?
|
|
123
|
+
|
|
124
|
+
k = key.strip
|
|
125
|
+
v = (value || "").strip
|
|
126
|
+
@cfg[:default_meta][k] = v
|
|
127
|
+
out line(green(" ✓ #{k}: #{v.inspect}"))
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def step_serializer
|
|
132
|
+
out section("Custom Serializer")
|
|
133
|
+
out line(" By default Respondo serializes ActiveRecord models, collections,")
|
|
134
|
+
out line(" hashes, and arrays automatically.")
|
|
135
|
+
out blank
|
|
136
|
+
out line(" You can override with any callable: ->(obj) { MySerializer.new(obj).as_json }")
|
|
137
|
+
out blank
|
|
138
|
+
@cfg[:custom_serializer] = confirm(" Add a custom serializer stub? (y/n) ")
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# =========================================================================
|
|
142
|
+
# Summary
|
|
143
|
+
# =========================================================================
|
|
144
|
+
|
|
145
|
+
def print_summary
|
|
146
|
+
out blank
|
|
147
|
+
out divider
|
|
148
|
+
out line(cyan(" Configuration Summary"))
|
|
149
|
+
out divider
|
|
150
|
+
out blank
|
|
151
|
+
summary_row "Project", @cfg[:project_name]
|
|
152
|
+
summary_row "API version", @cfg[:api_version]
|
|
153
|
+
summary_row "Success message", @cfg[:default_success_message]
|
|
154
|
+
summary_row "Error message", @cfg[:default_error_message]
|
|
155
|
+
summary_row "Include request_id", @cfg[:include_request_id]
|
|
156
|
+
summary_row "Camelize keys", @cfg[:camelize_keys]
|
|
157
|
+
summary_row "Custom serializer", @cfg[:custom_serializer]
|
|
158
|
+
|
|
159
|
+
unless @cfg[:default_meta].empty?
|
|
160
|
+
out blank
|
|
161
|
+
out line(" Global meta:")
|
|
162
|
+
@cfg[:default_meta].each { |k, v| out line(" #{k}: #{v.inspect}") }
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
out blank
|
|
166
|
+
out divider
|
|
167
|
+
out blank
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def summary_row(label, value)
|
|
171
|
+
bool_true = value == true
|
|
172
|
+
val_str = bool_true ? green(value.inspect) : yellow(value.inspect)
|
|
173
|
+
out " #{("#{label}:").ljust(24)}#{val_str}\n"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# =========================================================================
|
|
177
|
+
# Write file
|
|
178
|
+
# =========================================================================
|
|
179
|
+
|
|
180
|
+
def write_initializer
|
|
181
|
+
dir = File.join(destination_root, "config", "initializers")
|
|
182
|
+
path = File.join(dir, "respondo.rb")
|
|
183
|
+
FileUtils.mkdir_p(dir)
|
|
184
|
+
File.write(path, build_content)
|
|
185
|
+
out line(green(" ✅ Created config/initializers/respondo.rb"))
|
|
186
|
+
out blank
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def build_content
|
|
190
|
+
meta = { "api_version" => @cfg[:api_version] }.merge(@cfg[:default_meta])
|
|
191
|
+
b = Lines.new
|
|
192
|
+
|
|
193
|
+
b << "# frozen_string_literal: true"
|
|
194
|
+
b << ""
|
|
195
|
+
b << "# Respondo initializer — #{@cfg[:project_name]}"
|
|
196
|
+
b << "# Generated by: rails generate respondo:install"
|
|
197
|
+
b << "# Respondo version: #{Respondo::VERSION}"
|
|
198
|
+
b << "# Docs: https://github.com/your-org/respondo"
|
|
199
|
+
b << ""
|
|
200
|
+
b << "Respondo.configure do |config|"
|
|
201
|
+
b << ""
|
|
202
|
+
b << " # ── Messages ─────────────────────────────────────────────────────────"
|
|
203
|
+
b << " # Fallback when render_success / render_error is called without message:"
|
|
204
|
+
b << " config.default_success_message = #{@cfg[:default_success_message].inspect}"
|
|
205
|
+
b << " config.default_error_message = #{@cfg[:default_error_message].inspect}"
|
|
206
|
+
b << ""
|
|
207
|
+
b << " # ── Request ID ───────────────────────────────────────────────────────"
|
|
208
|
+
b << " # Includes Rails request.request_id in every response meta block."
|
|
209
|
+
b << " config.include_request_id = #{@cfg[:include_request_id]}"
|
|
210
|
+
b << ""
|
|
211
|
+
b << " # ── Key Format ───────────────────────────────────────────────────────"
|
|
212
|
+
b << " # CamelCase all JSON keys — recommended for Flutter / JS clients."
|
|
213
|
+
b << " config.camelize_keys = #{@cfg[:camelize_keys]}"
|
|
214
|
+
b << ""
|
|
215
|
+
b << " # ── Global Meta ──────────────────────────────────────────────────────"
|
|
216
|
+
b << " # Static fields merged into the meta block of every response."
|
|
217
|
+
|
|
218
|
+
if meta.empty?
|
|
219
|
+
b << " config.default_meta = {}"
|
|
220
|
+
else
|
|
221
|
+
b << " config.default_meta = {"
|
|
222
|
+
meta.each_with_index do |(k, v), i|
|
|
223
|
+
comma = i < meta.size - 1 ? "," : ""
|
|
224
|
+
b << " #{k}: #{v.inspect}#{comma}"
|
|
225
|
+
end
|
|
226
|
+
b << " }"
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
if @cfg[:custom_serializer]
|
|
230
|
+
b << ""
|
|
231
|
+
b << " # ── Custom Serializer ────────────────────────────────────────────────"
|
|
232
|
+
b << " # Replace the lambda body with your own serialization logic."
|
|
233
|
+
b << " # Examples:"
|
|
234
|
+
b << " # ActiveModelSerializers: ->(obj) { SomeSerializer.new(obj).as_json }"
|
|
235
|
+
b << " # Blueprinter: ->(obj) { UserBlueprint.render_as_hash(obj) }"
|
|
236
|
+
b << " #"
|
|
237
|
+
b << " # config.serializer = ->(obj) { MySerializer.new(obj).as_json }"
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
b << ""
|
|
241
|
+
b << "end"
|
|
242
|
+
b << ""
|
|
243
|
+
b.to_s
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# =========================================================================
|
|
247
|
+
# Done
|
|
248
|
+
# =========================================================================
|
|
249
|
+
|
|
250
|
+
def print_done
|
|
251
|
+
out divider
|
|
252
|
+
out blank
|
|
253
|
+
out line(cyan(" 🎉 Respondo is ready!"))
|
|
254
|
+
out blank
|
|
255
|
+
out line(" Next steps:")
|
|
256
|
+
out line(" 1. Review config/initializers/respondo.rb")
|
|
257
|
+
out line(" 2. Use render_success / render_error in your controllers")
|
|
258
|
+
out line(" 3. Re-run rails generate respondo:install to regenerate")
|
|
259
|
+
out blank
|
|
260
|
+
out divider
|
|
261
|
+
out blank
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# =========================================================================
|
|
265
|
+
# Output primitives — all output goes through $stdout, never through Thor
|
|
266
|
+
# =========================================================================
|
|
267
|
+
|
|
268
|
+
def out(str)
|
|
269
|
+
$stdout.print str
|
|
270
|
+
$stdout.flush
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def line(str) = "#{str}\n"
|
|
274
|
+
def blank = "\n"
|
|
275
|
+
|
|
276
|
+
def divider
|
|
277
|
+
" #{cyan("─" * 68)}\n"
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def section(title)
|
|
281
|
+
dashes = "─" * [0, 54 - title.length].max
|
|
282
|
+
"\n #{cyan("┌─ #{title} #{dashes}┐")}\n\n"
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def prompt_default(default)
|
|
286
|
+
$stdout.print " #{cyan("›")} #{yellow("[#{default}]")}: "
|
|
287
|
+
$stdout.flush
|
|
288
|
+
result = $stdin.gets.to_s.strip
|
|
289
|
+
result.empty? ? default.to_s : result
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def confirm(question)
|
|
293
|
+
$stdout.print question
|
|
294
|
+
$stdout.flush
|
|
295
|
+
$stdin.gets.to_s.strip.downcase.start_with?("y")
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# =========================================================================
|
|
299
|
+
# ANSI colors
|
|
300
|
+
# =========================================================================
|
|
301
|
+
|
|
302
|
+
def cyan(t) = "\e[36m#{t}\e[0m"
|
|
303
|
+
def green(t) = "\e[32m#{t}\e[0m"
|
|
304
|
+
def yellow(t) = "\e[33m#{t}\e[0m"
|
|
305
|
+
|
|
306
|
+
# =========================================================================
|
|
307
|
+
# ASCII logo
|
|
308
|
+
# =========================================================================
|
|
309
|
+
|
|
310
|
+
LOGOS = <<~'LOGO'
|
|
311
|
+
|
|
312
|
+
██████╗ ███████╗███████╗██████╗ ██████╗ ███╗ ██╗██████╗ ██████╗
|
|
313
|
+
██╔══██╗██╔════╝██╔════╝██╔══██╗██╔═══██╗████╗ ██║██╔══██╗██╔═══██╗
|
|
314
|
+
██████╔╝█████╗ ███████╗██████╔╝██║ ██║██╔██╗ ██║██║ ██║██║ ██║
|
|
315
|
+
██╔══██╗██╔══╝ ╚════██║██╔═══╝ ██║ ██║██║╚██╗██║██║ ██║██║ ██║
|
|
316
|
+
██║ ██║███████╗███████║██║ ╚██████╔╝██║ ╚████║██████╔╝╚██████╔╝
|
|
317
|
+
╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═════╝ ╚═════╝
|
|
318
|
+
|
|
319
|
+
Smart JSON API Response Formatter for Rails
|
|
320
|
+
─── v#{Respondo::VERSION} ───
|
|
321
|
+
|
|
322
|
+
LOGO
|
|
323
|
+
|
|
324
|
+
def logo_with_version
|
|
325
|
+
green(<<~LOGO)
|
|
326
|
+
|
|
327
|
+
██████╗ ███████╗███████╗██████╗ ██████╗ ███╗ ██╗██████╗ ██████╗
|
|
328
|
+
██╔══██╗██╔════╝██╔════╝██╔══██╗██╔═══██╗████╗ ██║██╔══██╗██╔═══██╗
|
|
329
|
+
██████╔╝█████╗ ███████╗██████╔╝██║ ██║██╔██╗ ██║██║ ██║██║ ██║
|
|
330
|
+
██╔══██╗██╔══╝ ╚════██║██╔═══╝ ██║ ██║██║╚██╗██║██║ ██║██║ ██║
|
|
331
|
+
██║ ██║███████╗███████║██║ ╚██████╔╝██║ ╚████║██████╔╝╚██████╔╝
|
|
332
|
+
╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═════╝ ╚═════╝
|
|
333
|
+
|
|
334
|
+
Smart JSON API Response Formatter for Rails
|
|
335
|
+
─── v#{Respondo::VERSION} ───
|
|
336
|
+
|
|
337
|
+
LOGO
|
|
338
|
+
end
|
|
339
|
+
# =========================================================================
|
|
340
|
+
# Simple line buffer for building file content
|
|
341
|
+
# =========================================================================
|
|
342
|
+
|
|
343
|
+
class Lines
|
|
344
|
+
def initialize = (@buf = [])
|
|
345
|
+
def <<(str) = @buf << str
|
|
346
|
+
def to_s = @buf.join("\n") + "\n"
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
@@ -146,7 +146,7 @@ module Respondo
|
|
|
146
146
|
# 204 No Content — deletions, actions with no response body
|
|
147
147
|
# Note: we still return our standard JSON structure for consistency
|
|
148
148
|
def render_no_content(message: "Deleted successfully", meta: {}, pagination: nil)
|
|
149
|
-
render_success(data: nil, message: message, meta: meta, pagination: pagination, code: 204, status: :
|
|
149
|
+
render_success(data: nil, message: message, meta: meta, pagination: pagination, code: 204, status: :no_content)
|
|
150
150
|
end
|
|
151
151
|
|
|
152
152
|
# 205 Reset Content — tell the client to reset the document view
|
|
@@ -97,28 +97,6 @@ module Respondo
|
|
|
97
97
|
meta
|
|
98
98
|
end
|
|
99
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
|
-
|
|
122
100
|
def current_timestamp
|
|
123
101
|
if defined?(Time.current)
|
|
124
102
|
Time.current.iso8601
|
data/lib/respondo/version.rb
CHANGED
data/respondo.gemspec
CHANGED
|
@@ -19,11 +19,20 @@ Gem::Specification.new do |spec|
|
|
|
19
19
|
spec.license = "MIT"
|
|
20
20
|
spec.required_ruby_version = ">= 2.7.0"
|
|
21
21
|
|
|
22
|
+
spec.metadata = {
|
|
23
|
+
"homepage_uri" => spec.homepage,
|
|
24
|
+
"source_code_uri" => spec.homepage,
|
|
25
|
+
"changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md",
|
|
26
|
+
"bug_tracker_uri" => "#{spec.homepage}/issues",
|
|
27
|
+
"rubygems_mfa_required" => "true"
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
spec.files = Dir["lib/**/*.rb", "README.md", "LICENSE.txt", "CHANGELOG.md", "respondo.gemspec"]
|
|
23
31
|
spec.require_paths = ["lib"]
|
|
24
32
|
|
|
25
|
-
spec.add_development_dependency "
|
|
26
|
-
spec.add_development_dependency "
|
|
27
|
-
spec.add_development_dependency "
|
|
28
|
-
spec.add_development_dependency
|
|
33
|
+
spec.add_development_dependency "railties", "~> 8.1.3"
|
|
34
|
+
spec.add_development_dependency "rspec", "~> 3.12"
|
|
35
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
36
|
+
spec.add_development_dependency "activesupport", "~> 8.1.3"
|
|
37
|
+
spec.add_development_dependency 'simplecov', "~> 0.22"
|
|
29
38
|
end
|
metadata
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: respondo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.1.1
|
|
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-04-
|
|
11
|
+
date: 2026-04-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: railties
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 8.1.3
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 8.1.3
|
|
13
27
|
- !ruby/object:Gem::Dependency
|
|
14
28
|
name: rspec
|
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -44,14 +58,14 @@ dependencies:
|
|
|
44
58
|
requirements:
|
|
45
59
|
- - "~>"
|
|
46
60
|
- !ruby/object:Gem::Version
|
|
47
|
-
version:
|
|
61
|
+
version: 8.1.3
|
|
48
62
|
type: :development
|
|
49
63
|
prerelease: false
|
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
65
|
requirements:
|
|
52
66
|
- - "~>"
|
|
53
67
|
- !ruby/object:Gem::Version
|
|
54
|
-
version:
|
|
68
|
+
version: 8.1.3
|
|
55
69
|
- !ruby/object:Gem::Dependency
|
|
56
70
|
name: simplecov
|
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -80,10 +94,10 @@ files:
|
|
|
80
94
|
- CHANGELOG.md
|
|
81
95
|
- LICENSE.txt
|
|
82
96
|
- README.md
|
|
97
|
+
- lib/generators/respondo/install/install_generator.rb
|
|
83
98
|
- lib/respondo.rb
|
|
84
99
|
- lib/respondo/configuration.rb
|
|
85
100
|
- lib/respondo/controller_helpers.rb
|
|
86
|
-
- lib/respondo/pagination.rb
|
|
87
101
|
- lib/respondo/railtie.rb
|
|
88
102
|
- lib/respondo/response_builder.rb
|
|
89
103
|
- lib/respondo/serializer.rb
|
|
@@ -92,7 +106,12 @@ files:
|
|
|
92
106
|
homepage: https://github.com/spatelpatidar/respondo
|
|
93
107
|
licenses:
|
|
94
108
|
- MIT
|
|
95
|
-
metadata:
|
|
109
|
+
metadata:
|
|
110
|
+
homepage_uri: https://github.com/spatelpatidar/respondo
|
|
111
|
+
source_code_uri: https://github.com/spatelpatidar/respondo
|
|
112
|
+
changelog_uri: https://github.com/spatelpatidar/respondo/blob/main/CHANGELOG.md
|
|
113
|
+
bug_tracker_uri: https://github.com/spatelpatidar/respondo/issues
|
|
114
|
+
rubygems_mfa_required: 'true'
|
|
96
115
|
post_install_message:
|
|
97
116
|
rdoc_options: []
|
|
98
117
|
require_paths:
|
data/lib/respondo/pagination.rb
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
# # frozen_string_literal: true
|
|
2
|
-
|
|
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
|
|
48
|
-
|
|
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?
|
|
53
|
-
|
|
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
|
|
64
|
-
|
|
65
|
-
# private
|
|
66
|
-
|
|
67
|
-
# module_function
|
|
68
|
-
|
|
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
|
|
82
|
-
|
|
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
|
|
93
|
-
|
|
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
|
|
109
|
-
|
|
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
|
|
120
|
-
|
|
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
|
|
152
|
-
|