gemfather-stable 2.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +145 -0
  3. data/bin/gemfather +6 -0
  4. data/gem_template/%app_name%.gemspec.tt +32 -0
  5. data/gem_template/.gitignore.tt +26 -0
  6. data/gem_template/.rubocop-rspec.yml.tt +38 -0
  7. data/gem_template/.rubocop-ruby.yml.tt +54 -0
  8. data/gem_template/.rubocop.yml.tt +11 -0
  9. data/gem_template/Gemfile.tt +17 -0
  10. data/gem_template/Makefile.tt +21 -0
  11. data/gem_template/README.md.tt +180 -0
  12. data/gem_template/Rakefile.tt +6 -0
  13. data/gem_template/bin/console.tt +21 -0
  14. data/gem_template/bin/generate.tt +6 -0
  15. data/gem_template/bin/rubocop.tt +29 -0
  16. data/gem_template/lib/%app_name%.rb.tt +25 -0
  17. data/gem_template/lib/%app_short_name%/client.rb.tt +18 -0
  18. data/gem_template/lib/%app_short_name%/http_errors.rb.tt +20 -0
  19. data/gem_template/lib/%app_short_name%/railtie.rb.tt +5 -0
  20. data/gem_template/lib/%app_short_name%/version.rb.tt +5 -0
  21. data/gem_template/log/.keep.tt +0 -0
  22. data/gem_template/spec/spec_helper.rb.tt +35 -0
  23. data/lib/api_generator/client/config.rb +57 -0
  24. data/lib/api_generator/client/connection.rb +149 -0
  25. data/lib/api_generator/commands/generate.rb +56 -0
  26. data/lib/api_generator/helpers/utils.rb +56 -0
  27. data/lib/api_generator/middleware/error_code_middleware.rb +43 -0
  28. data/lib/api_generator/middleware/error_handler_middleware.rb +26 -0
  29. data/lib/api_generator/middleware/handle_unsuccessful_request_middleware.rb +36 -0
  30. data/lib/api_generator/middleware/http_errors.rb +21 -0
  31. data/lib/api_generator/middleware/raise_error_base.rb +11 -0
  32. data/lib/api_generator/middleware/raise_error_dsl.rb +36 -0
  33. data/lib/api_generator/middleware.rb +4 -0
  34. data/lib/api_generator/models/base.rb +15 -0
  35. data/lib/api_generator/models/data.rb +16 -0
  36. data/lib/api_generator/pagination/dsl.rb +32 -0
  37. data/lib/api_generator/pagination/limit_offset_relation.rb +24 -0
  38. data/lib/api_generator/pagination/page_relation.rb +21 -0
  39. data/lib/api_generator/pagination/relation.rb +114 -0
  40. data/lib/api_generator/pagination.rb +2 -0
  41. data/lib/api_generator/railtie.rb +24 -0
  42. data/lib/api_generator/services/base_create.rb +56 -0
  43. data/lib/api_generator/services/create_api.rb +87 -0
  44. data/lib/api_generator/services/create_model.rb +69 -0
  45. data/lib/api_generator/services/create_scaffold.rb +53 -0
  46. data/lib/api_generator/services/gemfather.rb +81 -0
  47. data/lib/api_generator/version.rb +3 -0
  48. data/lib/api_generator.rb +18 -0
  49. data/templates/api/action.erb +10 -0
  50. data/templates/api/action_paginate.erb +10 -0
  51. data/templates/api/api_module.erb +11 -0
  52. data/templates/api/include_helpers.erb +9 -0
  53. data/templates/api/include_namespace.erb +1 -0
  54. data/templates/api/path.erb +1 -0
  55. data/templates/models/request.erb +11 -0
  56. data/templates/models/response.erb +11 -0
  57. data/templates/specs/api_spec.erb +15 -0
  58. data/templates/specs/request_spec.erb +12 -0
  59. data/templates/specs/response_spec.erb +12 -0
  60. metadata +229 -0
@@ -0,0 +1,114 @@
1
+ class ApiGenerator::Pagination::Relation
2
+ include Enumerable
3
+
4
+ attr_reader :options
5
+
6
+ def initialize(config, options = {}, &fetch_block)
7
+ @config = config
8
+ @options = options
9
+ @fetch_block = fetch_block
10
+ end
11
+
12
+ # Получение всех записей начиная с текущей страницы (страницы грузятся по мере необходимости,
13
+ # и элементы передаются в enumerator)
14
+ def all_remaining
15
+ expand(all_remaining: true)
16
+ end
17
+
18
+ def order(sort)
19
+ expand(sort: sort)
20
+ end
21
+
22
+ # @return [Enumerator, ApiGenerator::Pagination::Relation] Enumerator по всем элементам,
23
+ # Если задана опция all_remaining, клиент посылает запросы за следующими страницами
24
+ # по мере необходимости, иначе загружаются только элементы текущей страницы
25
+ def each(&block)
26
+ return to_enum(:each) unless block
27
+
28
+ data.each(&block)
29
+
30
+ return self if !all_remaining? || last_page?
31
+
32
+ next_relation.each(&block)
33
+ end
34
+
35
+ # Возвращает ответ в исходном виде
36
+ def response
37
+ @response ||= fetch_block.call(self)
38
+ end
39
+
40
+ # @return [Integer] количество элементов на текущей странице
41
+ def size
42
+ data.size
43
+ end
44
+
45
+ # @return [Integer] общее количество элементов на всех страницах
46
+ # @raise [NotImplementedError] в случае, если коллбэк `total` не задан в конфиге DSL
47
+ def total
48
+ raise NotImplementedError, 'Total callback is not set in configuration' if config[:total].blank?
49
+
50
+ config[:total]&.call(response)
51
+ end
52
+ alias total_count total
53
+
54
+ # @return [Integer] общее количество элементов на всех страницах (сначала загрузит все страницы)
55
+ def total!
56
+ total
57
+ rescue NotImplementedError
58
+ self.class.new(config, {}, &fetch_block).all_remaining.count
59
+ end
60
+ alias total_count! total!
61
+
62
+ alias all to_a
63
+
64
+ def inspect
65
+ class_name_with_status = [self.class, ('(not fetched)' if @response.blank?)].compact.join(' ')
66
+
67
+ "#<#{class_name_with_status} #{options}>"
68
+ end
69
+
70
+ alias to_s inspect
71
+
72
+ def pretty_print(pretty_printer)
73
+ pretty_printer.text(inspect)
74
+ end
75
+
76
+ def request_options
77
+ options.except(:all_remaining)
78
+ end
79
+
80
+ private
81
+
82
+ attr_reader :fetch_block, :config
83
+
84
+ def data
85
+ case data_key
86
+ when Symbol, String
87
+ response.public_send(data_key)
88
+ when Array
89
+ response.dig(*data_key)
90
+ else
91
+ raise ArgumentError, 'Data key should be a symbol, string or array'
92
+ end
93
+ end
94
+
95
+ def expand(**options)
96
+ self.class.new(config, self.options.merge(options), &fetch_block)
97
+ end
98
+
99
+ def data_key
100
+ config[:data_key] || :itself
101
+ end
102
+
103
+ def all_remaining?
104
+ options[:all_remaining]
105
+ end
106
+
107
+ def next_relation
108
+ raise NotImplementedError
109
+ end
110
+
111
+ def last_page?
112
+ raise NotImplementedError
113
+ end
114
+ end
@@ -0,0 +1,2 @@
1
+ module ApiGenerator::Pagination
2
+ end
@@ -0,0 +1,24 @@
1
+ module ApiGenerator
2
+ module Railtie
3
+ # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
4
+ def self.extended(klass)
5
+ return unless defined?(Rails)
6
+
7
+ klass.class_eval do |target_class|
8
+ initializer_name = "#{target_class.to_s.deconstantize.underscore}.configure"
9
+ initializer initializer_name, after: 'initialize_logger' do
10
+ target_class.name.deconstantize.constantize::Client.configure do |config|
11
+ config.user_agent = [
12
+ Rails.application.class.module_parent_name.underscore,
13
+ Rails.env,
14
+ config.user_agent,
15
+ ].join(' - ')
16
+
17
+ config.logger = Rails.logger if Rails.env.development? || Rails.env.test?
18
+ end
19
+ end
20
+ end
21
+ end
22
+ # rubocop:enable Metrics/MethodLength,Metrics/AbcSize
23
+ end
24
+ end
@@ -0,0 +1,56 @@
1
+ module ApiGenerator
2
+ module Services
3
+ class BaseCreate
4
+ include Helpers::Utils
5
+
6
+ attr_reader :target, :data
7
+
8
+ def initialize(target, data)
9
+ @target = target
10
+ @data = data
11
+ end
12
+
13
+ def call
14
+ raise NotImplementedError
15
+ end
16
+
17
+ private
18
+
19
+ def templates(name)
20
+ File.expand_path(templates_files[name.to_sym], templates_folder)
21
+ end
22
+
23
+ def folders(name)
24
+ File.expand_path(target_folders[name.to_sym], root)
25
+ end
26
+
27
+ def render_template(name)
28
+ render(templates(name.to_sym), data.for_template)
29
+ end
30
+
31
+ def root
32
+ target || File.expand_path(Dir.pwd)
33
+ end
34
+
35
+ def source_root
36
+ gemfather_lib_path = $LOAD_PATH.grep(/gems\/gemfather/).first
37
+
38
+ return File.expand_path('../', gemfather_lib_path) if gemfather_lib_path
39
+
40
+ File.expand_path('../../../', __dir__)
41
+ end
42
+
43
+ def templates_folder
44
+ File.expand_path('./templates', source_root)
45
+ end
46
+
47
+ def templates_files
48
+ raise NotImplementedError
49
+ end
50
+
51
+ def target_folders
52
+ raise NotImplementedError
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,87 @@
1
+ module ApiGenerator
2
+ module Services
3
+ class CreateApi < BaseCreate
4
+ PATH_MARKER = 'PATHS'.freeze
5
+ ACTION_MARKER = 'ACTIONS'.freeze
6
+ INCLUDE_MARKER = 'INCLUDES'.freeze
7
+
8
+ def call
9
+ prepare_folders
10
+
11
+ create_api_module
12
+ add_path
13
+ add_action
14
+ add_module_to_client
15
+
16
+ create_api_spec
17
+ end
18
+
19
+ private
20
+
21
+ def prepare_folders
22
+ create_dir(folders(:api))
23
+ create_dir(folders(:spec_api))
24
+ end
25
+
26
+ def create_api_module
27
+ return if File.exist?(api_module)
28
+
29
+ copy_template(templates(:api_module), api_module, data.for_template)
30
+ end
31
+
32
+ def add_path
33
+ inject_after_str(api_module, PATH_MARKER, render_template(:path))
34
+ end
35
+
36
+ def add_action
37
+ if data.paginate
38
+ inject_after_str(api_module, INCLUDE_MARKER, render_template(:include_helpers))
39
+ inject_after_str(api_module, ACTION_MARKER, render_template(:action_paginate))
40
+ else
41
+ inject_after_str(api_module, ACTION_MARKER, render_template(:action))
42
+ end
43
+ end
44
+
45
+ def add_module_to_client
46
+ inject_after_str(client_class, INCLUDE_MARKER, render_template(:include_namespace))
47
+ end
48
+
49
+ def create_api_spec
50
+ copy_template(templates(:api_spec), api_spec, data.for_template)
51
+ end
52
+
53
+ def api_module
54
+ File.expand_path("./#{data.namespace}.rb", folders(:api))
55
+ end
56
+
57
+ def client_class
58
+ File.expand_path('./client.rb', folders(:app))
59
+ end
60
+
61
+ def api_spec
62
+ File.expand_path("./#{data.namespace}_spec.rb", folders(:spec_api))
63
+ end
64
+
65
+ def templates_files
66
+ {
67
+ api_module: './api/api_module.erb',
68
+ path: './api/path.erb',
69
+ action: './api/action.erb',
70
+ action_paginate: './api/action_paginate.erb',
71
+ include_helpers: './api/include_helpers.erb',
72
+ include_namespace: './api/include_namespace.erb',
73
+ api_spec: './specs/api_spec.erb',
74
+ }
75
+ end
76
+
77
+ def target_folders
78
+ {
79
+ app: "./lib/#{data.app_short_name}",
80
+ api: "./lib/#{data.app_short_name}/api",
81
+ spec: './spec',
82
+ spec_api: './spec/api',
83
+ }
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,69 @@
1
+ module ApiGenerator
2
+ module Services
3
+ class CreateModel < BaseCreate
4
+ def call
5
+ prepare_folders
6
+
7
+ create_models
8
+ create_models_specs
9
+ end
10
+
11
+ private
12
+
13
+ def prepare_folders
14
+ create_dir(folders(:model_namespace_action))
15
+ create_dir(folders(:spec_model))
16
+ end
17
+
18
+ def create_models
19
+ models.each do |model|
20
+ copy_template(templates(model), target_model(model), data.for_template)
21
+ end
22
+ end
23
+
24
+ def create_models_specs
25
+ specs.each do |template|
26
+ copy_template(templates(template), target_spec(template), data.for_template)
27
+ end
28
+ end
29
+
30
+ def models
31
+ %i[request response]
32
+ end
33
+
34
+ def specs
35
+ %i[request_spec response_spec]
36
+ end
37
+
38
+ def target_spec(template)
39
+ File.expand_path("./#{template}.rb", folders(:spec_model))
40
+ end
41
+
42
+ def target_model(template)
43
+ File.expand_path("./#{template}.erb", folders(:model_namespace_action))
44
+ end
45
+
46
+ def target_folders
47
+ {
48
+ model: "./lib/#{data.app_short_name}/model",
49
+ model_namespace: "./lib/#{data.app_short_name}/model/#{data.namespace}",
50
+ model_namespace_action:
51
+ "./lib/#{data.app_short_name}/model/#{data.namespace}/#{data.action}",
52
+ spec: './spec',
53
+ spec_model: "./spec/model/#{data.namespace}/#{data.action}",
54
+ }
55
+ end
56
+
57
+ def templates_files
58
+ {
59
+ request: './models/request.erb',
60
+ response: './models/response.erb',
61
+ responses: './models/responses.erb',
62
+ paginated_response: './models/paginated_response.erb',
63
+ request_spec: './specs/request_spec.erb',
64
+ response_spec: './specs/response_spec.erb',
65
+ }
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,53 @@
1
+ module ApiGenerator
2
+ module Services
3
+ class CreateScaffold
4
+ attr_accessor :namespace, :action, :http_method, :paginate
5
+
6
+ # rubocop:disable Style/OptionalBooleanParameter
7
+ def initialize(namespace, action, http_method, paginate = false, root = nil)
8
+ @namespace = namespace
9
+ @action = action
10
+ @http_method = http_method
11
+ @paginate = paginate
12
+ @root = root || File.expand_path(Dir.pwd)
13
+ end
14
+ # rubocop:enable Style/OptionalBooleanParameter
15
+
16
+ def call
17
+ create_api_w_specs
18
+ create_models_w_specs
19
+ end
20
+
21
+ private
22
+
23
+ def create_api_w_specs
24
+ Services::CreateApi.new(@root, data).call
25
+ end
26
+
27
+ def create_models_w_specs
28
+ Services::CreateModel.new(@root, data).call
29
+ end
30
+
31
+ def data
32
+ Models::Data.new(
33
+ namespace: namespace,
34
+ action: action,
35
+ http_method: http_method,
36
+ paginate: paginate,
37
+ app_name: app_name,
38
+ app_short_name: app_name,
39
+ )
40
+ end
41
+
42
+ def app_name
43
+ @app_name ||= begin
44
+ gemspec = Dir[File.expand_path('./*', @root)].grep(/\.gemspec/).first
45
+
46
+ raise 'Not root of the API gem' unless gemspec
47
+
48
+ File.basename(gemspec, '.gemspec')
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,81 @@
1
+ module ApiGenerator
2
+ module Services
3
+ class Gemfather < Thor
4
+ include Thor::Actions
5
+
6
+ # octal notation for rubocop
7
+ EXEC_MODE = 0o0755
8
+
9
+ cattr_accessor :target_dir
10
+ attr_accessor :app_name, :app_name_class, :app_short_name,
11
+ :app_short_name_class, :author, :email, :version
12
+
13
+ # rubocop:disable Metrics/MethodLength
14
+ desc 'generates API gem', 'generates basic API gem with the provided name'
15
+ def generate_client(app_name)
16
+ check_app_name(app_name)
17
+ setup_template_variables(app_name)
18
+
19
+ check_if_folder_exists
20
+
21
+ directory(template_path, destination_path, { context: binding })
22
+
23
+ inside(destination_path) do
24
+ empty_directory('log')
25
+ run('bundle')
26
+ run('bundle binstubs rspec-core rubocop --force')
27
+ run('git init')
28
+ set_executables
29
+ end
30
+ end
31
+ # rubocop:enable Metrics/MethodLength
32
+
33
+ private
34
+
35
+ private_class_method :source_root
36
+
37
+ def check_app_name(app_name)
38
+ raise(Thor::Error, 'APP_NAME is not provided') unless app_name
39
+ raise(Thor::Error, "Folder #{app_name} name already exists") if Dir.exist?("./#{app_name}")
40
+ end
41
+
42
+ def setup_template_variables(app_name)
43
+ self.app_name = app_name
44
+ self.app_short_name = app_name
45
+ self.app_name_class = app_short_name.camelize
46
+ self.app_short_name_class = app_short_name.camelize
47
+ self.author = 'Domclick Ruby Team'
48
+ self.email = `git config user.email`
49
+ self.version = ApiGenerator::VERSION
50
+ end
51
+
52
+ def check_if_folder_exists
53
+ Dir.exist?(destination_path) &&
54
+ (file_collision(destination_path) ||
55
+ raise(Thor::Error, 'Directory already exists'))
56
+ end
57
+
58
+ def destination_path
59
+ File.expand_path(app_name, called_from_path)
60
+ end
61
+
62
+ def called_from_path
63
+ self.class.target_dir || Dir.pwd
64
+ end
65
+
66
+ def template_path
67
+ File.expand_path('./gem_template', self.class.source_root)
68
+ end
69
+
70
+ def set_executables
71
+ Dir[File.expand_path('./*', 'bin')].each do |file|
72
+ chmod(file, EXEC_MODE)
73
+ end
74
+ end
75
+
76
+ def self.source_root
77
+ File.expand_path('./../../../', __dir__)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,3 @@
1
+ module ApiGenerator
2
+ VERSION = '2.2.2'.freeze
3
+ end
@@ -0,0 +1,18 @@
1
+ require 'active_support'
2
+ require 'active_support/inflector'
3
+ require 'active_support/core_ext/module/introspection'
4
+ require 'active_support/core_ext/object/try'
5
+ require 'dry-configurable'
6
+ require 'faraday_middleware'
7
+ require 'faraday'
8
+ require 'hashie'
9
+ require 'delegate'
10
+ require 'thor'
11
+ require 'erb'
12
+
13
+ require 'zeitwerk'
14
+ loader = Zeitwerk::Loader.for_gem
15
+ loader.setup
16
+
17
+ module ApiGenerator
18
+ end
@@ -0,0 +1,10 @@
1
+ def <%= action %>(request_or_hash)
2
+ request = if request_or_hash.is_a?(Model::<%= namespace.camelize %>::<%= action.camelize %>::Request)
3
+ request_or_hash
4
+ else
5
+ Model::<%= namespace.camelize %>::<%= action.camelize %>::Request.new(params)
6
+ end
7
+
8
+ response = connection.<%= http_method %>(<%= action.upcase %>_PATH, request)
9
+ Model::<%= namespace.camelize %>::<%= action.camelize %>::Response.new(response.body)
10
+ end
@@ -0,0 +1,10 @@
1
+ def <%= action %>(request_or_hash, &req_block)
2
+ request = if request_or_hash.is_a?(Model::<%= namespace.camelize %>::<%= action.camelize %>::Request)
3
+ request_or_hash
4
+ else
5
+ Model::<%= namespace.camelize %>::<%= action.camelize %>::Request.new(params)
6
+ end
7
+
8
+ response = connection.<%= http_method %>(<%= action.upcase %>_PATH, request, &req_block)
9
+ Model::<%= namespace.camelize %>::<%= action.camelize %>::Response.new(response.body)
10
+ end
@@ -0,0 +1,11 @@
1
+ module <%= app_short_name.camelize %>
2
+ module Api
3
+ module <%= namespace.camelize %>
4
+ # INCLUDES
5
+
6
+ # PATHS
7
+
8
+ # ACTIONS
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ include ApiGenerator::Pagination::Dsl
2
+
3
+ paginate :<%= action %>,
4
+ with: :limit_offset,
5
+ on_request: proc { |req, limit: nil, offset: nil, sort: nil| }
6
+ data_key: :data_key,
7
+ limit: :limit.to_proc,
8
+ offset: :offset.to_proc,
9
+ total: :total.to_proc
@@ -0,0 +1 @@
1
+ include <%= app_short_name.camelize %>::Api::<%= namespace.camelize %>
@@ -0,0 +1 @@
1
+ <%= action.upcase %>_PATH = '/<%= action %>'.freeze
@@ -0,0 +1,11 @@
1
+ module <%= app_short_name.camelize %>
2
+ module Model
3
+ module <%= namespace.camelize %>
4
+ module <%= action.camelize %>
5
+ class Request < ApiGenerator::Models::Base
6
+
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module <%= app_short_name.camelize %>
2
+ module Model
3
+ module <%= namespace.camelize %>
4
+ module <%= action.camelize %>
5
+ class Response < ApiGenerator::Models::Base
6
+
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ require_relative '../spec_helper'
2
+
3
+ RSpec.describe <%= app_short_name.camelize %>::Api::<%= namespace.camelize %> do
4
+ describe '<%= namespace %>' do
5
+ let(:client) { <%= app_short_name.camelize %>.client }
6
+
7
+ describe '#<%= action %>' do
8
+ let(:params) { {} }
9
+
10
+ it 'makes successful request', vcr: { cassette_name: '<%= namespace.camelize %>/<%= action %>/success' } do
11
+ expect { client.<%= action %>(params) }.not_to raise_error
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe <%= app_short_name.camelize %>::Model::<%= namespace.camelize %>::<%= action.camelize %>::Request do
4
+ describe 'request model' do
5
+ let(:properties) { {} }
6
+ let(:model) { described_class.new(properties) }
7
+
8
+ it 'behaves like request' do
9
+ expect(model).to eq(properties)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe <%= app_short_name.camelize %>::Model::<%= namespace.camelize %>::<%= action.camelize %>::Response do
4
+ describe 'response model' do
5
+ let(:properties) { {} }
6
+ let(:model) { described_class.new(properties) }
7
+
8
+ it 'behaves like response' do
9
+ expect(model).to eq(properties)
10
+ end
11
+ end
12
+ end