uploadcare-ruby 1.2.2 → 3.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/gem-push.yml +20 -0
  3. data/.github/workflows/ruby.yml +52 -0
  4. data/.gitignore +13 -5
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +20 -0
  7. data/.yardopts +4 -0
  8. data/CHANGELOG.md +26 -26
  9. data/DEVELOPMENT.md +18 -0
  10. data/Gemfile +2 -0
  11. data/LICENSE +1 -1
  12. data/README.md +191 -519
  13. data/Rakefile +6 -4
  14. data/bin/console +15 -0
  15. data/bin/setup +8 -0
  16. data/lib/uploadcare.rb +36 -31
  17. data/lib/uploadcare/api/api.rb +25 -0
  18. data/lib/uploadcare/client/file_client.rb +44 -0
  19. data/lib/uploadcare/client/file_list_client.rb +46 -0
  20. data/lib/uploadcare/client/group_client.rb +45 -0
  21. data/lib/uploadcare/client/multipart_upload/chunks_client.rb +46 -0
  22. data/lib/uploadcare/client/multipart_upload_client.rb +62 -0
  23. data/lib/uploadcare/client/project_client.rb +18 -0
  24. data/lib/uploadcare/client/rest_client.rb +73 -0
  25. data/lib/uploadcare/client/rest_group_client.rb +23 -0
  26. data/lib/uploadcare/client/upload_client.rb +35 -0
  27. data/lib/uploadcare/client/uploader_client.rb +93 -0
  28. data/lib/uploadcare/client/webhook_client.rb +43 -0
  29. data/lib/uploadcare/concern/error_handler.rb +54 -0
  30. data/lib/uploadcare/concern/throttle_handler.rb +25 -0
  31. data/lib/uploadcare/concern/upload_error_handler.rb +32 -0
  32. data/lib/uploadcare/entity/decorator/paginator.rb +81 -0
  33. data/lib/uploadcare/entity/entity.rb +18 -0
  34. data/lib/uploadcare/entity/file.rb +81 -0
  35. data/lib/uploadcare/entity/file_list.rb +30 -0
  36. data/lib/uploadcare/entity/group.rb +41 -0
  37. data/lib/uploadcare/entity/group_list.rb +24 -0
  38. data/lib/uploadcare/entity/project.rb +13 -0
  39. data/lib/uploadcare/entity/uploader.rb +73 -0
  40. data/lib/uploadcare/entity/webhook.rb +14 -0
  41. data/lib/uploadcare/exception/request_error.rb +9 -0
  42. data/lib/uploadcare/exception/throttle_error.rb +14 -0
  43. data/lib/uploadcare/param/authentication_header.rb +25 -0
  44. data/lib/uploadcare/param/param.rb +10 -0
  45. data/lib/uploadcare/param/secure_auth_header.rb +37 -0
  46. data/lib/uploadcare/param/simple_auth_header.rb +14 -0
  47. data/lib/uploadcare/param/upload/signature_generator.rb +24 -0
  48. data/lib/uploadcare/param/upload/upload_params_generator.rb +23 -0
  49. data/lib/uploadcare/param/user_agent.rb +21 -0
  50. data/lib/uploadcare/ruby/version.rb +5 -0
  51. data/uploadcare-ruby.gemspec +50 -36
  52. metadata +112 -96
  53. data/.travis.yml +0 -12
  54. data/lib/uploadcare/api.rb +0 -24
  55. data/lib/uploadcare/api/file_api.rb +0 -7
  56. data/lib/uploadcare/api/file_list_api.rb +0 -8
  57. data/lib/uploadcare/api/group_api.rb +0 -38
  58. data/lib/uploadcare/api/group_list_api.rb +0 -8
  59. data/lib/uploadcare/api/project_api.rb +0 -9
  60. data/lib/uploadcare/api/raw_api.rb +0 -44
  61. data/lib/uploadcare/api/uploading_api.rb +0 -110
  62. data/lib/uploadcare/errors/errors.rb +0 -64
  63. data/lib/uploadcare/resources/file.rb +0 -164
  64. data/lib/uploadcare/resources/file_list.rb +0 -41
  65. data/lib/uploadcare/resources/group.rb +0 -115
  66. data/lib/uploadcare/resources/group_list.rb +0 -31
  67. data/lib/uploadcare/resources/project.rb +0 -13
  68. data/lib/uploadcare/rest/auth/auth.rb +0 -31
  69. data/lib/uploadcare/rest/auth/secure.rb +0 -43
  70. data/lib/uploadcare/rest/auth/simple.rb +0 -16
  71. data/lib/uploadcare/rest/connections/api_connection.rb +0 -32
  72. data/lib/uploadcare/rest/connections/upload_connection.rb +0 -21
  73. data/lib/uploadcare/rest/middlewares/auth_middleware.rb +0 -24
  74. data/lib/uploadcare/rest/middlewares/parse_json_middleware.rb +0 -33
  75. data/lib/uploadcare/rest/middlewares/raise_error_middleware.rb +0 -21
  76. data/lib/uploadcare/utils/parser.rb +0 -71
  77. data/lib/uploadcare/utils/user_agent.rb +0 -44
  78. data/lib/uploadcare/version.rb +0 -3
  79. data/spec/api/raw_api_spec.rb +0 -25
  80. data/spec/resources/file_list_spec.rb +0 -64
  81. data/spec/resources/file_spec.rb +0 -222
  82. data/spec/resources/group_list_spec.rb +0 -30
  83. data/spec/resources/group_spec.rb +0 -101
  84. data/spec/resources/operations_spec.rb +0 -59
  85. data/spec/resources/project_spec.rb +0 -21
  86. data/spec/rest/api_connection_spec.rb +0 -68
  87. data/spec/rest/auth/secure_spec.rb +0 -66
  88. data/spec/rest/auth/simple_spec.rb +0 -31
  89. data/spec/rest/errors_spec.rb +0 -75
  90. data/spec/rest/upload_connection_spec.rb +0 -19
  91. data/spec/spec_helper.rb +0 -41
  92. data/spec/uploadcare_spec.rb +0 -43
  93. data/spec/uploading/uploading_multiple_spec.rb +0 -43
  94. data/spec/uploading/uploading_spec.rb +0 -40
  95. data/spec/utils/parser_spec.rb +0 -87
  96. data/spec/utils/user_agent_spec.rb +0 -46
  97. data/spec/view.png +0 -0
  98. data/spec/view2.jpg +0 -0
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uploadcare/entity/file'
4
+ require 'uploadcare/entity/decorator/paginator'
5
+
6
+ module Uploadcare
7
+ module Entity
8
+ # This serializer returns lists of files
9
+ #
10
+ # This is a paginated list, so all pagination methods apply
11
+ # @see Uploadcare::Entity::Decorator::Paginator
12
+ class FileList < ApiStruct::Entity
13
+ include Uploadcare::Entity::Decorator::Paginator
14
+ client_service Client::FileListClient
15
+
16
+ attr_entity :next, :previous, :total, :per_page
17
+
18
+ has_entities :results, as: Uploadcare::Entity::File
19
+ has_entities :result, as: Uploadcare::Entity::File
20
+
21
+ # alias for result/results, depending on which API this FileList was initialized from
22
+ # @return [Array] of [Uploadcare::Entity::File]
23
+ def files
24
+ results
25
+ rescue ApiStruct::EntityError
26
+ result
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uploadcare/entity/file'
4
+
5
+ module Uploadcare
6
+ module Entity
7
+ # Groups serve a purpose of better organizing files in your Uploadcare projects.
8
+ #
9
+ # You can create one from a set of files by using their UUIDs.
10
+ #
11
+ # @see https://uploadcare.com/docs/api_reference/upload/groups/
12
+ class Group < Entity
13
+ client_service RestGroupClient, prefix: 'rest', only: :store
14
+ client_service GroupClient
15
+
16
+ attr_entity :id, :datetime_created, :datetime_stored, :files_count, :cdn_url, :url
17
+ has_entities :files, as: Uploadcare::Entity::File
18
+
19
+ # Remove these lines and bump api_struct version when this PR is accepted:
20
+ # @see https://github.com/rubygarage/api_struct/pull/15
21
+ def self.store(uuid)
22
+ rest_store(uuid)
23
+ end
24
+
25
+ # gets groups's id - even if it's only initialized with cdn_url
26
+ # @return [String]
27
+ def id
28
+ return @entity.id if @entity.id
29
+
30
+ id = @entity.cdn_url.gsub('https://ucarecdn.com/', '')
31
+ id = id.gsub(%r{\/.*}, '')
32
+ id
33
+ end
34
+
35
+ # loads group metadata, if it's initialized with url or id
36
+ def load
37
+ initialize(Group.info(id).entity)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uploadcare/entity/group'
4
+ require 'uploadcare/entity/decorator/paginator'
5
+
6
+ module Uploadcare
7
+ module Entity
8
+ # List of groups
9
+ #
10
+ # @see https://uploadcare.com/docs/api_reference/upload/groups/
11
+ #
12
+ # This is a paginated list, so all pagination methods apply
13
+ # @see Uploadcare::Entity::Decorator::Paginator
14
+ class GroupList < Entity
15
+ include Uploadcare::Entity::Decorator::Paginator
16
+ client_service RestGroupClient, only: :list
17
+
18
+ attr_entity :next, :previous, :total, :per_page, :results
19
+ has_entities :results, as: Group
20
+
21
+ alias groups results
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uploadcare
4
+ module Entity
5
+ # This serializer returns info about a project and its data
6
+ # @see https://uploadcare.com/docs/api_reference/rest/handling_projects/
7
+ class Project < Entity
8
+ client_service ProjectClient
9
+
10
+ attr_entity :collaborators, :pub_key, :name, :autostore_enabled
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uploadcare
4
+ module Entity
5
+ # This serializer lets user upload files by various means, and usually returns an array of files
6
+ # @see https://uploadcare.com/api-refs/upload-api/#tag/Upload
7
+ class Uploader < Entity
8
+ client_service UploaderClient
9
+ client_service MultipartUploaderClient, only: :upload, prefix: :multipart
10
+
11
+ attr_entity :files
12
+ has_entities :files, as: Uploadcare::Entity::File
13
+
14
+ # Upload file or group of files from array, File, or url
15
+ #
16
+ # @param object [Array], [String] or [File]
17
+ # @param [Hash] options options for upload
18
+ # @option options [Boolean] :store (false) whether to store file on servers.
19
+ def self.upload(object, **options)
20
+ if big_file?(object)
21
+ upload_big_file(object, **options)
22
+ elsif file?(object)
23
+ upload_file(object, **options)
24
+ elsif object.is_a?(Array)
25
+ upload_files(object, **options)
26
+ elsif object.is_a?(String)
27
+ upload_from_url(object, **options)
28
+ else
29
+ raise ArgumentError, "Expected input to be a file/Array/URL, given: `#{object}`"
30
+ end
31
+ end
32
+
33
+ # upload single file
34
+ def self.upload_file(file, **options)
35
+ response = UploaderClient.new.upload_many([file], **options)
36
+ Uploadcare::Entity::File.info(response.success.to_a.flatten[-1])
37
+ end
38
+
39
+ # upload multiple files
40
+ def self.upload_files(arr, **options)
41
+ response = UploaderClient.new.upload_many(arr, **options)
42
+ response.success.map { |pair| Uploadcare::Entity::File.new(uuid: pair[1], original_filename: pair[0]) }
43
+ end
44
+
45
+ # upload file of size above 10mb (involves multipart upload)
46
+ def self.upload_big_file(file, **_options)
47
+ response = MultipartUploaderClient.new.upload(file)
48
+ Uploadcare::Entity::File.new(response.success)
49
+ end
50
+
51
+ # upload files from url
52
+ # @param url [String]
53
+ def self.upload_from_url(url, **options)
54
+ response = UploaderClient.new.upload_from_url(url, **options)
55
+ response.success[:files].map { |file_data| Uploadcare::Entity::File.new(file_data) }
56
+ end
57
+
58
+ class << self
59
+ private
60
+
61
+ # check if object is a file
62
+ def file?(object)
63
+ object.respond_to?(:path) && ::File.exist?(object.path)
64
+ end
65
+
66
+ # check if object needs to be uploaded using multipart upload
67
+ def big_file?(object)
68
+ file?(object) && object.size >= Uploadcare.config.multipart_size_threshold
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uploadcare
4
+ module Entity
5
+ # This serializer is responsible for webhook handling
6
+ #
7
+ # @see https://uploadcare.com/docs/api_reference/rest/webhooks/
8
+ class Webhook < Entity
9
+ client_service WebhookClient
10
+
11
+ attr_entity :id, :created, :updated, :event, :target_url, :project, :is_active
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uploadcare
4
+ module Exception
5
+ # Standard error for invalid API responses
6
+ class RequestError < StandardError
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uploadcare
4
+ module Exception
5
+ # Exception for throttled requests
6
+ class ThrottleError < StandardError
7
+ attr_reader :timeout
8
+ # @param timeout [Float] Amount of seconds the request have been throttled for
9
+ def initialize(timeout = 10.0)
10
+ @timeout = timeout
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest/md5'
4
+ require 'param/secure_auth_header'
5
+ require 'param/simple_auth_header'
6
+
7
+ module Uploadcare
8
+ module Param
9
+ # This object returns headers needed for authentication
10
+ # This authentication method is more secure, but more tedious
11
+ class AuthenticationHeader
12
+ # @see https://uploadcare.com/docs/api_reference/rest/requests_auth/#auth-uploadcare
13
+ def self.call(**options)
14
+ case Uploadcare.config.auth_type
15
+ when 'Uploadcare'
16
+ SecureAuthHeader.call(**options)
17
+ when 'Uploadcare.Simple'
18
+ SimpleAuthHeader.call
19
+ else
20
+ raise ArgumentError, "Unknown auth_scheme: '#{Uploadcare.config.auth_type}'"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uploadcare
4
+ # @abstract
5
+ # This module is responsible for everything related to generation of request params -
6
+ # such as authentication headers, signatures and serialized uploads
7
+ module Param
8
+ end
9
+ include Param
10
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest/md5'
4
+
5
+ module Uploadcare
6
+ module Param
7
+ # This object returns headers needed for authentication
8
+ # This authentication method is more secure, but more tedious
9
+ class SecureAuthHeader
10
+ # @see https://uploadcare.com/docs/api_reference/rest/requests_auth/#auth-uploadcare
11
+ def self.call(**options)
12
+ @method = options[:method]
13
+ @body = options[:content] || ''
14
+ @content_type = options[:content_type]
15
+ @uri = options[:uri]
16
+ @date_for_header = timestamp
17
+ {
18
+ 'Date': @date_for_header,
19
+ 'Authorization': "Uploadcare #{Uploadcare.config.public_key}:#{signature}"
20
+ }
21
+ end
22
+
23
+ class << self
24
+ def signature
25
+ content_md5 = Digest::MD5.hexdigest(@body)
26
+ sign_string = [@method, content_md5, @content_type, @date_for_header, @uri].join("\n")
27
+ digest = OpenSSL::Digest.new('sha1')
28
+ OpenSSL::HMAC.hexdigest(digest, Uploadcare.config.secret_key, sign_string)
29
+ end
30
+
31
+ def timestamp
32
+ Time.now.gmtime.strftime('%a, %d %b %Y %H:%M:%S GMT')
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uploadcare
4
+ module Param
5
+ # This object returns simple header for authentication
6
+ # Simple header is relatively unsafe, but can be useful for debug and development
7
+ class SimpleAuthHeader
8
+ # @see https://uploadcare.com/docs/api_reference/rest/requests_auth/#auth-simple
9
+ def self.call
10
+ { 'Authorization': "Uploadcare.Simple #{Uploadcare.config.public_key}:#{Uploadcare.config.secret_key}" }
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+
5
+ module Uploadcare
6
+ module Param
7
+ module Upload
8
+ # This class generates signatures for protected uploads
9
+ class SignatureGenerator
10
+ # @see https://uploadcare.com/docs/api_reference/upload/signed_uploads/
11
+ # @return [Hash] signature and its expiration time
12
+ def self.call
13
+ expires_at = Time.now.to_i + Uploadcare.config.upload_signature_lifetime
14
+ to_sign = Uploadcare.config.secret_key + expires_at.to_s
15
+ signature = Digest::MD5.hexdigest(to_sign)
16
+ {
17
+ 'signature': signature,
18
+ 'expire': expires_at
19
+ }
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+
5
+ module Uploadcare
6
+ module Param
7
+ module Upload
8
+ # This class generates body params for uploads
9
+ class UploadParamsGenerator
10
+ # @see https://uploadcare.com/docs/api_reference/upload/request_based/
11
+ def self.call(store = 'auto')
12
+ store = '1' if store == true
13
+ store = '0' if store == false
14
+ {
15
+ 'UPLOADCARE_PUB_KEY' => Uploadcare.config.public_key,
16
+ 'UPLOADCARE_STORE' => store,
17
+ 'signature' => (Upload::SignatureGenerator.call if Uploadcare.config.sign_uploads)
18
+ }.reject { |_k, v| v.nil? }
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uploadcare'
4
+
5
+ module Uploadcare
6
+ module Param
7
+ # This header is added to track libraries using Uploadcare API
8
+ class UserAgent
9
+ # Generate header from Gem's config
10
+ #
11
+ # @example Uploadcare::Param::UserAgent.call
12
+ # UploadcareRuby/3.0.0-dev/Pubkey_(Ruby/2.6.3;UploadcareRuby)
13
+ def self.call
14
+ framework_data = Uploadcare.config.framework_data || ''
15
+ framework_data_string = '; ' + Uploadcare.config.framework_data unless framework_data.empty?
16
+ public_key = Uploadcare.config.public_key
17
+ "UploadcareRuby/#{VERSION}/#{public_key} (Ruby/#{RUBY_VERSION}#{framework_data_string})"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uploadcare
4
+ VERSION = '3.0.5'
5
+ end
@@ -1,37 +1,51 @@
1
- # -*- encoding: utf-8 -*-
2
- require File.expand_path('../lib/uploadcare/version', __FILE__)
3
-
4
- Gem::Specification.new do |gem|
5
- gem.name = "uploadcare-ruby"
6
- gem.authors = ["@rastyagaev (Vadim Rastyagaev)",
7
- "@dimituri (Dimitry Solovyov)",
8
- "@romanonthego (Roman Dubinin)"]
9
- gem.email = ["hello@uploadcare.com"]
10
- gem.summary = "Ruby gem for Uploadcare"
11
- gem.description = <<-EOF
12
- Ruby wrapper for Uploadcare service API.
13
- Full documentations on APIs can be found
14
- at https://uploadcare.com/documentation/rest/
15
- and https://uploadcare.com/documentation/upload/
16
- EOF
17
- gem.metadata = {
18
- "github" => "https://github.com/uploadcare/uploadcare-ruby",
19
- "issue_tracker" => "https://github.com/uploadcare/uploadcare-ruby/issues"
20
- }
21
- gem.homepage = "https://uploadcare.com/documentation/libs/"
22
- gem.license = "MIT"
23
-
24
- gem.files = `git ls-files`.split($\)
25
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
26
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
27
- gem.require_paths = ["lib"]
28
- gem.version = Uploadcare::VERSION
29
- gem.add_runtime_dependency 'faraday', '~> 0.8'
30
- gem.add_runtime_dependency 'faraday_middleware', '~> 0.9'
31
- gem.add_runtime_dependency 'multipart-post'
32
- gem.add_runtime_dependency 'mime-types'
33
-
34
- gem.add_development_dependency 'rspec', "~> 3"
35
- gem.add_development_dependency 'rake'
36
- gem.add_development_dependency 'pry'
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'uploadcare/ruby/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'uploadcare-ruby'
9
+ spec.version = Uploadcare::VERSION
10
+ spec.authors = ['Stepan Redka']
11
+ spec.email = ['stepan.redka@railsmuffin.com']
12
+
13
+ spec.summary = 'Ruby wrapper for uploadcare API'
14
+ spec.description = spec.summary
15
+ spec.homepage = 'https://github.com/uploadcare/uploadcare-ruby'
16
+ spec.license = 'MIT'
17
+
18
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
19
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
20
+ if spec.respond_to?(:metadata)
21
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
22
+
23
+ spec.metadata['homepage_uri'] = spec.homepage
24
+ spec.metadata['source_code_uri'] = 'https://github.com/uploadcare/uploadcare-ruby'
25
+ spec.metadata['changelog_uri'] = 'https://github.com/uploadcare/uploadcare-ruby/CHANGELOG.md'
26
+ else
27
+ raise 'RubyGems 2.0 or newer is required to protect against ' \
28
+ 'public gem pushes.'
29
+ end
30
+
31
+ # Specify which files should be added to the gem when it is released.
32
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
33
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
34
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
35
+ end
36
+ spec.bindir = 'exe'
37
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
38
+ spec.require_paths = ['lib', 'lib/uploadcare', 'lib/uploadcare/rest']
39
+
40
+ spec.add_dependency 'api_struct', '~> 1.0.1'
41
+ spec.add_dependency 'dry-configurable', '~> 0.9.0'
42
+ spec.add_dependency 'parallel'
43
+ spec.add_dependency 'retries'
44
+
45
+ spec.add_development_dependency 'byebug'
46
+ spec.add_development_dependency 'rake', '~> 13.0'
47
+ spec.add_development_dependency 'rspec', '~> 3.0'
48
+ spec.add_development_dependency 'rubocop'
49
+ spec.add_development_dependency 'vcr'
50
+ spec.add_development_dependency 'webmock'
37
51
  end