power_api 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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