jr-paperclip 7.3.0 → 8.0.0.beta.1
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.
- checksums.yaml +4 -4
- data/.github/workflows/{test.yml → tests.yml} +19 -9
- data/.rubocop.yml +2 -1
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +1 -0
- data/NEWS +16 -1
- data/README.md +119 -8
- data/UPGRADING +5 -0
- data/VIPS_MIGRATION_GUIDE.md +131 -0
- data/features/basic_integration.feature +27 -0
- data/features/step_definitions/attachment_steps.rb +17 -0
- data/gemfiles/7.0.gemfile +1 -0
- data/gemfiles/7.1.gemfile +1 -0
- data/gemfiles/7.2.gemfile +1 -0
- data/gemfiles/8.0.gemfile +1 -0
- data/gemfiles/8.1.gemfile +1 -0
- data/lib/paperclip/attachment.rb +3 -2
- data/lib/paperclip/errors.rb +4 -5
- data/lib/paperclip/geometry.rb +3 -3
- data/lib/paperclip/geometry_detector_factory.rb +52 -12
- data/lib/paperclip/helpers.rb +18 -0
- data/lib/paperclip/processor.rb +36 -4
- data/lib/paperclip/thumbnail.rb +568 -62
- data/lib/paperclip/version.rb +1 -1
- data/lib/paperclip.rb +26 -9
- data/paperclip.gemspec +3 -2
- data/spec/paperclip/attachment_definitions_spec.rb +300 -0
- data/spec/paperclip/attachment_spec.rb +1 -1
- data/spec/paperclip/geometry_detector_spec.rb +81 -32
- data/spec/paperclip/geometry_spec.rb +8 -5
- data/spec/paperclip/helpers_spec.rb +49 -0
- data/spec/paperclip/lazy_thumbnail_compatibility_spec.rb +266 -0
- data/spec/paperclip/processor_spec.rb +35 -1
- data/spec/paperclip/style_spec.rb +58 -0
- data/spec/paperclip/thumbnail_custom_options_spec.rb +173 -0
- data/spec/paperclip/thumbnail_loader_options_spec.rb +53 -0
- data/spec/paperclip/thumbnail_security_spec.rb +42 -0
- data/spec/paperclip/thumbnail_spec.rb +1127 -172
- metadata +36 -4
data/lib/paperclip/version.rb
CHANGED
data/lib/paperclip.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
# Paperclip allows file attachments that are stored in the filesystem.
|
|
2
|
-
# transformations are done using
|
|
1
|
+
# Paperclip allows file attachments that are stored in the filesystem. Graphical
|
|
2
|
+
# transformations are done using command line utilities (ImageMagick or libvips) and
|
|
3
3
|
# are stored in Tempfiles until the record is saved. Paperclip does not require a
|
|
4
4
|
# separate model for storing the attachment's information, instead adding a few simple
|
|
5
5
|
# columns to your table.
|
|
@@ -77,6 +77,17 @@ module Paperclip
|
|
|
77
77
|
extend Logger
|
|
78
78
|
extend ProcessorHelpers
|
|
79
79
|
|
|
80
|
+
AVAILABLE_BACKENDS = [:image_magick, :vips].freeze
|
|
81
|
+
|
|
82
|
+
def self.resolve_backend(backend)
|
|
83
|
+
backend ||= :image_magick
|
|
84
|
+
unless AVAILABLE_BACKENDS.include?(backend)
|
|
85
|
+
log("Warning: Invalid backend: #{backend}. Falling back to :image_magick. Allowed backends: #{AVAILABLE_BACKENDS.join(', ')}")
|
|
86
|
+
backend = :image_magick
|
|
87
|
+
end
|
|
88
|
+
backend
|
|
89
|
+
end
|
|
90
|
+
|
|
80
91
|
# Provides configurability to Paperclip. The options available are:
|
|
81
92
|
# * whiny: Will raise an error if Paperclip cannot process thumbnails of
|
|
82
93
|
# an uploaded image. Defaults to true.
|
|
@@ -98,7 +109,8 @@ module Paperclip
|
|
|
98
109
|
use_exif_orientation: true,
|
|
99
110
|
whiny: true,
|
|
100
111
|
is_windows: Gem.win_platform?,
|
|
101
|
-
add_validation_errors_to: :both
|
|
112
|
+
add_validation_errors_to: :both,
|
|
113
|
+
backend: :image_magick
|
|
102
114
|
}
|
|
103
115
|
end
|
|
104
116
|
|
|
@@ -155,12 +167,17 @@ module Paperclip
|
|
|
155
167
|
# * +whiny+: Will raise an error if Paperclip cannot post_process an uploaded file due
|
|
156
168
|
# to a command line error. This will override the global setting for this attachment.
|
|
157
169
|
# Defaults to true.
|
|
158
|
-
# * +
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
#
|
|
170
|
+
# * +backend+: Chooses the image processing backend. Options are :image_magick (default)
|
|
171
|
+
# or :vips. The vips backend uses libvips which is significantly faster and uses less
|
|
172
|
+
# memory. Can be set globally via Paperclip.options[:backend] or per-attachment.
|
|
173
|
+
# has_attached_file :avatar, :styles => { :thumb => "100x100#" }, :backend => :vips
|
|
174
|
+
# * +convert_options+: Options passed to the image processor when creating thumbnails.
|
|
175
|
+
# Works with both ImageMagick and libvips backends. Common cross-platform options:
|
|
176
|
+
# "-strip" (remove metadata), "-quality N" (output quality), "-rotate N" (rotation),
|
|
177
|
+
# "-flip"/"-flop" (mirror), "-blur 0xN" (gaussian blur), "-sharpen" (sharpen),
|
|
178
|
+
# "-colorspace Gray" (grayscale). Some options are ImageMagick-only and will be
|
|
179
|
+
# skipped with a warning when using the vips backend.
|
|
180
|
+
# This option takes a hash of options, each of which correspond to the style
|
|
164
181
|
# of thumbnail being generated. You can also specify :all as a key, which will apply
|
|
165
182
|
# to all of the thumbnails being generated. If you specify options for the :original,
|
|
166
183
|
# it would be best if you did not specify destructive options, as the intent of keeping
|
data/paperclip.gemspec
CHANGED
|
@@ -19,11 +19,12 @@ Gem::Specification.new do |s|
|
|
|
19
19
|
|
|
20
20
|
s.post_install_message = File.read("UPGRADING") if File.exist?("UPGRADING")
|
|
21
21
|
|
|
22
|
-
s.requirements << "ImageMagick"
|
|
23
|
-
s.required_ruby_version = ">= 3.
|
|
22
|
+
s.requirements << "ImageMagick or libvips"
|
|
23
|
+
s.required_ruby_version = ">= 3.0.0"
|
|
24
24
|
|
|
25
25
|
s.add_dependency("activemodel", ">= 7.0.0")
|
|
26
26
|
s.add_dependency("activesupport", ">= 7.0.0")
|
|
27
|
+
s.add_dependency("image_processing", "~> 1.14")
|
|
27
28
|
s.add_dependency("marcel", ">= 1.0.1")
|
|
28
29
|
s.add_dependency("mime-types")
|
|
29
30
|
s.add_dependency("terrapin", ">= 0.6.0", "< 2.0")
|
|
@@ -10,4 +10,304 @@ describe "Attachment Definitions" do
|
|
|
10
10
|
|
|
11
11
|
expect(Dummy.attachment_definitions).to eq expected
|
|
12
12
|
end
|
|
13
|
+
|
|
14
|
+
describe "configuration isolation between models" do
|
|
15
|
+
# Helper method to safely remove a constant and clean up its registry entries
|
|
16
|
+
def cleanup_model(class_name)
|
|
17
|
+
return unless Object.const_defined?(class_name)
|
|
18
|
+
|
|
19
|
+
# Remove from Paperclip's AttachmentRegistry to prevent test pollution
|
|
20
|
+
Paperclip::AttachmentRegistry.clear
|
|
21
|
+
# Remove the constant
|
|
22
|
+
Object.send(:remove_const, class_name)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
before do
|
|
26
|
+
# Clean up any existing class definitions before each test
|
|
27
|
+
cleanup_model(:ModelA)
|
|
28
|
+
cleanup_model(:ModelB)
|
|
29
|
+
|
|
30
|
+
# Create table for ModelA
|
|
31
|
+
ActiveRecord::Migration.create_table :model_as, force: true do |table|
|
|
32
|
+
table.column :attachment_file_name, :string
|
|
33
|
+
table.column :attachment_content_type, :string
|
|
34
|
+
table.column :attachment_file_size, :bigint
|
|
35
|
+
table.column :attachment_updated_at, :datetime
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Create table for ModelB
|
|
39
|
+
ActiveRecord::Migration.create_table :model_bs, force: true do |table|
|
|
40
|
+
table.column :attachment_file_name, :string
|
|
41
|
+
table.column :attachment_content_type, :string
|
|
42
|
+
table.column :attachment_file_size, :bigint
|
|
43
|
+
table.column :attachment_updated_at, :datetime
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
after do
|
|
48
|
+
# Clean up model classes to prevent test pollution
|
|
49
|
+
cleanup_model(:ModelA)
|
|
50
|
+
cleanup_model(:ModelB)
|
|
51
|
+
|
|
52
|
+
# Drop tables
|
|
53
|
+
ActiveRecord::Migration.drop_table :model_as, if_exists: true
|
|
54
|
+
ActiveRecord::Migration.drop_table :model_bs, if_exists: true
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "does not mutate default_options when configuring multiple models" do
|
|
58
|
+
# Capture original values of mutable options
|
|
59
|
+
original_path = Paperclip::Attachment.default_options[:path].dup
|
|
60
|
+
original_url = Paperclip::Attachment.default_options[:url].dup
|
|
61
|
+
original_default_url = Paperclip::Attachment.default_options[:default_url].dup
|
|
62
|
+
original_styles = Paperclip::Attachment.default_options[:styles].deep_dup
|
|
63
|
+
original_convert_options = Paperclip::Attachment.default_options[:convert_options].deep_dup
|
|
64
|
+
|
|
65
|
+
# Define ModelA with specific configuration
|
|
66
|
+
class ::ModelA < ActiveRecord::Base
|
|
67
|
+
include Paperclip::Glue
|
|
68
|
+
has_attached_file :attachment,
|
|
69
|
+
path: "/model_a/:filename",
|
|
70
|
+
styles: { thumb: "100x100" }
|
|
71
|
+
do_not_validate_attachment_file_type :attachment
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Define ModelB with different configuration
|
|
75
|
+
class ::ModelB < ActiveRecord::Base
|
|
76
|
+
include Paperclip::Glue
|
|
77
|
+
has_attached_file :attachment,
|
|
78
|
+
path: "/model_b/:filename",
|
|
79
|
+
styles: { large: "500x500" }
|
|
80
|
+
do_not_validate_attachment_file_type :attachment
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Verify default_options was not mutated
|
|
84
|
+
expect(Paperclip::Attachment.default_options[:path]).to eq original_path
|
|
85
|
+
expect(Paperclip::Attachment.default_options[:url]).to eq original_url
|
|
86
|
+
expect(Paperclip::Attachment.default_options[:default_url]).to eq original_default_url
|
|
87
|
+
expect(Paperclip::Attachment.default_options[:styles]).to eq original_styles
|
|
88
|
+
expect(Paperclip::Attachment.default_options[:convert_options]).to eq original_convert_options
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "keeps attachment configurations isolated between models" do
|
|
92
|
+
# Define ModelA with specific configuration
|
|
93
|
+
class ::ModelA < ActiveRecord::Base
|
|
94
|
+
include Paperclip::Glue
|
|
95
|
+
has_attached_file :attachment,
|
|
96
|
+
path: "/model_a/:filename",
|
|
97
|
+
styles: { thumb: "100x100" },
|
|
98
|
+
default_url: "/missing_a.png"
|
|
99
|
+
do_not_validate_attachment_file_type :attachment
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Define ModelB with different configuration
|
|
103
|
+
class ::ModelB < ActiveRecord::Base
|
|
104
|
+
include Paperclip::Glue
|
|
105
|
+
has_attached_file :attachment,
|
|
106
|
+
path: "/model_b/:filename",
|
|
107
|
+
styles: { large: "500x500" },
|
|
108
|
+
default_url: "/missing_b.png"
|
|
109
|
+
do_not_validate_attachment_file_type :attachment
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Create instances and access attachments
|
|
113
|
+
model_a = ModelA.new
|
|
114
|
+
model_b = ModelB.new
|
|
115
|
+
|
|
116
|
+
# Access attachment on ModelA first
|
|
117
|
+
attachment_a = model_a.attachment
|
|
118
|
+
|
|
119
|
+
# Access attachment on ModelB
|
|
120
|
+
attachment_b = model_b.attachment
|
|
121
|
+
|
|
122
|
+
# Verify configurations are isolated
|
|
123
|
+
expect(attachment_a.options[:path]).to eq "/model_a/:filename"
|
|
124
|
+
expect(attachment_a.options[:styles]).to eq({ thumb: "100x100" })
|
|
125
|
+
expect(attachment_a.options[:default_url]).to eq "/missing_a.png"
|
|
126
|
+
|
|
127
|
+
expect(attachment_b.options[:path]).to eq "/model_b/:filename"
|
|
128
|
+
expect(attachment_b.options[:styles]).to eq({ large: "500x500" })
|
|
129
|
+
expect(attachment_b.options[:default_url]).to eq "/missing_b.png"
|
|
130
|
+
|
|
131
|
+
# Verify attachment_definitions are also isolated
|
|
132
|
+
expect(ModelA.attachment_definitions[:attachment][:path]).to eq "/model_a/:filename"
|
|
133
|
+
expect(ModelB.attachment_definitions[:attachment][:path]).to eq "/model_b/:filename"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "does not leak configuration when accessing attachments in different order" do
|
|
137
|
+
# Define ModelA with specific configuration
|
|
138
|
+
class ::ModelA < ActiveRecord::Base
|
|
139
|
+
include Paperclip::Glue
|
|
140
|
+
has_attached_file :attachment,
|
|
141
|
+
path: "/model_a/:filename",
|
|
142
|
+
styles: { thumb: "100x100" }
|
|
143
|
+
do_not_validate_attachment_file_type :attachment
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Define ModelB with different configuration
|
|
147
|
+
class ::ModelB < ActiveRecord::Base
|
|
148
|
+
include Paperclip::Glue
|
|
149
|
+
has_attached_file :attachment,
|
|
150
|
+
path: "/model_b/:filename",
|
|
151
|
+
styles: { large: "500x500" }
|
|
152
|
+
do_not_validate_attachment_file_type :attachment
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Access ModelB first, then ModelA (reverse order of definition)
|
|
156
|
+
model_b = ModelB.new
|
|
157
|
+
attachment_b = model_b.attachment
|
|
158
|
+
|
|
159
|
+
model_a = ModelA.new
|
|
160
|
+
attachment_a = model_a.attachment
|
|
161
|
+
|
|
162
|
+
# Verify configurations remain correct
|
|
163
|
+
expect(attachment_a.options[:path]).to eq "/model_a/:filename"
|
|
164
|
+
expect(attachment_a.options[:styles]).to eq({ thumb: "100x100" })
|
|
165
|
+
|
|
166
|
+
expect(attachment_b.options[:path]).to eq "/model_b/:filename"
|
|
167
|
+
expect(attachment_b.options[:styles]).to eq({ large: "500x500" })
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it "does not share options between multiple instances of the same model" do
|
|
171
|
+
class ::ModelA < ActiveRecord::Base
|
|
172
|
+
include Paperclip::Glue
|
|
173
|
+
has_attached_file :attachment,
|
|
174
|
+
path: "/model_a/:filename",
|
|
175
|
+
styles: { thumb: "100x100" }
|
|
176
|
+
do_not_validate_attachment_file_type :attachment
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
instance1 = ModelA.new
|
|
180
|
+
instance2 = ModelA.new
|
|
181
|
+
|
|
182
|
+
attachment1 = instance1.attachment
|
|
183
|
+
attachment2 = instance2.attachment
|
|
184
|
+
|
|
185
|
+
# Verify they have the same configuration values
|
|
186
|
+
expect(attachment1.options[:path]).to eq attachment2.options[:path]
|
|
187
|
+
expect(attachment1.options[:styles]).to eq attachment2.options[:styles]
|
|
188
|
+
|
|
189
|
+
# Verify the values are correct for both instances
|
|
190
|
+
expect(attachment1.options[:path]).to eq "/model_a/:filename"
|
|
191
|
+
expect(attachment2.options[:path]).to eq "/model_a/:filename"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
context "with global default_options configured first" do
|
|
195
|
+
let(:original_default_options) { Paperclip::Attachment.default_options.deep_dup }
|
|
196
|
+
|
|
197
|
+
before do
|
|
198
|
+
# Simulate a Rails initializer setting global defaults
|
|
199
|
+
Paperclip::Attachment.default_options[:path] = "/global/:class/:attachment/:id/:style/:filename"
|
|
200
|
+
Paperclip::Attachment.default_options[:url] = "/global/:class/:attachment/:id/:style/:filename"
|
|
201
|
+
Paperclip::Attachment.default_options[:default_url] = "/global/missing.png"
|
|
202
|
+
Paperclip::Attachment.default_options[:styles] = { global_thumb: "50x50" }
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
after do
|
|
206
|
+
# Restore original default_options
|
|
207
|
+
Paperclip::Attachment.instance_variable_set(:@default_options, nil)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it "keeps model configurations isolated when global defaults are set" do
|
|
211
|
+
# Define ModelA that overrides some global defaults
|
|
212
|
+
class ::ModelA < ActiveRecord::Base
|
|
213
|
+
include Paperclip::Glue
|
|
214
|
+
has_attached_file :attachment,
|
|
215
|
+
path: "/model_a/:filename",
|
|
216
|
+
styles: { thumb: "100x100" }
|
|
217
|
+
do_not_validate_attachment_file_type :attachment
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Define ModelB that overrides different global defaults
|
|
221
|
+
class ::ModelB < ActiveRecord::Base
|
|
222
|
+
include Paperclip::Glue
|
|
223
|
+
has_attached_file :attachment,
|
|
224
|
+
path: "/model_b/:filename",
|
|
225
|
+
styles: { large: "500x500" }
|
|
226
|
+
do_not_validate_attachment_file_type :attachment
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
model_a = ModelA.new
|
|
230
|
+
model_b = ModelB.new
|
|
231
|
+
|
|
232
|
+
attachment_a = model_a.attachment
|
|
233
|
+
attachment_b = model_b.attachment
|
|
234
|
+
|
|
235
|
+
# Verify ModelA has its own path, and styles are deep_merged with global defaults
|
|
236
|
+
expect(attachment_a.options[:path]).to eq "/model_a/:filename"
|
|
237
|
+
expect(attachment_a.options[:styles]).to include(thumb: "100x100")
|
|
238
|
+
expect(attachment_a.options[:styles]).to include(global_thumb: "50x50") # inherited from global
|
|
239
|
+
expect(attachment_a.options[:url]).to eq "/global/:class/:attachment/:id/:style/:filename"
|
|
240
|
+
expect(attachment_a.options[:default_url]).to eq "/global/missing.png"
|
|
241
|
+
|
|
242
|
+
# Verify ModelB has its own path, and styles are deep_merged with global defaults
|
|
243
|
+
expect(attachment_b.options[:path]).to eq "/model_b/:filename"
|
|
244
|
+
expect(attachment_b.options[:styles]).to include(large: "500x500")
|
|
245
|
+
expect(attachment_b.options[:styles]).to include(global_thumb: "50x50") # inherited from global
|
|
246
|
+
expect(attachment_b.options[:url]).to eq "/global/:class/:attachment/:id/:style/:filename"
|
|
247
|
+
expect(attachment_b.options[:default_url]).to eq "/global/missing.png"
|
|
248
|
+
|
|
249
|
+
# Verify ModelA's styles do NOT leak to ModelB and vice versa
|
|
250
|
+
expect(attachment_a.options[:styles]).not_to have_key(:large)
|
|
251
|
+
expect(attachment_b.options[:styles]).not_to have_key(:thumb)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
it "does not mutate global defaults when models override them" do
|
|
255
|
+
# Capture global defaults before defining models
|
|
256
|
+
global_path = Paperclip::Attachment.default_options[:path]
|
|
257
|
+
global_styles = Paperclip::Attachment.default_options[:styles].deep_dup
|
|
258
|
+
|
|
259
|
+
class ::ModelA < ActiveRecord::Base
|
|
260
|
+
include Paperclip::Glue
|
|
261
|
+
has_attached_file :attachment,
|
|
262
|
+
path: "/model_a/:filename",
|
|
263
|
+
styles: { thumb: "100x100" }
|
|
264
|
+
do_not_validate_attachment_file_type :attachment
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Access the attachment to trigger any potential mutation
|
|
268
|
+
model_a = ModelA.new
|
|
269
|
+
_attachment_a = model_a.attachment
|
|
270
|
+
|
|
271
|
+
# Global defaults should remain unchanged
|
|
272
|
+
expect(Paperclip::Attachment.default_options[:path]).to eq global_path
|
|
273
|
+
expect(Paperclip::Attachment.default_options[:styles]).to eq global_styles
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
it "does not leak ModelA config to ModelB when both override global defaults" do
|
|
277
|
+
class ::ModelA < ActiveRecord::Base
|
|
278
|
+
include Paperclip::Glue
|
|
279
|
+
has_attached_file :attachment,
|
|
280
|
+
path: "/model_a/:filename",
|
|
281
|
+
styles: { thumb: "100x100" },
|
|
282
|
+
convert_options: { all: "-quality 80" }
|
|
283
|
+
do_not_validate_attachment_file_type :attachment
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
class ::ModelB < ActiveRecord::Base
|
|
287
|
+
include Paperclip::Glue
|
|
288
|
+
has_attached_file :attachment,
|
|
289
|
+
path: "/model_b/:filename",
|
|
290
|
+
styles: { large: "500x500" },
|
|
291
|
+
convert_options: { all: "-quality 90" }
|
|
292
|
+
do_not_validate_attachment_file_type :attachment
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Access ModelA first
|
|
296
|
+
model_a = ModelA.new
|
|
297
|
+
attachment_a = model_a.attachment
|
|
298
|
+
|
|
299
|
+
# Then access ModelB
|
|
300
|
+
model_b = ModelB.new
|
|
301
|
+
attachment_b = model_b.attachment
|
|
302
|
+
|
|
303
|
+
# Verify convert_options are isolated
|
|
304
|
+
expect(attachment_a.options[:convert_options]).to eq({ all: "-quality 80" })
|
|
305
|
+
expect(attachment_b.options[:convert_options]).to eq({ all: "-quality 90" })
|
|
306
|
+
|
|
307
|
+
# Verify other options remain isolated
|
|
308
|
+
expect(attachment_a.options[:path]).to eq "/model_a/:filename"
|
|
309
|
+
expect(attachment_b.options[:path]).to eq "/model_b/:filename"
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
end
|
|
13
313
|
end
|
|
@@ -670,7 +670,7 @@ describe Paperclip::Attachment do
|
|
|
670
670
|
context "when error is meaningful for the end user" do
|
|
671
671
|
before do
|
|
672
672
|
expect(Paperclip::Thumbnail).to receive(:make).and_raise(
|
|
673
|
-
Paperclip::Errors::
|
|
673
|
+
Paperclip::Errors::NotIdentifiedByBackendError,
|
|
674
674
|
"cannot be processed."
|
|
675
675
|
)
|
|
676
676
|
end
|
|
@@ -1,47 +1,96 @@
|
|
|
1
1
|
require "spec_helper"
|
|
2
2
|
|
|
3
3
|
describe Paperclip::GeometryDetector do
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
factory = Paperclip::GeometryDetector.new(file)
|
|
4
|
+
[:image_magick, :vips].each do |backend|
|
|
5
|
+
context "when configured to use #{backend}" do
|
|
6
|
+
let(:original_backend) { Paperclip.options[:backend] }
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
before do
|
|
9
|
+
Paperclip.options[:backend] = backend
|
|
10
|
+
end
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
after do
|
|
13
|
+
Paperclip.options[:backend] = original_backend
|
|
14
|
+
end
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
it "identifies an image and extract its dimensions" do
|
|
17
|
+
allow_any_instance_of(Paperclip::GeometryParser).to receive(:make).and_return(:correct)
|
|
18
|
+
file = fixture_file("5k.png")
|
|
19
|
+
factory = Paperclip::GeometryDetector.new(file)
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
output = factory.make
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
expect(output).to eq :correct
|
|
24
|
+
end
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
file = fixture_file("rotated.jpg")
|
|
29
|
-
factory = Paperclip::GeometryDetector.new(file)
|
|
26
|
+
it "identifies an image and extract its dimensions and orientation" do
|
|
27
|
+
allow_any_instance_of(Paperclip::GeometryParser).to receive(:make).and_return(:correct)
|
|
28
|
+
file = fixture_file("rotated.jpg")
|
|
29
|
+
factory = Paperclip::GeometryDetector.new(file)
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
output = factory.make
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
expect(output).to eq :correct
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "avoids reading EXIF orientation if so configured" do
|
|
37
|
+
begin
|
|
38
|
+
Paperclip.options[:use_exif_orientation] = false
|
|
39
|
+
allow_any_instance_of(Paperclip::GeometryParser).to receive(:make).and_return(:correct)
|
|
40
|
+
file = fixture_file("rotated.jpg")
|
|
41
|
+
factory = Paperclip::GeometryDetector.new(file)
|
|
42
|
+
|
|
43
|
+
output = factory.make
|
|
44
|
+
|
|
45
|
+
expect(output).to eq :correct
|
|
46
|
+
ensure
|
|
47
|
+
Paperclip.options[:use_exif_orientation] = true
|
|
48
|
+
end
|
|
49
|
+
end
|
|
38
50
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
51
|
+
it "raises an exception with a message when the file is not an image" do
|
|
52
|
+
file = fixture_file("text.txt")
|
|
53
|
+
factory = Paperclip::GeometryDetector.new(file)
|
|
42
54
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
55
|
+
expect do
|
|
56
|
+
factory.make
|
|
57
|
+
end.to raise_error(Paperclip::Errors::NotIdentifiedByBackendError, "Could not identify image size")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "uses the correct backend to identify the image" do
|
|
61
|
+
if backend == :vips
|
|
62
|
+
begin
|
|
63
|
+
require "vips"
|
|
64
|
+
rescue LoadError
|
|
65
|
+
skip "ruby-vips gem not available"
|
|
66
|
+
end
|
|
67
|
+
expect(Vips::Image).to receive(:new_from_file).and_call_original
|
|
68
|
+
expect(Paperclip).not_to receive(:run).with(include("identify"), any_args)
|
|
69
|
+
else
|
|
70
|
+
expect(Paperclip).to receive(:run).with(include("identify"), any_args).and_call_original
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
file = fixture_file("5k.png")
|
|
74
|
+
factory = Paperclip::GeometryDetector.new(file)
|
|
75
|
+
|
|
76
|
+
geometry = factory.make
|
|
77
|
+
expect(geometry.width).to eq(434)
|
|
78
|
+
expect(geometry.height).to eq(66)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if backend == :vips
|
|
82
|
+
it "raises CommandNotFoundError (and not NameError) when vips is missing" do
|
|
83
|
+
hide_const("Vips")
|
|
84
|
+
allow_any_instance_of(Paperclip::GeometryDetector).to receive(:require).with("vips").and_raise(LoadError)
|
|
85
|
+
|
|
86
|
+
file = fixture_file("5k.png")
|
|
87
|
+
factory = Paperclip::GeometryDetector.new(file)
|
|
88
|
+
|
|
89
|
+
expect {
|
|
90
|
+
factory.make
|
|
91
|
+
}.to raise_error(Paperclip::Errors::CommandNotFoundError, /Could not load ruby-vips/)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
46
95
|
end
|
|
47
96
|
end
|
|
@@ -149,7 +149,7 @@ describe Paperclip::Geometry do
|
|
|
149
149
|
file = "/home/This File Does Not Exist.omg"
|
|
150
150
|
expect do
|
|
151
151
|
@geo = Paperclip::Geometry.from_file(file)
|
|
152
|
-
end.to raise_error(Paperclip::Errors::
|
|
152
|
+
end.to raise_error(Paperclip::Errors::NotIdentifiedByBackendError,
|
|
153
153
|
"Could not identify image size")
|
|
154
154
|
end
|
|
155
155
|
|
|
@@ -157,7 +157,7 @@ describe Paperclip::Geometry do
|
|
|
157
157
|
file = ""
|
|
158
158
|
expect do
|
|
159
159
|
@geo = Paperclip::Geometry.from_file(file)
|
|
160
|
-
end.to raise_error(Paperclip::Errors::
|
|
160
|
+
end.to raise_error(Paperclip::Errors::NotIdentifiedByBackendError,
|
|
161
161
|
"Cannot find the geometry of a file with a blank name")
|
|
162
162
|
end
|
|
163
163
|
|
|
@@ -165,7 +165,7 @@ describe Paperclip::Geometry do
|
|
|
165
165
|
file = nil
|
|
166
166
|
expect do
|
|
167
167
|
@geo = Paperclip::Geometry.from_file(file)
|
|
168
|
-
end.to raise_error(Paperclip::Errors::
|
|
168
|
+
end.to raise_error(Paperclip::Errors::NotIdentifiedByBackendError,
|
|
169
169
|
"Cannot find the geometry of a file with a blank name")
|
|
170
170
|
end
|
|
171
171
|
|
|
@@ -174,13 +174,15 @@ describe Paperclip::Geometry do
|
|
|
174
174
|
allow(file).to receive(:respond_to?).with(:path).and_return(true)
|
|
175
175
|
expect do
|
|
176
176
|
@geo = Paperclip::Geometry.from_file(file)
|
|
177
|
-
end.to raise_error(Paperclip::Errors::
|
|
177
|
+
end.to raise_error(Paperclip::Errors::NotIdentifiedByBackendError,
|
|
178
178
|
"Cannot find the geometry of a file with a blank name")
|
|
179
179
|
end
|
|
180
180
|
|
|
181
|
-
it "lets us know when a command isn't found versus a processing error" do
|
|
181
|
+
it "lets us know when a command isn't found versus a processing error when using imagemagick" do
|
|
182
182
|
old_path = ENV["PATH"]
|
|
183
|
+
old_backend = Paperclip.options[:backend]
|
|
183
184
|
begin
|
|
185
|
+
Paperclip.options[:backend] = :image_magick
|
|
184
186
|
ENV["PATH"] = ""
|
|
185
187
|
assert_raises(Paperclip::Errors::CommandNotFoundError) do
|
|
186
188
|
file = fixture_file("5k.png")
|
|
@@ -188,6 +190,7 @@ describe Paperclip::Geometry do
|
|
|
188
190
|
end
|
|
189
191
|
ensure
|
|
190
192
|
ENV["PATH"] = old_path
|
|
193
|
+
Paperclip.options[:backend] = old_backend
|
|
191
194
|
end
|
|
192
195
|
end
|
|
193
196
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Paperclip::Helpers do
|
|
4
|
+
describe ".imagemagick7?" do
|
|
5
|
+
before do
|
|
6
|
+
if Paperclip.instance_variable_defined?(:@imagemagick7)
|
|
7
|
+
Paperclip.send(:remove_instance_variable, :@imagemagick7)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
after do
|
|
12
|
+
if Paperclip.instance_variable_defined?(:@imagemagick7)
|
|
13
|
+
Paperclip.send(:remove_instance_variable, :@imagemagick7)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "returns true if magick command is found" do
|
|
18
|
+
allow(Paperclip).to receive(:which).with("magick").and_return("/usr/bin/magick")
|
|
19
|
+
expect(Paperclip.imagemagick7?).to be true
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "returns false if magick command is not found" do
|
|
23
|
+
allow(Paperclip).to receive(:which).with("magick").and_return(nil)
|
|
24
|
+
expect(Paperclip.imagemagick7?).to be false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "caches the result" do
|
|
28
|
+
expect(Paperclip).to receive(:which).with("magick").once.and_return("/usr/bin/magick")
|
|
29
|
+
Paperclip.imagemagick7?
|
|
30
|
+
Paperclip.imagemagick7?
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe ".which" do
|
|
35
|
+
it "finds an executable in the PATH" do
|
|
36
|
+
allow(ENV).to receive(:[]).with("PATH").and_return("/usr/bin:/bin")
|
|
37
|
+
allow(ENV).to receive(:[]).with("PATHEXT").and_return(nil)
|
|
38
|
+
allow(File).to receive(:executable?).with("/usr/bin/ruby").and_return(true)
|
|
39
|
+
expect(Paperclip.which("ruby")).to eq("/usr/bin/ruby")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "returns nil if executable is not found" do
|
|
43
|
+
allow(ENV).to receive(:[]).with("PATH").and_return("/usr/bin:/bin")
|
|
44
|
+
allow(ENV).to receive(:[]).with("PATHEXT").and_return(nil)
|
|
45
|
+
allow(File).to receive(:executable?).and_return(false)
|
|
46
|
+
expect(Paperclip.which("nonexistent")).to be_nil
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|