railspp 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +168 -0
- data/bin/railspp +9 -0
- data/lib/commands/initialize.rb +67 -0
- data/lib/commands/make_test.rb +12 -0
- data/lib/commands/model.rb +36 -0
- data/lib/help/documentation.rb +30 -0
- data/lib/help/initialize.rb +21 -0
- data/lib/help/make_test.rb +13 -0
- data/lib/help/model.rb +30 -0
- data/lib/railspp.rb +78 -0
- data/lib/templates/api_documentation_initializer.txt +4 -0
- data/lib/templates/api_documentation_service.txt +206 -0
- data/lib/templates/controller.txt +2 -0
- data/lib/templates/documentation.index.erb.txt +129 -0
- data/lib/templates/documentation.layout.erb.txt +15 -0
- data/lib/templates/documentation_controller.txt +52 -0
- data/lib/templates/exception_handler.txt +19 -0
- data/lib/templates/global_controller.txt +190 -0
- data/lib/templates/rack_cors_initializer.txt +6 -0
- data/lib/templates/response.txt +7 -0
- data/lib/templates/routes_documentation.txt +10 -0
- data/lib/templates/routes_namespace.txt +10 -0
- data/lib/utils/strings.rb +61 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 12d4c74ed0ac4e2d6342bcdd8319d96e078c162a76aa422de2fab510e1bce423
|
4
|
+
data.tar.gz: c121223b43c6c248fa8066d8311a40526979729282f546764bbb36f51d9d03cd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a99075325a3cbeaa265eca334eb94b7827c572b129d5f1e49de13034535e5af35f450f795b0369a31924dfa743d7e343eaa1de093dae8706db44ae5f068fe5e3
|
7
|
+
data.tar.gz: beb144dee9e25d7876ca01f66a9e966f304b27de13f1de50f935d44c51ca3ab04af085983d8c19f284b000479f9652a1ad90ace189c8dd7ed89f86a93d9f2b81
|
data/README.md
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
# Rails++
|
2
|
+
|
3
|
+
Autogenerate your CRUD operations with Swagger like API documentation
|
4
|
+
|
5
|
+
## Requirements
|
6
|
+
|
7
|
+
Rails version >= 5
|
8
|
+
Ruby version >= 2.5
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
In your gem file
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem 'railspp'
|
16
|
+
```
|
17
|
+
|
18
|
+
In your terminal
|
19
|
+
|
20
|
+
```bash
|
21
|
+
gem isntall railspp
|
22
|
+
```
|
23
|
+
|
24
|
+
## CLI Commands
|
25
|
+
|
26
|
+
Your command CLI command bin path is `railspp`
|
27
|
+
|
28
|
+
<!-- ![CLI DOCUMENTATION](./docs/command_list.png) -->
|
29
|
+
|
30
|
+
## Getting Started
|
31
|
+
|
32
|
+
For existing project use Git and keep all the
|
33
|
+
changes from the initialize method while keeping your changes as well.
|
34
|
+
|
35
|
+
Example:
|
36
|
+
Create a new project and then initialize.
|
37
|
+
|
38
|
+
```bash
|
39
|
+
rails new (project-name)
|
40
|
+
cd (project-name)
|
41
|
+
# Install dependency in gem file and globally
|
42
|
+
railspp init
|
43
|
+
```
|
44
|
+
|
45
|
+
Run the initialize command to get started:
|
46
|
+
|
47
|
+
```bash
|
48
|
+
railspp init
|
49
|
+
```
|
50
|
+
|
51
|
+
Run the model command to generate a migration, model, and completed controller:
|
52
|
+
|
53
|
+
This controller is overwritable when you declare the same method name in it's controller class.
|
54
|
+
|
55
|
+
```bash
|
56
|
+
railspp model (model-name)
|
57
|
+
```
|
58
|
+
|
59
|
+
Update your migration and model file. Then declare your routes with `resources`.
|
60
|
+
The controller has all api resources completed by default. These will fail if your migration and
|
61
|
+
model are not completed. And your routes are not defined.
|
62
|
+
|
63
|
+
Steps to do after:
|
64
|
+
|
65
|
+
- Update model file
|
66
|
+
- Update migration file
|
67
|
+
- Define your routes with resources
|
68
|
+
|
69
|
+
Run the make_test command to make unit tests:
|
70
|
+
|
71
|
+
```bash
|
72
|
+
railspp make_test (resource-route-name)
|
73
|
+
```
|
74
|
+
|
75
|
+
After your tests are completed. Update your update and create request bodies
|
76
|
+
based on the controller's `params.permit`. Add the keys available to your request body.
|
77
|
+
If you have special headers like "Authorization" add those to the public
|
78
|
+
variable `@headers` in that file.
|
79
|
+
|
80
|
+
Steps to do after:
|
81
|
+
|
82
|
+
- Update request body based on the controller's permitted keys
|
83
|
+
- Update your headers for all the requests
|
84
|
+
|
85
|
+
```bash
|
86
|
+
|
87
|
+
```
|
88
|
+
|
89
|
+
## API Documentation
|
90
|
+
|
91
|
+
There are Swagger-like API Documentation that requires no configuration.
|
92
|
+
This was created on your initialize command. This docs with regenerate the javascript
|
93
|
+
on API route changes.
|
94
|
+
|
95
|
+
The API documentation lives on your web route '/documentation'
|
96
|
+
Run the server and listen to url:
|
97
|
+
|
98
|
+
`http://localhost:3000/documentation`
|
99
|
+
|
100
|
+
For more information on the API docs check out the details here: [Autogenerated API Documentation Docs](./docs/API_DOCUMENTATION.md)
|
101
|
+
|
102
|
+
## Gloabl Controller
|
103
|
+
|
104
|
+
All of your CRUD operations were created in the GlobalController.
|
105
|
+
The GlobalController gets your model based on the class name of the controller.
|
106
|
+
Do not change the class name of the generated controller.
|
107
|
+
|
108
|
+
All methods are overwritable but have the functionality complete by default.
|
109
|
+
Restrict the routes you want accessible through the apiResource declaration.
|
110
|
+
Update the tests for ignoring the routes that do not exist.
|
111
|
+
|
112
|
+
By Default these methods are created:
|
113
|
+
|
114
|
+
- index
|
115
|
+
- show
|
116
|
+
- store
|
117
|
+
- update
|
118
|
+
- destroy
|
119
|
+
|
120
|
+
There are many querystrings available in the index method.
|
121
|
+
These contain options like:
|
122
|
+
|
123
|
+
- include=(associated model names comma separated)
|
124
|
+
- where=(key:value rows comma separated and key value pairs are semi-colon separated)
|
125
|
+
- order=(key:(DESC||ASC) rows comma separated and key DESC/ASC pairs semi-colon separated)
|
126
|
+
- limit=(amount returned the default is 25)
|
127
|
+
- offset=(number_to_skip in rows)
|
128
|
+
- page=(which page the default is 1)
|
129
|
+
|
130
|
+
For more information about the Global Controller check out the details here: [Global Controller Docs](./docs/GLOBAL_CONTROLLER.md)
|
131
|
+
|
132
|
+
## Unit tests
|
133
|
+
|
134
|
+
By default the tests check the format of the response and the status code.
|
135
|
+
You must provide request params for your store method and update method based
|
136
|
+
on the params options in your model. They are at the top of the file with TODOS.
|
137
|
+
There are tests for the querystrings. That you can update. The include
|
138
|
+
querystring test must be updated based on your schema.
|
139
|
+
|
140
|
+
The tests cover all querystrings with the same successful data format as default index currently.
|
141
|
+
|
142
|
+
Test cover:
|
143
|
+
|
144
|
+
Index:
|
145
|
+
|
146
|
+
- status_code = 200
|
147
|
+
- json_format = { data: (array), count: (integer) }
|
148
|
+
|
149
|
+
Show:
|
150
|
+
|
151
|
+
- status_code = 200
|
152
|
+
- json_format = { id: (integer) }
|
153
|
+
|
154
|
+
Create:
|
155
|
+
|
156
|
+
- status_code = 201
|
157
|
+
- json_format = { id: (integer) }
|
158
|
+
|
159
|
+
Update:
|
160
|
+
|
161
|
+
- status_code = 200
|
162
|
+
- json_format = { id: (integer) }
|
163
|
+
|
164
|
+
Destroy:
|
165
|
+
|
166
|
+
- status_code = 204
|
167
|
+
|
168
|
+
For more information about the Unit Tests check out the details here: [Generated Unit Test Docs](./docs/RAILSUNIT.md)
|
data/bin/railspp
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require_relative '../utils/strings.rb'
|
2
|
+
|
3
|
+
|
4
|
+
class InitializeCommand < MoreUtils
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def run *args
|
8
|
+
lookup = flag_lookup(args)
|
9
|
+
|
10
|
+
# Add Initializers
|
11
|
+
cors_template = get_file_str("#{this_dir}/../templates/rack_cors_initializer.txt")
|
12
|
+
write_file("#{root}/config/initializers/rack_cors.rb", cors_template)
|
13
|
+
|
14
|
+
adi_template = get_file_str("#{this_dir}/../templates/api_documentation_initializer.txt")
|
15
|
+
write_file("#{root}/config/initializers/api_documentation_js.rb", adi_template)
|
16
|
+
|
17
|
+
# Add Controllers
|
18
|
+
dc_template = get_file_str("#{this_dir}/../templates/documentation_controller.txt")
|
19
|
+
write_file("#{root}/app/controllers/documentation_controller.rb", dc_template)
|
20
|
+
|
21
|
+
gc_template = get_file_str("#{this_dir}/../templates/global_controller.txt")
|
22
|
+
write_file("#{root}/app/controllers/global_controller.rb", gc_template)
|
23
|
+
|
24
|
+
# Add Concerns
|
25
|
+
ehc_template = get_file_str("#{this_dir}/../templates/exception_handler.txt")
|
26
|
+
write_file("#{root}/app/controllers/concerns/exception_handler.rb", ehc_template)
|
27
|
+
|
28
|
+
resp_template = get_file_str("#{this_dir}/../templates/response.txt")
|
29
|
+
write_file("#{root}/app/controllers/concerns/response.rb", resp_template)
|
30
|
+
|
31
|
+
# Add Service
|
32
|
+
system("mkdir -p #{root}/app/services")
|
33
|
+
aps_template = get_file_str("#{this_dir}/../templates/api_documentation_service.txt")
|
34
|
+
write_file("#{root}/app/services/api_documentation_service.rb", aps_template)
|
35
|
+
|
36
|
+
# Add Views
|
37
|
+
system("mkdir -p #{root}/app/views/documentation")
|
38
|
+
dihtml_template = get_file_str("#{this_dir}/../templates/documentation.index.erb.txt")
|
39
|
+
write_file("#{root}/app/views/documentation/index.html.erb", dihtml_template)
|
40
|
+
|
41
|
+
dlhtml_template = get_file_str("#{this_dir}/../templates/documentation.layout.erb.txt")
|
42
|
+
write_file("#{root}/app/views/layouts/documentation.html.erb", dlhtml_template)
|
43
|
+
|
44
|
+
# Update Routes
|
45
|
+
unless lookup[:"skip-routes"]
|
46
|
+
routes_template = get_file_str("#{this_dir}/../templates/routes_documentation.txt")
|
47
|
+
routes_file = get_file_str("#{root}/config/routes.rb")
|
48
|
+
routes_arr = routes_file.split("\n")
|
49
|
+
last_end_line = last_end_index(routes_arr)
|
50
|
+
|
51
|
+
new_routes = routes_arr.slice(0, last_end_line).join("\n") + "\n#{routes_template}\nend\n"
|
52
|
+
write_file("#{root}/config/routes.rb", new_routes)
|
53
|
+
end
|
54
|
+
|
55
|
+
puts "Your project has been initialized."
|
56
|
+
end
|
57
|
+
|
58
|
+
def last_end_index arr
|
59
|
+
arr.each_with_index.inject(0) do |acc, (e, i)|
|
60
|
+
acc = i if /(end)/.match(e)
|
61
|
+
acc
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative '../utils/strings.rb'
|
2
|
+
|
3
|
+
|
4
|
+
class ModelCommand < MoreUtils
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def run *args
|
8
|
+
lookup = flag_lookup(args)
|
9
|
+
arguments = get_args(args)
|
10
|
+
|
11
|
+
if arguments.length < 2
|
12
|
+
puts "Enter a valid model generation command."
|
13
|
+
return
|
14
|
+
end
|
15
|
+
|
16
|
+
model_name = arguments[0].camelcase
|
17
|
+
others = arguments[1..-1]
|
18
|
+
|
19
|
+
system("rails generate model #{model_name} #{others.join(' ')}")
|
20
|
+
|
21
|
+
api_version_path = lookup[:"api-version"] || 'api/v1'
|
22
|
+
system("mkdir -p #{root}/app/controllers/#{api_version_path}")
|
23
|
+
|
24
|
+
controller_prefix = api_version_path.split('/').map { |e| e.downcase.capitalize }.join('::')
|
25
|
+
controller_name = controller_prefix + '::' + model_name
|
26
|
+
|
27
|
+
controller_temp = get_file_str("#{this_dir}/../templates/controller.txt")
|
28
|
+
controller_regex = '{{ CONTROLLER_NAME }}'
|
29
|
+
controller_str = controller_temp.gsub(controller_regex, controller_name)
|
30
|
+
write_file("#{root}/app/controllers/#{api_version_path}/#{model_name.underscore}_controller.rb", controller_str)
|
31
|
+
|
32
|
+
puts "#{model_name} model, migration, and controller has been generated."
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative '../utils/strings.rb'
|
2
|
+
|
3
|
+
class DocumentationHelpCommand < MoreUtils
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def run descriptions
|
7
|
+
puts "#{ascii_art}
|
8
|
+
Rails Plus Plus Version: #{gem_version}
|
9
|
+
|
10
|
+
Rails Plus Plus: Command Line Interface to make your life easier.
|
11
|
+
=> The Rails Plus Plus command is 'railspp'. To blast this project into the fifth dimension.
|
12
|
+
=> Use '--help' on any of the commands listed below for more details.
|
13
|
+
|
14
|
+
List of commands:
|
15
|
+
#{descriptions}
|
16
|
+
"
|
17
|
+
end
|
18
|
+
|
19
|
+
def ascii_art
|
20
|
+
"______ _ __
|
21
|
+
| ___ \\ (_) | _ _
|
22
|
+
| |_/ /__ _ _| |___ _| |_ _| |_
|
23
|
+
| // _` | | / __|_ _|_ _|
|
24
|
+
| |\\ \\ (_| | | \\__ \\ |_| |_|
|
25
|
+
\\_| \\_\\__,_|_|_|___/
|
26
|
+
"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class InitializeHelpCommand
|
2
|
+
class << self
|
3
|
+
|
4
|
+
def run *args
|
5
|
+
puts "Options:
|
6
|
+
|
7
|
+
You have the availability to skip portions of the
|
8
|
+
initialize command.
|
9
|
+
|
10
|
+
If you would like to skip the initial route nmespace use:
|
11
|
+
|
12
|
+
--skip-routes
|
13
|
+
|
14
|
+
Run to initialize your project:
|
15
|
+
'railspp initialize'
|
16
|
+
"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
data/lib/help/model.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
class ModelHelpCommand
|
2
|
+
class << self
|
3
|
+
|
4
|
+
def run
|
5
|
+
puts "Options:
|
6
|
+
You have the abliity to enter an api-version through namespaces.
|
7
|
+
You can generate your controller in your nmespace directory.
|
8
|
+
By default the value is 'api/v1'
|
9
|
+
|
10
|
+
Enter your namespace directory-path from controllers directory without
|
11
|
+
a starting '/' in this option.
|
12
|
+
|
13
|
+
--api-version=(namespace-directory-path-from-controllers-directory)
|
14
|
+
|
15
|
+
Example:
|
16
|
+
|
17
|
+
--api-version=api/v2
|
18
|
+
|
19
|
+
Same command as:
|
20
|
+
rails generate model <model-name>
|
21
|
+
|
22
|
+
With your generated controller
|
23
|
+
|
24
|
+
Run to generate your model, migration, and controller:
|
25
|
+
'railspp model (model-name) (models-options) (your-api-version-flag)'
|
26
|
+
"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
data/lib/railspp.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require_relative './commands/initialize.rb'
|
2
|
+
require_relative './commands/make_test.rb'
|
3
|
+
require_relative './commands/model.rb'
|
4
|
+
require_relative './help/documentation.rb'
|
5
|
+
require_relative './help/initialize.rb'
|
6
|
+
require_relative './help/make_test.rb'
|
7
|
+
require_relative './help/model.rb'
|
8
|
+
require_relative './utils/strings.rb'
|
9
|
+
|
10
|
+
|
11
|
+
class RailsPlusPlus < MoreUtils
|
12
|
+
class << self
|
13
|
+
|
14
|
+
def error_string
|
15
|
+
'ERROR: Must make railspp commands in the rails root directory'
|
16
|
+
end
|
17
|
+
|
18
|
+
def check_directory
|
19
|
+
File.file?(root + '/bin/rails')
|
20
|
+
end
|
21
|
+
|
22
|
+
def run_command arguments
|
23
|
+
|
24
|
+
is_help = arguments.select { |e| e == '--help' || e == '-h' }.length > 0
|
25
|
+
command_name = arguments[0]
|
26
|
+
passable_args = arguments[1..-1]
|
27
|
+
lookup = is_help ? command_name_help_lookup : command_name_lookup
|
28
|
+
command_exists = !command_name.nil? && !lookup[command_name.to_sym].nil?
|
29
|
+
|
30
|
+
if command_exists
|
31
|
+
command_class = lookup[command_name.to_sym]
|
32
|
+
command_class.run(*passable_args)
|
33
|
+
else
|
34
|
+
DocumentationHelpCommand.run(descriptions.join("\n"))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def command_name_lookup
|
41
|
+
{
|
42
|
+
i: InitializeCommand,
|
43
|
+
init: InitializeCommand,
|
44
|
+
initialize: InitializeCommand,
|
45
|
+
m: ModelCommand,
|
46
|
+
model: ModelCommand,
|
47
|
+
make_test: MakeTestCommand,
|
48
|
+
mt: MakeTestCommand,
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def command_name_help_lookup
|
53
|
+
{
|
54
|
+
i: InitializeHelpCommand,
|
55
|
+
init: InitializeHelpCommand,
|
56
|
+
initialize: InitializeHelpCommand,
|
57
|
+
m: ModelHelpCommand,
|
58
|
+
model: ModelHelpCommand,
|
59
|
+
make_test: MakeTestHelpCommand,
|
60
|
+
mt: MakeTestHelpCommand,
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def descriptions
|
65
|
+
[
|
66
|
+
'- i => Initialize your project',
|
67
|
+
'- init => Initialize your project',
|
68
|
+
'- initialize => Initialize your project',
|
69
|
+
'- m => Generate your CRUD model, controller, and migration',
|
70
|
+
'- model => Generate your CRUD model, controller, and migration',
|
71
|
+
'- make_test => Generate your resource unit test',
|
72
|
+
'- mt => Generate your resource unit test'
|
73
|
+
]
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
@@ -0,0 +1,206 @@
|
|
1
|
+
class APIDocumentationService
|
2
|
+
class << self
|
3
|
+
def generate_js_file
|
4
|
+
all_routes = get_api_routes
|
5
|
+
documentation_js_str = all_routes.map { |e| handle_js_for_route(e) }.join("\n")
|
6
|
+
|
7
|
+
File.open(output_path, "w+") { |f| f.write(documentation_js_str) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def namespaces
|
11
|
+
# Blacklist namespaces
|
12
|
+
[:api]
|
13
|
+
end
|
14
|
+
|
15
|
+
def docs_namespaces
|
16
|
+
namespaces.each_with_object({}) do |item, acc|
|
17
|
+
acc[item] = true
|
18
|
+
acc
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_api_routes
|
23
|
+
get_all_routes.select do |e|
|
24
|
+
path_array = e[:route].split('/')
|
25
|
+
path_array.length > 1 && docs_namespaces[path_array[1].to_sym]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def camelize_route route
|
30
|
+
camel_route = route[:route].split('/').reject { |e| e == '' }.map { |e| /\:/.match(e) ? e.gsub!(':', '') + 'Param' : e }.map { |e| e.capitalize }.join('')
|
31
|
+
route[:method].downcase + camel_route
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def output_path
|
37
|
+
__dir__ + '/../../app/assets/javascripts/documentation.js'
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_all_routes
|
41
|
+
Rails.application.routes.routes.map { |e| { route: e.path.spec.to_s.split('(')[0], method: e.verb } }.map { |e| e[:route] && e[:method] ? e.merge(camel_cased: camelize_route(e)) : e }.select { |e| e[:route] }
|
42
|
+
end
|
43
|
+
|
44
|
+
def handle_js_for_route route
|
45
|
+
camel_cased = route[:camel_cased]
|
46
|
+
javascript_string = ''
|
47
|
+
javascript_string += "window.#{camel_cased} = function() {
|
48
|
+
var allData = {
|
49
|
+
method: '#{route[:method]}',
|
50
|
+
route: '#{route[:route]}',
|
51
|
+
};
|
52
|
+
var paramList = document.getElementById('#{camel_cased}ParamsForm') && document.getElementById('#{camel_cased}ParamsForm').elements ? document.getElementById('#{camel_cased}ParamsForm').elements : [];
|
53
|
+
var headerList = document.getElementById('#{camel_cased}HeadersForm') && document.getElementById('#{camel_cased}HeadersForm').elements ? document.getElementById('#{camel_cased}HeadersForm').elements : [];
|
54
|
+
|
55
|
+
var headerObject = {};
|
56
|
+
var tempHeaderKey = null;
|
57
|
+
for (var i = 0; i < headerList.length; i++) {
|
58
|
+
var eleHeader = headerList[i].value;
|
59
|
+
if (i % 2 !== 0 && tempHeaderKey) {
|
60
|
+
headerObject[tempHeaderKey] = eleHeader;
|
61
|
+
tempHeaderKey = null;
|
62
|
+
} else {
|
63
|
+
tempHeaderKey = eleHeader;
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
var hasHeaders = Object.keys(headerObject).length > 0;
|
68
|
+
var headers = { headers: headerObject };
|
69
|
+
|
70
|
+
var paramObject = {};
|
71
|
+
var tempParamKey = null;
|
72
|
+
for (var i = 0; i < paramList.length; i++) {
|
73
|
+
var eleParam = paramList[i].value;
|
74
|
+
if (i % 2 !== 0) {
|
75
|
+
paramObject[':' + tempParamKey] = eleParam;
|
76
|
+
tempParamKey = null;
|
77
|
+
} else {
|
78
|
+
tempParamKey = eleParam;
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
var routeName = allData.route.split('/').map(function(e) { return paramObject[e] ? paramObject[e] : e; }).join('/');
|
83
|
+
|
84
|
+
if (allData.method !== 'GET') {
|
85
|
+
var bodyDataType = document.getElementById('#{camel_cased}DataType') && document.getElementById('#{camel_cased}DataType').value ? document.getElementById('#{camel_cased}DataType').value : false;
|
86
|
+
var formBoolean = bodyDataType === 'Form Data';
|
87
|
+
|
88
|
+
var bodyElements = [];
|
89
|
+
var bodyRawElements = document.getElementById('#{camel_cased}BodyForm') && document.getElementById('#{camel_cased}BodyForm').elements ? document.getElementById('#{camel_cased}BodyForm').elements : [];
|
90
|
+
for (var i = 0; i < bodyRawElements.length; i++) {
|
91
|
+
var eleParam = bodyRawElements[i].files ? bodyRawElements[i].files[0] : (bodyRawElements[i].value || null);
|
92
|
+
bodyElements.push(eleParam);
|
93
|
+
}
|
94
|
+
bodyElements = bodyElements.filter(function(e) {
|
95
|
+
return !!e;
|
96
|
+
});
|
97
|
+
|
98
|
+
var bodyObject = bodyDataType === 'Form Data' ? new FormData() : {};
|
99
|
+
var tempBodyKey = null;
|
100
|
+
bodyElements.forEach(function(e, i) {
|
101
|
+
if (i % 2 !== 0) {
|
102
|
+
if (formBoolean) {
|
103
|
+
bodyObject.append(tempBodyKey, e);
|
104
|
+
tempBodyKey = null;
|
105
|
+
} else {
|
106
|
+
bodyObject[tempBodyKey] = e;
|
107
|
+
tempBodyKey = null;
|
108
|
+
}
|
109
|
+
} else {
|
110
|
+
if (formBoolean) {
|
111
|
+
tempBodyKey = e;
|
112
|
+
} else {
|
113
|
+
bodyObject[e] = null;
|
114
|
+
tempBodyKey = e;
|
115
|
+
}
|
116
|
+
}
|
117
|
+
});
|
118
|
+
}
|
119
|
+
|
120
|
+
var qsElements = [];
|
121
|
+
var qsRawElements = document.getElementById('#{camel_cased}QSForm') && document.getElementById('#{camel_cased}QSForm').elements ? document.getElementById('#{camel_cased}QSForm').elements : [];
|
122
|
+
for (var i = 0; i < qsRawElements.length; i++) {
|
123
|
+
var eleParam = qsRawElements[i].value || null;
|
124
|
+
qsElements.push(eleParam);
|
125
|
+
}
|
126
|
+
|
127
|
+
qsElements = qsElements.filter(function(e) { return !!e; });
|
128
|
+
|
129
|
+
var qsObject = {};
|
130
|
+
var tempQSKey = null;
|
131
|
+
qsElements.forEach(function(e, i) {
|
132
|
+
if (i % 2 !== 0) {
|
133
|
+
qsObject[tempQSKey] = e;
|
134
|
+
tempQSKey = null;
|
135
|
+
} else {
|
136
|
+
qsObject[e] = null;
|
137
|
+
tempQSKey = e;
|
138
|
+
}
|
139
|
+
});
|
140
|
+
|
141
|
+
var qsLength = Object.keys(qsObject).length;
|
142
|
+
var querystring = qsLength > 0 ? '?' : '';
|
143
|
+
|
144
|
+
var qsCount = 0;
|
145
|
+
var qsArray = [];
|
146
|
+
if (querystring === '?') {
|
147
|
+
for (var qs in qsObject) {
|
148
|
+
if (qs && qsObject[qs]) {
|
149
|
+
qsArray.push(qs + '=' + qsObject[qs]);
|
150
|
+
}
|
151
|
+
}
|
152
|
+
}
|
153
|
+
|
154
|
+
querystring += qsArray.join('&');
|
155
|
+
|
156
|
+
var args = allData.method === 'GET' || allData.method === 'DELETE' ? [routeName + querystring, headers] : [routeName + querystring, bodyObject, headers];
|
157
|
+
if (!hasHeaders) args.pop();
|
158
|
+
var resultElement = document.getElementById('#{camel_cased}-results');
|
159
|
+
|
160
|
+
axios[allData.method.toLowerCase()](...args)
|
161
|
+
.then(function(resp) {
|
162
|
+
if (resp.status <= 300) {
|
163
|
+
resultElement.innerText = JSON.stringify(resp.data, null, 4);
|
164
|
+
} else {
|
165
|
+
resultElement.innerText = JSON.stringify(resp.data, null, 4);
|
166
|
+
}
|
167
|
+
})
|
168
|
+
.catch(function(err) {
|
169
|
+
var error_ajax = err && err.response && err.response.data ? err.response.data : err;
|
170
|
+
resultElement.innerText = JSON.stringify(error_ajax, null, 4);
|
171
|
+
});
|
172
|
+
};
|
173
|
+
"
|
174
|
+
|
175
|
+
javascript_string += "window.#{camel_cased}NewBody = function() {
|
176
|
+
var ele = document.getElementById('#{camel_cased}BodyForm');
|
177
|
+
ele.innerHTML += '<div class=\"d-flex f-row\"><input class=\"w-100 m-1 form-control\" type=\"text\" placeholder=\"Enter key\"><input class=\"w-100 m-1 form-control\" type=\"text\" placeholder=\"Enter value\"></div>';
|
178
|
+
};
|
179
|
+
|
180
|
+
"
|
181
|
+
|
182
|
+
javascript_string += "window.#{camel_cased}NewBodyFile = function() {
|
183
|
+
var ele = document.getElementById('#{camel_cased}BodyForm');
|
184
|
+
ele.innerHTML += '<div class=\"d-flex f-row\"><input class=\"w-100 m-1 form-control\" type=\"text\" placeholder=\"Enter key\"><input class=\"w-100 m-1 form-control\" type=\"file\" multiple accept=\"*/*\" placeholder=\"Enter value\"></div>';
|
185
|
+
};
|
186
|
+
|
187
|
+
"
|
188
|
+
|
189
|
+
javascript_string += "window.#{camel_cased}NewQS = function() {
|
190
|
+
var ele = document.getElementById('#{camel_cased}QSForm');
|
191
|
+
ele.innerHTML += '<div class=\"d-flex f-row\"><input class=\"w-100 m-1 form-control\" type=\"text\" placeholder=\"Enter key\"><input class=\"w-100 m-1 form-control\" type=\"text\" placeholder=\"Enter value\"></div>';
|
192
|
+
};
|
193
|
+
"
|
194
|
+
|
195
|
+
javascript_string += "window.#{camel_cased}NewHeader = function() {
|
196
|
+
var ele = document.getElementById('#{camel_cased}HeadersForm');
|
197
|
+
ele.innerHTML += '<div class=\"d-flex f-row\"><input class=\"w-100 m-1 form-control\" type=\"text\" placeholder=\"Enter key\"><input class=\"w-100 m-1 form-control\" type=\"text\" placeholder=\"Enter value\"></div>';
|
198
|
+
};
|
199
|
+
|
200
|
+
";
|
201
|
+
|
202
|
+
javascript_string
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
<div class="header p-3 bg-dark card-shadow d-flex flex-row justify-content-between">
|
2
|
+
<div class="d-flex flex-row">
|
3
|
+
<a href='/' class="h-100">
|
4
|
+
<img class="d-flex flex-column align-items-center justify-content-center" src="https://s3-us-west-1.amazonaws.com/manoftech/ruby.png" alt="" height='50' width='50' />
|
5
|
+
</a>
|
6
|
+
<h1 class="h1 h-100 align-items-center text-white ml-3">API Documentation</h1>
|
7
|
+
</div>
|
8
|
+
<a class="d-flex align-items-center cursor-pointer">
|
9
|
+
<h2 class="h5 text-white" onclick="goBack()">Go Back</h2>
|
10
|
+
</a>
|
11
|
+
</div>
|
12
|
+
|
13
|
+
<div class="card-body p-3 w-100 p-1">
|
14
|
+
<% @all_routes.each do |data| %>
|
15
|
+
<div class="card card-outline-secondary w-100 flex-respond-row card-shadow">
|
16
|
+
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-6 col-xl-6 p-1 card-shadow">
|
17
|
+
<div class='card-header <%= "#{data[:route_header]}" %>'>
|
18
|
+
<span class="mr-1"><%= data[:method] %>:</span>
|
19
|
+
<span><%= data[:route] %></span>
|
20
|
+
</div>
|
21
|
+
<div class="card-body p-2 w-100" style="max-height: 550px; overflow: auto;">
|
22
|
+
<% if data[:middleware] && data[:middleware].length > 0 %>
|
23
|
+
<h2 class="h5">Middleware Used:</h2>
|
24
|
+
<ul>
|
25
|
+
<% data[:middleware].each do |ware| %>
|
26
|
+
<li class="h6"><%= "#{ware}" %></li>
|
27
|
+
<% end %>
|
28
|
+
</ul>
|
29
|
+
<% end %>
|
30
|
+
|
31
|
+
<h2 class="h5">Description:
|
32
|
+
<ul>
|
33
|
+
<% data[:description].each do |ware| %>
|
34
|
+
<li class="h6"><%= "#{ware}" %></li>
|
35
|
+
<% end %>
|
36
|
+
</ul>
|
37
|
+
</h2>
|
38
|
+
|
39
|
+
<div class="d-flex f-row w-100">
|
40
|
+
<h2 class="w-100 h6">Headers:</h2>
|
41
|
+
<button class="<%= "#{data[:submit_button_color]} rounded-circle" %>" onclick="<%= "#{data[:submit_button_color]}NewHeader()" %>">+</button>
|
42
|
+
</div>
|
43
|
+
<div class="d-flex f-row w-100">
|
44
|
+
<form id="<%= data[:camel_cased] %>HeadersForm" class="w-100">
|
45
|
+
<div class="d-flex f-row">
|
46
|
+
<input type="text" class="w-100 m-1 d-flex f-row form-control" placeholder="Enter key">
|
47
|
+
<input type="text" class="w-100 m-1 form-control" placeholder="Enter value">
|
48
|
+
</div>
|
49
|
+
</form>
|
50
|
+
</div>
|
51
|
+
|
52
|
+
<% if data[:allow_params] %>
|
53
|
+
<div class="d-flex f-row w-100">
|
54
|
+
<h2 class="w-100 h6">Params:</h2>
|
55
|
+
</div>
|
56
|
+
<div class="d-flex f-row w-100">
|
57
|
+
<form id="<%= "#{data[:camel_cased]}ParamsForm" %>" class="w-100">
|
58
|
+
<%= data[:params].each do |param| %>
|
59
|
+
<div class="d-flex f-row">
|
60
|
+
<input id="<%= "#{data[:camel_cased]}-#{param}" %>" type="text" class="w-100 m-1 d-flex f-row form-control" value="<%= "#{param}" %>">
|
61
|
+
<input id="<%= "#{data[:camel_cased]}-#{param}-value" %>" type="text" class="w-100 m-1 form-control" placeholder="Enter value">
|
62
|
+
</div>
|
63
|
+
<% end %>
|
64
|
+
</form>
|
65
|
+
</div>
|
66
|
+
<% else %>
|
67
|
+
<span></span>
|
68
|
+
<% end %>
|
69
|
+
<% if data[:allow_body] %>
|
70
|
+
<div class="d-flex f-row w-100">
|
71
|
+
<h2 class="w-100 h6">Body:</h2>
|
72
|
+
<button class="<%= "#{data[:submit_button_color]} rounded mr-2" %>" onclick="<%= "#{data[:camel_cased]}NewBodyFile()" %>">Add File</button>
|
73
|
+
<button class="<%= "#{data[:submit_button_color]} rounded-circle" %>" onclick="<%= "#{data[:camel_cased]}NewBody()" %>">+</button>
|
74
|
+
</div>
|
75
|
+
<div class="d-flex f-row w-100">
|
76
|
+
<form id='<%= "#{data[:camel_cased]}BodyForm" %>'class='w-100'>
|
77
|
+
<div class='d-flex f-row'>
|
78
|
+
<input type='text' class='w-100 m-1 form-control' placeholder='Enter key'>
|
79
|
+
<input type='text' class='w-100 m-1 form-control' placeholder='Enter value'>
|
80
|
+
</div>
|
81
|
+
</form>
|
82
|
+
</div>
|
83
|
+
<% else %>
|
84
|
+
<span></span>
|
85
|
+
<% end %>
|
86
|
+
<div class="d-flex f-row w-100">
|
87
|
+
<h2 class="w-100 h6">Querystrings: </h2>
|
88
|
+
<button class="<%= "#{data[:submit_button_color]} rounded-circle" %>" onclick="<%= "#{data[:camel_cased]}NewQS()" %>">+</button>
|
89
|
+
</div>
|
90
|
+
<div class="d-flex f-row w-100">
|
91
|
+
<form id='<%= "#{data[:camel_cased]}QSForm" %>' class="w-100">
|
92
|
+
<div class="d-flex f-row">
|
93
|
+
<input type="text" class="w-100 m-1 form-control" placeholder="Enter key">
|
94
|
+
<input type="text" class="w-100 m-1 form-control" placeholder="Enter value">
|
95
|
+
</div>
|
96
|
+
</form>
|
97
|
+
</div>
|
98
|
+
<% if data[:allow_body] %>
|
99
|
+
<h2 class="h6">Body Data Type:</h2>
|
100
|
+
<select id="<%= "#{data[:camel_cased]}DataType" %>" class='form-control'>
|
101
|
+
<option value=''></option>
|
102
|
+
<option value='JSON'>JSON</option>
|
103
|
+
<option value='Form Data'>Form Data</option>
|
104
|
+
</select>
|
105
|
+
<% else %>
|
106
|
+
<span></span>
|
107
|
+
<% end %>
|
108
|
+
<div class="w-100 mt-2">
|
109
|
+
<button class="<%= "#{data[:submit_button_color]} w-100" %>" onclick="<%= "#{data[:camel_cased]}()" %>">Submit</button>
|
110
|
+
</div>
|
111
|
+
</div>
|
112
|
+
</div>
|
113
|
+
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-6 col-xl-6 p-1 card-shadow p-1">
|
114
|
+
<div class="card-header">
|
115
|
+
<h2 class="h5">Sample Data</h2>
|
116
|
+
</div>
|
117
|
+
<div class='card-body' style="max-height: 550px; overflow: auto;">
|
118
|
+
<code style="overflow: auto; display: block;">
|
119
|
+
<span id="<%= "#{data[:camel_cased]}-results" %>" style="white-space: pre;"></span>
|
120
|
+
</code>
|
121
|
+
</div>
|
122
|
+
</div>
|
123
|
+
</div>
|
124
|
+
<% end %>
|
125
|
+
</div>
|
126
|
+
|
127
|
+
|
128
|
+
<script>function goBack() { window.history.back(); }</script>
|
129
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Automatic API Docs</title>
|
5
|
+
<%= csrf_meta_tags %>
|
6
|
+
<%= csp_meta_tag %>
|
7
|
+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
|
8
|
+
<style>.flex-respond-row {display: flex !important;flex-direction: column !important;}@media (min-width: 992px) {.flex-respond-row {display: flex !important;flex-direction: row !important;}}.card-shadow {box-shadow: 0 7px 14px 0 rgba(50,50,93,.1), 0 3px 6px 0 rgba(0,0,0,.07);}.cursor-pointer{ cursor: pointer !important; }</style>
|
9
|
+
</head>
|
10
|
+
|
11
|
+
<body style="width: 100%; overflow-x: hidden;">
|
12
|
+
<%= yield %>
|
13
|
+
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
|
14
|
+
</body>
|
15
|
+
</html>
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class DocumentationController < ApplicationController
|
2
|
+
|
3
|
+
def index
|
4
|
+
@all_routes = get_api_array
|
5
|
+
render template: 'documentation/index', layout: 'documentation'
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def get_api_array
|
11
|
+
get_api_routes.map { |e| get_api_object(e) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_api_object route_hash
|
15
|
+
{
|
16
|
+
method: route_hash[:method],
|
17
|
+
route: route_hash[:route],
|
18
|
+
params: get_params(route_hash[:route]),
|
19
|
+
middleware: ['Not Available'],
|
20
|
+
route_header: "text-white bg-#{get_method_color(route_hash[:method])}",
|
21
|
+
submit_button_color: "btn btn-outline-#{get_method_color(route_hash[:method])}",
|
22
|
+
camel_cased: camelize_route(route_hash),
|
23
|
+
allow_params: route_hash[:route].split(':').length > 1,
|
24
|
+
allow_body: route_hash[:method] != 'GET',
|
25
|
+
description: ['No Description available'],
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_method_color method
|
30
|
+
lookup = {
|
31
|
+
GET: 'success',
|
32
|
+
POST: 'info',
|
33
|
+
PATCH: 'warning',
|
34
|
+
PUT: 'warning',
|
35
|
+
DELETE: 'danger',
|
36
|
+
}
|
37
|
+
lookup[method.to_sym] || 'light'
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_params route
|
41
|
+
route.split('/').select { |e| /\:/.match(e) }.map { |e| e.split(':')[1] }
|
42
|
+
end
|
43
|
+
|
44
|
+
def camelize_route route
|
45
|
+
APIDocumentationService.camelize_route(route)
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_api_routes
|
49
|
+
APIDocumentationService.get_api_routes
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ExceptionHandler
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
rescue_from ActiveRecord::RecordNotFound do |e|
|
6
|
+
render_json({ error: e.message }, :not_found)
|
7
|
+
end
|
8
|
+
rescue_from ActiveRecord::RecordInvalid do |e|
|
9
|
+
render_json({ error: e.message }, :unprocessable_entity)
|
10
|
+
end
|
11
|
+
rescue_from ActiveRecord::ActiveRecordError do |e|
|
12
|
+
render_json({ error: e.message }, :bad_request)
|
13
|
+
end
|
14
|
+
rescue_from ActionController::InvalidAuthenticityToken do |e|
|
15
|
+
render_json({ error: e.message }, :forbidden)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
class GlobalController < ApplicationController
|
2
|
+
|
3
|
+
include Response
|
4
|
+
include ExceptionHandler
|
5
|
+
|
6
|
+
def default_per_page
|
7
|
+
25
|
8
|
+
end
|
9
|
+
|
10
|
+
def model
|
11
|
+
self.class.to_s.split('::').select { |e| /Controller/.match(e) }.first.chomp('Controller').singularize.constantize
|
12
|
+
end
|
13
|
+
|
14
|
+
def index
|
15
|
+
resp = index_response
|
16
|
+
past_pages = resp[:last_page] < resp[:current_page]
|
17
|
+
error = { error: 'Past the amount of pages available' }
|
18
|
+
render_json(past_pages ? error : index_response, past_pages ? 400 : 200)
|
19
|
+
end
|
20
|
+
|
21
|
+
def show
|
22
|
+
data = model.find(params[:id] || 0)
|
23
|
+
render_json(data, 200)
|
24
|
+
end
|
25
|
+
|
26
|
+
def create
|
27
|
+
data = model.create(create_params)
|
28
|
+
render_json(data, 201)
|
29
|
+
end
|
30
|
+
|
31
|
+
def update
|
32
|
+
data = model.where(params[:id] || 0).update(update_params).first
|
33
|
+
render_json(data, 200)
|
34
|
+
end
|
35
|
+
|
36
|
+
def destroy
|
37
|
+
model.destroy(params[:id] || 0)
|
38
|
+
render_json(nil, 204)
|
39
|
+
end
|
40
|
+
|
41
|
+
def bulk_csv_create
|
42
|
+
data = model.create(csv_to_json)
|
43
|
+
render_json(data, 201)
|
44
|
+
end
|
45
|
+
|
46
|
+
def bulk_create
|
47
|
+
data = model.create(bulk_create_params)
|
48
|
+
render_json(data, 201)
|
49
|
+
end
|
50
|
+
|
51
|
+
def bulk_update
|
52
|
+
data = model.where(id: bulk_update_params[:id]).update(bulk_update_params)
|
53
|
+
render_json(data, 201)
|
54
|
+
end
|
55
|
+
|
56
|
+
def bulk_destory
|
57
|
+
model.where(id: bulk_destroy_params)
|
58
|
+
render_json(nil, 204)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def csv_to_json param_key = :file, separated_by = ','
|
64
|
+
lines = params[param_key].split("\n")
|
65
|
+
keys = lines[0].split(separated_by)
|
66
|
+
rows = lines.slice(1).map { |e| e.split(separated_by) }
|
67
|
+
rows.map { |e| e.each_with_index.inject({}) { |acc, (e, i)| acc.merge("#{keys[i]}": e) } }
|
68
|
+
end
|
69
|
+
|
70
|
+
def create_params
|
71
|
+
bl = array_to_hash(black_list_create)
|
72
|
+
params.permit(*get_model_key.reject { |e| bl[e.to_sym] })
|
73
|
+
end
|
74
|
+
|
75
|
+
def update_params
|
76
|
+
bl = array_to_hash(black_list_update)
|
77
|
+
params.permit(*get_model_key.reject { |e| bl[e.to_sym] })
|
78
|
+
end
|
79
|
+
|
80
|
+
def bulk_create_params
|
81
|
+
params.permit(bulk: get_model_key)
|
82
|
+
end
|
83
|
+
|
84
|
+
def bulk_update_params
|
85
|
+
params.permit(bulk: get_model_key)
|
86
|
+
end
|
87
|
+
|
88
|
+
def bulk_destroy_params
|
89
|
+
params.permit(bulk: [:id])
|
90
|
+
end
|
91
|
+
|
92
|
+
def black_list_create
|
93
|
+
[:id, :created_at, :updated_at]
|
94
|
+
end
|
95
|
+
|
96
|
+
def black_list_update
|
97
|
+
[:id, :created_at, :updated_at]
|
98
|
+
end
|
99
|
+
|
100
|
+
def array_to_hash array
|
101
|
+
array.each_with_object({}) do |e, acc|
|
102
|
+
acc[e] = true
|
103
|
+
acc
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def handle_pagination
|
108
|
+
limit = (params[:limit] || default_per_page).to_i
|
109
|
+
offset = (params[:offset] || 0).to_i
|
110
|
+
page = (params[:page] || 1).to_i
|
111
|
+
|
112
|
+
data = model.limit(limit).offset(offset).offset((page - 1) * limit)
|
113
|
+
data = data.where(get_where) if params[:where]
|
114
|
+
data = data.includes(*get_includes) if params[:include]
|
115
|
+
data = data.order(get_order) if params[:order]
|
116
|
+
data = data.all
|
117
|
+
data
|
118
|
+
end
|
119
|
+
|
120
|
+
def handle_pagination_count
|
121
|
+
data = model
|
122
|
+
data = data.where(get_where) if params[:where]
|
123
|
+
data.count
|
124
|
+
end
|
125
|
+
|
126
|
+
def index_response
|
127
|
+
page = (params[:page] || 1).to_i
|
128
|
+
per_page = (params[:limit] || default_per_page).to_i
|
129
|
+
total = handle_pagination_count
|
130
|
+
path = request.original_url
|
131
|
+
path_path = add_pg_qs(path)
|
132
|
+
last = (total / per_page.to_f).ceil
|
133
|
+
nxt = (page + 1) > last ? nil : (page + 1)
|
134
|
+
prev = (page - 1) == 0 ? nil : (page - 1)
|
135
|
+
last = last == 0 ? 1 : last
|
136
|
+
data = handle_pagination
|
137
|
+
data_first = data.first
|
138
|
+
data_last = data.last
|
139
|
+
|
140
|
+
{
|
141
|
+
data: data,
|
142
|
+
to: data_first ? data_first.id : nil,
|
143
|
+
from: data_last ? data_last.id : nil,
|
144
|
+
total: total,
|
145
|
+
path: path,
|
146
|
+
current_page: page,
|
147
|
+
per_page: per_page,
|
148
|
+
last_page: last,
|
149
|
+
first_page_url: path_path + '1',
|
150
|
+
last_page_url: path_path + last.to_s,
|
151
|
+
next_page_url: nxt ? (path_path + nxt.to_s) : nil,
|
152
|
+
prev_page_url: prev ? (path_path + prev.to_s) : nil,
|
153
|
+
}
|
154
|
+
end
|
155
|
+
|
156
|
+
def add_pg_qs str
|
157
|
+
has_qs = /\?/.match(str)
|
158
|
+
return "#{str}?page=" unless has_qs
|
159
|
+
before_qs, after = str.split('?')
|
160
|
+
after_qs = after.split('&').reject { |e| /page\=/.match(e) }
|
161
|
+
after_qs.push('page=')
|
162
|
+
"#{before_qs}?#{after_qs.join('&')}"
|
163
|
+
end
|
164
|
+
|
165
|
+
def get_includes
|
166
|
+
comma_separated(params[:include]).map(&:to_sym)
|
167
|
+
end
|
168
|
+
|
169
|
+
def get_order
|
170
|
+
params[:order].split(',').flat_map { |e| e.split(':') }.join(' ')
|
171
|
+
end
|
172
|
+
|
173
|
+
def get_where
|
174
|
+
arrays = params[:where].split(',').map { |e| e.split(':') }
|
175
|
+
arrays.inject({}) do |acc, item|
|
176
|
+
key, val = item
|
177
|
+
acc[key] = val
|
178
|
+
acc
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def comma_separated key
|
183
|
+
params[:key].split(',')
|
184
|
+
end
|
185
|
+
|
186
|
+
def get_model_key
|
187
|
+
model.columns.map { |e| e.name.to_sym }
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class String
|
2
|
+
|
3
|
+
def underscore
|
4
|
+
self.gsub(/::/, '/').
|
5
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
6
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
7
|
+
tr("-", "_").
|
8
|
+
downcase
|
9
|
+
end
|
10
|
+
|
11
|
+
def camelcase
|
12
|
+
self.split(/(?=[A-Z])|(_)/)
|
13
|
+
.reject { |e| e == '_' }
|
14
|
+
.map(&:capitalize)
|
15
|
+
.join('')
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
class MoreUtils
|
21
|
+
class << self
|
22
|
+
|
23
|
+
def gem_version
|
24
|
+
"0.0.3"
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_file_str path
|
28
|
+
File.open(path, 'r:UTF-8', &:read)
|
29
|
+
end
|
30
|
+
|
31
|
+
def write_file path, str
|
32
|
+
File.write(path, str)
|
33
|
+
end
|
34
|
+
|
35
|
+
def this_dir
|
36
|
+
__dir__
|
37
|
+
end
|
38
|
+
|
39
|
+
def root
|
40
|
+
Dir.pwd
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_flags arr
|
44
|
+
arr.select { |e| /--/.match(e) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_args arr
|
48
|
+
arr.reject { |e| /--/.match(e) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def flag_lookup arr
|
52
|
+
arr.each_with_object({}) do |e, acc|
|
53
|
+
e = e.gsub('--', '')
|
54
|
+
key, val = e.split('=')
|
55
|
+
acc[key.to_sym] = val
|
56
|
+
acc
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: railspp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Layne Faler
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-01-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rack-cors
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: faker
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.9'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.9'
|
55
|
+
description: Scaffolding your CRUD operations
|
56
|
+
email: laynefaler@gmail.com
|
57
|
+
executables:
|
58
|
+
- railspp
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- README.md
|
63
|
+
- bin/railspp
|
64
|
+
- lib/commands/initialize.rb
|
65
|
+
- lib/commands/make_test.rb
|
66
|
+
- lib/commands/model.rb
|
67
|
+
- lib/help/documentation.rb
|
68
|
+
- lib/help/initialize.rb
|
69
|
+
- lib/help/make_test.rb
|
70
|
+
- lib/help/model.rb
|
71
|
+
- lib/railspp.rb
|
72
|
+
- lib/templates/api_documentation_initializer.txt
|
73
|
+
- lib/templates/api_documentation_service.txt
|
74
|
+
- lib/templates/controller.txt
|
75
|
+
- lib/templates/documentation.index.erb.txt
|
76
|
+
- lib/templates/documentation.layout.erb.txt
|
77
|
+
- lib/templates/documentation_controller.txt
|
78
|
+
- lib/templates/exception_handler.txt
|
79
|
+
- lib/templates/global_controller.txt
|
80
|
+
- lib/templates/rack_cors_initializer.txt
|
81
|
+
- lib/templates/response.txt
|
82
|
+
- lib/templates/routes_documentation.txt
|
83
|
+
- lib/templates/routes_namespace.txt
|
84
|
+
- lib/utils/strings.rb
|
85
|
+
homepage:
|
86
|
+
licenses: []
|
87
|
+
metadata: {}
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.5'
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubygems_version: 3.0.1
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: Scaffold your CRUD operations
|
107
|
+
test_files: []
|