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,5 @@
1
+ module <%= app_short_name_class %>
2
+ class Railtie < Rails::Railtie
3
+ extend ApiGenerator::Railtie
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module <%= app_short_name_class %>
2
+ module Version
3
+ VERSION = '0.0.1'.freeze
4
+ end
5
+ end
File without changes
@@ -0,0 +1,35 @@
1
+ require 'bundler/setup'
2
+ require '<%= app_name %>'
3
+ require 'dotenv/load'
4
+ require 'vcr'
5
+
6
+ VCR.configure do |config|
7
+ config.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
8
+
9
+ config.hook_into :faraday
10
+
11
+ config.before_record do |vcr|
12
+ vcr.response.body.force_encoding('UTF-8')
13
+ end
14
+
15
+ config.configure_rspec_metadata!
16
+ end
17
+
18
+ RSpec.configure do |config|
19
+ config.expect_with :rspec do |c|
20
+ c.syntax = :expect
21
+ end
22
+
23
+ config.order = :random
24
+
25
+ Kernel.srand config.seed
26
+
27
+ config.before do
28
+ <%= app_name_class %>::Client.configure do |configuration|
29
+ configuration.api_endpoint = 'http://<%= app_name %>.dev'
30
+ configuration.api_header = 'X-Source-Token'
31
+ configuration.api_token = 'TEST_TOKEN'
32
+ configuration.logger = Logger.new(Pathname.new(__dir__).join('..', 'log', 'test.log'))
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,57 @@
1
+ module ApiGenerator
2
+ module Client
3
+ module Config
4
+ class ConfigurationError < StandardError; end
5
+
6
+ USER_AGENT = 'Ruby API Client'.freeze
7
+ OPEN_TIMEOUT = 3
8
+ READ_TIMEOUT = 3
9
+ MAX_RETRIES = 3
10
+ RETRY_INTERVAL = 1
11
+ RETRY_BACKOFF_FACTOR = 1
12
+ CONNECTION_ERROR = ApiGenerator::Middleware::HttpErrors::ConnectionError
13
+
14
+ # rubocop:disable Metrics/MethodLength
15
+ def self.extended(klass)
16
+ klass.extend Dry::Configurable
17
+
18
+ klass.class_eval do
19
+ setting :api_endpoint, reader: true
20
+ setting :user_agent, default: USER_AGENT, reader: true
21
+
22
+ setting :api_header, reader: true
23
+ setting :api_token, reader: true
24
+
25
+ setting :api_user, reader: true
26
+ setting :api_password, reader: true
27
+
28
+ setting :ssl_verify, reader: true
29
+ setting :ca_file, reader: true
30
+
31
+ setting :open_timeout, default: OPEN_TIMEOUT, reader: true
32
+ setting :read_timeout, default: READ_TIMEOUT, reader: true
33
+ setting :max_retries, default: MAX_RETRIES, reader: true
34
+ setting :retry_interval, default: RETRY_INTERVAL, reader: true
35
+ setting :retry_backoff_factor, default: RETRY_BACKOFF_FACTOR, reader: true
36
+ setting :logger, reader: true
37
+
38
+ setting :retriable_errors, default: [CONNECTION_ERROR], reader: true
39
+ end
40
+ end
41
+ # rubocop:enable Metrics/MethodLength
42
+
43
+ def check_config
44
+ raise ConfigurationError, 'API endpoint is not configured.' if config.api_endpoint.nil?
45
+ end
46
+
47
+ def inherit_config!(other_config)
48
+ config.to_h.each_key { |setting| config[setting] = other_config[setting] }
49
+ config
50
+ end
51
+
52
+ def same_config?(other_config)
53
+ config.to_h == other_config.to_h
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,149 @@
1
+ module ApiGenerator
2
+ module Client
3
+ # rubocop:disable Metrics/ClassLength
4
+ class Connection < SimpleDelegator
5
+ extend Config
6
+
7
+ class << self
8
+ def inherited(subclass)
9
+ subclass.extend Middleware::RaiseErrorDsl
10
+
11
+ error_namespace = begin
12
+ subclass.module_parent::HttpErrors
13
+ rescue NameError
14
+ ApiGenerator::Middleware::HttpErrors
15
+ end
16
+
17
+ subclass.setup_middleware(error_namespace)
18
+
19
+ super
20
+ end
21
+
22
+ def setup_middleware(error_namespace)
23
+ unsuccessful_request_middleware = Class.new(
24
+ Middleware::HandleUnsuccessfulRequestMiddleware,
25
+ )
26
+ unsuccessful_request_middleware.error_namespace = error_namespace if error_namespace
27
+ const_set(:HandleUnsuccessfulRequestMiddleware, unsuccessful_request_middleware)
28
+
29
+ error_handler_middleware = Class.new(Middleware::ErrorHandlerMiddleware)
30
+ error_handler_middleware.error_namespace = error_namespace if error_namespace
31
+ const_set(:ErrorHandlerMiddleware, error_handler_middleware)
32
+ end
33
+ end
34
+
35
+ ACCEPT_HEADER = 'application/json'.freeze
36
+
37
+ attr_accessor :customization_block
38
+
39
+ def initialize(**settings, &blk)
40
+ if instance_of?(Connection)
41
+ raise LoadError, 'This class is abstract and is not intended to be initialized directly'
42
+ end
43
+
44
+ self.customization_block = blk if blk
45
+
46
+ config = self.class.inherit_config!(self.class.config)
47
+ settings.each { |setting, value| config[setting] = value }
48
+ self.class.check_config
49
+
50
+ super(connection)
51
+ end
52
+
53
+ private
54
+
55
+ # rubocop:disable Metrics/MethodLength
56
+ def connection
57
+ @connection ||= Faraday.new(url: self.class.api_endpoint) do |connection|
58
+ setup_auth!(connection)
59
+
60
+ setup_failed_req_handling!(connection)
61
+ setup_timeouts!(connection)
62
+ setup_user_agent!(connection)
63
+ setup_logger!(connection)
64
+ setup_retries!(connection)
65
+ setup_error_handling!(connection)
66
+ setup_error_code_middleware!(connection)
67
+ setup_ssl(connection)
68
+
69
+ customize_connection!(connection)
70
+
71
+ # When set last - req/res parsed as json in middleware
72
+ setup_json!(connection)
73
+
74
+ connection.adapter(Faraday.default_adapter)
75
+ end
76
+ end
77
+ # rubocop:enable Metrics/MethodLength
78
+
79
+ # rubocop:disable Metrics/AbcSize
80
+ def setup_auth!(connection)
81
+ if self.class.api_token && self.class.api_header
82
+ connection.headers[self.class.api_header] = self.class.api_token
83
+ elsif self.class.api_user && self.class.api_password
84
+ connection.use(
85
+ Faraday::Request::BasicAuthentication,
86
+ self.class.api_user,
87
+ self.class.api_password,
88
+ )
89
+ end
90
+ end
91
+ # rubocop:enable Metrics/AbcSize
92
+
93
+ def setup_json!(connection)
94
+ connection.headers[:accept] = ACCEPT_HEADER
95
+ connection.request(:json)
96
+ connection.response(:json)
97
+ end
98
+
99
+ def setup_timeouts!(connection)
100
+ connection.options.open_timeout = self.class.open_timeout
101
+ connection.options.timeout = self.class.read_timeout
102
+ end
103
+
104
+ def setup_user_agent!(connection)
105
+ connection.headers[:user_agent] = self.class.user_agent
106
+ end
107
+
108
+ def setup_logger!(connection)
109
+ connection.response(:logger, self.class.logger) if self.class.logger
110
+ end
111
+
112
+ def setup_ssl(connection)
113
+ connection.ssl.verify = self.class.ssl_verify
114
+ connection.ssl.ca_file = self.class.ca_file
115
+ end
116
+
117
+ def setup_retries!(connection)
118
+ return if self.class.max_retries <= 0
119
+
120
+ connection.request(
121
+ :retry,
122
+ max: self.class.max_retries,
123
+ interval: self.class.retry_interval,
124
+ backoff_factor: self.class.retry_backoff_factor,
125
+ exceptions: self.class.retriable_errors,
126
+ )
127
+ end
128
+
129
+ def setup_failed_req_handling!(connection)
130
+ connection.use(self.class::HandleUnsuccessfulRequestMiddleware)
131
+ end
132
+
133
+ def setup_error_handling!(connection)
134
+ connection.use(self.class::ErrorHandlerMiddleware)
135
+ end
136
+
137
+ def setup_error_code_middleware!(connection)
138
+ connection.use(self.class::ErrorCodeMiddleware)
139
+ end
140
+
141
+ def customize_connection!(connection)
142
+ return unless customization_block
143
+
144
+ customization_block.call(connection)
145
+ end
146
+ end
147
+ # rubocop:enable Metrics/ClassLength
148
+ end
149
+ end
@@ -0,0 +1,56 @@
1
+ module ApiGenerator
2
+ module Commands
3
+ class Generate
4
+ # rubocop:disable Layout/LineLength
5
+ ARGS_REGEX = /(?<namespace>[\w\-_]+):(?<action>[\w\-_]+):(?<http_method>get|post|patch|put)/i.freeze
6
+ # rubocop:enable Layout/LineLength
7
+
8
+ def initialize(argv)
9
+ @argv = argv[0..1]
10
+ end
11
+
12
+ def call
13
+ service_args = [*parse_parameters(@argv), paginate(@argv)]
14
+
15
+ shutdown unless service_args[0..2].all?(&:present?)
16
+
17
+ Services::CreateScaffold.new(*service_args).call
18
+ end
19
+
20
+ private
21
+
22
+ def usage
23
+ <<~USAGE
24
+ Generate API scaffold (api_namespace, models, specs)
25
+ Usage: bin/generate NAMESPACE:ACTION:HTTP_METHOD [--paginate]
26
+ Examples:
27
+ bin/generate deals:search:post --paginate
28
+ bin/generate persons:create:post
29
+ bin/generate deals:update:put
30
+ USAGE
31
+ end
32
+
33
+ def shutdown
34
+ puts(usage) || exit
35
+ end
36
+
37
+ def parse_parameters(args)
38
+ params = args.find { |arg| arg =~ ARGS_REGEX }
39
+
40
+ shutdown unless params
41
+
42
+ match_data = params.match(ARGS_REGEX)
43
+
44
+ [
45
+ match_data['namespace'],
46
+ match_data['action'],
47
+ match_data['http_method'],
48
+ ]
49
+ end
50
+
51
+ def paginate(args)
52
+ args.find { |arg| arg == '--paginate' }
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,56 @@
1
+ module ApiGenerator
2
+ module Helpers
3
+ module Utils
4
+ INDENTATION_REGEX = /^([ \t]*)(?=\S)/.freeze
5
+ WRITE_MODE = 'w'.freeze
6
+
7
+ def create_dir(dir)
8
+ FileUtils.mkdir_p(dir) && log(dir)
9
+ end
10
+
11
+ def log(path)
12
+ path.gsub!(root, '.') if path.match?(Regexp.new(root))
13
+ shell.say_status('create', path, :green)
14
+ end
15
+
16
+ def shell
17
+ Thor::Shell::Color.new
18
+ end
19
+
20
+ def copy_template(template, destination, data_binding)
21
+ content = render(template, data_binding)
22
+ destination.gsub!(/\.erb$/, '.rb')
23
+
24
+ File.write(destination, content, mode: WRITE_MODE) && log(destination)
25
+ end
26
+
27
+ def render(path, data_binding)
28
+ ERB.new(File.read(path)).result(data_binding)
29
+ end
30
+
31
+ def inject_after_str(file, str, insert)
32
+ text = File.read(file)
33
+ result = inject_after(text, str, insert)
34
+ File.write(file, result, mode: WRITE_MODE)
35
+ end
36
+
37
+ def inject_after(text, str, insert)
38
+ lines = text.each_line.to_a
39
+ regex = Regexp.new(str)
40
+ match_line_idx = lines.index { |line| line =~ regex }
41
+
42
+ return unless match_line_idx
43
+
44
+ line = lines[match_line_idx]
45
+ indented_insert = insert.each_line.map { |insert_line| indent_as(line, insert_line) }
46
+ lines.insert(match_line_idx + 1, *indented_insert)
47
+ lines.map(&:chomp).join("\n")
48
+ end
49
+
50
+ def indent_as(source, target)
51
+ indentation = INDENTATION_REGEX.match(source).captures.first.to_s
52
+ indentation + target
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,43 @@
1
+ module ApiGenerator
2
+ module Middleware
3
+ class ErrorCodeMiddleware < Faraday::Response::Middleware
4
+ def self.inherited(subclass)
5
+ class << subclass
6
+ attr_accessor :error_mapping, :error_namespace
7
+
8
+ def add_error(code, exception)
9
+ error_mapping[code] = exception
10
+ end
11
+ end
12
+
13
+ subclass.error_mapping ||= {}
14
+ subclass.error_namespace ||= ApiGenerator::Middleware::HttpErrors
15
+
16
+ super
17
+ end
18
+
19
+ def on_complete(env)
20
+ error_class = error_class(env.status)
21
+ return unless error_class
22
+
23
+ raise(error_class.new(message(env), env.body))
24
+ end
25
+
26
+ private
27
+
28
+ def message(env)
29
+ "Server returned #{env.status}: #{env.body}. Headers: #{env.response_headers.inspect}"
30
+ end
31
+
32
+ def error_class(code)
33
+ self.class.error_mapping.fetch(code) do
34
+ if code >= 500
35
+ self.class.error_namespace::ServerError
36
+ elsif code >= 400
37
+ self.class.error_namespace::ClientError
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,26 @@
1
+ module ApiGenerator
2
+ module Middleware
3
+ class ErrorHandlerMiddleware < Faraday::Middleware
4
+ def self.inherited(subclass)
5
+ class << subclass
6
+ attr_accessor :error_namespace
7
+ end
8
+
9
+ subclass.error_namespace ||= ApiGenerator::Middleware::HttpErrors
10
+ super
11
+ end
12
+
13
+ FARADAY_CONNECTION_ERRORS = [
14
+ Faraday::TimeoutError,
15
+ Faraday::ConnectionFailed,
16
+ Faraday::SSLError,
17
+ ].freeze
18
+
19
+ def call(env)
20
+ @app.call(env)
21
+ rescue *FARADAY_CONNECTION_ERRORS => e
22
+ raise self.class.error_namespace::ConnectionError, e
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,36 @@
1
+ module ApiGenerator
2
+ module Middleware
3
+ class HandleUnsuccessfulRequestMiddleware < Faraday::Response::Middleware
4
+ def self.inherited(subclass)
5
+ class << subclass
6
+ attr_accessor :error_namespace
7
+ end
8
+
9
+ subclass.error_namespace ||= ApiGenerator::Middleware::HttpErrors
10
+ super
11
+ end
12
+
13
+ def on_complete(env)
14
+ response_body = env.body
15
+
16
+ return unless response_body.is_a?(Hash) && response_body['success'] == false
17
+
18
+ raise(
19
+ self.class.error_namespace::UnsuccessfulRequestError.new(
20
+ message(env), env.body,
21
+ ),
22
+ )
23
+ end
24
+
25
+ private
26
+
27
+ def message(env)
28
+ <<~MSG
29
+ Server is unable to process the request: #{env.request_body}.
30
+ Headers: #{env.request_headers.inspect}
31
+ Server returned: #{env.body}. Headers: #{env.response_headers.inspect}
32
+ MSG
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,21 @@
1
+ module ApiGenerator
2
+ module Middleware
3
+ module HttpErrors
4
+ class ConnectionError < StandardError; end
5
+
6
+ class BaseError < StandardError
7
+ attr_reader :body
8
+
9
+ def initialize(msg = nil, body = nil)
10
+ @body = body if body
11
+ super(msg)
12
+ end
13
+ end
14
+
15
+ class ServerError < BaseError; end
16
+ class ClientError < BaseError; end
17
+
18
+ class UnsuccessfulRequestError < ClientError; end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ module ApiGenerator
2
+ module Middleware
3
+ class RaiseErrorBase < Faraday::Response::Middleware
4
+ private
5
+
6
+ def message(env)
7
+ "Server returned #{env.status}: #{env.body}. Headers: #{env.response_headers.inspect}"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,36 @@
1
+ module ApiGenerator
2
+ module Middleware
3
+ module RaiseErrorDsl
4
+ def self.extended(klass)
5
+ klass.singleton_class.class_eval { attr_accessor :error_namespace }
6
+ klass.error_namespace ||= if klass.module_parent.const_defined?(:HttpErrors)
7
+ klass.module_parent::HttpErrors
8
+ else
9
+ ApiGenerator::Middleware::HttpErrors
10
+ end
11
+ error_code_middleware = Class.new(ErrorCodeMiddleware)
12
+ error_code_middleware.error_namespace = klass.error_namespace
13
+ klass.const_set(:ErrorCodeMiddleware, error_code_middleware)
14
+
15
+ super
16
+ end
17
+
18
+ def on_status(code, error:)
19
+ self::ErrorCodeMiddleware.add_error(code, error_const(code, error))
20
+ end
21
+
22
+ private
23
+
24
+ def error_const(code, error_name)
25
+ const_name = error_name.to_s.camelize
26
+
27
+ return error_namespace.const_get(const_name) if error_namespace.const_defined?(const_name)
28
+
29
+ error_namespace.const_set(
30
+ const_name,
31
+ Class.new(code >= 500 ? error_namespace::ServerError : error_namespace::ClientError),
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,4 @@
1
+ module ApiGenerator
2
+ module Middleware
3
+ end
4
+ end
@@ -0,0 +1,15 @@
1
+ module ApiGenerator
2
+ module Models
3
+ class Base < Hashie::Trash
4
+ include Hashie::Extensions::Dash::Coercion
5
+ include Hashie::Extensions::Dash::IndifferentAccess
6
+ include Hashie::Extensions::IgnoreUndeclared
7
+
8
+ def self.parse_time
9
+ ->(time) { Time.parse(time) if time.present? }
10
+ end
11
+
12
+ private_class_method :parse_time
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ module ApiGenerator
2
+ module Models
3
+ class Data < Base
4
+ property :namespace
5
+ property :action
6
+ property :http_method
7
+ property :paginate
8
+ property :app_name
9
+ property :app_short_name
10
+
11
+ def for_template
12
+ binding
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,32 @@
1
+ module ApiGenerator::Pagination
2
+ module Dsl
3
+ STRATEGIES = {
4
+ limit_offset: LimitOffsetRelation,
5
+ page: PageRelation,
6
+ }.freeze
7
+
8
+ # rubocop:disable Metrics/MethodLength
9
+ # @param [Array<Symbol] methods список методов для применения пагинации
10
+ # @param [Symbol, Class] with стратегия пагинации (например, :limit_offset, :page)
11
+ # @param data_key ключ, по которому можно получить массив данных в ответе
12
+ # @param [Proc] on_request
13
+ # коллбэк, вызываемый при запросе и принимающий как аргументы запрос и параметры
14
+ # @param [Hash] config дополнительная конфигурация для стратегии пагинации
15
+ def paginate(*methods, with:, data_key:, on_request:, **config)
16
+ relation_class = STRATEGIES.fetch(with, with)
17
+
18
+ prepend(
19
+ Module.new do
20
+ methods.each do |method|
21
+ define_method(method) do |*args|
22
+ relation_class.new(config.merge(data_key: data_key)) do |relation|
23
+ super(*args) { |req| on_request.call(req, **relation.request_options) }
24
+ end
25
+ end
26
+ end
27
+ end,
28
+ )
29
+ end
30
+ # rubocop:enable Metrics/MethodLength
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ class ApiGenerator::Pagination::LimitOffsetRelation < ApiGenerator::Pagination::Relation
2
+ def limit(limit)
3
+ expand(limit: limit)
4
+ end
5
+
6
+ def offset(offset)
7
+ expand(offset: offset)
8
+ end
9
+
10
+ private
11
+
12
+ def next_relation
13
+ limit = config[:limit]&.call(response) || size
14
+ offset = config[:offset]&.call(response) || options[:offset].to_i
15
+
16
+ expand(offset: offset + limit)
17
+ end
18
+
19
+ def last_page?
20
+ return size < config[:limit].call(response) if config[:limit]
21
+
22
+ data.empty?
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ class ApiGenerator::Pagination::PageRelation < ApiGenerator::Pagination::Relation
2
+ def page(page)
3
+ expand(page: page)
4
+ end
5
+
6
+ def per(size)
7
+ expand(per_page: size)
8
+ end
9
+
10
+ private
11
+
12
+ def next_relation
13
+ expand(page: [options[:page].to_i, 1].max + 1)
14
+ end
15
+
16
+ def last_page?
17
+ return data.size < config[:per_page].call(response) if config[:per_page]
18
+
19
+ data.empty?
20
+ end
21
+ end