power_api 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (138) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +9 -0
  4. data/.hound.yml +4 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +479 -0
  7. data/.ruby-version +1 -0
  8. data/.travis.yml +15 -0
  9. data/CHANGELOG.md +7 -0
  10. data/Gemfile +18 -0
  11. data/Gemfile.lock +310 -0
  12. data/Guardfile +15 -0
  13. data/LICENSE.txt +21 -0
  14. data/README.md +904 -0
  15. data/Rakefile +10 -0
  16. data/app/assets/config/power_api_manifest.js +2 -0
  17. data/app/assets/images/power_api/.keep +0 -0
  18. data/app/assets/javascripts/power_api/application.js +13 -0
  19. data/app/assets/stylesheets/power_api/application.css +15 -0
  20. data/app/controllers/concerns/api/deprecated.rb +23 -0
  21. data/app/controllers/concerns/api/error.rb +37 -0
  22. data/app/controllers/concerns/api/filtered.rb +15 -0
  23. data/app/controllers/concerns/api/versioned.rb +36 -0
  24. data/app/controllers/power_api/base_controller.rb +14 -0
  25. data/app/helpers/power_api/application_helper.rb +4 -0
  26. data/app/jobs/power_api/application_job.rb +4 -0
  27. data/app/mailers/power_api/application_mailer.rb +6 -0
  28. data/app/models/power_api/application_record.rb +5 -0
  29. data/app/responders/api_responder.rb +13 -0
  30. data/app/views/layouts/power_api/application.html.erb +14 -0
  31. data/bin/rails +14 -0
  32. data/config/routes.rb +2 -0
  33. data/lib/generators/power_api/controller/USAGE +5 -0
  34. data/lib/generators/power_api/controller/controller_generator.rb +152 -0
  35. data/lib/generators/power_api/install/USAGE +5 -0
  36. data/lib/generators/power_api/install/install_generator.rb +67 -0
  37. data/lib/generators/power_api/version/USAGE +5 -0
  38. data/lib/generators/power_api/version/version_generator.rb +54 -0
  39. data/lib/power_api.rb +35 -0
  40. data/lib/power_api/engine.rb +28 -0
  41. data/lib/power_api/errors.rb +4 -0
  42. data/lib/power_api/generator_helper/active_record_resource.rb +192 -0
  43. data/lib/power_api/generator_helper/ams_helper.rb +43 -0
  44. data/lib/power_api/generator_helper/controller_helper.rb +184 -0
  45. data/lib/power_api/generator_helper/pagination_helper.rb +48 -0
  46. data/lib/power_api/generator_helper/resource_helper.rb +33 -0
  47. data/lib/power_api/generator_helper/routes_helper.rb +91 -0
  48. data/lib/power_api/generator_helper/rubocop_helper.rb +11 -0
  49. data/lib/power_api/generator_helper/simple_token_auth_helper.rb +124 -0
  50. data/lib/power_api/generator_helper/swagger_helper.rb +463 -0
  51. data/lib/power_api/generator_helper/template_builder_helper.rb +23 -0
  52. data/lib/power_api/generator_helper/version_helper.rb +16 -0
  53. data/lib/power_api/generator_helpers.rb +25 -0
  54. data/lib/power_api/version.rb +3 -0
  55. data/lib/tasks/power_api_tasks.rake +4 -0
  56. data/power_api.gemspec +44 -0
  57. data/spec/dummy/Rakefile +6 -0
  58. data/spec/dummy/app/assets/config/manifest.js +5 -0
  59. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  60. data/spec/dummy/app/assets/javascripts/cable.js +13 -0
  61. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  62. data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
  63. data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
  64. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  65. data/spec/dummy/app/controllers/concerns/api/deprecated_spec.rb +37 -0
  66. data/spec/dummy/app/controllers/concerns/api/error_spec.rb +97 -0
  67. data/spec/dummy/app/controllers/concerns/api/filtered_spec.rb +42 -0
  68. data/spec/dummy/app/controllers/concerns/api/versioned_spec.rb +64 -0
  69. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  70. data/spec/dummy/app/jobs/application_job.rb +2 -0
  71. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  72. data/spec/dummy/app/models/application_record.rb +3 -0
  73. data/spec/dummy/app/models/blog.rb +5 -0
  74. data/spec/dummy/app/models/portfolio.rb +5 -0
  75. data/spec/dummy/app/models/user.rb +3 -0
  76. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  77. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  78. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  79. data/spec/dummy/bin/bundle +3 -0
  80. data/spec/dummy/bin/rails +4 -0
  81. data/spec/dummy/bin/rake +4 -0
  82. data/spec/dummy/bin/setup +38 -0
  83. data/spec/dummy/bin/update +29 -0
  84. data/spec/dummy/bin/yarn +11 -0
  85. data/spec/dummy/config.ru +5 -0
  86. data/spec/dummy/config/application.rb +26 -0
  87. data/spec/dummy/config/boot.rb +5 -0
  88. data/spec/dummy/config/cable.yml +10 -0
  89. data/spec/dummy/config/database.yml +25 -0
  90. data/spec/dummy/config/environment.rb +5 -0
  91. data/spec/dummy/config/environments/development.rb +54 -0
  92. data/spec/dummy/config/environments/production.rb +91 -0
  93. data/spec/dummy/config/environments/test.rb +42 -0
  94. data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
  95. data/spec/dummy/config/initializers/assets.rb +14 -0
  96. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  97. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  98. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  99. data/spec/dummy/config/initializers/inflections.rb +16 -0
  100. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  101. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  102. data/spec/dummy/config/locales/en.yml +33 -0
  103. data/spec/dummy/config/puma.rb +56 -0
  104. data/spec/dummy/config/routes.rb +12 -0
  105. data/spec/dummy/config/secrets.yml +32 -0
  106. data/spec/dummy/config/spring.rb +6 -0
  107. data/spec/dummy/db/migrate/20190322205209_create_blogs.rb +10 -0
  108. data/spec/dummy/db/migrate/20200215225917_create_users.rb +9 -0
  109. data/spec/dummy/db/migrate/20200227150449_create_portfolios.rb +10 -0
  110. data/spec/dummy/db/migrate/20200227150548_add_portfolio_id_blogs.rb +5 -0
  111. data/spec/dummy/db/schema.rb +38 -0
  112. data/spec/dummy/package.json +5 -0
  113. data/spec/dummy/public/404.html +67 -0
  114. data/spec/dummy/public/422.html +67 -0
  115. data/spec/dummy/public/500.html +66 -0
  116. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  117. data/spec/dummy/public/apple-touch-icon.png +0 -0
  118. data/spec/dummy/public/favicon.ico +0 -0
  119. data/spec/dummy/spec/assets/image.png +0 -0
  120. data/spec/dummy/spec/assets/video.mp4 +0 -0
  121. data/spec/dummy/spec/factories/blogs.rb +7 -0
  122. data/spec/dummy/spec/factories/portfolios.rb +6 -0
  123. data/spec/dummy/spec/factories/users.rb +5 -0
  124. data/spec/dummy/spec/lib/power_api/generator_helper/ams_helper_spec.rb +87 -0
  125. data/spec/dummy/spec/lib/power_api/generator_helper/controller_helper_spec.rb +361 -0
  126. data/spec/dummy/spec/lib/power_api/generator_helper/pagination_helper_spec.rb +56 -0
  127. data/spec/dummy/spec/lib/power_api/generator_helper/resource_helper_spec.rb +31 -0
  128. data/spec/dummy/spec/lib/power_api/generator_helper/routes_helper_spec.rb +179 -0
  129. data/spec/dummy/spec/lib/power_api/generator_helper/simple_token_auth_helper_spec.rb +164 -0
  130. data/spec/dummy/spec/lib/power_api/generator_helper/swagger_helper_spec.rb +451 -0
  131. data/spec/dummy/spec/lib/power_api/generator_helper/version_helper_spec.rb +55 -0
  132. data/spec/dummy/spec/support/shared_examples/active_record_resource.rb +101 -0
  133. data/spec/dummy/spec/support/shared_examples/active_record_resource_atrributes.rb +164 -0
  134. data/spec/dummy/spec/support/test_generator_helpers.rb +29 -0
  135. data/spec/dummy/spec/support/test_helpers.rb +11 -0
  136. data/spec/rails_helper.rb +49 -0
  137. data/spec/spec_helper.rb +9 -0
  138. metadata +602 -0
@@ -0,0 +1,48 @@
1
+ module PowerApi::GeneratorHelper::PaginationHelper
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ attr_accessor :use_paginator
6
+ end
7
+
8
+ def api_pagination_initializer_path
9
+ "config/initializers/api_pagination.rb"
10
+ end
11
+
12
+ def api_pagination_initializer_tpl
13
+ <<~API_PAGINATION
14
+ ApiPagination.configure do |config|
15
+ # If you have more than one gem included, you can choose a paginator.
16
+ config.paginator = :kaminari
17
+
18
+ # By default, this is set to 'Total'
19
+ config.total_header = 'X-Total'
20
+
21
+ # By default, this is set to 'Per-Page'
22
+ config.per_page_header = 'X-Per-Page'
23
+
24
+ # Optional: set this to add a header with the current page number.
25
+ config.page_header = 'X-Page'
26
+
27
+ # Optional: set this to add other response format. Useful with tools that define :jsonapi format
28
+ # config.response_formats = [:json, :xml, :jsonapi]
29
+ config.response_formats = [:jsonapi]
30
+
31
+ # Optional: what parameter should be used to set the page option
32
+ config.page_param do |params|
33
+ params[:page][:number] if params[:page].is_a?(ActionController::Parameters)
34
+ end
35
+
36
+ # Optional: what parameter should be used to set the per page option
37
+ config.per_page_param do |params|
38
+ params[:page][:size] if params[:page].is_a?(ActionController::Parameters)
39
+ end
40
+
41
+ # Optional: Include the total and last_page link header
42
+ # By default, this is set to true
43
+ # Note: When using kaminari, this prevents the count call to the database
44
+ config.include_total = true
45
+ end
46
+ API_PAGINATION
47
+ end
48
+ end
@@ -0,0 +1,33 @@
1
+ module PowerApi::GeneratorHelper::ResourceHelper
2
+ extend ActiveSupport::Concern
3
+
4
+ class Resource
5
+ include PowerApi::GeneratorHelper::ActiveRecordResource
6
+
7
+ def initialize(resource_name)
8
+ self.resource_name = resource_name
9
+ end
10
+ end
11
+
12
+ included do
13
+ attr_reader :resource, :parent_resource
14
+ end
15
+
16
+ def resource=(value)
17
+ @resource = Resource.new(value)
18
+ end
19
+
20
+ def parent_resource=(value)
21
+ return if value.blank?
22
+
23
+ @parent_resource = Resource.new(value)
24
+ end
25
+
26
+ def resource_attributes=(collection)
27
+ resource.resource_attributes = collection
28
+ end
29
+
30
+ def parent_resource?
31
+ !!parent_resource
32
+ end
33
+ end
@@ -0,0 +1,91 @@
1
+ # rubocop:disable Layout/AlignArguments
2
+ module PowerApi::GeneratorHelper::RoutesHelper
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ include PowerApi::GeneratorHelper::VersionHelper
7
+ include PowerApi::GeneratorHelper::ResourceHelper
8
+ include PowerApi::GeneratorHelper::TemplateBuilderHelper
9
+ end
10
+
11
+ def routes_path
12
+ "config/routes.rb"
13
+ end
14
+
15
+ def routes_line_to_inject_new_version
16
+ return "routes.draw do\n" if first_version?
17
+
18
+ "'/api' do\n"
19
+ end
20
+
21
+ def api_version_routes_line_regex
22
+ /Api::V#{version_number}[^\n]*/
23
+ end
24
+
25
+ def parent_resource_routes_line_regex
26
+ /#{parent_resource_route_tpl}[^\n]*/
27
+ end
28
+
29
+ def version_route_tpl
30
+ return first_version_route_tpl if first_version?
31
+
32
+ new_version_route_tpl
33
+ end
34
+
35
+ def resource_route_tpl(actions: [], is_parent: false)
36
+ res = (is_parent ? parent_resource : resource).plural
37
+ line = "resources :#{res}"
38
+ line += ", only: [#{actions.map { |a| ":#{a}" }.join(', ')}]" if actions.any?
39
+ line
40
+ end
41
+
42
+ def parent_route_exist?
43
+ routes_match_regex?(/#{parent_resource_route_tpl}/)
44
+ end
45
+
46
+ def parent_route_already_have_children?
47
+ routes_match_regex?(/#{parent_resource_route_tpl}[\W\w]*do/)
48
+ end
49
+
50
+ private
51
+
52
+ def resource_route_statements(actions: [])
53
+ line = "resources :#{resource.plural}"
54
+ line += ", only: [#{actions.map { |a| ":#{a}" }.join(', ')}]" if actions.any?
55
+ line
56
+ end
57
+
58
+ def routes_match_regex?(regex)
59
+ path = File.join(Rails.root, routes_path)
60
+ File.readlines(path).grep(regex).any?
61
+ end
62
+
63
+ def parent_resource_route_tpl
64
+ raise PowerApi::GeneratorError.new("missing parent_resource") unless parent_resource?
65
+
66
+ "resources :#{parent_resource.plural}"
67
+ end
68
+
69
+ def first_version_route_tpl
70
+ concat_tpl_statements(
71
+ "scope path: '/api' do",
72
+ "api_version(#{api_version_params}) do",
73
+ "end",
74
+ "end\n"
75
+ )
76
+ end
77
+
78
+ def new_version_route_tpl
79
+ concat_tpl_statements(
80
+ "api_version(#{api_version_params}) do",
81
+ "end"
82
+ )
83
+ end
84
+
85
+ def api_version_params
86
+ "module: 'Api::V#{version_number}', \
87
+ path: { value: 'v#{version_number}' }, \
88
+ defaults: { format: 'json' }"
89
+ end
90
+ end
91
+ # rubocop:enable Layout/AlignArguments
@@ -0,0 +1,11 @@
1
+ module PowerApi::GeneratorHelper::RubocopHelper
2
+ extend ActiveSupport::Concern
3
+
4
+ def format_ruby_file(path)
5
+ return unless File.exist?(path)
6
+
7
+ options, paths = RuboCop::Options.new.parse(["-a", "-fa", path])
8
+ runner = RuboCop::Runner.new(options, RuboCop::ConfigStore.new)
9
+ runner.run(paths)
10
+ end
11
+ end
@@ -0,0 +1,124 @@
1
+ module PowerApi::GeneratorHelper::SimpleTokenAuthHelper
2
+ extend ActiveSupport::Concern
3
+
4
+ class SimpleTokenAuthResource
5
+ include PowerApi::GeneratorHelper::ActiveRecordResource
6
+ include PowerApi::GeneratorHelper::ResourceHelper
7
+
8
+ def initialize(resource)
9
+ self.resource_name = resource
10
+ end
11
+
12
+ def authenticated_resource_migration
13
+ "migration add_authentication_token_to_#{plural} \
14
+ authentication_token:string{30}:uniq"
15
+ end
16
+ end
17
+
18
+ included do
19
+ attr_reader :authenticated_resources, :authenticated_resource
20
+ attr_accessor :owned_by_authenticated_resource
21
+ end
22
+
23
+ def authenticated_resources=(values)
24
+ @authenticated_resources = values.map { |value| SimpleTokenAuthResource.new(value) }
25
+ end
26
+
27
+ def authenticated_resource=(value)
28
+ return if value.blank?
29
+
30
+ @authenticated_resource = SimpleTokenAuthResource.new(value)
31
+ end
32
+
33
+ def authenticated_resource?
34
+ !!authenticated_resource
35
+ end
36
+
37
+ def owned_by_authenticated_resource?
38
+ owned_by_authenticated_resource && authenticated_resource? && !parent_resource?
39
+ end
40
+
41
+ def current_authenticated_resource
42
+ "current_#{authenticated_resource.snake_case}"
43
+ end
44
+
45
+ def simple_token_auth_method
46
+ <<-METHOD
47
+ acts_as_token_authenticatable
48
+
49
+ METHOD
50
+ end
51
+
52
+ def simple_token_auth_initializer_path
53
+ "config/initializers/simple_token_authentication.rb"
54
+ end
55
+
56
+ def simple_token_auth_initializer_tpl
57
+ <<~INITIALIZER
58
+ SimpleTokenAuthentication.configure do |config|
59
+ # Configure the session persistence policy after a successful sign in,
60
+ # in other words, if the authentication token acts as a signin token.
61
+ # If true, user is stored in the session and the authentication token and
62
+ # email may be provided only once.
63
+ # If false, users must provide their authentication token and email at every request.
64
+ # config.sign_in_token = false
65
+
66
+ # Configure the name of the HTTP headers watched for authentication.
67
+ #
68
+ # Default header names for a given token authenticatable entity follow the pattern:
69
+ # { entity: { authentication_token: 'X-Entity-Token', email: 'X-Entity-Email'} }
70
+ #
71
+ # When several token authenticatable models are defined, custom header names
72
+ # can be specified for none, any, or all of them.
73
+ #
74
+ # Note: when using the identifiers options, this option behaviour is modified.
75
+ # Please see the example below.
76
+ #
77
+ # Examples
78
+ #
79
+ # Given User and SuperAdmin are token authenticatable,
80
+ # When the following configuration is used:
81
+ # `config.header_names = { super_admin: { authentication_token: 'X-Admin-Auth-Token' } }`
82
+ # Then the token authentification handler for User watches the following headers:
83
+ # `X-User-Token, X-User-Email`
84
+ # And the token authentification handler for SuperAdmin watches the following headers:
85
+ # `X-Admin-Auth-Token, X-SuperAdmin-Email`
86
+ #
87
+ # When the identifiers option is set:
88
+ # `config.identifiers = { super_admin: :phone_number }`
89
+ # Then both the header names identifier key and default value are modified accordingly:
90
+ # `config.header_names = { super_admin: { phone_number: 'X-SuperAdmin-PhoneNumber' } }`
91
+ #
92
+ # config.header_names = { user: { authentication_token: 'X-User-Token', email: 'X-User-Email' } }
93
+
94
+ # Configure the name of the attribute used to identify the user for authentication.
95
+ # That attribute must exist in your model.
96
+ #
97
+ # The default identifiers follow the pattern:
98
+ # { entity: 'email' }
99
+ #
100
+ # Note: the identifer must match your Devise configuration,
101
+ # see https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-sign-in-using-their-username-or-email-address#tell-devise-to-use-username-in-the-authentication_keys
102
+ #
103
+ # Note: setting this option does modify the header_names behaviour,
104
+ # see the header_names section above.
105
+ #
106
+ # Example:
107
+ #
108
+ # `config.identifiers = { super_admin: 'phone_number', user: 'uuid' }`
109
+ #
110
+ # config.identifiers = { user: 'email' }
111
+
112
+ # Configure the Devise trackable strategy integration.
113
+ #
114
+ # If true, tracking is disabled for token authentication: signing in through
115
+ # token authentication won't modify the Devise trackable statistics.
116
+ #
117
+ # If false, given Devise trackable is configured for the relevant model,
118
+ # then signing in through token authentication will be tracked as any other sign in.
119
+ #
120
+ # config.skip_devise_trackable = true
121
+ end
122
+ INITIALIZER
123
+ end
124
+ end
@@ -0,0 +1,463 @@
1
+ # rubocop:disable Metrics/ModuleLength
2
+ # rubocop:disable Metrics/MethodLength
3
+ # rubocop:disable Layout/AlignArguments
4
+ module PowerApi::GeneratorHelper::SwaggerHelper
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ include PowerApi::GeneratorHelper::VersionHelper
9
+ include PowerApi::GeneratorHelper::ResourceHelper
10
+ include PowerApi::GeneratorHelper::SimpleTokenAuthHelper
11
+ include PowerApi::GeneratorHelper::TemplateBuilderHelper
12
+ end
13
+
14
+ def swagger_helper_path
15
+ "spec/swagger_helper.rb"
16
+ end
17
+
18
+ def spec_swagger_path
19
+ "spec/swagger/.gitkeep"
20
+ end
21
+
22
+ def spec_integration_path
23
+ "spec/integration/.gitkeep"
24
+ end
25
+
26
+ def rswag_ui_initializer_path
27
+ "config/initializers/rswag-ui.rb"
28
+ end
29
+
30
+ def swagger_schemas_path
31
+ "spec/swagger/v#{version_number}/schemas/.gitkeep"
32
+ end
33
+
34
+ def swagger_resource_spec_path
35
+ "spec/integration/api/v#{version_number}/#{resource.plural}_spec.rb"
36
+ end
37
+
38
+ def swagger_version_definition_path
39
+ "spec/swagger/v#{version_number}/definition.rb"
40
+ end
41
+
42
+ def swagger_resource_schema_path
43
+ "spec/swagger/v#{version_number}/schemas/#{resource.snake_case}_schema.rb"
44
+ end
45
+
46
+ def rswag_ui_configure_line
47
+ "Rswag::Ui.configure do |c|\n"
48
+ end
49
+
50
+ def swagger_helper_api_definition_line
51
+ "config.swagger_docs = {\n"
52
+ end
53
+
54
+ def swagger_definition_line_to_inject_schema
55
+ /definitions: {/
56
+ end
57
+
58
+ def rswag_ui_initializer_tpl
59
+ <<~INITIALIZER
60
+ Rswag::Ui.configure do |c|
61
+ end
62
+ INITIALIZER
63
+ end
64
+
65
+ def swagger_helper_tpl
66
+ <<~SWAGGER
67
+ require 'rails_helper'
68
+
69
+ Dir[::Rails.root.join("spec/swagger/**/schemas/*.rb")].each { |f| require f }
70
+ Dir[::Rails.root.join("spec/swagger/**/definition.rb")].each { |f| require f }
71
+
72
+ RSpec.configure do |config|
73
+ # Specify a root folder where Swagger JSON files are generated
74
+ # NOTE: If you're using the rswag-api to serve API descriptions, you'll need
75
+ # to ensure that it's confiugred to serve Swagger from the same folder
76
+ config.swagger_root = Rails.root.to_s + '/swagger'
77
+
78
+ # Define one or more Swagger documents and provide global metadata for each one
79
+ # When you run the 'rswag:specs:to_swagger' rake task, the complete Swagger will
80
+ # be generated at the provided relative path under swagger_root
81
+ # By default, the operations defined in spec files are added to the first
82
+ # document below. You can override this behavior by adding a swagger_doc tag to the
83
+ # the root example_group in your specs, e.g. describe '...', swagger_doc: 'v2/swagger.json'
84
+ config.swagger_docs = {
85
+ }
86
+ end
87
+ SWAGGER
88
+ end
89
+
90
+ def rswag_ui_swagger_endpoint
91
+ " c.swagger_endpoint '/api-docs/v#{version_number}/swagger.json', \
92
+ 'API V#{version_number} Docs'\n"
93
+ end
94
+
95
+ def swagger_helper_api_definition
96
+ content = " 'v#{version_number}/swagger.json' => API_V#{version_number}"
97
+ content = "#{content}," unless first_version?
98
+ "#{content}\n"
99
+ end
100
+
101
+ def swagger_definition_tpl
102
+ <<~DEFINITION
103
+ API_V#{version_number} = {
104
+ swagger: '2.0',
105
+ info: {
106
+ title: 'API V#{version_number}',
107
+ version: 'v#{version_number}'
108
+ },
109
+ basePath: '/api/v#{version_number}',
110
+ definitions: {
111
+ }
112
+ }
113
+ DEFINITION
114
+ end
115
+
116
+ def swagger_schema_tpl
117
+ <<~SCHEMA
118
+ #{swagger_model_definition_const} = {
119
+ type: :object,
120
+ properties: {
121
+ id: { type: :string, example: '1' },
122
+ type: { type: :string, example: '#{resource.snake_case}' },
123
+ attributes: {
124
+ type: :object,
125
+ properties: {#{get_swagger_schema_attributes_definitions}
126
+ },
127
+ required: [#{get_swagger_schema_attributes_names}
128
+ ]
129
+ }
130
+ },
131
+ required: [
132
+ :id,
133
+ :type,
134
+ :attributes
135
+ ]
136
+ }
137
+
138
+ #{swagger_collection_definition_const} = {
139
+ type: "object",
140
+ properties: {
141
+ data: {
142
+ type: "array",
143
+ items: { "$ref" => "#/definitions/#{resource.snake_case}" }
144
+ }
145
+ },
146
+ required: [
147
+ :data
148
+ ]
149
+ }
150
+
151
+ #{swagger_resource_definition_const} = {
152
+ type: "object",
153
+ properties: {
154
+ data: { "$ref" => "#/definitions/#{resource.snake_case}" }
155
+ },
156
+ required: [
157
+ :data
158
+ ]
159
+ }
160
+ SCHEMA
161
+ end
162
+
163
+ def swagger_definition_entry
164
+ [
165
+ "\n #{resource.snake_case}: #{swagger_model_definition_const},",
166
+ "\n #{resource.plural}_collection: #{swagger_collection_definition_const},",
167
+ "\n #{resource.snake_case}_resource: #{swagger_resource_definition_const},"
168
+ ].join
169
+ end
170
+
171
+ def swagger_resource_spec_tpl
172
+ concat_tpl_statements(
173
+ "require 'swagger_helper'\n",
174
+ concat_tpl_statements(
175
+ spec_tpl_initial_describe_line,
176
+ spec_tpl_authenticated_resource,
177
+ spec_tpl_let_parent_resource,
178
+ spec_tpl_collection_path_statements,
179
+ spec_tpl_resource_path_statements,
180
+ "end\n"
181
+ )
182
+ )
183
+ end
184
+
185
+ private
186
+
187
+ def spec_tpl_initial_describe_line
188
+ "describe 'API V#{version_number} #{resource.plural_titleized}', \
189
+ swagger_doc: 'v#{version_number}/swagger.json' do"
190
+ end
191
+
192
+ def spec_tpl_authenticated_resource
193
+ return unless authenticated_resource?
194
+
195
+ res_name = authenticated_resource.snake_case
196
+ concat_tpl_statements(
197
+ "let(:#{res_name}) { create(:#{res_name}) }",
198
+ "let(:#{res_name}_email) { #{res_name}.email }",
199
+ "let(:#{res_name}_token) { #{res_name}.authentication_token }\n"
200
+ )
201
+ end
202
+
203
+ def spec_tpl_collection_path_statements
204
+ concat_tpl_statements(
205
+ "path '/#{spec_tpl_collection_path}' do",
206
+ spec_tpl_parent_resource_parameter,
207
+ spec_tpl_authenticated_resource_params,
208
+ spec_tpl_index,
209
+ spec_tpl_create,
210
+ "end\n"
211
+ )
212
+ end
213
+
214
+ def spec_tpl_collection_path
215
+ return resource.plural unless parent_resource?
216
+
217
+ "#{parent_resource.plural}/{#{parent_resource.id}}/#{resource.plural}"
218
+ end
219
+
220
+ def spec_tpl_resource_path_statements
221
+ concat_tpl_statements(
222
+ "path '/#{resource.plural}/{id}' do",
223
+ spec_tpl_authenticated_resource_params,
224
+ spec_tpl_let_existent_resource,
225
+ spec_tpl_show,
226
+ spec_tpl_update,
227
+ spec_tpl_destroy,
228
+ "end\n"
229
+ )
230
+ end
231
+
232
+ def spec_tpl_let_existent_resource
233
+ statement = ["let(:existent_#{resource.snake_case}) { create(:#{resource.snake_case}"]
234
+ load_owner_resource_option(statement)
235
+
236
+ concat_tpl_statements(
237
+ "parameter name: :id, in: :path, type: :integer\n",
238
+ "#{statement.join(', ')}) }",
239
+ "let(:id) { existent_#{resource.snake_case}.id }\n"
240
+ )
241
+ end
242
+
243
+ def spec_tpl_authenticated_resource_params
244
+ return unless authenticated_resource?
245
+
246
+ res_name = authenticated_resource.snake_case
247
+ concat_tpl_statements(
248
+ "parameter name: :#{res_name}_email, in: :query, type: :string",
249
+ "parameter name: :#{res_name}_token, in: :query, type: :string\n"
250
+ )
251
+ end
252
+
253
+ def spec_tpl_index
254
+ concat_tpl_statements(
255
+ "get 'Retrieves #{resource.plural_titleized}' do",
256
+ "description 'Retrieves all the #{resource.plural}'",
257
+ "produces 'application/json'\n",
258
+ "let(:collection_count) { 5 }",
259
+ "let(:expected_collection_count) { collection_count }\n",
260
+ "before { #{spec_tpl_index_creation_list} }",
261
+ "response '200', '#{resource.plural_titleized} retrieved' do",
262
+ "schema('$ref' => '#/definitions/#{resource.plural}_collection')\n",
263
+ "run_test! do |response|",
264
+ "expect(JSON.parse(response.body)['data'].count).to eq(expected_collection_count)",
265
+ "end",
266
+ "end\n",
267
+ spec_tpl_invalid_credentials,
268
+ "end\n"
269
+ )
270
+ end
271
+
272
+ def spec_tpl_index_creation_list
273
+ statement = ["create_list(:#{resource.snake_case}, collection_count"]
274
+ load_owner_resource_option(statement)
275
+ statement.join(', ') + ')'
276
+ end
277
+
278
+ def load_owner_resource_option(statement)
279
+ if parent_resource?
280
+ statement << "#{parent_resource.snake_case}: #{parent_resource.snake_case}"
281
+ end
282
+
283
+ if owned_by_authenticated_resource?
284
+ statement << "#{authenticated_resource.snake_case}: #{authenticated_resource.snake_case}"
285
+ end
286
+ end
287
+
288
+ def spec_tpl_create
289
+ concat_tpl_statements(
290
+ "post 'Creates #{resource.titleized}' do",
291
+ "description 'Creates #{resource.titleized}'",
292
+ "consumes 'application/json'",
293
+ "produces 'application/json'",
294
+ "parameter(name: :#{resource.snake_case}, in: :body)\n",
295
+ "response '201', '#{resource.snake_case} created' do",
296
+ "let(:#{resource.snake_case}) do",
297
+ "{#{resource_params}}",
298
+ "end\n",
299
+ "run_test!",
300
+ "end\n",
301
+ spec_tpl_create_invalid_attrs_test,
302
+ spec_tpl_invalid_credentials(with_body: true),
303
+ "end\n"
304
+ )
305
+ end
306
+
307
+ def spec_tpl_parent_resource_parameter
308
+ return unless parent_resource?
309
+
310
+ "parameter name: :#{parent_resource.id}, in: :path, type: :integer"
311
+ end
312
+
313
+ def spec_tpl_let_parent_resource
314
+ return unless parent_resource?
315
+
316
+ concat_tpl_statements(
317
+ "let(:#{parent_resource.snake_case}) { create(:#{parent_resource.snake_case}) }",
318
+ "let(:#{parent_resource.id}) { #{parent_resource.snake_case}.id }\n"
319
+ )
320
+ end
321
+
322
+ def spec_tpl_show
323
+ concat_tpl_statements(
324
+ "get 'Retrieves #{resource.titleized}' do",
325
+ "produces 'application/json'\n",
326
+ "response '200', '#{resource.snake_case} retrieved' do",
327
+ "schema('$ref' => '#/definitions/#{resource.snake_case}_resource')\n",
328
+ "run_test!",
329
+ "end\n",
330
+ "response '404', 'invalid #{resource.snake_case} id' do",
331
+ "let(:id) { 'invalid' }",
332
+ "run_test!",
333
+ "end\n",
334
+ spec_tpl_invalid_credentials,
335
+ "end\n"
336
+ )
337
+ end
338
+
339
+ def spec_tpl_update
340
+ concat_tpl_statements(
341
+ "put 'Updates #{resource.titleized}' do",
342
+ "description 'Updates #{resource.titleized}'",
343
+ "consumes 'application/json'",
344
+ "produces 'application/json'",
345
+ "parameter(name: :#{resource.snake_case}, in: :body)\n",
346
+ "response '200', '#{resource.snake_case} updated' do",
347
+ "let(:#{resource.snake_case}) do",
348
+ "{#{resource_params}}",
349
+ "end\n",
350
+ "run_test!",
351
+ "end\n",
352
+ spec_tpl_update_invalid_attrs_test,
353
+ spec_tpl_invalid_credentials(with_body: true),
354
+ "end\n"
355
+ )
356
+ end
357
+
358
+ def spec_tpl_destroy
359
+ concat_tpl_statements(
360
+ "delete 'Deletes #{resource.titleized}' do",
361
+ "produces 'application/json'",
362
+ "description 'Deletes specific #{resource.snake_case}'\n",
363
+ "response '204', '#{resource.snake_case} deleted' do",
364
+ "run_test!",
365
+ "end\n",
366
+ "response '404', '#{resource.snake_case} not found' do",
367
+ "let(:id) { 'invalid' }\n",
368
+ "run_test!",
369
+ "end\n",
370
+ spec_tpl_invalid_credentials,
371
+ "end\n"
372
+ )
373
+ end
374
+
375
+ def spec_tpl_invalid_credentials(with_body: false)
376
+ return unless authenticated_resource?
377
+
378
+ authenticated_resource_name = authenticated_resource.snake_case
379
+ concat_tpl_statements(
380
+ "response '401', '#{authenticated_resource_name} unauthorized' do",
381
+ with_body ? "let(:#{resource.snake_case}) { {} }" : nil,
382
+ "let(:user_token) { 'invalid' }\n",
383
+ "run_test!",
384
+ "end\n"
385
+ )
386
+ end
387
+
388
+ def spec_tpl_update_invalid_attrs_test
389
+ spec_tpl_create_invalid_attrs_test
390
+ end
391
+
392
+ def spec_tpl_create_invalid_attrs_test
393
+ return if resource.required_resource_attributes.blank?
394
+
395
+ concat_tpl_statements(
396
+ "response '400', 'invalid attributes' do",
397
+ "let(:#{resource.snake_case}) do",
398
+ "{#{invalid_resource_params}}",
399
+ "end\n",
400
+ "run_test!",
401
+ "end\n"
402
+ )
403
+ end
404
+
405
+ def resource_params
406
+ attrs = if resource.required_resource_attributes.any?
407
+ resource.required_resource_attributes
408
+ else
409
+ resource.optional_resource_attributes
410
+ end
411
+
412
+ for_each_schema_attribute(attrs) do |attr|
413
+ "#{attr[:name]}: #{attr[:example]},"
414
+ end
415
+ end
416
+
417
+ def invalid_resource_params
418
+ return unless resource.required_resource_attributes.any?
419
+
420
+ for_each_schema_attribute([resource.required_resource_attributes.first]) do |attr|
421
+ "#{attr[:name]}: nil,"
422
+ end
423
+ end
424
+
425
+ def swagger_model_definition_const
426
+ "#{resource.upcase}_SCHEMA"
427
+ end
428
+
429
+ def swagger_collection_definition_const
430
+ "#{resource.upcase_plural}_COLLECTION_SCHEMA"
431
+ end
432
+
433
+ def swagger_resource_definition_const
434
+ "#{resource.upcase}_RESOURCE_SCHEMA"
435
+ end
436
+
437
+ def get_swagger_schema_attributes_definitions
438
+ for_each_schema_attribute(resource.resource_attributes) do |attr|
439
+ opts = ["example: #{attr[:example]}"]
440
+ opts << "'x-nullable': true" unless attr[:required]
441
+ opts
442
+
443
+ "#{attr[:name]}: { type: :#{attr[:swagger_type]}, #{opts.join(', ')} },"
444
+ end
445
+ end
446
+
447
+ def get_swagger_schema_attributes_names
448
+ for_each_schema_attribute(resource.required_resource_attributes) do |attr|
449
+ ":#{attr[:name]},"
450
+ end
451
+ end
452
+
453
+ def for_each_schema_attribute(attributes)
454
+ attributes.inject("") do |memo, attr|
455
+ memo += "\n"
456
+ memo += yield(attr)
457
+ memo
458
+ end.delete_suffix(",")
459
+ end
460
+ end
461
+ # rubocop:enable Metrics/ModuleLength
462
+ # rubocop:enable Metrics/MethodLength
463
+ # rubocop:enable Layout/AlignArguments