gemfather-stable 2.2.2

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 (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