resizing 1.2.0 → 1.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +17 -2
  3. data/.gitignore +6 -0
  4. data/Gemfile +6 -2
  5. data/README.md +59 -32
  6. data/lib/resizing/active_storage/service/resizing_service.rb +6 -2
  7. data/lib/resizing/active_storage/service.rb +9 -0
  8. data/lib/resizing/active_storage.rb +7 -0
  9. data/lib/resizing/carrier_wave/storage/file.rb +68 -24
  10. data/lib/resizing/carrier_wave/storage/remote.rb +7 -3
  11. data/lib/resizing/carrier_wave.rb +19 -11
  12. data/lib/resizing/client.rb +25 -23
  13. data/lib/resizing/configurable.rb +1 -1
  14. data/lib/resizing/configuration.rb +6 -16
  15. data/lib/resizing/http_clientable.rb +3 -3
  16. data/lib/resizing/mock_client.rb +6 -5
  17. data/lib/resizing/public_id.rb +5 -4
  18. data/lib/resizing/version.rb +1 -1
  19. data/lib/resizing.rb +15 -12
  20. data/resizing.gemspec +5 -8
  21. data/test/resizing/active_storage_service_test.rb +98 -0
  22. data/test/resizing/carrier_wave/storage/file_test.rb +149 -8
  23. data/test/resizing/carrier_wave/storage/remote_test.rb +75 -0
  24. data/test/resizing/carrier_wave_test.rb +373 -32
  25. data/test/resizing/client_test.rb +68 -12
  26. data/test/resizing/configurable_test.rb +82 -0
  27. data/test/resizing/configuration_test.rb +118 -2
  28. data/test/resizing/constants_test.rb +25 -0
  29. data/test/resizing/error_test.rb +73 -0
  30. data/test/resizing/http_clientable_test.rb +84 -0
  31. data/test/resizing/mock_client_test.rb +75 -0
  32. data/test/resizing/public_id_test.rb +1 -1
  33. data/test/resizing_module_test.rb +206 -0
  34. data/test/test_helper.rb +148 -9
  35. data/test/vcr/carrier_wave_test/update_image.yml +63 -0
  36. metadata +29 -36
  37. /data/{exe → bin}/console +0 -0
  38. /data/{exe → bin}/generate-changelog +0 -0
  39. /data/{exe → bin}/setup +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f29ef7826174501bc395b977648ea813a942ef8360e47611b9bbc8f87f153f37
4
- data.tar.gz: daa52c23493aea0ac244f99827023a44c7cd0c9b000f2fcb60493bdf7d8161f8
3
+ metadata.gz: 97be4b8cf7324441cfa73563ea4f102ac47b947fc95adb9ccbad206fe802f785
4
+ data.tar.gz: ae8df42cb9197bdfc81874d83027e266ba523e3e0c90605ddafc93b310da4368
5
5
  SHA512:
6
- metadata.gz: b00781a214e9c92780912bcaf0176927caf7f86ebac96fdabf6c178128862ac60540a2c953085c645fba9eb34a9b6808ec40e05327d400c2849f48397f4cf196
7
- data.tar.gz: 59c63f87c52c42d807f8de5897d4ff3cfdebbca4ab9be807bfe31d29de8a4ff11313f22d8209d6a324abf25ce593a17510de4cebda5894b67b4f88cace308d75
6
+ metadata.gz: 56a57d325523e21d22a6e3c77c3907e6ec8860433e6524d8ee89da79381f29f56bb7b79af37805ab4be46c921b4e70c5cceb99039de947231c50f7838c4fa544
7
+ data.tar.gz: 18c995f91897d11813477ea16298c2e3101c352f611ecf84e23fb57954e86b6d2c44d28a48515f3df42548b27b0bbebb2b413af85797ce246e9ef22e52d71639
@@ -7,18 +7,33 @@ on:
7
7
  - master
8
8
 
9
9
  permissions:
10
- id-token: write # This is required for requesting the JWT
10
+ id-token: write # This is required for codecov/codecov-action
11
11
  contents: read # This is required for actions/checkout
12
12
 
13
13
  jobs:
14
14
  test:
15
+ name: Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }}
15
16
  strategy:
16
17
  fail-fast: false
17
18
  matrix:
18
- ruby: ['2.7.6', '3.1', '3.2','3.3']
19
+ ruby: ['3.1', '3.2','3.3', '3.4']
20
+ rails: ['6.1', '7.0', '7.1', '7.2']
21
+ exclude:
22
+ # Rails 7.2 requires Ruby 3.1+
23
+ - ruby: '3.1'
24
+ rails: '7.2'
25
+ # Rails 7.1 requires Ruby 3.1+
26
+ - ruby: '3.1'
27
+ rails: '7.1'
28
+ # Rails 6.1 requires Ruby under 3.3
29
+ - ruby: '3.4'
30
+ rails: '6.1'
19
31
 
20
32
  runs-on: ubuntu-22.04
21
33
 
34
+ env:
35
+ RAILS_VERSION: ${{ matrix.rails }}
36
+
22
37
  steps:
23
38
  - uses: actions/checkout@v4
24
39
 
data/.gitignore CHANGED
@@ -6,3 +6,9 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ vendor/
10
+ Gemfile.lock
11
+ tags
12
+ sample1.jpg
13
+ .ruby-version
14
+ uploads/
data/Gemfile CHANGED
@@ -5,8 +5,12 @@ source 'https://rubygems.org'
5
5
  # Specify your gem's dependencies in resizing.gemspec
6
6
  gemspec
7
7
 
8
- gem 'rake', '~> 13.0'
8
+ # Allow testing against different Rails versions
9
+ rails_version = ENV['RAILS_VERSION'] || '7.0'
10
+ gem 'rails', "~> #{rails_version}"
11
+
12
+ gem 'byebug'
9
13
  gem 'github_changelog_generator'
10
14
  gem 'mysql2'
11
- gem 'byebug'
12
15
  gem 'pry-byebug'
16
+ gem 'rake', '~> 13.0'
data/README.md CHANGED
@@ -1,8 +1,14 @@
1
1
  # Resizing
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/resizing`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ [![Gem Version](https://img.shields.io/gem/v/resizing.svg)](https://rubygems.org/gems/resizing)
4
+ [![test](https://github.com/jksy/resizing-gem/actions/workflows/test.yml/badge.svg)](https://github.com/jksy/resizing-gem/actions/workflows/test.yml)
5
+ [![codecov](https://codecov.io/gh/jksy/resizing-gem/graph/badge.svg)](https://codecov.io/gh/jksy/resizing-gem)
4
6
 
5
- TODO: Delete this and the text above, and describe your gem
7
+ Client and utilities for [Resizing](https://www.resizing.net/) - an image hosting and transformation service.
8
+
9
+ ## Requirements
10
+
11
+ - Ruby 3.1.0 or later
6
12
 
7
13
  ## Installation
8
14
 
@@ -20,37 +26,59 @@ Or install it yourself as:
20
26
 
21
27
  $ gem install resizing
22
28
 
29
+ ## Configuration
30
+
31
+ ```ruby
32
+ Resizing.configure = {
33
+ image_host: 'https://img.resizing.net',
34
+ video_host: 'https://video.resizing.net',
35
+ project_id: 'your-project-id',
36
+ secret_token: 'your-secret-token'
37
+ }
38
+ ```
39
+
23
40
  ## Usage
24
41
 
42
+ ### Basic Client Usage
43
+
44
+ ```ruby
45
+ # Initialize client
46
+ client = Resizing::Client.new
47
+
48
+ # Upload image to resizing
49
+ file = File.open('sample.jpg', 'r')
50
+ response = client.post(file)
51
+ # => {
52
+ # "id"=>"a4ed2bf0-a4cf-44fa-9c82-b53e581cb469",
53
+ # "project_id"=>"098a2a0d-0000-0000-0000-000000000000",
54
+ # "content_type"=>"image/jpeg",
55
+ # "latest_version_id"=>"LJY5bxBF7Ryxfr5kC1F.63W8bzp3pcUm",
56
+ # "latest_etag"=>"\"190143614e6c342637584f46f18f8c58\"",
57
+ # "created_at"=>"2020-05-15T15:33:10.711Z",
58
+ # "updated_at"=>"2020-05-15T15:33:10.711Z",
59
+ # "url"=>"/projects/098a2a0d-0000-0000-0000-000000000000/upload/images/a4ed2bf0-a4cf-44fa-9c82-b53e581cb469"
60
+ # }
61
+
62
+ # Generate transformation URL
63
+ image_id = response['id']
64
+ transformation_url = Resizing.url_from_image_id(image_id, nil, ['w_200', 'h_300'])
65
+ # => "https://img.resizing.net/projects/.../upload/images/.../w_200,h_300"
25
66
  ```
26
- # initialize client
27
- options = {
28
- project_id: '098a2a0d-0000-0000-0000-000000000000',
29
- secret_token: '4g1cshg......rbs6'
30
- }
31
- client = Resizing::Client.new(options)
32
-
33
- # upload image to resizing
34
- file = File.open('sample.jpg', 'r')
35
- response = client.post(file)
36
- => {
37
- "id"=>"a4ed2bf0-a4cf-44fa-9c82-b53e581cb469",
38
- "project_id"=>"098a2a0d-0000-0000-0000-000000000000",
39
- "content_type"=>"image/jpeg",
40
- "latest_version_id"=>"LJY5bxBF7Ryxfr5kC1F.63W8bzp3pcUm",
41
- "latest_etag"=>"\"190143614e6c342637584f46f18f8c58\"",
42
- "created_at"=>"2020-05-15T15:33:10.711Z",
43
- "updated_at"=>"2020-05-15T15:33:10.711Z",
44
- "url"=>"/projects/098a2a0d-0000-0000-0000-000000000000/upload/images/a4ed2bf0-a4cf-44fa-9c82-b53e581cb469"
45
- }
46
-
47
- name = response['url']
48
- # get transformation url
49
- name = response['url']
50
- transform = {width: 200, height: 300}
51
-
52
- transformation_url = Resizing.url(name, transform)
53
- => "https://www.resizing.net/projects/098a2a0d-0000-0000-0000-000000000000/upload/images/a4ed2bf0-a4cf-44fa-9c82-b53e581cb469/width_200,height_300"
67
+
68
+ ### CarrierWave Integration
69
+
70
+ ```ruby
71
+ class ImageUploader < CarrierWave::Uploader::Base
72
+ include Resizing::CarrierWave
73
+
74
+ version :list_smallest do
75
+ process resize_to_fill: [200, 200]
76
+ end
77
+ end
78
+
79
+ class User
80
+ mount_uploader :image, ImageUploader
81
+ end
54
82
  ```
55
83
 
56
84
  ## Development
@@ -61,8 +89,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
61
89
 
62
90
  ## Contributing
63
91
 
64
- Bug reports and pull requests are welcome on GitHub at https://github.com/jksy/resizing.
65
-
92
+ Bug reports and pull requests are welcome on GitHub at https://github.com/jksy/resizing-gem.
66
93
 
67
94
  ## License
68
95
 
@@ -1,15 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_storage/service'
4
+
3
5
  module Resizing
4
6
  module ActiveStorage
5
7
  module Service
6
8
  # ref.
7
9
  # https://github.com/rails/rails/blob/master/activestorage/lib/active_storage/service/s3_service.rb
8
10
  #
9
- # rubocop:disable Lint/UnusedMethodArgument,Metrics/ParameterLists
11
+ # rubocop:disable Metrics/ParameterLists
10
12
  class ResizingService < ::ActiveStorage::Service
11
13
  # def initialize(bucket:, upload: {}, public: false, **options)
14
+ # rubocop:disable Lint/MissingSuper
12
15
  def initialize; end
16
+ # rubocop:enable Lint/MissingSuper
13
17
 
14
18
  def upload(_key, _io, checksum: nil, filename: nil, content_type: nil, disposition: nil, **)
15
19
  raise NotImplementedError, 'upload is not implemented'
@@ -51,7 +55,7 @@ module Resizing
51
55
  raise NotImplementedError, 'public_url is not implemented'
52
56
  end
53
57
  end
54
- # rubocop:enable Lint/UnusedMethodArgument,Metrics/ParameterLists
58
+ # rubocop:enable Metrics/ParameterLists
55
59
  end
56
60
  end
57
61
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resizing
4
+ module ActiveStorage
5
+ module Service
6
+ autoload :ResizingService, 'resizing/active_storage/service/resizing_service'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resizing
4
+ module ActiveStorage
5
+ autoload :Service, 'resizing/active_storage/service'
6
+ end
7
+ end
@@ -3,6 +3,7 @@
3
3
  module Resizing
4
4
  module CarrierWave
5
5
  module Storage
6
+ # rubocop:disable Metrics/ClassLength
6
7
  class File
7
8
  include ::CarrierWave::Utilities::Uri
8
9
 
@@ -28,23 +29,42 @@ module Resizing
28
29
  @content_type || file.try(:content_type)
29
30
  end
30
31
 
32
+ # rubocop:disable Metrics/AbcSize
31
33
  def delete
32
- @public_id = Resizing::PublicId.new(model.send :read_attribute, serialization_column)
33
- return if @public_id.empty? # do nothing
34
+ # Use the identifier from constructor if available, otherwise try to get from model
35
+ if @public_id.present?
36
+ # Already set from constructor or retrieve
37
+ elsif model.respond_to?(:attribute_was)
38
+ # Try to get the value before changes (for remove! scenario)
39
+ column_value = model.attribute_was(serialization_column) || model.send(:read_attribute,
40
+ serialization_column)
41
+ @public_id = Resizing::PublicId.new(column_value)
42
+ else
43
+ column_value = model.send(:read_attribute, serialization_column)
44
+ @public_id = Resizing::PublicId.new(column_value)
45
+ end
46
+
47
+ return if @public_id.empty?
34
48
 
35
49
  resp = client.delete(@public_id.image_id)
50
+
51
+ # NOTE: 削除時のカラムクリアは以下の理由で必要:
52
+ # - 画像更新時: 古い画像IDと新しい画像IDが異なるため、古い画像削除時に新しいIDを消さないようにする
53
+ # - 明示的なremove!時: カラムをnilにする必要がある
54
+ # - clear_column_if_current_imageは削除される画像IDと現在のカラム値を比較して判断
36
55
  if resp['error'] == 'ActiveRecord::RecordNotFound' # 404 not found
37
- model.send :write_attribute, serialization_column, nil unless model.destroyed?
56
+ clear_column_if_current_image
38
57
  return
39
58
  end
40
59
 
41
60
  if @public_id.image_id == resp['id']
42
- model.send :write_attribute, serialization_column, nil unless model.destroyed?
61
+ clear_column_if_current_image
43
62
  return
44
63
  end
45
64
 
46
65
  raise APIError, "raise someone error:#{resp.inspect}"
47
66
  end
67
+ # rubocop:enable Metrics/AbcSize
48
68
 
49
69
  def extension
50
70
  raise NotImplementedError, 'this method is do not used. maybe'
@@ -79,34 +99,38 @@ module Resizing
79
99
  end
80
100
 
81
101
  def current_path
102
+ # Return the path from @public_id if set (for retrieve scenarios),
103
+ # otherwise fall back to reading from model
104
+ return @public_id.to_s if @public_id.present?
105
+
82
106
  @current_path = model.send :read_attribute, serialization_column
83
107
  end
84
108
  alias path current_path
85
109
 
110
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
111
+ # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
86
112
  def store(new_file)
87
113
  if new_file.is_a?(self.class)
88
114
  # new_file.copy_to(path)
89
115
  raise NotImplementedError, 'new file is required duplicating'
90
116
  end
91
117
 
92
- if new_file.respond_to? :content_type
93
- @content_type ||= new_file.content_type
94
- else
95
- # guess content-type from extension
96
- @content_type ||= MIME::Types.type_for(new_file.path).first.content_type
97
- end
118
+ @content_type ||= if new_file.respond_to? :content_type
119
+ new_file.content_type
120
+ else
121
+ # guess content-type from extension
122
+ MIME::Types.type_for(new_file.path).first.content_type
123
+ end
98
124
 
99
125
  original_filename = new_file.try(:original_filename) || new_file.try(:filename) || new_file.try(:path)
100
- if original_filename.present?
101
- original_filename = ::File.basename(original_filename)
102
- end
126
+ original_filename = ::File.basename(original_filename) if original_filename.present?
103
127
 
104
128
  content = if new_file.respond_to?(:to_io)
105
129
  new_file.to_io.tap(&:rewind)
106
130
  elsif new_file.respond_to?(:read) && new_file.respond_to?(:rewind)
107
- new_file.read.tap do
108
- new_file.rewind
109
- end
131
+ # Pass the IO object itself, not the read result
132
+ new_file.rewind
133
+ new_file
110
134
  else
111
135
  new_file
112
136
  end
@@ -115,19 +139,24 @@ module Resizing
115
139
  @public_id = Resizing::PublicId.new(@response['public_id'])
116
140
  @content_type = @response['content_type']
117
141
 
118
- # force update column
119
- # model_class
120
- # .where(primary_key_name => model.send(primary_key_name))
121
- # .update_all(serialization_column=>@public_id)
122
-
142
+ # NOTE: 理想的にはStorage::File内でモデルのカラムをいじらず、CarrierWaveに任せるべきだが、
143
+ # 現在の実装では以下の理由で必要:
144
+ # - CarrierWaveは write_uploader(column, mounter.identifiers.first) でカラムを更新
145
+ # - mounter.identifiers -> uploaders.map(&:identifier) -> storage.identifier -> uploader.filename
146
+ # - resizing-gemの filenameメソッドは read_column を返す(既存のカラム値)
147
+ # - そのため、CarrierWaveに任せると旧い値が書き戻されてしまう
148
+ # TODO: これを修正するには、Remote#identifierをオーバーライドして@public_id.to_sを返すか、
149
+ # uploader.filenameの実装を変更する必要がある
123
150
  # save new value to model class
124
151
  model.send :write_attribute, serialization_column, @public_id.to_s
125
152
 
126
153
  true
127
154
  end
155
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
156
+ # rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity
128
157
 
129
- def name(options = {})
130
- @public_id = PublicId.new(model.send :read_attribute, serialization_column)
158
+ def name(_options = {})
159
+ @public_id = PublicId.new(model.send(:read_attribute, serialization_column))
131
160
  CGI.unescape(@public_id.filename)
132
161
  end
133
162
 
@@ -138,6 +167,7 @@ module Resizing
138
167
  def retrieve(identifier)
139
168
  @public_id = Resizing::PublicId.new(identifier)
140
169
  end
170
+
141
171
  private
142
172
 
143
173
  attr_reader :uploader
@@ -158,6 +188,20 @@ module Resizing
158
188
  @serialization_column ||= model.send(:_mounter, uploader.mounted_as).send(:serialization_column)
159
189
  end
160
190
 
191
+ # Only clear the column if the deleted image is the current one
192
+ # (not when deleting an old image during update)
193
+ def clear_column_if_current_image
194
+ return if model.destroyed?
195
+
196
+ current_value = model.send(:read_attribute, serialization_column)
197
+ current_public_id = Resizing::PublicId.new(current_value)
198
+
199
+ # Only clear if the deleted image is the same as the current one
200
+ return unless current_public_id.image_id == @public_id.image_id
201
+
202
+ model.send :write_attribute, serialization_column, nil
203
+ end
204
+
161
205
  ##
162
206
  # client of Resizing
163
207
  def client
@@ -195,7 +239,6 @@ module Resizing
195
239
  parameters.count == 2 && parameters[1].include?(:options)
196
240
  end
197
241
 
198
-
199
242
  # def retrieve_from_cache!(identifier)
200
243
  # raise NotImplementedError,
201
244
  # "Need to implement #retrieve_from_cache! if you want to use #{self.class.name} as a cache storage."
@@ -211,6 +254,7 @@ module Resizing
211
254
  # "Need to implement #clean_cache! if you want to use #{self.class.name} as a cache storage."
212
255
  # end
213
256
  end
257
+ # rubocop:enable Metrics/ClassLength
214
258
  end
215
259
  end
216
260
  end
@@ -18,9 +18,13 @@ module Resizing
18
18
  f
19
19
  end
20
20
 
21
- # def retrieve!(identifier)
22
- # super
23
- # end
21
+ def retrieve!(identifier)
22
+ return nil if identifier.blank?
23
+
24
+ f = Resizing::CarrierWave::Storage::File.new(uploader, identifier)
25
+ f.retrieve(identifier)
26
+ f
27
+ end
24
28
 
25
29
  def cache!(new_file)
26
30
  f = Resizing::CarrierWave::Storage::File.new(uploader)
@@ -4,6 +4,7 @@ require 'resizing/carrier_wave/storage/file'
4
4
  require 'resizing/carrier_wave/storage/remote'
5
5
 
6
6
  module Resizing
7
+ # rubocop:disable Metrics/ModuleLength
7
8
  module CarrierWave
8
9
  class Railtie < ::Rails::Railtie
9
10
  # Railtie skelton codes
@@ -28,15 +29,25 @@ module Resizing
28
29
  def initialize(*args)
29
30
  @requested_format = nil
30
31
  @default_format = nil
32
+ @retrieved_identifier = nil
31
33
  super
32
34
  end
33
35
 
36
+ # Override to store the identifier and set up @file for later use
37
+ def retrieve_from_store!(identifier)
38
+ @retrieved_identifier = identifier
39
+ super
40
+ # Ensure @file is set up so that remove! can call @file.delete
41
+ file
42
+ end
43
+
34
44
  def file
35
- return if identifier.nil?
36
- return @file unless defined? @file
45
+ file_identifier = @retrieved_identifier || identifier || read_column
46
+
47
+ return nil if file_identifier.blank?
37
48
 
38
49
  @file ||= Resizing::CarrierWave::Storage::File.new(self)
39
- @file.retrieve(identifier)
50
+ @file.retrieve(file_identifier)
40
51
  @file
41
52
  end
42
53
 
@@ -45,7 +56,8 @@ module Resizing
45
56
 
46
57
  transforms = args.map do |version|
47
58
  version = version.intern
48
- raise "No version is found: #{version}, #{versions.keys} are exists." unless versions.has_key? version
59
+ raise "No version is found: #{version}, #{versions.keys} are exists." unless versions.key? version
60
+
49
61
  versions[version].transform_string
50
62
  end.compact
51
63
 
@@ -108,11 +120,6 @@ module Resizing
108
120
  read_column
109
121
  end
110
122
 
111
- def store!
112
- # DO NOTHING
113
- super
114
- end
115
-
116
123
  def serialization_column
117
124
  model.send(:_mounter, mounted_as).send(:serialization_column)
118
125
  end
@@ -150,7 +157,7 @@ module Resizing
150
157
 
151
158
  private
152
159
 
153
- # rubocop:disable Metrics/AbcSize
160
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
154
161
  def transform_string_from(processor)
155
162
  action = processor.first
156
163
  value = processor.second
@@ -173,6 +180,7 @@ module Resizing
173
180
  "#{key}_#{value}"
174
181
  end.compact.join(',')
175
182
  end
176
- # rubocop:enable Metrics/AbcSize
183
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
177
184
  end
185
+ # rubocop:enable Metrics/ModuleLength
178
186
  end
@@ -24,6 +24,7 @@ module Resizing
24
24
  # }
25
25
  #
26
26
  #++
27
+ # rubocop:disable Metrics/ClassLength
27
28
  class Client
28
29
  include Resizing::Constants
29
30
  include Resizing::Configurable
@@ -53,8 +54,7 @@ module Resizing
53
54
  end
54
55
  end
55
56
 
56
- result = handle_create_response(response)
57
- result
57
+ handle_create_response(response)
58
58
  end
59
59
 
60
60
  def put(image_id, filename_or_io, options)
@@ -73,8 +73,7 @@ module Resizing
73
73
  end
74
74
  end
75
75
 
76
- result = handle_create_response(response)
77
- result
76
+ handle_create_response(response)
78
77
  end
79
78
 
80
79
  def delete(image_id)
@@ -86,8 +85,7 @@ module Resizing
86
85
  end
87
86
  end
88
87
 
89
- result = handle_delete_response(response)
90
- result
88
+ handle_delete_response(response)
91
89
  end
92
90
 
93
91
  def metadata(image_id, options = {})
@@ -99,8 +97,7 @@ module Resizing
99
97
  end
100
98
  end
101
99
 
102
- result = handle_metadata_response(response, options)
103
- result
100
+ handle_metadata_response(response, options)
104
101
  end
105
102
 
106
103
  private
@@ -114,8 +111,7 @@ module Resizing
114
111
  end
115
112
 
116
113
  def gather_filename(filename_or_io, options)
117
- filename = options[:filename]
118
- filename ||= filename_or_io.respond_to?(:path) ? File.basename(filename_or_io.path) : nil
114
+ options[:filename] || (filename_or_io.respond_to?(:path) ? File.basename(filename_or_io.path) : nil)
119
115
  end
120
116
 
121
117
  def build_put_url(image_id)
@@ -137,17 +133,17 @@ module Resizing
137
133
  def ensure_filename_or_io(filename_or_io)
138
134
  return if filename_or_io.is_a?(File)
139
135
 
140
- if filename_or_io.is_a?(String)
141
- if File.exist?(filename_or_io)
142
- return
143
- end
144
- end
136
+ # Accept IO-like objects (StringIO, Tempfile, etc.)
137
+ return if filename_or_io.respond_to?(:read) && filename_or_io.respond_to?(:rewind)
145
138
 
146
- raise ArgumentError, "filename_or_io must be a File object or a path to a file (#{filename_or_io.class})"
139
+ return if filename_or_io.is_a?(String) && File.exist?(filename_or_io)
140
+
141
+ raise ArgumentError,
142
+ "filename_or_io must be a File object, an IO-like object, or a path to a file (#{filename_or_io.class})"
147
143
  end
148
144
 
149
145
  def handle_create_response(response)
150
- raise APIError, "No response is returned" if response.nil?
146
+ raise APIError, 'No response is returned' if response.nil?
151
147
 
152
148
  case response.status
153
149
  when HTTP_STATUS_OK, HTTP_STATUS_CREATED
@@ -158,7 +154,7 @@ module Resizing
158
154
  end
159
155
 
160
156
  def handle_delete_response(response)
161
- raise APIError, "No response is returned" if response.nil?
157
+ raise APIError, 'No response is returned' if response.nil?
162
158
 
163
159
  case response.status
164
160
  when HTTP_STATUS_OK, HTTP_STATUS_NOT_FOUND
@@ -171,24 +167,30 @@ module Resizing
171
167
  def handle_metadata_response(response, options = {})
172
168
  when_not_found = options[:when_not_found] || nil
173
169
 
174
- raise APIError, "No response is returned" if response.nil?
170
+ raise APIError, 'No response is returned' if response.nil?
175
171
 
176
172
  case response.status
177
- when HTTP_STATUS_OK, HTTP_STATUS_NOT_FOUND
173
+ when HTTP_STATUS_OK
178
174
  JSON.parse(response.body)
179
175
  when HTTP_STATUS_NOT_FOUND
180
176
  raise decode_error_from(response) if when_not_found == :raise
181
- nil
177
+
178
+ JSON.parse(response.body)
182
179
  else
183
180
  raise decode_error_from(response)
184
181
  end
185
182
  end
186
183
 
187
- def decode_error_from response
188
- result = JSON.parse(response.body) rescue {}
184
+ def decode_error_from(response)
185
+ result = begin
186
+ JSON.parse(response.body)
187
+ rescue StandardError
188
+ {}
189
+ end
189
190
  err = APIError.new(result['message'] || "invalid http status code #{response.status}")
190
191
  err.decoded_body = result
191
192
  err
192
193
  end
193
194
  end
195
+ # rubocop:enable Metrics/ClassLength
194
196
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Resizing
4
4
  module Configurable
5
- def self.included mod
5
+ def self.included(mod)
6
6
  mod.send(:attr_reader, :config)
7
7
  end
8
8