Skip to content

Commit e9a64d5

Browse files
Merge pull request #4 from SwitchDreams/feature/versioning_scope
[CU-338n06m] versioning working
2 parents 6a29943 + 262157a commit e9a64d5

27 files changed

+489
-81
lines changed

.rubocop.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ AllCops:
99
- 'tmp/**/*'
1010
- '**/*/bundle'
1111

12+
Naming/FileName:
13+
Enabled: false
14+
1215
Style/Documentation:
1316
Enabled: false
1417

@@ -21,5 +24,8 @@ Metrics/MethodLength:
2124
Metrics/AbcSize:
2225
Max: 25
2326

27+
RSpec/ExampleLength:
28+
Max: 6
29+
2430
Metrics/BlockLength:
2531
AllowedMethods: [ 'describe', 'context' ]

Gemfile.lock

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ PATH
22
remote: .
33
specs:
44
rest-api-generator (0.1.1)
5-
actionview (>= 6.0)
6-
activerecord (>= 6.0)
7-
activesupport (>= 6.0)
8-
railties (>= 5.0.0)
5+
rails (>= 5.0)
96

107
GEM
118
remote: https://rubygems.org/
@@ -226,7 +223,6 @@ PLATFORMS
226223
DEPENDENCIES
227224
ammeter (~> 1.1.5)
228225
database_cleaner
229-
rails (>= 6.0)
230226
rake (~> 13.0)
231227
rest-api-generator!
232228
rspec (~> 3.0)

README.md

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ Following [Switch Dreams's](https://www.switchdreams.com.br/]) coding practices,
1919
## Current Features
2020

2121
- [Automatic rest api crud generation](#example)
22-
- Modular error handler
22+
- [Nested Resource](#nested-resource)
23+
- [Modular error handler](#modular-error-handler)
2324
- [Resource ordering](#ordering)
2425
- [Resource filter](#filtering)
2526

@@ -100,15 +101,83 @@ $ rails g rest_api_generator:resource car name:string color:string
100101
Will generate following controller and the other files:
101102

102103
```ruby
103-
104+
# app/controllers/cars_controller.rb
104105
class CarsController < RestApiGenerator::ResourceController
105106
end
106107
```
107108

108109
For a better experience you can override some methods from the
109110
[default controller](https://github.com/SwitchDreams/rest-api-generator/blob/main/lib/rest_api_generator/resource_controller.rb)
110111

111-
### Example with eject
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
112181

113182
Or you can use the `eject` option for create the controller with the implemented methods:
114183

@@ -154,10 +223,23 @@ class CarsController < ApplicationController
154223
params.require(:car).permit(:name, :color)
155224
end
156225
end
226+
```
227+
228+
### Resource Features
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.
157233

234+
```json
235+
{
236+
"status": 422,
237+
"error": "",
238+
"message": ""
239+
}
158240
```
159241

160-
### Features
242+
This is good to padronize the error handler in front-end too.
161243

162244
#### Ordering
163245

lib/generators/rest_api_generator/helpers.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,35 @@ def editable_attributes
1717
Rails::Generators::GeneratedAttribute.new(column.name.to_s, column.type.to_s)
1818
end
1919
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
2050
end
2151
end

lib/generators/rest_api_generator/resource_generator.rb

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class ResourceGenerator < Rails::Generators::NamedBase
1212
argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
1313
class_option :eject, type: :boolean, default: false
1414
class_option :scope, type: :string, default: ""
15+
class_option :father, type: :string, default: ""
1516

1617
API_CONTROLLER_DIR_PATH = "app/controllers"
1718
API_TEST_DIR_PATH = "spec/requests"
@@ -20,30 +21,66 @@ def create_service_file
2021
create_model_files
2122

2223
# Create controller and specs
23-
scope_path = options["scope"].present? ? "/#{options["scope"].pluralize}" : ""
2424
controller_path = "#{API_CONTROLLER_DIR_PATH}#{scope_path}/#{file_name.pluralize}_controller.rb"
2525
controller_test_path = "#{API_TEST_DIR_PATH}#{scope_path}/#{file_name.pluralize}_controller_spec.rb"
2626

2727
template controller_template, controller_path
2828
template spec_controller_template, controller_test_path
2929

30-
route "resources :#{file_name.pluralize}"
30+
if options["scope"].blank? && options["father"].blank?
31+
route "resources :#{file_name.pluralize}"
32+
else
33+
log("You need to manually setup routes files for nested or scoped resource")
34+
end
3135
end
3236

3337
private
3438

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+
3568
def controller_template
3669
if options["eject"]
37-
"rest_api_controller.rb"
38-
elsif options["scope"].present?
39-
"child_api_controller.rb"
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"
4077
else
4178
"implicit_resource_controller.rb"
4279
end
4380
end
4481

4582
def spec_controller_template
46-
if options["scope"].present?
83+
if options["father"].present?
4784
"child_api_spec.rb"
4885
else
4986
"rest_api_spec.rb"
Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
<% scope_namespacing do -%>
12
class <%= class_name.pluralize %>Controller < ApplicationController
2-
before_action :set_<%= options['scope'].downcase %>
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 ]
45

56
def index
6-
@<%= plural_name %> = @<%= options['scope'].downcase %>.<%= plural_name %>
7+
@<%= plural_name %> = @<%= options['father'].downcase.singularize %>.<%= plural_name %>
78
render json: @<%= plural_name %>, status: :ok
89
end
910

@@ -12,7 +13,7 @@ class <%= class_name.pluralize %>Controller < ApplicationController
1213
end
1314

1415
def create
15-
@<%= singular_name %> = @<%= options['scope'].downcase %>.<%= plural_name %>.create!(<%= singular_name %>_params)
16+
@<%= singular_name %> = @<%= options['father'].downcase.singularize %>.<%= plural_name %>.create!(<%= singular_name %>_params)
1617
render json: @<%= singular_name %>, status: :created
1718
end
1819

@@ -25,18 +26,18 @@ class <%= class_name.pluralize %>Controller < ApplicationController
2526
@<%= singular_name %>.destroy!
2627
end
2728

28-
2929
private
3030

31-
def set_<%= options['scope'].downcase %>
32-
@<%= options['scope'].downcase %> = <%= options['scope'] %>.find(params[:<%= options['scope'].downcase %>_id])
31+
def set_<%= options['father'].downcase.singularize %>
32+
@<%= options['father'].downcase %> = <%= options['father'] %>.find(params[:<%= options['father'].downcase.singularize %>_id])
3333
end
3434

3535
def set_<%= singular_name %>
36-
@<%= singular_name %> = @<%= options['scope'].downcase %>.<%= plural_name %>.find(params[:id])
36+
@<%= singular_name %> = @<%= options['father'].downcase.singularize %>.<%= plural_name %>.find(params[:id])
3737
end
3838

3939
def <%= singular_name %>_params
4040
params.require(:<%= singular_name %>).permit(<%= attributes.map { |a| ':' + a.name }.join(', ') %>)
4141
end
42-
end
42+
end
43+
<% end -%>

0 commit comments

Comments
 (0)