respondo 2.0.0 → 2.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 +69 -0
- data/README.md +83 -7
- data/lib/generators/respondo/install/install_generator.rb +350 -0
- data/lib/respondo/version.rb +1 -1
- data/respondo.gemspec +13 -4
- metadata +25 -6
- data/lib/respondo/pagination.rb +0 -152
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fb5118a5648863ec54d68805d897485f40da5b81b9b628b9b9a08d6ee2f735d7
|
|
4
|
+
data.tar.gz: 4282b491b0bd587ba668e7accaf19f6dfb879cc61261d64d744de69c803ae8a0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4ae4daae47ef9c2c66388fc6d8f3a915b66439208fafa25b7046234813229704de0e53863921f2b4e2100817218b2a315ef40a564c5f672a9848194e55814906
|
|
7
|
+
data.tar.gz: 86a9ecb6864c1bba75dc4d47941bd0f80c837f4dbc72ed424526e1dad3f05ead92b738a417a239f3fb160e7043fc465418fe6670948be147399ea90ee3ce85d8
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,74 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.1.0] — Interactive Install Generator
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
#### `rails generate respondo:install` — interactive setup wizard
|
|
8
|
+
A new generator that walks developers through configuration interactively at
|
|
9
|
+
install time, writing a fully commented `config/initializers/respondo.rb` so
|
|
10
|
+
they never have to read the README to get started.
|
|
11
|
+
|
|
12
|
+
**Usage:**
|
|
13
|
+
```bash
|
|
14
|
+
rails generate respondo:install
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**What the wizard collects:**
|
|
18
|
+
|
|
19
|
+
| Prompt | Config written |
|
|
20
|
+
|--------|---------------|
|
|
21
|
+
| Project / app name | Comment header in initializer |
|
|
22
|
+
| API version | Auto-added to `config.default_meta` |
|
|
23
|
+
| Default success message | `config.default_success_message` |
|
|
24
|
+
| Default error message | `config.default_error_message` |
|
|
25
|
+
| Include request ID? | `config.include_request_id` |
|
|
26
|
+
| Camelize keys? | `config.camelize_keys` |
|
|
27
|
+
| Extra global meta fields | `config.default_meta` (merged with api_version) |
|
|
28
|
+
| Custom serializer stub? | Commented example added to initializer |
|
|
29
|
+
|
|
30
|
+
**Example output — `config/initializers/respondo.rb`:**
|
|
31
|
+
```ruby
|
|
32
|
+
# frozen_string_literal: true
|
|
33
|
+
|
|
34
|
+
# Respondo initializer — MyApp
|
|
35
|
+
# Generated by: rails generate respondo:install
|
|
36
|
+
# Respondo version: 2.1.0
|
|
37
|
+
|
|
38
|
+
Respondo.configure do |config|
|
|
39
|
+
|
|
40
|
+
# ── Messages ─────────────────────────────────────────────────────────
|
|
41
|
+
config.default_success_message = "Success"
|
|
42
|
+
config.default_error_message = "Something went wrong"
|
|
43
|
+
|
|
44
|
+
# ── Request ID ───────────────────────────────────────────────────────
|
|
45
|
+
config.include_request_id = true
|
|
46
|
+
|
|
47
|
+
# ── Key Format ───────────────────────────────────────────────────────
|
|
48
|
+
config.camelize_keys = true
|
|
49
|
+
|
|
50
|
+
# ── Global Meta ──────────────────────────────────────────────────────
|
|
51
|
+
config.default_meta = {
|
|
52
|
+
api_version: "v1",
|
|
53
|
+
platform: "mobile"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# ── Custom Serializer ────────────────────────────────────────────────
|
|
57
|
+
# config.serializer = ->(obj) { MySerializer.new(obj).as_json }
|
|
58
|
+
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Generator file location:**
|
|
63
|
+
```
|
|
64
|
+
lib/generators/respondo/install/install_generator.rb
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Rails auto-discovers this path — no manual require needed. Re-running the
|
|
68
|
+
generator overwrites the existing initializer with fresh answers.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
3
72
|
## [2.0.0] — Full HTTP Coverage
|
|
4
73
|
|
|
5
74
|
### Breaking Changes
|
data/README.md
CHANGED
|
@@ -42,6 +42,78 @@ gem "respondo"
|
|
|
42
42
|
|
|
43
43
|
## Setup
|
|
44
44
|
|
|
45
|
+
### ✅ Recommended — use the install generator
|
|
46
|
+
|
|
47
|
+
After adding the gem, run:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
rails generate respondo:install
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The interactive wizard walks you through every option and writes a fully commented `config/initializers/respondo.rb` tailored to your project. No need to read the full README or copy-paste config by hand.
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
┌─ Project Info ─────────────────────────────────────────────────────┐
|
|
57
|
+
|
|
58
|
+
Project / app name
|
|
59
|
+
(Used as a comment header in the initializer)
|
|
60
|
+
› [MyApp]:
|
|
61
|
+
|
|
62
|
+
API version (e.g. v1 — added to every response meta block)
|
|
63
|
+
› [v1]:
|
|
64
|
+
|
|
65
|
+
┌─ Response Messages ────────────────────────────────────────────────┐
|
|
66
|
+
|
|
67
|
+
Default success message
|
|
68
|
+
› [Success]:
|
|
69
|
+
|
|
70
|
+
Default error message
|
|
71
|
+
› [An error occurred]:
|
|
72
|
+
|
|
73
|
+
...
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The generator produces a file like this:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
# frozen_string_literal: true
|
|
80
|
+
|
|
81
|
+
# Respondo initializer — MyApp
|
|
82
|
+
# Generated by: rails generate respondo:install
|
|
83
|
+
# Respondo version: 2.1.0
|
|
84
|
+
|
|
85
|
+
Respondo.configure do |config|
|
|
86
|
+
|
|
87
|
+
# ── Messages ─────────────────────────────────────────────────────────
|
|
88
|
+
config.default_success_message = "Success"
|
|
89
|
+
config.default_error_message = "Something went wrong"
|
|
90
|
+
|
|
91
|
+
# ── Request ID ───────────────────────────────────────────────────────
|
|
92
|
+
config.include_request_id = true
|
|
93
|
+
|
|
94
|
+
# ── Key Format ───────────────────────────────────────────────────────
|
|
95
|
+
config.camelize_keys = true
|
|
96
|
+
|
|
97
|
+
# ── Global Meta ──────────────────────────────────────────────────────
|
|
98
|
+
config.default_meta = {
|
|
99
|
+
api_version: "v1",
|
|
100
|
+
platform: "mobile"
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# ── Custom Serializer ────────────────────────────────────────────────
|
|
104
|
+
# config.serializer = ->(obj) { MySerializer.new(obj).as_json }
|
|
105
|
+
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Re-run the generator any time to regenerate with different answers — it overwrites the existing initializer.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### Manual setup (alternative)
|
|
114
|
+
|
|
115
|
+
If you prefer to write the initializer yourself, create `config/initializers/respondo.rb`:
|
|
116
|
+
|
|
45
117
|
```ruby
|
|
46
118
|
# config/initializers/respondo.rb
|
|
47
119
|
Respondo.configure do |config|
|
|
@@ -770,13 +842,17 @@ class ApiResponse<T> {
|
|
|
770
842
|
```
|
|
771
843
|
lib/
|
|
772
844
|
├── respondo.rb # Entry point, configure, Railtie hook
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
845
|
+
├── respondo/
|
|
846
|
+
│ ├── version.rb # VERSION
|
|
847
|
+
│ ├── configuration.rb # Config with defaults
|
|
848
|
+
│ ├── serializer.rb # Auto-detects and serializes any object
|
|
849
|
+
│ ├── response_builder.rb # Assembles the final Hash
|
|
850
|
+
│ ├── controller_helpers.rb # All render_* helpers (1xx–5xx)
|
|
851
|
+
│ └── railtie.rb # Auto-includes into Rails controllers
|
|
852
|
+
└── generators/
|
|
853
|
+
└── respondo/
|
|
854
|
+
└── install/
|
|
855
|
+
└── install_generator.rb # rails generate respondo:install
|
|
780
856
|
```
|
|
781
857
|
|
|
782
858
|
---
|
|
@@ -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
|
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}/auditron/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.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-04-
|
|
11
|
+
date: 2026-04-13 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/auditron/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
|
-
|