rest-api-generator 0.1.1 → 0.1.2
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/Gemfile +1 -1
- data/Gemfile.lock +170 -43
- data/README.md +155 -7
- data/Rakefile +1 -1
- data/lib/generators/rest_api_generator/helpers.rb +30 -0
- data/lib/generators/rest_api_generator/resource_generator.rb +73 -13
- data/lib/generators/rest_api_generator/templates/child_api_controller.rb.tt +10 -9
- data/lib/generators/rest_api_generator/templates/child_api_spec.rb.tt +37 -34
- data/lib/generators/rest_api_generator/templates/implicit_child_resource_controller.rb.tt +4 -0
- data/lib/generators/rest_api_generator/templates/implicit_resource_controller.rb.tt +4 -0
- data/lib/generators/rest_api_generator/templates/rest_api_controller.rb.tt +2 -0
- data/lib/generators/rest_api_generator/templates/rest_api_spec.rb.tt +5 -5
- data/lib/rest-api-generator.rb +3 -0
- data/lib/rest_api_generator/application_controller.rb +8 -0
- data/lib/rest_api_generator/child_resource_controller.rb +97 -0
- data/lib/rest_api_generator/filterable.rb +30 -0
- data/lib/rest_api_generator/helpers/render.rb +1 -1
- data/lib/rest_api_generator/orderable.rb +30 -0
- data/lib/rest_api_generator/resource_controller.rb +73 -0
- data/lib/rest_api_generator/version.rb +1 -1
- data/lib/rest_api_generator.rb +11 -1
- data/rest-api-generator.gemspec +9 -3
- metadata +70 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf4062a70bed1e9006d96375d4110b3918f3bf5d074304c4619abfa4fe33eb03
|
4
|
+
data.tar.gz: ba52d11b288dd8004e68327a8db4aabac8bef8be93eea85382f3bf349406ea39
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1ef3d60ca714e5edf5db7cdcf6b0287ef22c2123c907070cb3b6608ca159ceba200642a8b45894cb88e0fab73d5abaeb728e19a05c9e2760cd0e17ccb155732
|
7
|
+
data.tar.gz: 9849fa88d3b3a9f5a070c7f4a4ca29fc3deb8447adb80260004aa68076cd0a27db9cbeb5d214aed2505df4a1c1a48e26378dc41b6626159eb635d1a7d2c0218e
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,107 +1,234 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rest-api-generator (0.1.
|
5
|
-
|
4
|
+
rest-api-generator (0.1.2)
|
5
|
+
rails (>= 5.0)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
|
11
|
-
|
12
|
-
activesupport (=
|
13
|
-
|
10
|
+
actioncable (7.0.4)
|
11
|
+
actionpack (= 7.0.4)
|
12
|
+
activesupport (= 7.0.4)
|
13
|
+
nio4r (~> 2.0)
|
14
|
+
websocket-driver (>= 0.6.1)
|
15
|
+
actionmailbox (7.0.4)
|
16
|
+
actionpack (= 7.0.4)
|
17
|
+
activejob (= 7.0.4)
|
18
|
+
activerecord (= 7.0.4)
|
19
|
+
activestorage (= 7.0.4)
|
20
|
+
activesupport (= 7.0.4)
|
21
|
+
mail (>= 2.7.1)
|
22
|
+
net-imap
|
23
|
+
net-pop
|
24
|
+
net-smtp
|
25
|
+
actionmailer (7.0.4)
|
26
|
+
actionpack (= 7.0.4)
|
27
|
+
actionview (= 7.0.4)
|
28
|
+
activejob (= 7.0.4)
|
29
|
+
activesupport (= 7.0.4)
|
30
|
+
mail (~> 2.5, >= 2.5.4)
|
31
|
+
net-imap
|
32
|
+
net-pop
|
33
|
+
net-smtp
|
34
|
+
rails-dom-testing (~> 2.0)
|
35
|
+
actionpack (7.0.4)
|
36
|
+
actionview (= 7.0.4)
|
37
|
+
activesupport (= 7.0.4)
|
38
|
+
rack (~> 2.0, >= 2.2.0)
|
14
39
|
rack-test (>= 0.6.3)
|
15
40
|
rails-dom-testing (~> 2.0)
|
16
41
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
17
|
-
|
18
|
-
|
42
|
+
actiontext (7.0.4)
|
43
|
+
actionpack (= 7.0.4)
|
44
|
+
activerecord (= 7.0.4)
|
45
|
+
activestorage (= 7.0.4)
|
46
|
+
activesupport (= 7.0.4)
|
47
|
+
globalid (>= 0.6.0)
|
48
|
+
nokogiri (>= 1.8.5)
|
49
|
+
actionview (7.0.4)
|
50
|
+
activesupport (= 7.0.4)
|
19
51
|
builder (~> 3.1)
|
20
52
|
erubi (~> 1.4)
|
21
53
|
rails-dom-testing (~> 2.0)
|
22
54
|
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
23
|
-
|
55
|
+
activejob (7.0.4)
|
56
|
+
activesupport (= 7.0.4)
|
57
|
+
globalid (>= 0.3.6)
|
58
|
+
activemodel (7.0.4)
|
59
|
+
activesupport (= 7.0.4)
|
60
|
+
activerecord (7.0.4)
|
61
|
+
activemodel (= 7.0.4)
|
62
|
+
activesupport (= 7.0.4)
|
63
|
+
activestorage (7.0.4)
|
64
|
+
actionpack (= 7.0.4)
|
65
|
+
activejob (= 7.0.4)
|
66
|
+
activerecord (= 7.0.4)
|
67
|
+
activesupport (= 7.0.4)
|
68
|
+
marcel (~> 1.0)
|
69
|
+
mini_mime (>= 1.1.0)
|
70
|
+
activesupport (7.0.4)
|
24
71
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
25
72
|
i18n (>= 1.6, < 2)
|
26
73
|
minitest (>= 5.1)
|
27
74
|
tzinfo (~> 2.0)
|
28
|
-
|
75
|
+
ammeter (1.1.5)
|
76
|
+
activesupport (>= 3.0)
|
77
|
+
railties (>= 3.0)
|
78
|
+
rspec-rails (>= 2.2)
|
29
79
|
ast (2.4.2)
|
30
80
|
builder (3.2.4)
|
31
81
|
concurrent-ruby (1.1.10)
|
32
82
|
crass (1.0.6)
|
83
|
+
database_cleaner (2.0.1)
|
84
|
+
database_cleaner-active_record (~> 2.0.0)
|
85
|
+
database_cleaner-active_record (2.0.1)
|
86
|
+
activerecord (>= 5.a)
|
87
|
+
database_cleaner-core (~> 2.0.0)
|
88
|
+
database_cleaner-core (2.0.1)
|
89
|
+
date (3.3.3)
|
33
90
|
diff-lcs (1.5.0)
|
34
|
-
erubi (1.
|
35
|
-
|
91
|
+
erubi (1.12.0)
|
92
|
+
globalid (1.0.0)
|
93
|
+
activesupport (>= 5.0)
|
94
|
+
i18n (1.12.0)
|
36
95
|
concurrent-ruby (~> 1.0)
|
37
|
-
|
96
|
+
json (2.6.3)
|
97
|
+
loofah (2.19.1)
|
38
98
|
crass (~> 1.0.2)
|
39
99
|
nokogiri (>= 1.5.9)
|
100
|
+
mail (2.8.0)
|
101
|
+
mini_mime (>= 0.1.1)
|
102
|
+
net-imap
|
103
|
+
net-pop
|
104
|
+
net-smtp
|
105
|
+
marcel (1.0.2)
|
40
106
|
method_source (1.0.0)
|
41
|
-
|
42
|
-
|
107
|
+
mini_mime (1.1.2)
|
108
|
+
minitest (5.16.3)
|
109
|
+
net-imap (0.3.4)
|
110
|
+
date
|
111
|
+
net-protocol
|
112
|
+
net-pop (0.1.2)
|
113
|
+
net-protocol
|
114
|
+
net-protocol (0.2.1)
|
115
|
+
timeout
|
116
|
+
net-smtp (0.3.3)
|
117
|
+
net-protocol
|
118
|
+
nio4r (2.5.8)
|
119
|
+
nokogiri (1.13.10-x86_64-linux)
|
43
120
|
racc (~> 1.4)
|
44
121
|
parallel (1.22.1)
|
45
|
-
parser (3.1.
|
122
|
+
parser (3.1.3.0)
|
46
123
|
ast (~> 2.4.1)
|
47
|
-
racc (1.6.
|
124
|
+
racc (1.6.2)
|
48
125
|
rack (2.2.4)
|
49
126
|
rack-test (2.0.2)
|
50
127
|
rack (>= 1.3)
|
128
|
+
rails (7.0.4)
|
129
|
+
actioncable (= 7.0.4)
|
130
|
+
actionmailbox (= 7.0.4)
|
131
|
+
actionmailer (= 7.0.4)
|
132
|
+
actionpack (= 7.0.4)
|
133
|
+
actiontext (= 7.0.4)
|
134
|
+
actionview (= 7.0.4)
|
135
|
+
activejob (= 7.0.4)
|
136
|
+
activemodel (= 7.0.4)
|
137
|
+
activerecord (= 7.0.4)
|
138
|
+
activestorage (= 7.0.4)
|
139
|
+
activesupport (= 7.0.4)
|
140
|
+
bundler (>= 1.15.0)
|
141
|
+
railties (= 7.0.4)
|
51
142
|
rails-dom-testing (2.0.3)
|
52
143
|
activesupport (>= 4.2.0)
|
53
144
|
nokogiri (>= 1.6)
|
54
|
-
rails-html-sanitizer (1.4.
|
55
|
-
loofah (~> 2.
|
56
|
-
railties (
|
57
|
-
actionpack (=
|
58
|
-
activesupport (=
|
145
|
+
rails-html-sanitizer (1.4.4)
|
146
|
+
loofah (~> 2.19, >= 2.19.1)
|
147
|
+
railties (7.0.4)
|
148
|
+
actionpack (= 7.0.4)
|
149
|
+
activesupport (= 7.0.4)
|
59
150
|
method_source
|
60
151
|
rake (>= 12.2)
|
61
152
|
thor (~> 1.0)
|
153
|
+
zeitwerk (~> 2.5)
|
62
154
|
rainbow (3.1.1)
|
63
155
|
rake (13.0.6)
|
64
|
-
regexp_parser (2.
|
156
|
+
regexp_parser (2.6.1)
|
65
157
|
rexml (3.2.5)
|
66
|
-
rspec (3.
|
67
|
-
rspec-core (~> 3.
|
68
|
-
rspec-expectations (~> 3.
|
69
|
-
rspec-mocks (~> 3.
|
70
|
-
rspec-core (3.
|
71
|
-
rspec-support (~> 3.
|
72
|
-
rspec-expectations (3.
|
158
|
+
rspec (3.12.0)
|
159
|
+
rspec-core (~> 3.12.0)
|
160
|
+
rspec-expectations (~> 3.12.0)
|
161
|
+
rspec-mocks (~> 3.12.0)
|
162
|
+
rspec-core (3.12.0)
|
163
|
+
rspec-support (~> 3.12.0)
|
164
|
+
rspec-expectations (3.12.1)
|
73
165
|
diff-lcs (>= 1.2.0, < 2.0)
|
74
|
-
rspec-support (~> 3.
|
75
|
-
rspec-mocks (3.
|
166
|
+
rspec-support (~> 3.12.0)
|
167
|
+
rspec-mocks (3.12.1)
|
76
168
|
diff-lcs (>= 1.2.0, < 2.0)
|
77
|
-
rspec-support (~> 3.
|
78
|
-
rspec-
|
79
|
-
|
169
|
+
rspec-support (~> 3.12.0)
|
170
|
+
rspec-rails (6.0.1)
|
171
|
+
actionpack (>= 6.1)
|
172
|
+
activesupport (>= 6.1)
|
173
|
+
railties (>= 6.1)
|
174
|
+
rspec-core (~> 3.11)
|
175
|
+
rspec-expectations (~> 3.11)
|
176
|
+
rspec-mocks (~> 3.11)
|
177
|
+
rspec-support (~> 3.11)
|
178
|
+
rspec-support (3.12.0)
|
179
|
+
rubocop (1.40.0)
|
180
|
+
json (~> 2.3)
|
80
181
|
parallel (~> 1.10)
|
81
|
-
parser (>= 3.1.
|
182
|
+
parser (>= 3.1.2.1)
|
82
183
|
rainbow (>= 2.2.2, < 4.0)
|
83
184
|
regexp_parser (>= 1.8, < 3.0)
|
84
|
-
rexml
|
85
|
-
rubocop-ast (>= 1.
|
185
|
+
rexml (>= 3.2.5, < 4.0)
|
186
|
+
rubocop-ast (>= 1.23.0, < 2.0)
|
86
187
|
ruby-progressbar (~> 1.7)
|
87
188
|
unicode-display_width (>= 1.4.0, < 3.0)
|
88
|
-
rubocop-ast (1.
|
189
|
+
rubocop-ast (1.24.0)
|
89
190
|
parser (>= 3.1.1.0)
|
191
|
+
rubocop-performance (1.15.2)
|
192
|
+
rubocop (>= 1.7.0, < 2.0)
|
193
|
+
rubocop-ast (>= 0.4.0)
|
194
|
+
rubocop-rails (2.17.4)
|
195
|
+
activesupport (>= 4.2.0)
|
196
|
+
rack (>= 1.1)
|
197
|
+
rubocop (>= 1.33.0, < 2.0)
|
198
|
+
rubocop-rspec (2.16.0)
|
199
|
+
rubocop (~> 1.33)
|
200
|
+
rubocop-shopify (2.10.1)
|
201
|
+
rubocop (~> 1.35)
|
90
202
|
ruby-progressbar (1.11.0)
|
203
|
+
sqlite3 (1.5.4-x86_64-linux)
|
204
|
+
switchcop (0.1.2)
|
205
|
+
rubocop (~> 1.40.0)
|
206
|
+
rubocop-performance (~> 1.15.1)
|
207
|
+
rubocop-rails (~> 2.17.3)
|
208
|
+
rubocop-rspec (~> 2.16.0)
|
209
|
+
rubocop-shopify (~> 2.10.1)
|
91
210
|
thor (1.2.1)
|
92
|
-
|
211
|
+
timeout (0.3.1)
|
212
|
+
tzinfo (2.0.5)
|
93
213
|
concurrent-ruby (~> 1.0)
|
94
|
-
unicode-display_width (2.
|
95
|
-
|
214
|
+
unicode-display_width (2.3.0)
|
215
|
+
websocket-driver (0.7.5)
|
216
|
+
websocket-extensions (>= 0.1.0)
|
217
|
+
websocket-extensions (0.1.5)
|
218
|
+
zeitwerk (2.6.6)
|
96
219
|
|
97
220
|
PLATFORMS
|
98
221
|
x86_64-linux
|
99
222
|
|
100
223
|
DEPENDENCIES
|
224
|
+
ammeter (~> 1.1.5)
|
225
|
+
database_cleaner
|
101
226
|
rake (~> 13.0)
|
102
227
|
rest-api-generator!
|
103
228
|
rspec (~> 3.0)
|
104
|
-
|
229
|
+
rspec-rails (~> 6.0.0)
|
230
|
+
sqlite3
|
231
|
+
switchcop
|
105
232
|
|
106
233
|
BUNDLED WITH
|
107
234
|
2.3.3
|
data/README.md
CHANGED
@@ -5,7 +5,7 @@ practices.
|
|
5
5
|
|
6
6
|
## How it works?
|
7
7
|
|
8
|
-
The gems use vanilla Rails generators
|
8
|
+
The gems use vanilla Rails generators in combination with our templates to create all the resources needed to build a
|
9
9
|
REST API.
|
10
10
|
|
11
11
|
Following [Switch Dreams's](https://www.switchdreams.com.br/]) coding practices, the controllers are built with:
|
@@ -16,14 +16,32 @@ Following [Switch Dreams's](https://www.switchdreams.com.br/]) coding practices,
|
|
16
16
|
|
17
17
|
- For tests, we use RSpec and FactoryBot.
|
18
18
|
|
19
|
+
## Current Features
|
20
|
+
|
21
|
+
- [Automatic rest api crud generation](#example)
|
22
|
+
- [Nested Resource](#nested-resource)
|
23
|
+
- [Modular error handler](#modular-error-handler)
|
24
|
+
- [Resource ordering](#ordering)
|
25
|
+
- [Resource filter](#filtering)
|
26
|
+
|
27
|
+
## Next Features
|
28
|
+
|
29
|
+
- Generate nested resource end-points 🚧
|
30
|
+
- Automated documentation 🚧 https://github.com/SwitchDreams/rest-api-generator/issues/12
|
31
|
+
- Serialization https://github.com/SwitchDreams/rest-api-generator/issues/14
|
32
|
+
https://github.com/SwitchDreams/rest-api-generator/issues/11
|
33
|
+
- Pagination https://github.com/SwitchDreams/rest-api-generator/issues/15
|
34
|
+
- Integration with AVO
|
35
|
+
- Select fields
|
36
|
+
- User auth module
|
37
|
+
|
19
38
|
## Installation
|
20
39
|
|
21
40
|
Add this line to your application's Gemfile:
|
22
41
|
|
23
42
|
```ruby
|
24
|
-
|
25
|
-
|
26
|
-
end
|
43
|
+
# Build a Ruby on Rails REST API faster
|
44
|
+
gem 'rest-api-generator'
|
27
45
|
```
|
28
46
|
|
29
47
|
And then execute:
|
@@ -61,13 +79,15 @@ This error handler will rescue from: `ActiveRecord::RecordNotFound`
|
|
61
79
|
### Generate Resource
|
62
80
|
|
63
81
|
```bash
|
64
|
-
$ rails g
|
82
|
+
$ rails g rest_api_generator:resource table_name attributes
|
65
83
|
```
|
66
84
|
|
67
85
|
This command will create:
|
68
86
|
|
69
87
|
- **Model and Migration**: Using rails default model generator
|
70
|
-
- **Controller**: A controller
|
88
|
+
- **Controller**: A controller that implementes CRUD by inheritance of `RestApiGenerator::ResourceController`, or you
|
89
|
+
can use eject option for create a controller
|
90
|
+
that implements index, show, create, update and destroy methods.
|
71
91
|
- **Specs for the created controller**
|
72
92
|
- **Factory bot factory for created model**
|
73
93
|
- **Routes**: with rails resources
|
@@ -75,11 +95,96 @@ This command will create:
|
|
75
95
|
### Example
|
76
96
|
|
77
97
|
```bash
|
78
|
-
$ rails g
|
98
|
+
$ rails g rest_api_generator:resource car name:string color:string
|
79
99
|
```
|
80
100
|
|
81
101
|
Will generate following controller and the other files:
|
82
102
|
|
103
|
+
```ruby
|
104
|
+
# app/controllers/cars_controller.rb
|
105
|
+
class CarsController < RestApiGenerator::ResourceController
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
For a better experience you can override some methods from the
|
110
|
+
[default controller](https://github.com/SwitchDreams/rest-api-generator/blob/main/lib/rest_api_generator/resource_controller.rb)
|
111
|
+
|
112
|
+
### Options
|
113
|
+
|
114
|
+
| Option | Goal | Default | Usage Example |
|
115
|
+
|--------|--------------------------------------------------------------|---------|-----------------|
|
116
|
+
| father | Generate nested resource | nil | --father Users |
|
117
|
+
| scope | Scope the resource for other route or namespace organization | nil | --scope Api::V1 |
|
118
|
+
| eject | Eject the controller to high customization | false | true |
|
119
|
+
|
120
|
+
#### Scope
|
121
|
+
|
122
|
+
In REST api one of the best practices is versioning the end-points, and you can achieve this using scope options,
|
123
|
+
example:
|
124
|
+
|
125
|
+
```bash
|
126
|
+
# Command
|
127
|
+
rails g rest_api_generator:resource car name:string color:string --scope Api::V1
|
128
|
+
```
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
# GET api/v1/cars
|
132
|
+
module Api::V1
|
133
|
+
class CarsController < RestApiGenerator::ResourceController
|
134
|
+
end
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
For this option you need to manually setup routes, for this example:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
# routes.rb
|
142
|
+
namespace :api do
|
143
|
+
namespace :v1 do
|
144
|
+
resources :cars
|
145
|
+
end
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
#### Nested resource
|
150
|
+
|
151
|
+
In REST api sometimes we need to build a nested resource, for example when we need to get all devices from a user, for
|
152
|
+
this we have nested resource option:
|
153
|
+
|
154
|
+
```bash
|
155
|
+
# Command
|
156
|
+
rails g rest_api_generator:resource Devices name:string color:string users:references --scope Users
|
157
|
+
```
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
# GET users/:user_id/devices
|
161
|
+
module Users
|
162
|
+
class DevicesController < RestApiGenerator::ChildResourceController
|
163
|
+
end
|
164
|
+
end
|
165
|
+
```
|
166
|
+
|
167
|
+
For this option you need to manually setup routes, for this example:
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
# routes.rb
|
171
|
+
resources :users do
|
172
|
+
resources :devices, controller: 'users/devices'
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
Considerations:
|
177
|
+
|
178
|
+
- The children model needs to belongs_to parent model and parent model needs to have has_many children model
|
179
|
+
|
180
|
+
#### Eject
|
181
|
+
|
182
|
+
Or you can use the `eject` option for create the controller with the implemented methods:
|
183
|
+
|
184
|
+
```bash
|
185
|
+
rails g rest_api_generator:resource car name:string color:string --eject true
|
186
|
+
```
|
187
|
+
|
83
188
|
```ruby
|
84
189
|
|
85
190
|
class CarsController < ApplicationController
|
@@ -118,9 +223,52 @@ class CarsController < ApplicationController
|
|
118
223
|
params.require(:car).permit(:name, :color)
|
119
224
|
end
|
120
225
|
end
|
226
|
+
```
|
227
|
+
|
228
|
+
### Resource Features
|
121
229
|
|
230
|
+
#### Modular Error Handler
|
231
|
+
|
232
|
+
The error module will return a json in this following format when any active record or custom error raises.
|
233
|
+
|
234
|
+
```json
|
235
|
+
{
|
236
|
+
"status": 422,
|
237
|
+
"error": "",
|
238
|
+
"message": ""
|
239
|
+
}
|
122
240
|
```
|
123
241
|
|
242
|
+
This is good to padronize the error handler in front-end too.
|
243
|
+
|
244
|
+
#### Ordering
|
245
|
+
|
246
|
+
For ordering use this format:
|
247
|
+
|
248
|
+
- Ordering asc: `GET /cars?sort=+name or GET /cars?sort=name`
|
249
|
+
- Ordering desc: `GET /card?sort=-name`
|
250
|
+
|
251
|
+
By default, every resource column can be the key for ordering.
|
252
|
+
|
253
|
+
#### Filtering
|
254
|
+
|
255
|
+
For filter is needed to add some scopes in Model file, example:
|
256
|
+
|
257
|
+
```ruby
|
258
|
+
# app/models/car.rb
|
259
|
+
|
260
|
+
class Car < ApplicationRecord
|
261
|
+
include RestApiGenerator::Filterable
|
262
|
+
|
263
|
+
filter_scope :filter_by_color, ->(color) { where(color: color) }
|
264
|
+
filter_scope :filter_by_name, ->(name) { where("name LIKE ?", "%#{name}%") }
|
265
|
+
end
|
266
|
+
```
|
267
|
+
|
268
|
+
And It's done, you can filter your index end-point:
|
269
|
+
|
270
|
+
- `GET /cars?color=blue or GET /cars?color=red&name=Ferrari`
|
271
|
+
|
124
272
|
## Development
|
125
273
|
|
126
274
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can
|
data/Rakefile
CHANGED
@@ -17,5 +17,35 @@ module RestApiGenerator
|
|
17
17
|
Rails::Generators::GeneratedAttribute.new(column.name.to_s, column.type.to_s)
|
18
18
|
end
|
19
19
|
end
|
20
|
+
|
21
|
+
def option_to_path(option)
|
22
|
+
option.downcase.split("::").join("/")
|
23
|
+
end
|
24
|
+
|
25
|
+
def scope_route_path
|
26
|
+
return "" if options["scope"].blank?
|
27
|
+
|
28
|
+
option_to_path(options["scope"])
|
29
|
+
end
|
30
|
+
|
31
|
+
def nested_routes
|
32
|
+
return "" if options["father"].blank?
|
33
|
+
|
34
|
+
"#{options["father"].downcase.pluralize}/\#{#{options["father"].singularize.downcase}.id}/#{plural_name}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def initial_route
|
38
|
+
scope_route_path + "/" + nested_routes
|
39
|
+
end
|
40
|
+
|
41
|
+
def spec_routes
|
42
|
+
{
|
43
|
+
index: initial_route,
|
44
|
+
show: initial_route + "\#{#{singular_name}.id}",
|
45
|
+
create: initial_route,
|
46
|
+
update: initial_route + "\#{#{singular_name}.id}",
|
47
|
+
delete: initial_route + "\#{#{singular_name}.id}",
|
48
|
+
}
|
49
|
+
end
|
20
50
|
end
|
21
51
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "rails/generators"
|
4
|
+
require "rails/generators/active_model"
|
5
|
+
require "rails/generators/rails/model/model_generator"
|
4
6
|
require "generators/rest_api_generator/helpers"
|
5
7
|
module RestApiGenerator
|
6
8
|
class ResourceGenerator < Rails::Generators::NamedBase
|
@@ -8,31 +10,89 @@ module RestApiGenerator
|
|
8
10
|
source_root File.expand_path("templates", __dir__)
|
9
11
|
|
10
12
|
argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
|
13
|
+
class_option :eject, type: :boolean, default: false
|
11
14
|
class_option :scope, type: :string, default: ""
|
15
|
+
class_option :father, type: :string, default: ""
|
12
16
|
|
13
17
|
API_CONTROLLER_DIR_PATH = "app/controllers"
|
14
18
|
API_TEST_DIR_PATH = "spec/requests"
|
15
19
|
|
16
20
|
def create_service_file
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
create_model_files
|
22
|
+
|
23
|
+
# Create controller and specs
|
24
|
+
controller_path = "#{API_CONTROLLER_DIR_PATH}#{scope_path}/#{file_name.pluralize}_controller.rb"
|
25
|
+
controller_test_path = "#{API_TEST_DIR_PATH}#{scope_path}/#{file_name.pluralize}_controller_spec.rb"
|
26
|
+
|
27
|
+
template controller_template, controller_path
|
28
|
+
template spec_controller_template, controller_test_path
|
29
|
+
|
30
|
+
if options["scope"].blank? && options["father"].blank?
|
31
|
+
route "resources :#{file_name.pluralize}"
|
25
32
|
else
|
26
|
-
|
27
|
-
controller_path = "#{API_CONTROLLER_DIR_PATH}/#{scope_path}/#{file_name.pluralize}_controller.rb"
|
28
|
-
controller_test_path = "#{API_TEST_DIR_PATH}/#{scope_path}/#{file_name.pluralize}_controller_spec.rb"
|
29
|
-
template "child_api_controller.rb", controller_path
|
30
|
-
template "child_api_spec.rb", controller_test_path
|
33
|
+
log("You need to manually setup routes files for nested or scoped resource")
|
31
34
|
end
|
32
35
|
end
|
33
36
|
|
34
37
|
private
|
35
38
|
|
39
|
+
def scope_path
|
40
|
+
return "" if options["scope"].blank? && options["father"].blank?
|
41
|
+
|
42
|
+
if options["scope"].present? && options["father"].present?
|
43
|
+
"/" + option_to_path(options["scope"]) + "/" + option_to_path(options["father"])
|
44
|
+
else
|
45
|
+
"/" + option_to_path(options["scope"]) + option_to_path(options["father"])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def scope_namespacing(&block)
|
50
|
+
content = capture(&block)
|
51
|
+
content = wrap_with_scope(content) if options["scope"].present? || options["father"].present?
|
52
|
+
concat(content)
|
53
|
+
end
|
54
|
+
|
55
|
+
def module_namespace
|
56
|
+
if options["scope"].present? && options["father"].present?
|
57
|
+
options["scope"] + "::" + options["father"]
|
58
|
+
else
|
59
|
+
options["scope"] + options["father"]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def wrap_with_scope(content)
|
64
|
+
content = indent(content).chomp
|
65
|
+
"module #{module_namespace}\n#{content}\nend\n"
|
66
|
+
end
|
67
|
+
|
68
|
+
def controller_template
|
69
|
+
if options["eject"]
|
70
|
+
if options["father"].present?
|
71
|
+
"child_api_controller.rb"
|
72
|
+
else
|
73
|
+
"rest_api_controller.rb"
|
74
|
+
end
|
75
|
+
elsif options["father"].present?
|
76
|
+
"implicit_child_resource_controller.rb"
|
77
|
+
else
|
78
|
+
"implicit_resource_controller.rb"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def spec_controller_template
|
83
|
+
if options["father"].present?
|
84
|
+
"child_api_spec.rb"
|
85
|
+
else
|
86
|
+
"rest_api_spec.rb"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def create_model_files
|
91
|
+
g = Rails::Generators::ModelGenerator.new([file_name, build_model_attributes])
|
92
|
+
g.destination_root = destination_root
|
93
|
+
g.invoke_all
|
94
|
+
end
|
95
|
+
|
36
96
|
def build_model_attributes
|
37
97
|
model_attributes = []
|
38
98
|
attributes.each do |attribute|
|
@@ -1,9 +1,10 @@
|
|
1
|
+
<% scope_namespacing do -%>
|
1
2
|
class <%= class_name.pluralize %>Controller < ApplicationController
|
2
|
-
before_action :set_<%= options['
|
3
|
-
before_action :set_<%= singular_name %>, only: %i[show update destroy]
|
3
|
+
before_action :set_<%= options['father'].downcase.singularize %>
|
4
|
+
before_action :set_<%= singular_name %>, only: %i[ show update destroy ]
|
4
5
|
|
5
6
|
def index
|
6
|
-
@<%= plural_name %> = @<%= options['
|
7
|
+
@<%= plural_name %> = @<%= options['father'].downcase.singularize %>.<%= plural_name %>
|
7
8
|
render json: @<%= plural_name %>, status: :ok
|
8
9
|
end
|
9
10
|
|
@@ -12,7 +13,7 @@ class <%= class_name.pluralize %>Controller < ApplicationController
|
|
12
13
|
end
|
13
14
|
|
14
15
|
def create
|
15
|
-
@<%= singular_name %> = @<%= options['
|
16
|
+
@<%= singular_name %> = @<%= options['father'].downcase.singularize %>.<%= plural_name %>.create!(<%= singular_name %>_params)
|
16
17
|
render json: @<%= singular_name %>, status: :created
|
17
18
|
end
|
18
19
|
|
@@ -25,18 +26,18 @@ class <%= class_name.pluralize %>Controller < ApplicationController
|
|
25
26
|
@<%= singular_name %>.destroy!
|
26
27
|
end
|
27
28
|
|
28
|
-
|
29
29
|
private
|
30
30
|
|
31
|
-
def set_<%= options['
|
32
|
-
@<%= options['
|
31
|
+
def set_<%= options['father'].downcase.singularize %>
|
32
|
+
@<%= options['father'].downcase %> = <%= options['father'] %>.find(params[:<%= options['father'].downcase.singularize %>_id])
|
33
33
|
end
|
34
34
|
|
35
35
|
def set_<%= singular_name %>
|
36
|
-
@<%= singular_name %> = @<%= options['
|
36
|
+
@<%= singular_name %> = @<%= options['father'].downcase.singularize %>.<%= plural_name %>.find(params[:id])
|
37
37
|
end
|
38
38
|
|
39
39
|
def <%= singular_name %>_params
|
40
40
|
params.require(:<%= singular_name %>).permit(<%= attributes.map { |a| ':' + a.name }.join(', ') %>)
|
41
41
|
end
|
42
|
-
end
|
42
|
+
end
|
43
|
+
<% end -%>
|
@@ -1,14 +1,36 @@
|
|
1
|
-
require
|
1
|
+
require "rails_helper"
|
2
2
|
|
3
|
-
RSpec.describe "<%=class_name %>", type: :request do
|
3
|
+
RSpec.describe "<%= class_name %>", type: :request do
|
4
4
|
let(:valid_attributes) { attributes_for(:<%= singular_table_name %>) }
|
5
5
|
|
6
|
-
describe "
|
6
|
+
describe "GET /<%= plural_name %>" do
|
7
|
+
let(:<%= options['father'].downcase.singularize %>) { create(:<%= options['father'].downcase.singularize %>) }
|
8
|
+
let(:<%= singular_table_name %>) { create(:<%=singular_table_name %>, <%= options['father'].downcase %>: <%= options['father'].downcase %>) }
|
9
|
+
|
10
|
+
it "renders a successful response" do
|
11
|
+
get "<%= spec_routes[:index] %>"
|
12
|
+
expect(response).to be_successful
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
describe "GET /<%= singular_name %>" do
|
18
|
+
let(:<%= options['father'].downcase.singularize %>) { create(:<%= options['father'].downcase.singularize %>) }
|
19
|
+
let(:<%= singular_table_name %>) { create(:<%=singular_table_name %>, <%= options['father'].downcase.singularize %>: <%= options['father'].downcase %>)}
|
20
|
+
|
21
|
+
it "gets an <%= singular_name %>" do
|
22
|
+
get "<%= spec_routes[:show] %>"
|
23
|
+
expect(response).to be_successful
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
describe "POST /<%= plural_name %>" do
|
7
29
|
context "with valid parameters" do
|
8
|
-
|
9
|
-
|
30
|
+
let(:<%= options['father'].downcase.singularize %>) { create(:<%= options['father'].downcase.singularize %>) }
|
31
|
+
it "creates a new <%= singular_name %>" do
|
10
32
|
expect do
|
11
|
-
post "
|
33
|
+
post "<%= spec_routes[:create] %>",
|
12
34
|
params: { <%= singular_name %>: valid_attributes }
|
13
35
|
end.to change(<%= class_name %>, :count).by(1)
|
14
36
|
end
|
@@ -16,44 +38,25 @@ RSpec.describe "<%=class_name %>", type: :request do
|
|
16
38
|
end
|
17
39
|
|
18
40
|
describe "PATCH /<%= plural_name %>" do
|
19
|
-
let(:<%= options['
|
20
|
-
let(:<%= singular_table_name %>) { create(:<%=singular_table_name %>, <%= options['
|
41
|
+
let(:<%= options['father'].downcase.singularize %>) { create(:<%= options['father'].downcase.singularize %>) }
|
42
|
+
let(:<%= singular_table_name %>) { create(:<%=singular_table_name %>, <%= options['father'].downcase.singularize %>: <%= options['father'].downcase.singularize %>)}
|
21
43
|
|
22
|
-
it "updates an <%=singular_name %>" do
|
23
|
-
patch "
|
44
|
+
it "updates an <%= singular_name %>" do
|
45
|
+
patch "<%= spec_routes[:update] %>",
|
24
46
|
params: { <%= singular_name %>: valid_attributes }
|
25
47
|
expect(response).to have_http_status(:success)
|
26
48
|
end
|
27
49
|
end
|
28
50
|
|
29
51
|
describe "DELETE /<%= singular_name %>" do
|
30
|
-
let(:<%= options['
|
52
|
+
let(:<%= options['father'].downcase.singularize %>) { create(:<%= options['father'].downcase.singularize %>) }
|
31
53
|
|
32
|
-
it "deletes an <%=singular_name %>" do
|
33
|
-
<%= singular_table_name %> = create(:<%=singular_table_name %>, <%= options['
|
54
|
+
it "deletes an <%= singular_name %>" do
|
55
|
+
<%= singular_table_name %> = create(:<%=singular_table_name %>, <%= options['father'].downcase.singularize %>: <%= options['father'].downcase.singularize %>)
|
34
56
|
expect do
|
35
|
-
delete "
|
57
|
+
delete "<%= spec_routes[:delete] %>"
|
36
58
|
end.to change(<%= class_name %>, :count).by(-1)
|
37
59
|
end
|
38
60
|
end
|
39
61
|
|
40
|
-
|
41
|
-
let(:<%= options['scope'].downcase %>) {create(:<%= options['scope'].downcase %>)}
|
42
|
-
let(:<%= singular_table_name %>) { create(:<%=singular_table_name %>, <%= options['scope'].downcase %>: <%= options['scope'].downcase %>)}
|
43
|
-
|
44
|
-
it "gets an <%=singular_name %>" do
|
45
|
-
get "/<%= options['scope'].downcase.pluralize %>/#{<%= options['scope'].downcase %>.id}/<%= plural_name %>/#{<%= singular_name %>.id}"
|
46
|
-
expect(response).to be_successful
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
describe "GET /<%= plural_name %>" do
|
51
|
-
let(:<%= options['scope'].downcase %>) {create(:<%= options['scope'].downcase %>)}
|
52
|
-
let(:<%= singular_table_name %>) { create(:<%=singular_table_name %>, <%= options['scope'].downcase %>: <%= options['scope'].downcase %>) }
|
53
|
-
|
54
|
-
it "renders a successful response" do
|
55
|
-
get "/<%= options['scope'].downcase.pluralize %>/#{<%= options['scope'].downcase %>.id}/<%= plural_name %>"
|
56
|
-
expect(response).to be_successful
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
62
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
<% scope_namespacing do -%>
|
1
2
|
class <%= class_name.pluralize %>Controller < ApplicationController
|
2
3
|
before_action :set_<%= singular_name %>, only: %i[show update destroy]
|
3
4
|
|
@@ -34,3 +35,4 @@ class <%= class_name.pluralize %>Controller < ApplicationController
|
|
34
35
|
params.require(:<%= singular_name %>).permit(<%= attributes.map { |a| ':' + a.name }.join(', ') %>)
|
35
36
|
end
|
36
37
|
end
|
38
|
+
<% end -%>
|
@@ -7,14 +7,14 @@ RSpec.describe "<%= class_name %>", type: :request do
|
|
7
7
|
describe "GET /<%= plural_name %>" do
|
8
8
|
it "returns http success" do
|
9
9
|
create(:<%= singular_table_name %>)
|
10
|
-
get "
|
10
|
+
get "<%= spec_routes[:index] %>"
|
11
11
|
expect(response).to have_http_status(:success)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
15
|
describe "GET /<%= plural_name %>/:id" do
|
16
16
|
it "returns http success" do
|
17
|
-
get "
|
17
|
+
get "<%= spec_routes[:show] %>"
|
18
18
|
expect(response).to have_http_status(:success)
|
19
19
|
end
|
20
20
|
end
|
@@ -22,7 +22,7 @@ RSpec.describe "<%= class_name %>", type: :request do
|
|
22
22
|
describe "POST /<%= plural_name %>" do
|
23
23
|
it "creates a new item" do
|
24
24
|
expect do
|
25
|
-
post "
|
25
|
+
post "<%= spec_routes[:create] %>",
|
26
26
|
params: { <%= singular_name %>: valid_attributes }
|
27
27
|
end.to change(<%= class_name %>, :count).by(1)
|
28
28
|
end
|
@@ -30,7 +30,7 @@ RSpec.describe "<%= class_name %>", type: :request do
|
|
30
30
|
|
31
31
|
describe "PATCH /<%= plural_name %>/:id" do
|
32
32
|
it "return http success" do
|
33
|
-
patch "
|
33
|
+
patch "<%= spec_routes[:update] %>",
|
34
34
|
params: { <%= singular_name %>: valid_attributes }
|
35
35
|
expect(response).to have_http_status(:success)
|
36
36
|
end
|
@@ -40,7 +40,7 @@ RSpec.describe "<%= class_name %>", type: :request do
|
|
40
40
|
it "deletes an <%= plural_name %>" do
|
41
41
|
item = create(:<%= singular_name %>)
|
42
42
|
expect do
|
43
|
-
delete "
|
43
|
+
delete "<%= spec_routes[:delete] %>"
|
44
44
|
end.to change(<%= class_name %>, :count).by(-1)
|
45
45
|
end
|
46
46
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RestApiGenerator
|
4
|
+
class ChildResourceController < RestApiGenerator.parent_controller.constantize
|
5
|
+
include Orderable
|
6
|
+
|
7
|
+
before_action :set_parent_resource
|
8
|
+
before_action :set_resource, only: [:show, :update, :destroy]
|
9
|
+
|
10
|
+
def index
|
11
|
+
@resources = resources
|
12
|
+
@resources = @resources.filter_resource(params_for_filter) if resource_class.include?(Filterable)
|
13
|
+
@resources = @resources.order(ordering_params(params[:sort])) if params[:sort]
|
14
|
+
render json: @resources, status: :ok
|
15
|
+
end
|
16
|
+
|
17
|
+
def show
|
18
|
+
render json: @resource, status: :ok
|
19
|
+
end
|
20
|
+
|
21
|
+
def create
|
22
|
+
@resource = resources.create!(resource_created_params)
|
23
|
+
render json: @resource, status: :created
|
24
|
+
end
|
25
|
+
|
26
|
+
def update
|
27
|
+
@resource.update!(resource_updated_params)
|
28
|
+
render json: @resource, status: :ok
|
29
|
+
end
|
30
|
+
|
31
|
+
def destroy
|
32
|
+
@resource.destroy!
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def resources
|
38
|
+
@parent_resource.send(resource_class.to_s.downcase.pluralize)
|
39
|
+
end
|
40
|
+
|
41
|
+
def parent_resource_class
|
42
|
+
parent_resource_by_controller_name
|
43
|
+
end
|
44
|
+
|
45
|
+
def resource_class
|
46
|
+
resource_by_controller_name
|
47
|
+
end
|
48
|
+
|
49
|
+
# Params
|
50
|
+
def resource_created_params
|
51
|
+
resource_params
|
52
|
+
end
|
53
|
+
|
54
|
+
def resource_updated_params
|
55
|
+
resource_params
|
56
|
+
end
|
57
|
+
|
58
|
+
def resource_params
|
59
|
+
params.require(resource_class.model_name.singular.to_sym).permit(resource_attributes)
|
60
|
+
end
|
61
|
+
|
62
|
+
def resource_attributes
|
63
|
+
resource_class.attribute_names.map(&:to_sym)
|
64
|
+
end
|
65
|
+
|
66
|
+
def params_for_filter
|
67
|
+
params.slice(*resource_class.filter_scopes)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Before actions
|
71
|
+
def set_parent_resource
|
72
|
+
@parent_resource = parent_resource_class.find(parent_record_id)
|
73
|
+
end
|
74
|
+
|
75
|
+
def set_resource
|
76
|
+
@resource = resources.find(record_id)
|
77
|
+
end
|
78
|
+
|
79
|
+
# UsersController => User
|
80
|
+
def resource_by_controller_name(controller_name = self.class.to_s)
|
81
|
+
controller_name.split(Regexp.union(["Controller", "::"]))[-1].singularize.constantize
|
82
|
+
end
|
83
|
+
|
84
|
+
# Users::DevicesController => User
|
85
|
+
def parent_resource_by_controller_name(controller_name = self.class.to_s)
|
86
|
+
controller_name.split(Regexp.union(["Controller", "::"]))[-2].singularize.constantize
|
87
|
+
end
|
88
|
+
|
89
|
+
def parent_record_id
|
90
|
+
params.dig("#{parent_resource_class.to_s.downcase.singularize}_id")
|
91
|
+
end
|
92
|
+
|
93
|
+
def record_id
|
94
|
+
params.permit(:id)[:id]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module RestApiGenerator
|
6
|
+
module Filterable
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
@filter_scopes ||= []
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
attr_reader :filter_scopes
|
15
|
+
|
16
|
+
def filter_scope(name, *args)
|
17
|
+
scope name, *args
|
18
|
+
@filter_scopes << name.to_s.gsub("filter_by_", "").to_sym
|
19
|
+
end
|
20
|
+
|
21
|
+
def filter_resource(params)
|
22
|
+
results = where(nil)
|
23
|
+
params.each do |key, value|
|
24
|
+
results = results.public_send("filter_by_#{key}", value) if value.present?
|
25
|
+
end
|
26
|
+
results
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support"
|
4
|
+
require "active_support/concern"
|
5
|
+
|
6
|
+
module RestApiGenerator
|
7
|
+
module Orderable
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
SORT_ORDER = { " " => :asc, "+" => :asc, "-" => :desc }
|
11
|
+
|
12
|
+
# Returns params for order in active record order syntax
|
13
|
+
# GET /api/v1/transactions?sort=-amount
|
14
|
+
#
|
15
|
+
# ordering_params(params) # => { amount: :desc }
|
16
|
+
def ordering_params(order_params)
|
17
|
+
ordering = {}
|
18
|
+
if order_params
|
19
|
+
sorted_params = order_params.split(",")
|
20
|
+
sorted_params.each do |attr|
|
21
|
+
sort_sign = /\A[ +-]/.match?(attr) ? attr.slice!(0) : "+"
|
22
|
+
if resource_class.attribute_names.include?(attr)
|
23
|
+
ordering[attr] = SORT_ORDER[sort_sign]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
ordering
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RestApiGenerator
|
4
|
+
class ResourceController < RestApiGenerator.parent_controller.constantize
|
5
|
+
include Orderable
|
6
|
+
|
7
|
+
before_action :set_resource, only: [:show, :update, :destroy]
|
8
|
+
|
9
|
+
def index
|
10
|
+
@resources = resource_class.all
|
11
|
+
@resources = @resources.filter_resource(params_for_filter) if resource_class.include?(Filterable)
|
12
|
+
@resources = @resources.order(ordering_params(params[:sort])) if params[:sort]
|
13
|
+
render json: @resources, status: :ok
|
14
|
+
end
|
15
|
+
|
16
|
+
def show
|
17
|
+
render json: @resource, status: :ok
|
18
|
+
end
|
19
|
+
|
20
|
+
def create
|
21
|
+
@resource = resource_class.create!(resource_created_params)
|
22
|
+
render json: @resource, status: :created
|
23
|
+
end
|
24
|
+
|
25
|
+
def update
|
26
|
+
@resource.update!(resource_updated_params)
|
27
|
+
render json: @resource, status: :ok
|
28
|
+
end
|
29
|
+
|
30
|
+
def destroy
|
31
|
+
@resource.destroy!
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def params_for_filter
|
37
|
+
params.slice(*resource_class.filter_scopes)
|
38
|
+
end
|
39
|
+
|
40
|
+
def resource_class
|
41
|
+
resource_by_controller_name
|
42
|
+
end
|
43
|
+
|
44
|
+
def resource_created_params
|
45
|
+
resource_params
|
46
|
+
end
|
47
|
+
|
48
|
+
def resource_updated_params
|
49
|
+
resource_params
|
50
|
+
end
|
51
|
+
|
52
|
+
def resource_params
|
53
|
+
params.require(resource_class.model_name.singular.to_sym).permit(resource_attributes)
|
54
|
+
end
|
55
|
+
|
56
|
+
def resource_attributes
|
57
|
+
resource_class.attribute_names.map(&:to_sym)
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_resource
|
61
|
+
@resource = resource_class.find(record_id)
|
62
|
+
end
|
63
|
+
|
64
|
+
# UsersController => User
|
65
|
+
def resource_by_controller_name(controller_name = self.class.to_s)
|
66
|
+
controller_name.split(Regexp.union(["Controller", "::"]))[-1].singularize.constantize
|
67
|
+
end
|
68
|
+
|
69
|
+
def record_id
|
70
|
+
params.permit(:id)[:id]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/rest_api_generator.rb
CHANGED
@@ -1,11 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "rails"
|
3
4
|
require_relative "rest_api_generator/version"
|
5
|
+
require_relative "rest_api_generator/application_controller"
|
4
6
|
require_relative "rest_api_generator/error_handler"
|
5
7
|
require_relative "rest_api_generator/custom_error"
|
6
8
|
require_relative "rest_api_generator/helpers/render"
|
9
|
+
require_relative "rest_api_generator/filterable"
|
10
|
+
require_relative "rest_api_generator/orderable"
|
7
11
|
|
8
12
|
module RestApiGenerator
|
9
13
|
class Error < StandardError; end
|
10
|
-
|
14
|
+
|
15
|
+
def self.parent_controller
|
16
|
+
"RestApiGenerator::ApplicationController"
|
17
|
+
end
|
11
18
|
end
|
19
|
+
|
20
|
+
require_relative "rest_api_generator/resource_controller"
|
21
|
+
require_relative "rest_api_generator/child_resource_controller"
|
data/rest-api-generator.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
3
|
|
3
4
|
require_relative "lib/rest_api_generator/version"
|
4
|
-
require_relative "lib/rest_api_generator"
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "rest-api-generator"
|
@@ -27,9 +27,15 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
28
28
|
spec.require_paths = ["lib"]
|
29
29
|
|
30
|
-
spec.add_runtime_dependency("railties", ">= 5.0.0")
|
31
|
-
|
32
30
|
# For more information and examples about making a new gem, check out our
|
33
31
|
# guide at: https://bundler.io/guides/creating_gem.html
|
34
32
|
spec.metadata["rubygems_mfa_required"] = "true"
|
33
|
+
|
34
|
+
spec.add_dependency "rails", ">= 5.0"
|
35
|
+
|
36
|
+
spec.add_development_dependency 'ammeter', '~> 1.1.5'
|
37
|
+
spec.add_development_dependency 'rspec-rails', '~> 6.0.0'
|
38
|
+
spec.add_development_dependency 'sqlite3'
|
39
|
+
spec.add_development_dependency 'database_cleaner'
|
40
|
+
|
35
41
|
end
|
metadata
CHANGED
@@ -1,29 +1,85 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rest-api-generator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- PedroAugustoRamalhoDuarte
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-12-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: rails
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 5.0
|
19
|
+
version: '5.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 5.0
|
26
|
+
version: '5.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: ammeter
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.1.5
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.1.5
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec-rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 6.0.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 6.0.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sqlite3
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: database_cleaner
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
27
83
|
description: This gem helps you to build a Ruby on Rails REST API faster, using a
|
28
84
|
scaffold-like generator that follows the best practices.
|
29
85
|
email:
|
@@ -43,12 +99,20 @@ files:
|
|
43
99
|
- lib/generators/rest_api_generator/resource_generator.rb
|
44
100
|
- lib/generators/rest_api_generator/templates/child_api_controller.rb.tt
|
45
101
|
- lib/generators/rest_api_generator/templates/child_api_spec.rb.tt
|
102
|
+
- lib/generators/rest_api_generator/templates/implicit_child_resource_controller.rb.tt
|
103
|
+
- lib/generators/rest_api_generator/templates/implicit_resource_controller.rb.tt
|
46
104
|
- lib/generators/rest_api_generator/templates/rest_api_controller.rb.tt
|
47
105
|
- lib/generators/rest_api_generator/templates/rest_api_spec.rb.tt
|
106
|
+
- lib/rest-api-generator.rb
|
48
107
|
- lib/rest_api_generator.rb
|
108
|
+
- lib/rest_api_generator/application_controller.rb
|
109
|
+
- lib/rest_api_generator/child_resource_controller.rb
|
49
110
|
- lib/rest_api_generator/custom_error.rb
|
50
111
|
- lib/rest_api_generator/error_handler.rb
|
112
|
+
- lib/rest_api_generator/filterable.rb
|
51
113
|
- lib/rest_api_generator/helpers/render.rb
|
114
|
+
- lib/rest_api_generator/orderable.rb
|
115
|
+
- lib/rest_api_generator/resource_controller.rb
|
52
116
|
- lib/rest_api_generator/version.rb
|
53
117
|
- rest-api-generator.gemspec
|
54
118
|
- sig/rest_api_generator.rbs
|
@@ -76,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
140
|
- !ruby/object:Gem::Version
|
77
141
|
version: '0'
|
78
142
|
requirements: []
|
79
|
-
rubygems_version: 3.3.
|
143
|
+
rubygems_version: 3.3.7
|
80
144
|
signing_key:
|
81
145
|
specification_version: 4
|
82
146
|
summary: Build a Ruby on Rails REST API faster
|