paperclip 4.2.4 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of paperclip might be problematic. Click here for more details.

Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.hound.yml +1066 -0
  3. data/.rubocop.yml +1 -0
  4. data/.travis.yml +5 -3
  5. data/Appraisals +1 -6
  6. data/CONTRIBUTING.md +3 -3
  7. data/Gemfile +1 -0
  8. data/LICENSE +1 -1
  9. data/NEWS +14 -1
  10. data/README.md +137 -77
  11. data/RELEASING.md +17 -0
  12. data/Rakefile +1 -1
  13. data/features/basic_integration.feature +7 -4
  14. data/features/step_definitions/attachment_steps.rb +7 -1
  15. data/gemfiles/3.2.gemfile +2 -1
  16. data/gemfiles/4.1.gemfile +1 -0
  17. data/gemfiles/4.2.gemfile +2 -1
  18. data/lib/paperclip.rb +13 -3
  19. data/lib/paperclip/attachment.rb +3 -1
  20. data/lib/paperclip/attachment_registry.rb +1 -1
  21. data/lib/paperclip/content_type_detector.rb +26 -11
  22. data/lib/paperclip/file_command_content_type_detector.rb +6 -8
  23. data/lib/paperclip/glue.rb +1 -1
  24. data/lib/paperclip/has_attached_file.rb +2 -1
  25. data/lib/paperclip/interpolations.rb +1 -1
  26. data/lib/paperclip/io_adapters/abstract_adapter.rb +1 -0
  27. data/lib/paperclip/media_type_spoof_detector.rb +1 -1
  28. data/lib/paperclip/rails_environment.rb +25 -0
  29. data/lib/paperclip/storage/fog.rb +4 -4
  30. data/lib/paperclip/storage/s3.rb +2 -3
  31. data/lib/paperclip/thumbnail.rb +2 -3
  32. data/lib/paperclip/version.rb +1 -1
  33. data/lib/tasks/paperclip.rake +16 -0
  34. data/paperclip.gemspec +3 -4
  35. data/spec/paperclip/attachment_definitions_spec.rb +1 -1
  36. data/spec/paperclip/attachment_registry_spec.rb +56 -13
  37. data/spec/paperclip/attachment_spec.rb +57 -19
  38. data/spec/paperclip/content_type_detector_spec.rb +8 -1
  39. data/spec/paperclip/file_command_content_type_detector_spec.rb +0 -1
  40. data/spec/paperclip/interpolations_spec.rb +2 -9
  41. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +2 -1
  42. data/spec/paperclip/io_adapters/file_adapter_spec.rb +4 -1
  43. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +4 -0
  44. data/spec/paperclip/media_type_spoof_detector_spec.rb +16 -2
  45. data/spec/paperclip/rails_environment_spec.rb +33 -0
  46. data/spec/paperclip/storage/fog_spec.rb +11 -2
  47. data/spec/paperclip/storage/s3_spec.rb +43 -25
  48. data/spec/paperclip/tempfile_factory_spec.rb +4 -0
  49. data/spec/paperclip/thumbnail_spec.rb +16 -0
  50. data/spec/paperclip/url_generator_spec.rb +1 -1
  51. data/spec/support/fake_model.rb +4 -0
  52. data/spec/support/fixtures/empty.xlsx +0 -0
  53. data/spec/support/matchers/have_column.rb +11 -2
  54. metadata +30 -12
  55. data/RUNNING_TESTS.md +0 -4
  56. data/gemfiles/4.0.gemfile +0 -19
  57. data/spec/support/mock_model.rb +0 -2
@@ -8,6 +8,6 @@ describe "Attachment Definitions" do
8
8
  Dummy.do_not_validate_attachment_file_type :avatar
9
9
  expected = {avatar: {path: "abc"}, other_attachment: {url: "123"}}
10
10
 
11
- assert_equal expected, Dummy.attachment_definitions
11
+ expect(Dummy.attachment_definitions).to eq expected
12
12
  end
13
13
  end
@@ -31,8 +31,8 @@ describe 'Attachment Registry' do
31
31
  it 'calls the block with the class, attachment name, and options' do
32
32
  foo = Class.new
33
33
  expected_accumulations = [
34
- [foo, :avatar, { yo: 'greeting' }],
35
- [foo, :greeter, { ciao: 'greeting' }]
34
+ [foo, :avatar, { yo: "greeting" }],
35
+ [foo, :greeter, { ciao: "greeting" }]
36
36
  ]
37
37
  expected_accumulations.each do |args|
38
38
  Paperclip::AttachmentRegistry.register(*args)
@@ -50,25 +50,64 @@ describe 'Attachment Registry' do
50
50
  context '.definitions_for' do
51
51
  it 'produces the attachment name and options' do
52
52
  expected_definitions = {
53
- avatar: { yo: 'greeting' },
54
- greeter: { ciao: 'greeting' }
53
+ avatar: { yo: "greeting" },
54
+ greeter: { ciao: "greeting" }
55
55
  }
56
56
  foo = Class.new
57
- Paperclip::AttachmentRegistry.register(foo, :avatar, { yo: 'greeting' })
58
- Paperclip::AttachmentRegistry.register(foo, :greeter, { ciao: 'greeting' })
57
+ Paperclip::AttachmentRegistry.register(
58
+ foo,
59
+ :avatar,
60
+ yo: "greeting"
61
+ )
62
+ Paperclip::AttachmentRegistry.register(
63
+ foo,
64
+ :greeter,
65
+ ciao: "greeting"
66
+ )
59
67
 
60
68
  definitions = Paperclip::AttachmentRegistry.definitions_for(foo)
61
69
 
62
70
  assert_equal expected_definitions, definitions
63
71
  end
64
72
 
65
- it "produces defintions for subclasses" do
66
- expected_definitions = { avatar: { yo: 'greeting' } }
67
- Foo = Class.new
68
- Bar = Class.new(Foo)
69
- Paperclip::AttachmentRegistry.register(Foo, :avatar, expected_definitions[:avatar])
73
+ it 'produces defintions for subclasses' do
74
+ expected_definitions = { avatar: { yo: "greeting" } }
75
+ foo = Class.new
76
+ bar = Class.new(foo)
77
+ Paperclip::AttachmentRegistry.register(
78
+ foo,
79
+ :avatar,
80
+ expected_definitions[:avatar]
81
+ )
82
+
83
+ definitions = Paperclip::AttachmentRegistry.definitions_for(bar)
84
+
85
+ assert_equal expected_definitions, definitions
86
+ end
70
87
 
71
- definitions = Paperclip::AttachmentRegistry.definitions_for(Bar)
88
+ it 'produces defintions for subclasses but deep merging them' do
89
+ foo_definitions = { avatar: { yo: "greeting" } }
90
+ bar_definitions = { avatar: { ciao: "greeting" } }
91
+ expected_definitions = {
92
+ avatar: {
93
+ yo: "greeting",
94
+ ciao: "greeting"
95
+ }
96
+ }
97
+ foo = Class.new
98
+ bar = Class.new(foo)
99
+ Paperclip::AttachmentRegistry.register(
100
+ foo,
101
+ :avatar,
102
+ foo_definitions[:avatar]
103
+ )
104
+ Paperclip::AttachmentRegistry.register(
105
+ bar,
106
+ :avatar,
107
+ bar_definitions[:avatar]
108
+ )
109
+
110
+ definitions = Paperclip::AttachmentRegistry.definitions_for(bar)
72
111
 
73
112
  assert_equal expected_definitions, definitions
74
113
  end
@@ -77,7 +116,11 @@ describe 'Attachment Registry' do
77
116
  context '.clear' do
78
117
  it 'removes all of the existing attachment definitions' do
79
118
  foo = Class.new
80
- Paperclip::AttachmentRegistry.register(foo, :greeter, { ciao: 'greeting' })
119
+ Paperclip::AttachmentRegistry.register(
120
+ foo,
121
+ :greeter,
122
+ ciao: "greeting"
123
+ )
81
124
 
82
125
  Paperclip::AttachmentRegistry.clear
83
126
 
@@ -13,7 +13,7 @@ describe Paperclip::Attachment do
13
13
  it "is present when the file is set" do
14
14
  rebuild_class
15
15
  dummy = Dummy.new
16
- dummy.avatar = File.new(fixture_file("50x50.png"), "rb")
16
+ dummy.avatar = File.new(fixture_file("50x50.png"), "rb")
17
17
  expect(dummy.avatar).to_not be_blank
18
18
  expect(dummy.avatar).to be_present
19
19
  end
@@ -34,9 +34,9 @@ describe Paperclip::Attachment do
34
34
  it "does not delete styles that don't get reprocessed" do
35
35
  file = File.new(fixture_file("50x50.png"), 'rb')
36
36
  rebuild_class styles: {
37
- small: '100x>',
38
- large: '500x>',
39
- original: '42x42#'
37
+ small: "100x>",
38
+ large: "500x>",
39
+ original: "42x42#"
40
40
  }
41
41
 
42
42
  dummy = Dummy.new
@@ -75,7 +75,11 @@ describe Paperclip::Attachment do
75
75
 
76
76
  it "handles a boolean second argument to #url" do
77
77
  mock_url_generator_builder = MockUrlGeneratorBuilder.new
78
- attachment = Paperclip::Attachment.new(:name, :instance, url_generator: mock_url_generator_builder)
78
+ attachment = Paperclip::Attachment.new(
79
+ :name,
80
+ FakeModel.new,
81
+ url_generator: mock_url_generator_builder
82
+ )
79
83
 
80
84
  attachment.url(:style_name, true)
81
85
  expect(mock_url_generator_builder.has_generated_url_with_options?(timestamp: true, escape: true)).to eq true
@@ -86,7 +90,11 @@ describe Paperclip::Attachment do
86
90
 
87
91
  it "passes the style and options through to the URL generator on #url" do
88
92
  mock_url_generator_builder = MockUrlGeneratorBuilder.new
89
- attachment = Paperclip::Attachment.new(:name, :instance, url_generator: mock_url_generator_builder)
93
+ attachment = Paperclip::Attachment.new(
94
+ :name,
95
+ FakeModel.new,
96
+ url_generator: mock_url_generator_builder
97
+ )
90
98
 
91
99
  attachment.url(:style_name, options: :values)
92
100
  expect(mock_url_generator_builder.has_generated_url_with_options?(options: :values)).to eq true
@@ -95,7 +103,7 @@ describe Paperclip::Attachment do
95
103
  it "passes default options through when #url is given one argument" do
96
104
  mock_url_generator_builder = MockUrlGeneratorBuilder.new
97
105
  attachment = Paperclip::Attachment.new(:name,
98
- :instance,
106
+ FakeModel.new,
99
107
  url_generator: mock_url_generator_builder,
100
108
  use_timestamp: true)
101
109
 
@@ -106,7 +114,7 @@ describe Paperclip::Attachment do
106
114
  it "passes default style and options through when #url is given no arguments" do
107
115
  mock_url_generator_builder = MockUrlGeneratorBuilder.new
108
116
  attachment = Paperclip::Attachment.new(:name,
109
- :instance,
117
+ FakeModel.new,
110
118
  default_style: 'default style',
111
119
  url_generator: mock_url_generator_builder,
112
120
  use_timestamp: true)
@@ -119,7 +127,7 @@ describe Paperclip::Attachment do
119
127
  it "passes the option timestamp: true if :use_timestamp is true and :timestamp is not passed" do
120
128
  mock_url_generator_builder = MockUrlGeneratorBuilder.new
121
129
  attachment = Paperclip::Attachment.new(:name,
122
- :instance,
130
+ FakeModel.new,
123
131
  url_generator: mock_url_generator_builder,
124
132
  use_timestamp: true)
125
133
 
@@ -130,7 +138,7 @@ describe Paperclip::Attachment do
130
138
  it "passes the option timestamp: false if :use_timestamp is false and :timestamp is not passed" do
131
139
  mock_url_generator_builder = MockUrlGeneratorBuilder.new
132
140
  attachment = Paperclip::Attachment.new(:name,
133
- :instance,
141
+ FakeModel.new,
134
142
  url_generator: mock_url_generator_builder,
135
143
  use_timestamp: false)
136
144
 
@@ -141,7 +149,7 @@ describe Paperclip::Attachment do
141
149
  it "does not change the :timestamp if :timestamp is passed" do
142
150
  mock_url_generator_builder = MockUrlGeneratorBuilder.new
143
151
  attachment = Paperclip::Attachment.new(:name,
144
- :instance,
152
+ FakeModel.new,
145
153
  url_generator: mock_url_generator_builder,
146
154
  use_timestamp: false)
147
155
 
@@ -152,7 +160,7 @@ describe Paperclip::Attachment do
152
160
  it "renders JSON as default style" do
153
161
  mock_url_generator_builder = MockUrlGeneratorBuilder.new
154
162
  attachment = Paperclip::Attachment.new(:name,
155
- :instance,
163
+ FakeModel.new,
156
164
  default_style: 'default style',
157
165
  url_generator: mock_url_generator_builder)
158
166
 
@@ -163,7 +171,7 @@ describe Paperclip::Attachment do
163
171
  it "passes the option escape: true if :escape_url is true and :escape is not passed" do
164
172
  mock_url_generator_builder = MockUrlGeneratorBuilder.new
165
173
  attachment = Paperclip::Attachment.new(:name,
166
- :instance,
174
+ FakeModel.new,
167
175
  url_generator: mock_url_generator_builder,
168
176
  escape_url: true)
169
177
 
@@ -174,7 +182,7 @@ describe Paperclip::Attachment do
174
182
  it "passes the option escape: false if :escape_url is false and :escape is not passed" do
175
183
  mock_url_generator_builder = MockUrlGeneratorBuilder.new
176
184
  attachment = Paperclip::Attachment.new(:name,
177
- :instance,
185
+ FakeModel.new,
178
186
  url_generator: mock_url_generator_builder,
179
187
  escape_url: false)
180
188
 
@@ -212,6 +220,7 @@ describe Paperclip::Attachment do
212
220
  dummy = Dummy.new
213
221
  dummy.id = 1234
214
222
  dummy.avatar_file_name = "fake.jpg"
223
+ dummy.stubs(:new_record?).returns(false)
215
224
  expected_string = '{"avatar":"/system/dummies/avatars/000/001/234/original/fake.jpg"}'
216
225
  if ActiveRecord::Base.include_root_in_json # This is true by default in Rails 3, and false in 4
217
226
  expected_string = %({"dummy":#{expected_string}})
@@ -251,6 +260,10 @@ describe Paperclip::Attachment do
251
260
  it "returns false when asked exists?" do
252
261
  assert !@dummy.avatar.exists?
253
262
  end
263
+
264
+ it "#url returns nil" do
265
+ assert_nil @dummy.avatar.url
266
+ end
254
267
  end
255
268
 
256
269
  context "on an Attachment" do
@@ -635,15 +648,40 @@ describe Paperclip::Attachment do
635
648
  before do
636
649
  rebuild_model processor: [:thumbnail], styles: { small: '' }, whiny_thumbnails: true
637
650
  @dummy = Dummy.new
638
- Paperclip::Thumbnail.expects(:make).raises(Paperclip::Error, "cannot be processed.")
639
651
  @file = StringIO.new("...")
640
652
  @file.stubs(:to_tempfile).returns(@file)
641
- @dummy.avatar = @file
642
653
  end
643
654
 
644
- it "correctly forwards processing error message to the instance" do
645
- @dummy.valid?
646
- assert_contains @dummy.errors.full_messages, "Avatar cannot be processed."
655
+ context "when error is meaningful for the end user" do
656
+ before do
657
+ Paperclip::Thumbnail.expects(:make).raises(
658
+ Paperclip::Errors::NotIdentifiedByImageMagickError,
659
+ "cannot be processed."
660
+ )
661
+ end
662
+
663
+ it "correctly forwards processing error message to the instance" do
664
+ @dummy.avatar = @file
665
+ @dummy.valid?
666
+ assert_contains(
667
+ @dummy.errors.full_messages,
668
+ "Avatar cannot be processed."
669
+ )
670
+ end
671
+ end
672
+
673
+ context "when error is intended for the developer" do
674
+ before do
675
+ Paperclip::Thumbnail.expects(:make).raises(
676
+ Paperclip::Errors::CommandNotFoundError
677
+ )
678
+ end
679
+
680
+ it "propagates the error" do
681
+ assert_raises(Paperclip::Errors::CommandNotFoundError) do
682
+ @dummy.avatar = @file
683
+ end
684
+ end
647
685
  end
648
686
  end
649
687
 
@@ -1,6 +1,12 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Paperclip::ContentTypeDetector do
4
+ it 'returns a meaningful content type for open xml spreadsheets' do
5
+ file = File.new(fixture_file("empty.xlsx"))
6
+ assert_equal "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
7
+ Paperclip::ContentTypeDetector.new(file.path).detect
8
+ end
9
+
4
10
  it 'gives a sensible default when the name is empty' do
5
11
  assert_equal "application/octet-stream", Paperclip::ContentTypeDetector.new("").detect
6
12
  end
@@ -13,7 +19,8 @@ describe Paperclip::ContentTypeDetector do
13
19
 
14
20
  it 'returns content type of file if it is an acceptable type' do
15
21
  MIME::Types.stubs(:type_for).returns([MIME::Type.new('application/mp4'), MIME::Type.new('video/mp4'), MIME::Type.new('audio/mp4')])
16
- Paperclip.stubs(:run).returns("video/mp4")
22
+ Paperclip::ContentTypeDetector.any_instance
23
+ .stubs(:type_from_file_contents).returns("video/mp4")
17
24
  @filename = "my_file.mp4"
18
25
  assert_equal "video/mp4", Paperclip::ContentTypeDetector.new(@filename).detect
19
26
  end
@@ -24,4 +24,3 @@ describe Paperclip::FileCommandContentTypeDetector do
24
24
  Paperclip::FileCommandContentTypeDetector.new("windows").detect
25
25
  end
26
26
  end
27
-
@@ -138,14 +138,7 @@ describe Paperclip::Interpolations do
138
138
  assert_equal "000/000/023", Paperclip::Interpolations.id_partition(attachment, :style)
139
139
  end
140
140
 
141
- it "returns the partitioned id of the attachment when the id is a short string" do
142
- attachment = mock
143
- attachment.expects(:id).returns("fnj23")
144
- attachment.expects(:instance).returns(attachment)
145
- assert_equal "000/0fn/j23", Paperclip::Interpolations.id_partition(attachment, :style)
146
- end
147
-
148
- it "returns the partitioned id of the attachment when the id is a long string" do
141
+ it "returns the partitioned id of the attachment when the id is a string" do
149
142
  attachment = mock
150
143
  attachment.expects(:id).returns("32fnj23oio2f")
151
144
  attachment.expects(:instance).returns(attachment)
@@ -211,7 +204,7 @@ describe Paperclip::Interpolations do
211
204
  attachment.stubs(:original_filename).returns("one")
212
205
  assert_equal "one", Paperclip::Interpolations.filename(attachment, :style)
213
206
  end
214
-
207
+
215
208
  it "returns the basename when the extension contains regexp special characters" do
216
209
  attachment = mock
217
210
  attachment.stubs(:styles).returns({})
@@ -9,11 +9,12 @@ describe Paperclip::AbstractAdapter do
9
9
  end
10
10
  end
11
11
 
12
- context "content type from file command" do
12
+ context "content type from file contents" do
13
13
  before do
14
14
  @adapter = TestAdapter.new
15
15
  @adapter.stubs(:path).returns("image.png")
16
16
  Paperclip.stubs(:run).returns("image/png\n")
17
+ Paperclip::ContentTypeDetector.any_instance.stubs(:type_from_mime_magic).returns("image/png")
17
18
  end
18
19
 
19
20
  it "returns the content type without newline" do
@@ -73,10 +73,13 @@ describe Paperclip::FileAdapter do
73
73
  end
74
74
  end
75
75
 
76
- context "file with content type derived from file command on *nix" do
76
+ context "file with content type derived from file contents on *nix" do
77
77
  before do
78
78
  MIME::Types.stubs(:type_for).returns([])
79
79
  Paperclip.stubs(:run).returns("application/vnd.ms-office\n")
80
+ Paperclip::ContentTypeDetector.any_instance
81
+ .stubs(:type_from_mime_magic).returns("application/vnd.ms-office")
82
+
80
83
  @subject = Paperclip.io_adapters.for(@file)
81
84
  end
82
85
 
@@ -20,6 +20,10 @@ describe Paperclip::StringioAdapter do
20
20
  assert_equal 6, @subject.size
21
21
  end
22
22
 
23
+ it "returns the length of the data" do
24
+ assert_equal 6, @subject.length
25
+ end
26
+
23
27
  it "generates an MD5 hash of the contents" do
24
28
  assert_equal Digest::MD5.hexdigest(@contents), @subject.fingerprint
25
29
  end
@@ -18,12 +18,12 @@ describe Paperclip::MediaTypeSpoofDetector do
18
18
 
19
19
  it 'does not reject a file that does not have a name' do
20
20
  file = File.open(fixture_file("empty.html"))
21
- assert ! Paperclip::MediaTypeSpoofDetector.using(file, "", "").spoofed?
21
+ assert ! Paperclip::MediaTypeSpoofDetector.using(file, "", "text/html").spoofed?
22
22
  end
23
23
 
24
24
  it 'does not reject a file that does have an extension' do
25
25
  file = File.open(fixture_file("empty.html"))
26
- assert ! Paperclip::MediaTypeSpoofDetector.using(file, "data", "").spoofed?
26
+ assert ! Paperclip::MediaTypeSpoofDetector.using(file, "data", "text/html").spoofed?
27
27
  end
28
28
 
29
29
  it 'does not reject when the supplied file is an IOAdapter' do
@@ -53,4 +53,18 @@ describe Paperclip::MediaTypeSpoofDetector do
53
53
  file = File.open(fixture_file("empty.html"))
54
54
  assert ! Paperclip::MediaTypeSpoofDetector.using(file, "empty.html", "").spoofed?
55
55
  end
56
+
57
+ it 'does allow array as :content_type_mappings' do
58
+ begin
59
+ Paperclip.options[:content_type_mappings] = {
60
+ html: ['binary', 'text/html']
61
+ }
62
+ file = File.open(fixture_file('empty.html'))
63
+ spoofed = Paperclip::MediaTypeSpoofDetector
64
+ .using(file, "empty.html", "text/html").spoofed?
65
+ assert !spoofed
66
+ ensure
67
+ Paperclip.options[:content_type_mappings] = {}
68
+ end
69
+ end
56
70
  end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe Paperclip::RailsEnvironment do
4
+
5
+ it "returns nil when Rails isn't defined" do
6
+ resetting_rails_to(nil) do
7
+ expect(Paperclip::RailsEnvironment.get).to be_nil
8
+ end
9
+ end
10
+
11
+ it "returns nil when Rails.env isn't defined" do
12
+ resetting_rails_to({}) do
13
+ expect(Paperclip::RailsEnvironment.get).to be_nil
14
+ end
15
+ end
16
+
17
+ it "returns the value of Rails.env if it is set" do
18
+ resetting_rails_to(OpenStruct.new(env: "foo")) do
19
+ expect(Paperclip::RailsEnvironment.get).to eq "foo"
20
+ end
21
+ end
22
+
23
+ def resetting_rails_to(new_value)
24
+ begin
25
+ previous_rails = Object.send(:remove_const, "Rails")
26
+ Object.const_set("Rails", new_value) unless new_value.nil?
27
+ yield
28
+ ensure
29
+ Object.send(:remove_const, "Rails") if Object.const_defined?("Rails")
30
+ Object.const_set("Rails", previous_rails)
31
+ end
32
+ end
33
+ end