kt-paperclip 6.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (191) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +17 -0
  3. data/.github/issue_template.md +3 -0
  4. data/.gitignore +19 -0
  5. data/.hound.yml +1050 -0
  6. data/.rubocop.yml +1 -0
  7. data/.travis.yml +47 -0
  8. data/Appraisals +24 -0
  9. data/CONTRIBUTING.md +86 -0
  10. data/Gemfile +18 -0
  11. data/LICENSE +24 -0
  12. data/NEWS +515 -0
  13. data/README.md +1053 -0
  14. data/RELEASING.md +17 -0
  15. data/Rakefile +52 -0
  16. data/UPGRADING +17 -0
  17. data/features/basic_integration.feature +85 -0
  18. data/features/migration.feature +29 -0
  19. data/features/rake_tasks.feature +62 -0
  20. data/features/step_definitions/attachment_steps.rb +110 -0
  21. data/features/step_definitions/html_steps.rb +15 -0
  22. data/features/step_definitions/rails_steps.rb +257 -0
  23. data/features/step_definitions/s3_steps.rb +14 -0
  24. data/features/step_definitions/web_steps.rb +106 -0
  25. data/features/support/env.rb +12 -0
  26. data/features/support/fakeweb.rb +11 -0
  27. data/features/support/file_helpers.rb +34 -0
  28. data/features/support/fixtures/boot_config.txt +15 -0
  29. data/features/support/fixtures/gemfile.txt +5 -0
  30. data/features/support/fixtures/preinitializer.txt +20 -0
  31. data/features/support/paths.rb +28 -0
  32. data/features/support/rails.rb +39 -0
  33. data/features/support/selectors.rb +19 -0
  34. data/gemfiles/4.2.gemfile +20 -0
  35. data/gemfiles/5.0.gemfile +20 -0
  36. data/gemfiles/5.1.gemfile +20 -0
  37. data/gemfiles/5.2.gemfile +20 -0
  38. data/gemfiles/6.0.gemfile +20 -0
  39. data/lib/generators/paperclip/USAGE +8 -0
  40. data/lib/generators/paperclip/paperclip_generator.rb +36 -0
  41. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
  42. data/lib/paperclip.rb +215 -0
  43. data/lib/paperclip/attachment.rb +617 -0
  44. data/lib/paperclip/attachment_registry.rb +60 -0
  45. data/lib/paperclip/callbacks.rb +42 -0
  46. data/lib/paperclip/content_type_detector.rb +80 -0
  47. data/lib/paperclip/errors.rb +34 -0
  48. data/lib/paperclip/file_command_content_type_detector.rb +28 -0
  49. data/lib/paperclip/filename_cleaner.rb +15 -0
  50. data/lib/paperclip/geometry.rb +157 -0
  51. data/lib/paperclip/geometry_detector_factory.rb +45 -0
  52. data/lib/paperclip/geometry_parser_factory.rb +31 -0
  53. data/lib/paperclip/glue.rb +17 -0
  54. data/lib/paperclip/has_attached_file.rb +116 -0
  55. data/lib/paperclip/helpers.rb +60 -0
  56. data/lib/paperclip/interpolations.rb +201 -0
  57. data/lib/paperclip/interpolations/plural_cache.rb +18 -0
  58. data/lib/paperclip/io_adapters/abstract_adapter.rb +75 -0
  59. data/lib/paperclip/io_adapters/attachment_adapter.rb +47 -0
  60. data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
  61. data/lib/paperclip/io_adapters/empty_string_adapter.rb +19 -0
  62. data/lib/paperclip/io_adapters/file_adapter.rb +26 -0
  63. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +16 -0
  64. data/lib/paperclip/io_adapters/identity_adapter.rb +17 -0
  65. data/lib/paperclip/io_adapters/nil_adapter.rb +37 -0
  66. data/lib/paperclip/io_adapters/registry.rb +36 -0
  67. data/lib/paperclip/io_adapters/stringio_adapter.rb +36 -0
  68. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +44 -0
  69. data/lib/paperclip/io_adapters/uri_adapter.rb +68 -0
  70. data/lib/paperclip/locales/en.yml +18 -0
  71. data/lib/paperclip/logger.rb +21 -0
  72. data/lib/paperclip/matchers.rb +64 -0
  73. data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
  74. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +101 -0
  75. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
  76. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +97 -0
  77. data/lib/paperclip/media_type_spoof_detector.rb +90 -0
  78. data/lib/paperclip/missing_attachment_styles.rb +84 -0
  79. data/lib/paperclip/processor.rb +56 -0
  80. data/lib/paperclip/processor_helpers.rb +52 -0
  81. data/lib/paperclip/rails_environment.rb +21 -0
  82. data/lib/paperclip/railtie.rb +31 -0
  83. data/lib/paperclip/schema.rb +81 -0
  84. data/lib/paperclip/storage.rb +3 -0
  85. data/lib/paperclip/storage/filesystem.rb +99 -0
  86. data/lib/paperclip/storage/fog.rb +252 -0
  87. data/lib/paperclip/storage/s3.rb +461 -0
  88. data/lib/paperclip/style.rb +106 -0
  89. data/lib/paperclip/tempfile.rb +42 -0
  90. data/lib/paperclip/tempfile_factory.rb +22 -0
  91. data/lib/paperclip/thumbnail.rb +131 -0
  92. data/lib/paperclip/url_generator.rb +76 -0
  93. data/lib/paperclip/validators.rb +73 -0
  94. data/lib/paperclip/validators/attachment_content_type_validator.rb +88 -0
  95. data/lib/paperclip/validators/attachment_file_name_validator.rb +75 -0
  96. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +28 -0
  97. data/lib/paperclip/validators/attachment_presence_validator.rb +28 -0
  98. data/lib/paperclip/validators/attachment_size_validator.rb +109 -0
  99. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +29 -0
  100. data/lib/paperclip/version.rb +3 -0
  101. data/lib/tasks/paperclip.rake +140 -0
  102. data/paperclip.gemspec +50 -0
  103. data/shoulda_macros/paperclip.rb +134 -0
  104. data/spec/database.yml +4 -0
  105. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  106. data/spec/paperclip/attachment_processing_spec.rb +79 -0
  107. data/spec/paperclip/attachment_registry_spec.rb +158 -0
  108. data/spec/paperclip/attachment_spec.rb +1590 -0
  109. data/spec/paperclip/content_type_detector_spec.rb +47 -0
  110. data/spec/paperclip/file_command_content_type_detector_spec.rb +40 -0
  111. data/spec/paperclip/filename_cleaner_spec.rb +13 -0
  112. data/spec/paperclip/geometry_detector_spec.rb +38 -0
  113. data/spec/paperclip/geometry_parser_spec.rb +73 -0
  114. data/spec/paperclip/geometry_spec.rb +255 -0
  115. data/spec/paperclip/glue_spec.rb +42 -0
  116. data/spec/paperclip/has_attached_file_spec.rb +78 -0
  117. data/spec/paperclip/integration_spec.rb +702 -0
  118. data/spec/paperclip/interpolations_spec.rb +270 -0
  119. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +160 -0
  120. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +140 -0
  121. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +88 -0
  122. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  123. data/spec/paperclip/io_adapters/file_adapter_spec.rb +131 -0
  124. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +137 -0
  125. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
  126. data/spec/paperclip/io_adapters/nil_adapter_spec.rb +25 -0
  127. data/spec/paperclip/io_adapters/registry_spec.rb +35 -0
  128. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +64 -0
  129. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +146 -0
  130. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +221 -0
  131. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
  132. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +108 -0
  133. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
  134. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
  135. data/spec/paperclip/media_type_spoof_detector_spec.rb +120 -0
  136. data/spec/paperclip/meta_class_spec.rb +30 -0
  137. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +88 -0
  138. data/spec/paperclip/paperclip_spec.rb +196 -0
  139. data/spec/paperclip/plural_cache_spec.rb +37 -0
  140. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  141. data/spec/paperclip/processor_spec.rb +26 -0
  142. data/spec/paperclip/rails_environment_spec.rb +30 -0
  143. data/spec/paperclip/rake_spec.rb +103 -0
  144. data/spec/paperclip/schema_spec.rb +252 -0
  145. data/spec/paperclip/storage/filesystem_spec.rb +79 -0
  146. data/spec/paperclip/storage/fog_spec.rb +560 -0
  147. data/spec/paperclip/storage/s3_live_spec.rb +188 -0
  148. data/spec/paperclip/storage/s3_spec.rb +1695 -0
  149. data/spec/paperclip/style_spec.rb +251 -0
  150. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  151. data/spec/paperclip/tempfile_spec.rb +35 -0
  152. data/spec/paperclip/thumbnail_spec.rb +504 -0
  153. data/spec/paperclip/url_generator_spec.rb +221 -0
  154. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
  155. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +159 -0
  156. data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
  157. data/spec/paperclip/validators/attachment_size_validator_spec.rb +235 -0
  158. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +48 -0
  159. data/spec/paperclip/validators_spec.rb +164 -0
  160. data/spec/spec_helper.rb +45 -0
  161. data/spec/support/assertions.rb +84 -0
  162. data/spec/support/fake_model.rb +24 -0
  163. data/spec/support/fake_rails.rb +12 -0
  164. data/spec/support/fixtures/12k.png +0 -0
  165. data/spec/support/fixtures/50x50.png +0 -0
  166. data/spec/support/fixtures/5k.png +0 -0
  167. data/spec/support/fixtures/animated +0 -0
  168. data/spec/support/fixtures/animated.gif +0 -0
  169. data/spec/support/fixtures/animated.unknown +0 -0
  170. data/spec/support/fixtures/bad.png +1 -0
  171. data/spec/support/fixtures/empty.html +1 -0
  172. data/spec/support/fixtures/empty.xlsx +0 -0
  173. data/spec/support/fixtures/fog.yml +8 -0
  174. data/spec/support/fixtures/rotated.jpg +0 -0
  175. data/spec/support/fixtures/s3.yml +8 -0
  176. data/spec/support/fixtures/spaced file.jpg +0 -0
  177. data/spec/support/fixtures/spaced file.png +0 -0
  178. data/spec/support/fixtures/text.txt +1 -0
  179. data/spec/support/fixtures/twopage.pdf +0 -0
  180. data/spec/support/fixtures/uppercase.PNG +0 -0
  181. data/spec/support/matchers/accept.rb +5 -0
  182. data/spec/support/matchers/exist.rb +5 -0
  183. data/spec/support/matchers/have_column.rb +23 -0
  184. data/spec/support/mock_attachment.rb +24 -0
  185. data/spec/support/mock_interpolator.rb +24 -0
  186. data/spec/support/mock_url_generator_builder.rb +26 -0
  187. data/spec/support/model_reconstruction.rb +72 -0
  188. data/spec/support/reporting.rb +11 -0
  189. data/spec/support/test_data.rb +13 -0
  190. data/spec/support/version_helper.rb +9 -0
  191. metadata +586 -0
@@ -0,0 +1,50 @@
1
+ $LOAD_PATH.push File.expand_path("lib", __dir__)
2
+ require "paperclip/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "kt-paperclip"
6
+ s.version = Paperclip::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.author = "Surendra Singhi"
9
+ s.email = ["ssinghi@kreeti.com"]
10
+ s.homepage = "https://github.com/kreeti/paperclip"
11
+ s.summary = "File attachments as attributes for ActiveRecord"
12
+ s.description = "Easy upload management for ActiveRecord"
13
+ s.license = "MIT"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.post_install_message = File.read("UPGRADING") if File.exist?("UPGRADING")
21
+
22
+ s.requirements << "ImageMagick"
23
+ s.required_ruby_version = ">= 2.1.0"
24
+
25
+ s.add_dependency("activemodel", ">= 4.2.0")
26
+ s.add_dependency("activesupport", ">= 4.2.0")
27
+ s.add_dependency("mime-types")
28
+ s.add_dependency("mimemagic", "~> 0.3.0")
29
+ s.add_dependency("terrapin", "~> 0.6.0")
30
+
31
+ s.add_development_dependency("activerecord", ">= 4.2.0")
32
+ s.add_development_dependency("appraisal")
33
+ s.add_development_dependency("aruba", "~> 0.9.0")
34
+ s.add_development_dependency("aws-sdk-s3")
35
+ s.add_development_dependency("bundler")
36
+ s.add_development_dependency("capybara")
37
+ s.add_development_dependency("cucumber-expressions", "4.0.3") # TODO: investigate failures on 4.0.4
38
+ s.add_development_dependency("cucumber-rails")
39
+ s.add_development_dependency("fakeweb")
40
+ s.add_development_dependency("fog-aws")
41
+ s.add_development_dependency("fog-local")
42
+ s.add_development_dependency("generator_spec")
43
+ s.add_development_dependency("launchy")
44
+ s.add_development_dependency("nokogiri")
45
+ s.add_development_dependency("railties")
46
+ s.add_development_dependency("rake")
47
+ s.add_development_dependency("rspec", "~> 3.0")
48
+ s.add_development_dependency("shoulda")
49
+ s.add_development_dependency("timecop")
50
+ end
@@ -0,0 +1,134 @@
1
+ require "paperclip/matchers"
2
+
3
+ module Paperclip
4
+ # =Paperclip Shoulda Macros
5
+ #
6
+ # These macros are intended for use with shoulda, and will be included into
7
+ # your tests automatically. All of the macros use the standard shoulda
8
+ # assumption that the name of the test is based on the name of the model
9
+ # you're testing (that is, UserTest is the test for the User model), and
10
+ # will load that class for testing purposes.
11
+ module Shoulda
12
+ include Matchers
13
+ # This will test whether you have defined your attachment correctly by
14
+ # checking for all the required fields exist after the definition of the
15
+ # attachment.
16
+ def should_have_attached_file(name)
17
+ klass = self.name.gsub(/Test$/, "").constantize
18
+ matcher = have_attached_file name
19
+ should matcher.description do
20
+ assert_accepts(matcher, klass)
21
+ end
22
+ end
23
+
24
+ # Tests for validations on the presence of the attachment.
25
+ def should_validate_attachment_presence(name)
26
+ klass = self.name.gsub(/Test$/, "").constantize
27
+ matcher = validate_attachment_presence name
28
+ should matcher.description do
29
+ assert_accepts(matcher, klass)
30
+ end
31
+ end
32
+
33
+ # Tests that you have content_type validations specified. There are two
34
+ # options, :valid and :invalid. Both accept an array of strings. The
35
+ # strings should be a list of content types which will pass and fail
36
+ # validation, respectively.
37
+ def should_validate_attachment_content_type(name, options = {})
38
+ klass = self.name.gsub(/Test$/, "").constantize
39
+ valid = [options[:valid]].flatten
40
+ invalid = [options[:invalid]].flatten
41
+ matcher = validate_attachment_content_type(name).allowing(valid).rejecting(invalid)
42
+ should matcher.description do
43
+ assert_accepts(matcher, klass)
44
+ end
45
+ end
46
+
47
+ # Tests to ensure that you have file size validations turned on. You
48
+ # can pass the same options to this that you can to
49
+ # validate_attachment_file_size - :less_than, :greater_than, and :in.
50
+ # :less_than checks that a file is less than a certain size, :greater_than
51
+ # checks that a file is more than a certain size, and :in takes a Range or
52
+ # Array which specifies the lower and upper limits of the file size.
53
+ def should_validate_attachment_size(name, options = {})
54
+ klass = self.name.gsub(/Test$/, "").constantize
55
+ min = options[:greater_than] || (options[:in] && options[:in].first) || 0
56
+ max = options[:less_than] || (options[:in] && options[:in].last) || (1.0 / 0)
57
+ range = (min..max)
58
+ matcher = validate_attachment_size(name).in(range)
59
+ should matcher.description do
60
+ assert_accepts(matcher, klass)
61
+ end
62
+ end
63
+
64
+ # Stubs the HTTP PUT for an attachment using S3 storage.
65
+ #
66
+ # @example
67
+ # stub_paperclip_s3('user', 'avatar', 'png')
68
+ def stub_paperclip_s3(model, attachment, _extension)
69
+ definition = model.gsub(" ", "_").classify.constantize.
70
+ attachment_definitions[attachment.to_sym]
71
+
72
+ path = "http://s3.amazonaws.com/:id/#{definition[:path]}"
73
+ path.gsub!(/:([^\/\.]+)/) do |_match|
74
+ "([^\/\.]+)"
75
+ end
76
+
77
+ begin
78
+ FakeWeb.register_uri(:put, Regexp.new(path), body: "OK")
79
+ rescue NameError
80
+ raise NameError, "the stub_paperclip_s3 shoulda macro requires the fakeweb gem."
81
+ end
82
+ end
83
+
84
+ # Stub S3 and return a file for attachment. Best with Factory Girl.
85
+ # Uses a strict directory convention:
86
+ #
87
+ # features/support/paperclip
88
+ #
89
+ # This method is used by the Paperclip-provided Cucumber step:
90
+ #
91
+ # When I attach a "demo_tape" "mp3" file to a "band" on S3
92
+ #
93
+ # @example
94
+ # Factory.define :band_with_demo_tape, :parent => :band do |band|
95
+ # band.demo_tape { band.paperclip_fixture("band", "demo_tape", "png") }
96
+ # end
97
+ def paperclip_fixture(model, attachment, extension)
98
+ stub_paperclip_s3(model, attachment, extension)
99
+ base_path = File.join(File.dirname(__FILE__), "..", "..",
100
+ "features", "support", "paperclip")
101
+ File.new(File.join(base_path, model, "#{attachment}.#{extension}"))
102
+ end
103
+ end
104
+ end
105
+
106
+ if defined?(ActionDispatch::Integration::Session)
107
+ class ActionDispatch::IntegrationTest::Session #:nodoc:
108
+ include Paperclip::Shoulda
109
+ end
110
+ elsif defined?(ActionController::Integration::Session)
111
+ class ActionController::Integration::Session #:nodoc:
112
+ include Paperclip::Shoulda
113
+ end
114
+ end
115
+
116
+ if defined?(FactoryGirl::Factory)
117
+ class FactoryGirl::Factory
118
+ include Paperclip::Shoulda #:nodoc:
119
+ end
120
+ else
121
+ class Factory
122
+ include Paperclip::Shoulda #:nodoc:
123
+ end
124
+ end
125
+
126
+ if defined?(Minitest)
127
+ class Minitest::Unit::TestCase #:nodoc:
128
+ extend Paperclip::Shoulda
129
+ end
130
+ elsif defined?(Test)
131
+ class Test::Unit::TestCase #:nodoc:
132
+ extend Paperclip::Shoulda
133
+ end
134
+ end
@@ -0,0 +1,4 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: ":memory:"
4
+
@@ -0,0 +1,13 @@
1
+ require "spec_helper"
2
+
3
+ describe "Attachment Definitions" do
4
+ it "returns all of the attachments on the class" do
5
+ reset_class "Dummy"
6
+ Dummy.has_attached_file :avatar, path: "abc"
7
+ Dummy.has_attached_file :other_attachment, url: "123"
8
+ Dummy.do_not_validate_attachment_file_type :avatar
9
+ expected = { avatar: { path: "abc" }, other_attachment: { url: "123" } }
10
+
11
+ expect(Dummy.attachment_definitions).to eq expected
12
+ end
13
+ end
@@ -0,0 +1,79 @@
1
+ require "spec_helper"
2
+
3
+ describe "Attachment Processing" do
4
+ before { rebuild_class }
5
+
6
+ context "using validates_attachment_content_type" do
7
+ it "processes attachments given a valid assignment" do
8
+ file = File.new(fixture_file("5k.png"))
9
+ Dummy.validates_attachment_content_type :avatar, content_type: "image/png"
10
+ instance = Dummy.new
11
+ attachment = instance.avatar
12
+ expect(attachment).to receive(:post_process_styles)
13
+
14
+ attachment.assign(file)
15
+ end
16
+
17
+ it "does not process attachments given an invalid assignment with :not" do
18
+ file = File.new(fixture_file("5k.png"))
19
+ Dummy.validates_attachment_content_type :avatar, not: "image/png"
20
+ instance = Dummy.new
21
+ attachment = instance.avatar
22
+ expect(attachment).not_to receive(:post_process_styles)
23
+
24
+ attachment.assign(file)
25
+ end
26
+
27
+ it "does not process attachments given an invalid assignment with :content_type" do
28
+ file = File.new(fixture_file("5k.png"))
29
+ Dummy.validates_attachment_content_type :avatar, content_type: "image/tiff"
30
+ instance = Dummy.new
31
+ attachment = instance.avatar
32
+ expect(attachment).not_to receive(:post_process_styles)
33
+
34
+ attachment.assign(file)
35
+ end
36
+
37
+ it "allows what would be an invalid assignment when validation :if clause returns false" do
38
+ invalid_assignment = File.new(fixture_file("5k.png"))
39
+ Dummy.validates_attachment_content_type :avatar, content_type: "image/tiff", if: lambda { false }
40
+ instance = Dummy.new
41
+ attachment = instance.avatar
42
+ expect(attachment).to receive(:post_process_styles)
43
+
44
+ attachment.assign(invalid_assignment)
45
+ end
46
+ end
47
+
48
+ context "using validates_attachment" do
49
+ it "processes attachments given a valid assignment" do
50
+ file = File.new(fixture_file("5k.png"))
51
+ Dummy.validates_attachment :avatar, content_type: { content_type: "image/png" }
52
+ instance = Dummy.new
53
+ attachment = instance.avatar
54
+ expect(attachment).to receive(:post_process_styles)
55
+
56
+ attachment.assign(file)
57
+ end
58
+
59
+ it "does not process attachments given an invalid assignment with :not" do
60
+ file = File.new(fixture_file("5k.png"))
61
+ Dummy.validates_attachment :avatar, content_type: { not: "image/png" }
62
+ instance = Dummy.new
63
+ attachment = instance.avatar
64
+ expect(attachment).not_to receive(:post_process_styles)
65
+
66
+ attachment.assign(file)
67
+ end
68
+
69
+ it "does not process attachments given an invalid assignment with :content_type" do
70
+ file = File.new(fixture_file("5k.png"))
71
+ Dummy.validates_attachment :avatar, content_type: { content_type: "image/tiff" }
72
+ instance = Dummy.new
73
+ attachment = instance.avatar
74
+ expect(attachment).not_to receive(:post_process_styles)
75
+
76
+ attachment.assign(file)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,158 @@
1
+ require "spec_helper"
2
+
3
+ describe "Attachment Registry" do
4
+ before do
5
+ Paperclip::AttachmentRegistry.clear
6
+ end
7
+
8
+ context ".names_for" do
9
+ it "includes attachment names for the given class" do
10
+ foo = Class.new
11
+ Paperclip::AttachmentRegistry.register(foo, :avatar, {})
12
+
13
+ assert_equal [:avatar], Paperclip::AttachmentRegistry.names_for(foo)
14
+ end
15
+
16
+ it "does not include attachment names for other classes" do
17
+ foo = Class.new
18
+ bar = Class.new
19
+ Paperclip::AttachmentRegistry.register(foo, :avatar, {})
20
+ Paperclip::AttachmentRegistry.register(bar, :lover, {})
21
+
22
+ assert_equal [:lover], Paperclip::AttachmentRegistry.names_for(bar)
23
+ end
24
+
25
+ it "produces the empty array for a missing key" do
26
+ assert_empty Paperclip::AttachmentRegistry.names_for(Class.new)
27
+ end
28
+ end
29
+
30
+ context ".each_definition" do
31
+ it "calls the block with the class, attachment name, and options" do
32
+ foo = Class.new
33
+ expected_accumulations = [
34
+ [foo, :avatar, { yo: "greeting" }],
35
+ [foo, :greeter, { ciao: "greeting" }]
36
+ ]
37
+ expected_accumulations.each do |args|
38
+ Paperclip::AttachmentRegistry.register(*args)
39
+ end
40
+ accumulations = []
41
+
42
+ Paperclip::AttachmentRegistry.each_definition do |*args|
43
+ accumulations << args
44
+ end
45
+
46
+ assert_equal expected_accumulations, accumulations
47
+ end
48
+ end
49
+
50
+ context ".definitions_for" do
51
+ it "produces the attachment name and options" do
52
+ expected_definitions = {
53
+ avatar: { yo: "greeting" },
54
+ greeter: { ciao: "greeting" }
55
+ }
56
+ foo = Class.new
57
+ Paperclip::AttachmentRegistry.register(
58
+ foo,
59
+ :avatar,
60
+ yo: "greeting"
61
+ )
62
+ Paperclip::AttachmentRegistry.register(
63
+ foo,
64
+ :greeter,
65
+ ciao: "greeting"
66
+ )
67
+
68
+ definitions = Paperclip::AttachmentRegistry.definitions_for(foo)
69
+
70
+ assert_equal expected_definitions, definitions
71
+ end
72
+
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
87
+
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)
111
+
112
+ assert_equal expected_definitions, definitions
113
+ end
114
+
115
+ it "allows subclasses to override attachment defitions" do
116
+ foo_definitions = { avatar: { yo: "greeting" } }
117
+ bar_definitions = { avatar: { yo: "hello" } }
118
+
119
+ expected_definitions = {
120
+ avatar: {
121
+ yo: "hello"
122
+ }
123
+ }
124
+
125
+ foo = Class.new
126
+ bar = Class.new(foo)
127
+ Paperclip::AttachmentRegistry.register(
128
+ foo,
129
+ :avatar,
130
+ foo_definitions[:avatar]
131
+ )
132
+ Paperclip::AttachmentRegistry.register(
133
+ bar,
134
+ :avatar,
135
+ bar_definitions[:avatar]
136
+ )
137
+
138
+ definitions = Paperclip::AttachmentRegistry.definitions_for(bar)
139
+
140
+ assert_equal expected_definitions, definitions
141
+ end
142
+ end
143
+
144
+ context ".clear" do
145
+ it "removes all of the existing attachment definitions" do
146
+ foo = Class.new
147
+ Paperclip::AttachmentRegistry.register(
148
+ foo,
149
+ :greeter,
150
+ ciao: "greeting"
151
+ )
152
+
153
+ Paperclip::AttachmentRegistry.clear
154
+
155
+ assert_empty Paperclip::AttachmentRegistry.names_for(foo)
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,1590 @@
1
+ require "spec_helper"
2
+
3
+ describe Paperclip::Attachment do
4
+ it "is not present when file not set" do
5
+ rebuild_class
6
+ dummy = Dummy.new
7
+ expect(dummy.avatar).to be_blank
8
+ expect(dummy.avatar).to_not be_present
9
+ end
10
+
11
+ it "is present when the file is set" do
12
+ rebuild_class
13
+ dummy = Dummy.new
14
+ dummy.avatar = File.new(fixture_file("50x50.png"), "rb")
15
+ expect(dummy.avatar).to_not be_blank
16
+ expect(dummy.avatar).to be_present
17
+ end
18
+
19
+ it "processes :original style first" do
20
+ file = File.new(fixture_file("50x50.png"), "rb")
21
+ rebuild_class styles: { small: "100x>", original: "42x42#" }
22
+ dummy = Dummy.new
23
+ dummy.avatar = file
24
+ dummy.save
25
+
26
+ # :small avatar should be 42px wide (processed original), not 50px (preprocessed original)
27
+ expect(`identify -format "%w" "#{dummy.avatar.path(:small)}"`.strip).to eq "42"
28
+
29
+ file.close
30
+ end
31
+
32
+ it "does not delete styles that don't get reprocessed" do
33
+ file = File.new(fixture_file("50x50.png"), "rb")
34
+ rebuild_class styles: {
35
+ small: "100x>",
36
+ large: "500x>",
37
+ original: "42x42#"
38
+ }
39
+
40
+ dummy = Dummy.new
41
+ dummy.avatar = file
42
+ dummy.save
43
+
44
+ expect(dummy.avatar.path(:small)).to exist
45
+ expect(dummy.avatar.path(:large)).to exist
46
+ expect(dummy.avatar.path(:original)).to exist
47
+
48
+ dummy.avatar.reprocess!(:small)
49
+
50
+ expect(dummy.avatar.path(:small)).to exist
51
+ expect(dummy.avatar.path(:large)).to exist
52
+ expect(dummy.avatar.path(:original)).to exist
53
+ end
54
+
55
+ it "reprocess works with virtual content_type attribute" do
56
+ rebuild_class styles: { small: "100x>" }
57
+ modify_table { |t| t.remove :avatar_content_type }
58
+ Dummy.send :attr_accessor, :avatar_content_type
59
+ Dummy.validates_attachment_content_type(
60
+ :avatar,
61
+ content_type: %w(image/jpeg image/png)
62
+ )
63
+ Dummy.create!(avatar: File.new(fixture_file("50x50.png"), "rb"))
64
+
65
+ dummy = Dummy.first
66
+ dummy.avatar.reprocess!(:small)
67
+
68
+ expect(dummy.avatar.path(:small)).to exist
69
+ end
70
+
71
+ context "having a not empty hash as a default option" do
72
+ before do
73
+ @old_default_options = Paperclip::Attachment.default_options.dup
74
+ @new_default_options = { convert_options: { all: "-background white" } }
75
+ Paperclip::Attachment.default_options.merge!(@new_default_options)
76
+ end
77
+
78
+ after do
79
+ Paperclip::Attachment.default_options.merge!(@old_default_options)
80
+ end
81
+
82
+ it "deep merges when it is overridden" do
83
+ new_options = { convert_options: { thumb: "-thumbnailize" } }
84
+ attachment = Paperclip::Attachment.new(:name, :instance, new_options)
85
+
86
+ expect(Paperclip::Attachment.default_options.deep_merge(new_options)).to eq attachment.instance_variable_get("@options")
87
+ end
88
+ end
89
+
90
+ it "handles a boolean second argument to #url" do
91
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
92
+ attachment = Paperclip::Attachment.new(
93
+ :name,
94
+ FakeModel.new,
95
+ url_generator: mock_url_generator_builder
96
+ )
97
+
98
+ attachment.url(:style_name, true)
99
+ expect(mock_url_generator_builder.has_generated_url_with_options?(timestamp: true, escape: true)).to eq true
100
+
101
+ attachment.url(:style_name, false)
102
+ expect(mock_url_generator_builder.has_generated_url_with_options?(timestamp: false, escape: true)).to eq true
103
+ end
104
+
105
+ it "passes the style and options through to the URL generator on #url" do
106
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
107
+ attachment = Paperclip::Attachment.new(
108
+ :name,
109
+ FakeModel.new,
110
+ url_generator: mock_url_generator_builder
111
+ )
112
+
113
+ attachment.url(:style_name, options: :values)
114
+ expect(mock_url_generator_builder.has_generated_url_with_options?(options: :values)).to eq true
115
+ end
116
+
117
+ it "passes default options through when #url is given one argument" do
118
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
119
+ attachment = Paperclip::Attachment.new(:name,
120
+ FakeModel.new,
121
+ url_generator: mock_url_generator_builder,
122
+ use_timestamp: true)
123
+
124
+ attachment.url(:style_name)
125
+ assert mock_url_generator_builder.has_generated_url_with_options?(escape: true, timestamp: true)
126
+ end
127
+
128
+ it "passes default style and options through when #url is given no arguments" do
129
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
130
+ attachment = Paperclip::Attachment.new(:name,
131
+ FakeModel.new,
132
+ default_style: "default style",
133
+ url_generator: mock_url_generator_builder,
134
+ use_timestamp: true)
135
+
136
+ attachment.url
137
+ assert mock_url_generator_builder.has_generated_url_with_options?(escape: true, timestamp: true)
138
+ assert mock_url_generator_builder.has_generated_url_with_style_name?("default style")
139
+ end
140
+
141
+ it "passes the option timestamp: true if :use_timestamp is true and :timestamp is not passed" do
142
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
143
+ attachment = Paperclip::Attachment.new(:name,
144
+ FakeModel.new,
145
+ url_generator: mock_url_generator_builder,
146
+ use_timestamp: true)
147
+
148
+ attachment.url(:style_name)
149
+ assert mock_url_generator_builder.has_generated_url_with_options?(escape: true, timestamp: true)
150
+ end
151
+
152
+ it "passes the option timestamp: false if :use_timestamp is false and :timestamp is not passed" do
153
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
154
+ attachment = Paperclip::Attachment.new(:name,
155
+ FakeModel.new,
156
+ url_generator: mock_url_generator_builder,
157
+ use_timestamp: false)
158
+
159
+ attachment.url(:style_name)
160
+ assert mock_url_generator_builder.has_generated_url_with_options?(escape: true, timestamp: false)
161
+ end
162
+
163
+ it "does not change the :timestamp if :timestamp is passed" do
164
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
165
+ attachment = Paperclip::Attachment.new(:name,
166
+ FakeModel.new,
167
+ url_generator: mock_url_generator_builder,
168
+ use_timestamp: false)
169
+
170
+ attachment.url(:style_name, timestamp: true)
171
+ assert mock_url_generator_builder.has_generated_url_with_options?(escape: true, timestamp: true)
172
+ end
173
+
174
+ it "renders JSON as default style" do
175
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
176
+ attachment = Paperclip::Attachment.new(:name,
177
+ FakeModel.new,
178
+ default_style: "default style",
179
+ url_generator: mock_url_generator_builder)
180
+
181
+ attachment.as_json
182
+ assert mock_url_generator_builder.has_generated_url_with_style_name?("default style")
183
+ end
184
+
185
+ it "passes the option escape: true if :escape_url is true and :escape is not passed" do
186
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
187
+ attachment = Paperclip::Attachment.new(:name,
188
+ FakeModel.new,
189
+ url_generator: mock_url_generator_builder,
190
+ escape_url: true)
191
+
192
+ attachment.url(:style_name)
193
+ assert mock_url_generator_builder.has_generated_url_with_options?(escape: true)
194
+ end
195
+
196
+ it "passes the option escape: false if :escape_url is false and :escape is not passed" do
197
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
198
+ attachment = Paperclip::Attachment.new(:name,
199
+ FakeModel.new,
200
+ url_generator: mock_url_generator_builder,
201
+ escape_url: false)
202
+
203
+ attachment.url(:style_name)
204
+ assert mock_url_generator_builder.has_generated_url_with_options?(escape: false)
205
+ end
206
+
207
+ it "returns the path based on the url by default" do
208
+ @attachment = attachment url: "/:class/:id/:basename"
209
+ @model = @attachment.instance
210
+ @model.id = 1234
211
+ @model.avatar_file_name = "fake.jpg"
212
+ assert_equal "#{Rails.root}/public/fake_models/1234/fake", @attachment.path
213
+ end
214
+
215
+ it "defaults to a path that scales" do
216
+ avatar_attachment = attachment
217
+ model = avatar_attachment.instance
218
+ model.id = 1234
219
+ model.avatar_file_name = "fake.jpg"
220
+ expected_path = "#{Rails.root}/public/system/fake_models/avatars/000/001/234/original/fake.jpg"
221
+ assert_equal expected_path, avatar_attachment.path
222
+ end
223
+
224
+ it "renders JSON as the URL to the attachment" do
225
+ avatar_attachment = attachment
226
+ model = avatar_attachment.instance
227
+ model.id = 1234
228
+ model.avatar_file_name = "fake.jpg"
229
+ assert_equal attachment.url, attachment.as_json
230
+ end
231
+
232
+ it "renders JSON from the model when requested by :methods" do
233
+ rebuild_model
234
+ dummy = Dummy.new
235
+ dummy.id = 1234
236
+ dummy.avatar_file_name = "fake.jpg"
237
+ allow(dummy).to receive(:new_record?).and_return(false)
238
+ expected_string = '{"avatar":"/system/dummies/avatars/000/001/234/original/fake.jpg"}'
239
+ # active_model pre-3.2 checks only by calling any? on it, thus it doesn't work if it is empty
240
+ assert_equal expected_string, dummy.to_json(only: [:dummy_key_for_old_active_model], methods: [:avatar])
241
+ end
242
+
243
+ context "Attachment default_options" do
244
+ before do
245
+ rebuild_model
246
+ @old_default_options = Paperclip::Attachment.default_options.dup
247
+ @new_default_options = @old_default_options.merge(
248
+ path: "argle/bargle",
249
+ url: "fooferon",
250
+ default_url: "not here.png"
251
+ )
252
+ end
253
+
254
+ after do
255
+ Paperclip::Attachment.default_options.merge! @old_default_options
256
+ end
257
+
258
+ it "is overrideable" do
259
+ Paperclip::Attachment.default_options.merge!(@new_default_options)
260
+ @new_default_options.keys.each do |key|
261
+ assert_equal @new_default_options[key],
262
+ Paperclip::Attachment.default_options[key]
263
+ end
264
+ end
265
+
266
+ context "without an Attachment" do
267
+ before do
268
+ rebuild_model default_url: "default.url"
269
+ @dummy = Dummy.new
270
+ end
271
+
272
+ it "returns false when asked exists?" do
273
+ assert !@dummy.avatar.exists?
274
+ end
275
+
276
+ it "#url returns the default_url" do
277
+ expect(@dummy.avatar.url).to eq "default.url"
278
+ end
279
+ end
280
+
281
+ context "on an Attachment" do
282
+ before do
283
+ @dummy = Dummy.new
284
+ @attachment = @dummy.avatar
285
+ end
286
+
287
+ Paperclip::Attachment.default_options.keys.each do |key|
288
+ it "is the default_options for #{key}" do
289
+ assert_equal @old_default_options[key],
290
+ @attachment.instance_variable_get("@options")[key],
291
+ key.to_s
292
+ end
293
+ end
294
+
295
+ context "when redefined" do
296
+ before do
297
+ Paperclip::Attachment.default_options.merge!(@new_default_options)
298
+ @dummy = Dummy.new
299
+ @attachment = @dummy.avatar
300
+ end
301
+
302
+ Paperclip::Attachment.default_options.keys.each do |key|
303
+ it "is the new default_options for #{key}" do
304
+ assert_equal @new_default_options[key],
305
+ @attachment.instance_variable_get("@options")[key],
306
+ key.to_s
307
+ end
308
+ end
309
+ end
310
+ end
311
+ end
312
+
313
+ context "An attachment with similarly named interpolations" do
314
+ before do
315
+ rebuild_model path: ":id.omg/:id-bbq/:idwhat/:id_partition.wtf"
316
+ @dummy = Dummy.new
317
+
318
+ allow(@dummy).to receive(:id).and_return(1024)
319
+ @file = File.new(fixture_file("5k.png"), "rb")
320
+ @dummy.avatar = @file
321
+ end
322
+
323
+ after { @file.close }
324
+
325
+ it "makes sure that they are interpolated correctly" do
326
+ assert_equal "1024.omg/1024-bbq/1024what/000/001/024.wtf", @dummy.avatar.path
327
+ end
328
+ end
329
+
330
+ context "An attachment with :timestamp interpolations" do
331
+ before do
332
+ @file = StringIO.new("...")
333
+ @zone = "UTC"
334
+ allow(Time).to receive(:zone).and_return(@zone)
335
+ @zone_default = "Eastern Time (US & Canada)"
336
+ allow(Time).to receive(:zone_default).and_return(@zone_default)
337
+ end
338
+
339
+ context "using default time zone" do
340
+ before do
341
+ rebuild_model path: ":timestamp", use_default_time_zone: true
342
+ @dummy = Dummy.new
343
+ @dummy.avatar = @file
344
+ end
345
+
346
+ it "returns a time in the default zone" do
347
+ assert_equal @dummy.avatar_updated_at.in_time_zone(@zone_default).to_s, @dummy.avatar.path
348
+ end
349
+ end
350
+
351
+ context "using per-thread time zone" do
352
+ before do
353
+ rebuild_model path: ":timestamp", use_default_time_zone: false
354
+ @dummy = Dummy.new
355
+ @dummy.avatar = @file
356
+ end
357
+
358
+ it "returns a time in the per-thread zone" do
359
+ assert_equal @dummy.avatar_updated_at.in_time_zone(@zone).to_s, @dummy.avatar.path
360
+ end
361
+ end
362
+ end
363
+
364
+ context "An attachment with :hash interpolations" do
365
+ before do
366
+ @file = File.open(fixture_file("5k.png"))
367
+ end
368
+
369
+ after do
370
+ @file.close
371
+ end
372
+
373
+ it "raises if no secret is provided" do
374
+ rebuild_model path: ":hash"
375
+ @attachment = Dummy.new.avatar
376
+ @attachment.assign @file
377
+
378
+ assert_raises ArgumentError do
379
+ @attachment.path
380
+ end
381
+ end
382
+
383
+ context "when secret is set" do
384
+ before do
385
+ rebuild_model path: ":hash",
386
+ hash_secret: "w00t",
387
+ hash_data: ":class/:attachment/:style/:filename"
388
+ @attachment = Dummy.new.avatar
389
+ @attachment.assign @file
390
+ end
391
+
392
+ it "results in the correct interpolation" do
393
+ assert_equal "dummies/avatars/original/5k.png",
394
+ @attachment.send(:interpolate, @attachment.options[:hash_data])
395
+ assert_equal "dummies/avatars/thumb/5k.png",
396
+ @attachment.send(:interpolate, @attachment.options[:hash_data], :thumb)
397
+ end
398
+
399
+ it "results in a correct hash" do
400
+ assert_equal "0a59e9142bba11576de1d353d8747b1acad5ad34", @attachment.path
401
+ assert_equal "b39a062c1e62e85a6c785ed00cf3bebf5f850e2b", @attachment.path(:thumb)
402
+ end
403
+ end
404
+ end
405
+
406
+ context "An attachment with a :rails_env interpolation" do
407
+ before do
408
+ @rails_env = "blah"
409
+ @id = 1024
410
+ rebuild_model path: ":rails_env/:id.png"
411
+ @dummy = Dummy.new
412
+ allow(@dummy).to receive(:id).and_return(@id)
413
+ @file = StringIO.new(".")
414
+ @dummy.avatar = @file
415
+ allow(Rails).to receive(:env).and_return(@rails_env)
416
+ end
417
+
418
+ it "returns the proper path" do
419
+ assert_equal "#{@rails_env}/#{@id}.png", @dummy.avatar.path
420
+ end
421
+ end
422
+
423
+ context "An attachment with a default style and an extension interpolation" do
424
+ before do
425
+ rebuild_model path: ":basename.:extension",
426
+ styles: { default: ["100x100", :jpg] },
427
+ default_style: :default
428
+ @attachment = Dummy.new.avatar
429
+ @file = File.open(fixture_file("5k.png"))
430
+ allow(@file).to receive(:original_filename).and_return("file.png")
431
+ end
432
+ it "returns the right extension for the path" do
433
+ @attachment.assign(@file)
434
+ assert_equal "file.jpg", @attachment.path
435
+ end
436
+ end
437
+
438
+ context "An attachment with :convert_options" do
439
+ before do
440
+ rebuild_model styles: {
441
+ thumb: "100x100",
442
+ large: "400x400"
443
+ },
444
+ convert_options: {
445
+ all: "-do_stuff",
446
+ thumb: "-thumbnailize"
447
+ }
448
+ @dummy = Dummy.new
449
+ @dummy.avatar
450
+ end
451
+
452
+ it "reports the correct options when sent #extra_options_for(:thumb)" do
453
+ assert_equal "-thumbnailize -do_stuff", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect
454
+ end
455
+
456
+ it "reports the correct options when sent #extra_options_for(:large)" do
457
+ assert_equal "-do_stuff", @dummy.avatar.send(:extra_options_for, :large)
458
+ end
459
+ end
460
+
461
+ context "An attachment with :source_file_options" do
462
+ before do
463
+ rebuild_model styles: {
464
+ thumb: "100x100",
465
+ large: "400x400"
466
+ },
467
+ source_file_options: {
468
+ all: "-density 400",
469
+ thumb: "-depth 8"
470
+ }
471
+ @dummy = Dummy.new
472
+ @dummy.avatar
473
+ end
474
+
475
+ it "reports the correct options when sent #extra_source_file_options_for(:thumb)" do
476
+ assert_equal "-depth 8 -density 400", @dummy.avatar.send(:extra_source_file_options_for, :thumb), @dummy.avatar.source_file_options.inspect
477
+ end
478
+
479
+ it "reports the correct options when sent #extra_source_file_options_for(:large)" do
480
+ assert_equal "-density 400", @dummy.avatar.send(:extra_source_file_options_for, :large)
481
+ end
482
+ end
483
+
484
+ context "An attachment with :only_process" do
485
+ before do
486
+ rebuild_model styles: {
487
+ thumb: "100x100",
488
+ large: "400x400"
489
+ },
490
+ only_process: [:thumb]
491
+ @file = StringIO.new("...")
492
+ @attachment = Dummy.new.avatar
493
+ end
494
+
495
+ it "only processes the provided style" do
496
+ expect(@attachment).to receive(:post_process).with(:thumb)
497
+ expect(@attachment).to receive(:post_process).with(:large).never
498
+ @attachment.assign(@file)
499
+ end
500
+ end
501
+
502
+ context "An attachment with :only_process that is a proc" do
503
+ before do
504
+ rebuild_model styles: {
505
+ thumb: "100x100",
506
+ large: "400x400"
507
+ },
508
+ only_process: lambda { |_attachment| [:thumb] }
509
+
510
+ @file = StringIO.new("...")
511
+ @attachment = Dummy.new.avatar
512
+ end
513
+
514
+ it "only processes the provided style" do
515
+ expect(@attachment).to receive(:post_process).with(:thumb)
516
+ expect(@attachment).to receive(:post_process).with(:large).never
517
+ @attachment.assign(@file)
518
+ @attachment.save
519
+ end
520
+ end
521
+
522
+ context "An attachment with :convert_options that is a proc" do
523
+ before do
524
+ rebuild_model styles: {
525
+ thumb: "100x100",
526
+ large: "400x400"
527
+ },
528
+ convert_options: {
529
+ all: lambda { |i| i.all },
530
+ thumb: lambda { |i| i.thumb }
531
+ }
532
+ Dummy.class_eval do
533
+ def all; "-all"; end
534
+
535
+ def thumb; "-thumb"; end
536
+ end
537
+ @dummy = Dummy.new
538
+ @dummy.avatar
539
+ end
540
+
541
+ it "reports the correct options when sent #extra_options_for(:thumb)" do
542
+ assert_equal "-thumb -all", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect
543
+ end
544
+
545
+ it "reports the correct options when sent #extra_options_for(:large)" do
546
+ assert_equal "-all", @dummy.avatar.send(:extra_options_for, :large)
547
+ end
548
+ end
549
+
550
+ context "An attachment with :path that is a proc" do
551
+ before do
552
+ rebuild_model path: lambda { |attachment| "path/#{attachment.instance.other}.:extension" }
553
+
554
+ @file = File.new(fixture_file("5k.png"), "rb")
555
+ @dummyA = Dummy.new(other: "a")
556
+ @dummyA.avatar = @file
557
+ @dummyB = Dummy.new(other: "b")
558
+ @dummyB.avatar = @file
559
+ end
560
+
561
+ after { @file.close }
562
+
563
+ it "returns correct path" do
564
+ assert_equal "path/a.png", @dummyA.avatar.path
565
+ assert_equal "path/b.png", @dummyB.avatar.path
566
+ end
567
+ end
568
+
569
+ context "An attachment with :styles that is a proc" do
570
+ before do
571
+ rebuild_model styles: lambda { |_attachment| { thumb: "50x50#", large: "400x400" } }
572
+
573
+ @attachment = Dummy.new.avatar
574
+ end
575
+
576
+ it "has the correct geometry" do
577
+ assert_equal "50x50#", @attachment.styles[:thumb][:geometry]
578
+ end
579
+ end
580
+
581
+ context "An attachment with conditional :styles that is a proc" do
582
+ before do
583
+ rebuild_model styles: lambda { |attachment| attachment.instance.other == "a" ? { thumb: "50x50#" } : { large: "400x400" } }
584
+
585
+ @dummy = Dummy.new(other: "a")
586
+ end
587
+
588
+ it "has the correct styles for the assigned instance values" do
589
+ assert_equal "50x50#", @dummy.avatar.styles[:thumb][:geometry]
590
+ assert_nil @dummy.avatar.styles[:large]
591
+
592
+ @dummy.other = "b"
593
+
594
+ assert_equal "400x400", @dummy.avatar.styles[:large][:geometry]
595
+ assert_nil @dummy.avatar.styles[:thumb]
596
+ end
597
+ end
598
+
599
+ geometry_specs = [
600
+ [lambda { |_z| "50x50#" }, :png],
601
+ lambda { |_z| "50x50#" },
602
+ { geometry: lambda { |_z| "50x50#" } }
603
+ ]
604
+ geometry_specs.each do |geometry_spec|
605
+ context "An attachment geometry like #{geometry_spec}" do
606
+ before do
607
+ rebuild_model styles: { normal: geometry_spec }
608
+ @attachment = Dummy.new.avatar
609
+ end
610
+
611
+ context "when assigned" do
612
+ before do
613
+ @file = StringIO.new(".")
614
+ @attachment.assign(@file)
615
+ end
616
+
617
+ it "has the correct geometry" do
618
+ assert_equal "50x50#", @attachment.styles[:normal][:geometry]
619
+ end
620
+ end
621
+ end
622
+ end
623
+
624
+ context "An attachment with both 'normal' and hash-style styles" do
625
+ before do
626
+ rebuild_model styles: {
627
+ normal: ["50x50#", :png],
628
+ hash: { geometry: "50x50#", format: :png }
629
+ }
630
+ @dummy = Dummy.new
631
+ @attachment = @dummy.avatar
632
+ end
633
+
634
+ [:processors, :whiny, :convert_options, :geometry, :format].each do |field|
635
+ it "has the same #{field} field" do
636
+ assert_equal @attachment.styles[:normal][field], @attachment.styles[:hash][field]
637
+ end
638
+ end
639
+ end
640
+
641
+ context "An attachment with :processors that is a proc" do
642
+ before do
643
+ class Paperclip::Test < Paperclip::Processor; end
644
+ @file = StringIO.new("...")
645
+ allow(Paperclip::Test).to receive(:make).and_return(@file)
646
+
647
+ rebuild_model styles: { normal: "" }, processors: lambda { |_a| [:test] }
648
+ @attachment = Dummy.new.avatar
649
+ end
650
+
651
+ context "when assigned" do
652
+ before do
653
+ @attachment.assign(StringIO.new("."))
654
+ end
655
+
656
+ it "has the correct processors" do
657
+ assert_equal [:test], @attachment.styles[:normal][:processors]
658
+ end
659
+ end
660
+ end
661
+
662
+ context "An attachment with erroring processor" do
663
+ before do
664
+ rebuild_model processor: [:thumbnail], styles: { small: "" }, whiny_thumbnails: true
665
+ @dummy = Dummy.new
666
+ @file = StringIO.new("...")
667
+ allow(@file).to receive(:to_tempfile).and_return(@file)
668
+ end
669
+
670
+ context "when error is meaningful for the end user" do
671
+ before do
672
+ expect(Paperclip::Thumbnail).to receive(:make).and_raise(
673
+ Paperclip::Errors::NotIdentifiedByImageMagickError,
674
+ "cannot be processed."
675
+ )
676
+ end
677
+
678
+ it "correctly forwards processing error message to the instance" do
679
+ @dummy.avatar = @file
680
+ @dummy.valid?
681
+ assert_contains(
682
+ @dummy.errors.full_messages,
683
+ "Avatar cannot be processed."
684
+ )
685
+ end
686
+ end
687
+
688
+ context "when error is intended for the developer" do
689
+ before do
690
+ expect(Paperclip::Thumbnail).to receive(:make).and_raise(
691
+ Paperclip::Errors::CommandNotFoundError
692
+ )
693
+ end
694
+
695
+ it "propagates the error" do
696
+ assert_raises(Paperclip::Errors::CommandNotFoundError) do
697
+ @dummy.avatar = @file
698
+ end
699
+ end
700
+ end
701
+ end
702
+
703
+ context "An attachment with multiple processors" do
704
+ before do
705
+ class Paperclip::Test < Paperclip::Processor; end
706
+ @style_params = { once: { one: 1, two: 2 } }
707
+ rebuild_model processors: [:thumbnail, :test], styles: @style_params
708
+ @dummy = Dummy.new
709
+ @file = StringIO.new("...")
710
+ allow(@file).to receive(:close)
711
+ allow(Paperclip::Test).to receive(:make).and_return(@file)
712
+ allow(Paperclip::Thumbnail).to receive(:make).and_return(@file)
713
+ end
714
+
715
+ context "when assigned" do
716
+ it "calls #make on all specified processors" do
717
+ @dummy.avatar = @file
718
+
719
+ expect(Paperclip::Thumbnail).to have_received(:make)
720
+ expect(Paperclip::Test).to have_received(:make)
721
+ end
722
+
723
+ it "calls #make with the right parameters passed as second argument" do
724
+ expected_params = @style_params[:once].merge(
725
+ style: :once,
726
+ processors: [:thumbnail, :test],
727
+ whiny: true,
728
+ convert_options: "",
729
+ source_file_options: ""
730
+ )
731
+
732
+ @dummy.avatar = @file
733
+
734
+ expect(Paperclip::Thumbnail).to have_received(:make).with(anything, expected_params, anything)
735
+ end
736
+
737
+ it "calls #make with attachment passed as third argument" do
738
+ @dummy.avatar = @file
739
+
740
+ expect(Paperclip::Test).to have_received(:make).with(anything, anything, @dummy.avatar)
741
+ end
742
+
743
+ it "calls #make and unlinks intermediary files afterward" do
744
+ expect(@dummy.avatar).to receive(:unlink_files).with([@file, @file])
745
+
746
+ @dummy.avatar = @file
747
+ end
748
+ end
749
+ end
750
+
751
+ context "An attachment with a processor that returns original file" do
752
+ before do
753
+ class Paperclip::Test < Paperclip::Processor
754
+ def make; @file; end
755
+ end
756
+ rebuild_model processors: [:test], styles: { once: "100x100" }
757
+ @file = StringIO.new("...")
758
+ allow(@file).to receive(:close)
759
+ @dummy = Dummy.new
760
+ end
761
+
762
+ context "when assigned" do
763
+ it "#calls #make and doesn't unlink the original file" do
764
+ expect(@dummy.avatar).to receive(:unlink_files).with([])
765
+
766
+ @dummy.avatar = @file
767
+ end
768
+ end
769
+ end
770
+
771
+ it "includes the filesystem module when loading the filesystem storage" do
772
+ rebuild_model storage: :filesystem
773
+ @dummy = Dummy.new
774
+ assert @dummy.avatar.is_a?(Paperclip::Storage::Filesystem)
775
+ end
776
+
777
+ it "includes the filesystem module even if capitalization is wrong" do
778
+ rebuild_model storage: :FileSystem
779
+ @dummy = Dummy.new
780
+ assert @dummy.avatar.is_a?(Paperclip::Storage::Filesystem)
781
+
782
+ rebuild_model storage: :Filesystem
783
+ @dummy = Dummy.new
784
+ assert @dummy.avatar.is_a?(Paperclip::Storage::Filesystem)
785
+ end
786
+
787
+ it "converts underscored storage name to camelcase" do
788
+ rebuild_model storage: :not_here
789
+ @dummy = Dummy.new
790
+ exception = assert_raises(Paperclip::Errors::StorageMethodNotFound, /NotHere/) do
791
+ @dummy.avatar
792
+ end
793
+ end
794
+
795
+ it "raises an error if you try to include a storage module that doesn't exist" do
796
+ rebuild_model storage: :not_here
797
+ @dummy = Dummy.new
798
+ assert_raises(Paperclip::Errors::StorageMethodNotFound) do
799
+ @dummy.avatar
800
+ end
801
+ end
802
+
803
+ context "An attachment with styles but no processors defined" do
804
+ before do
805
+ rebuild_model processors: [], styles: { something: "1" }
806
+ @dummy = Dummy.new
807
+ @file = StringIO.new("...")
808
+ end
809
+ it "raises when assigned to" do
810
+ assert_raises(RuntimeError) { @dummy.avatar = @file }
811
+ end
812
+ end
813
+
814
+ context "An attachment without styles and with no processors defined" do
815
+ before do
816
+ rebuild_model processors: [], styles: {}
817
+ @dummy = Dummy.new
818
+ @file = StringIO.new("...")
819
+ end
820
+ it "does not raise when assigned to" do
821
+ @dummy.avatar = @file
822
+ end
823
+ end
824
+
825
+ context "Assigning an attachment with post_process hooks" do
826
+ before do
827
+ rebuild_class styles: { something: "100x100#" }
828
+ Dummy.class_eval do
829
+ before_avatar_post_process :do_before_avatar
830
+ after_avatar_post_process :do_after_avatar
831
+ before_post_process :do_before_all
832
+ after_post_process :do_after_all
833
+ def do_before_avatar; end
834
+
835
+ def do_after_avatar; end
836
+
837
+ def do_before_all; end
838
+
839
+ def do_after_all; end
840
+ end
841
+ @file = StringIO.new(".")
842
+ allow(@file).to receive(:to_tempfile).and_return(@file)
843
+ @dummy = Dummy.new
844
+ allow(Paperclip::Thumbnail).to receive(:make).and_return(@file)
845
+ @attachment = @dummy.avatar
846
+ end
847
+
848
+ it "calls the defined callbacks when assigned" do
849
+ expect(@dummy).to receive(:do_before_avatar)
850
+ expect(@dummy).to receive(:do_after_avatar)
851
+ expect(@dummy).to receive(:do_before_all)
852
+ expect(@dummy).to receive(:do_after_all)
853
+ expect(Paperclip::Thumbnail).to receive(:make).and_return(@file)
854
+ @dummy.avatar = @file
855
+ end
856
+
857
+ it "does not cancel the processing if a before_post_process returns nil" do
858
+ expect(@dummy).to receive(:do_before_avatar).and_return(nil)
859
+ expect(@dummy).to receive(:do_after_avatar)
860
+ expect(@dummy).to receive(:do_before_all).and_return(nil)
861
+ expect(@dummy).to receive(:do_after_all)
862
+ expect(Paperclip::Thumbnail).to receive(:make).and_return(@file)
863
+ @dummy.avatar = @file
864
+ end
865
+
866
+ it "cancels the processing if a before_post_process returns false" do
867
+ expect(@dummy).to receive(:do_before_avatar).never
868
+ expect(@dummy).to receive(:do_after_avatar).never
869
+ expect(@dummy).to receive(:do_before_all).and_return(false)
870
+ expect(@dummy).to receive(:do_after_all)
871
+ expect(Paperclip::Thumbnail).not_to receive(:make)
872
+ @dummy.avatar = @file
873
+ end
874
+
875
+ it "cancels the processing if a before_avatar_post_process returns false" do
876
+ expect(@dummy).to receive(:do_before_avatar).and_return(false)
877
+ expect(@dummy).to receive(:do_after_avatar)
878
+ expect(@dummy).to receive(:do_before_all).and_return(true)
879
+ expect(@dummy).to receive(:do_after_all)
880
+ expect(Paperclip::Thumbnail).not_to receive(:make)
881
+ @dummy.avatar = @file
882
+ end
883
+
884
+ it "should not call process hooks if validation fails" do
885
+ Dummy.class_eval do
886
+ validates_attachment_content_type :avatar, content_type: 'image/jpeg'
887
+ end
888
+ expect(@dummy).not_to receive(:do_before_avatar)
889
+ expect(@dummy).not_to receive(:do_after_avatar)
890
+ expect(@dummy).not_to receive(:do_before_all)
891
+ expect(@dummy).not_to receive(:do_after_all)
892
+ expect(Paperclip::Thumbnail).not_to receive(:make)
893
+ @dummy.avatar = @file
894
+ end
895
+
896
+ it "should call process hooks if validation would fail but check validity flag is false" do
897
+ Dummy.class_eval do
898
+ validates_attachment_content_type :avatar, content_type: 'image/jpeg'
899
+ end
900
+ @dummy.avatar.options[:check_validity_before_processing] = false
901
+ expect(@dummy).to receive(:do_before_avatar)
902
+ expect(@dummy).to receive(:do_after_avatar)
903
+ expect(@dummy).to receive(:do_before_all)
904
+ expect(@dummy).to receive(:do_after_all)
905
+ expect(Paperclip::Thumbnail).to receive(:make).and_return(@file)
906
+ @dummy.avatar = @file
907
+ end
908
+ end
909
+
910
+ context "Assigning an attachment" do
911
+ before do
912
+ rebuild_model styles: { something: "100x100#" }
913
+ @file = File.new(fixture_file("5k.png"), "rb")
914
+ @dummy = Dummy.new
915
+ @dummy.avatar = @file
916
+ end
917
+
918
+ it "strips whitespace from original_filename field" do
919
+ assert_equal "5k.png", @dummy.avatar.original_filename
920
+ end
921
+
922
+ it "strips whitespace from content_type field" do
923
+ assert_equal "image/png", @dummy.avatar.instance.avatar_content_type
924
+ end
925
+ end
926
+
927
+ context "Assigning an attachment" do
928
+ before do
929
+ rebuild_model styles: { something: "100x100#" }
930
+ @file = File.new(fixture_file("5k.png"), "rb")
931
+ @dummy = Dummy.new
932
+ @dummy.avatar = @file
933
+ end
934
+
935
+ it "makes sure the content_type is a string" do
936
+ assert_equal "image/png", @dummy.avatar.instance.avatar_content_type
937
+ end
938
+ end
939
+
940
+ context "Attachment with strange letters" do
941
+ before do
942
+ rebuild_model
943
+ @file = File.new(fixture_file("5k.png"), "rb")
944
+ allow(@file).to receive(:original_filename).and_return("sheep_say_bæ.png")
945
+ @dummy = Dummy.new
946
+ @dummy.avatar = @file
947
+ end
948
+
949
+ it "does not remove strange letters" do
950
+ assert_equal "sheep_say_bæ.png", @dummy.avatar.original_filename
951
+ end
952
+ end
953
+
954
+ context "Attachment with reserved filename" do
955
+ before do
956
+ rebuild_model
957
+ @file = Tempfile.new(["filename", "png"])
958
+ end
959
+
960
+ after do
961
+ @file.unlink
962
+ end
963
+
964
+ context "with default configuration" do
965
+ "&$+,/:;=?@<>[]{}|\^~%# ".split(//).each do |character|
966
+ context "with character #{character}" do
967
+ context "at beginning of filename" do
968
+ before do
969
+ allow(@file).to receive(:original_filename).and_return("#{character}filename.png")
970
+ @dummy = Dummy.new
971
+ @dummy.avatar = @file
972
+ end
973
+
974
+ it "converts special character into underscore" do
975
+ assert_equal "_filename.png", @dummy.avatar.original_filename
976
+ end
977
+ end
978
+
979
+ context "at end of filename" do
980
+ before do
981
+ allow(@file).to receive(:original_filename).and_return("filename.png#{character}")
982
+ @dummy = Dummy.new
983
+ @dummy.avatar = @file
984
+ end
985
+
986
+ it "converts special character into underscore" do
987
+ assert_equal "filename.png_", @dummy.avatar.original_filename
988
+ end
989
+ end
990
+
991
+ context "in the middle of filename" do
992
+ before do
993
+ allow(@file).to receive(:original_filename).and_return("file#{character}name.png")
994
+ @dummy = Dummy.new
995
+ @dummy.avatar = @file
996
+ end
997
+
998
+ it "converts special character into underscore" do
999
+ assert_equal "file_name.png", @dummy.avatar.original_filename
1000
+ end
1001
+ end
1002
+ end
1003
+ end
1004
+ end
1005
+
1006
+ context "with specified regexp replacement" do
1007
+ before do
1008
+ @old_defaults = Paperclip::Attachment.default_options.dup
1009
+ end
1010
+
1011
+ after do
1012
+ Paperclip::Attachment.default_options.merge! @old_defaults
1013
+ end
1014
+
1015
+ context "as another regexp" do
1016
+ before do
1017
+ Paperclip::Attachment.default_options.merge! restricted_characters: /o/
1018
+
1019
+ allow(@file).to receive(:original_filename).and_return("goood.png")
1020
+ @dummy = Dummy.new
1021
+ @dummy.avatar = @file
1022
+ end
1023
+
1024
+ it "matches and converts that character" do
1025
+ assert_equal "g___d.png", @dummy.avatar.original_filename
1026
+ end
1027
+ end
1028
+
1029
+ context "as nil" do
1030
+ before do
1031
+ Paperclip::Attachment.default_options.merge! restricted_characters: nil
1032
+
1033
+ allow(@file).to receive(:original_filename).and_return("goood.png")
1034
+ @dummy = Dummy.new
1035
+ @dummy.avatar = @file
1036
+ end
1037
+
1038
+ it "ignores and returns the original file name" do
1039
+ assert_equal "goood.png", @dummy.avatar.original_filename
1040
+ end
1041
+ end
1042
+ end
1043
+
1044
+ context "with specified cleaner" do
1045
+ before do
1046
+ @old_defaults = Paperclip::Attachment.default_options.dup
1047
+ end
1048
+
1049
+ after do
1050
+ Paperclip::Attachment.default_options.merge! @old_defaults
1051
+ end
1052
+
1053
+ it "calls the given proc and take the result as cleaned filename" do
1054
+ Paperclip::Attachment.default_options[:filename_cleaner] = lambda do |str|
1055
+ "from_proc_#{str}"
1056
+ end
1057
+
1058
+ allow(@file).to receive(:original_filename).and_return("goood.png")
1059
+ @dummy = Dummy.new
1060
+ @dummy.avatar = @file
1061
+ assert_equal "from_proc_goood.png", @dummy.avatar.original_filename
1062
+ end
1063
+
1064
+ it "calls the given object and take the result as the cleaned filename" do
1065
+ class MyCleaner
1066
+ def call(_filename)
1067
+ "foo"
1068
+ end
1069
+ end
1070
+ Paperclip::Attachment.default_options[:filename_cleaner] = MyCleaner.new
1071
+
1072
+ allow(@file).to receive(:original_filename).and_return("goood.png")
1073
+ @dummy = Dummy.new
1074
+ @dummy.avatar = @file
1075
+ assert_equal "foo", @dummy.avatar.original_filename
1076
+ end
1077
+ end
1078
+ end
1079
+
1080
+ context "Attachment with uppercase extension and a default style" do
1081
+ before do
1082
+ @old_defaults = Paperclip::Attachment.default_options.dup
1083
+ Paperclip::Attachment.default_options.merge!(
1084
+ path: ":rails_root/:attachment/:class/:style/:id/:basename.:extension"
1085
+ )
1086
+ FileUtils.rm_rf("tmp")
1087
+ rebuild_model styles: { large: ["400x400", :jpg],
1088
+ medium: ["100x100", :jpg],
1089
+ small: ["32x32#", :jpg] },
1090
+ default_style: :small
1091
+ @instance = Dummy.new
1092
+ allow(@instance).to receive(:id).and_return 123
1093
+ @file = File.new(fixture_file("uppercase.PNG"), "rb")
1094
+
1095
+ @attachment = @instance.avatar
1096
+
1097
+ now = Time.now
1098
+ allow(Time).to receive(:now).and_return(now)
1099
+ @attachment.assign(@file)
1100
+ @attachment.save
1101
+ end
1102
+
1103
+ after do
1104
+ @file.close
1105
+ Paperclip::Attachment.default_options.merge!(@old_defaults)
1106
+ end
1107
+
1108
+ it "has matching to_s and url methods" do
1109
+ assert_equal @attachment.to_s, @attachment.url
1110
+ assert_equal @attachment.to_s(:small), @attachment.url(:small)
1111
+ end
1112
+
1113
+ it "has matching expiring_url and url methods when using the filesystem storage" do
1114
+ assert_equal @attachment.expiring_url, @attachment.url
1115
+ end
1116
+ end
1117
+
1118
+ context "An attachment" do
1119
+ before do
1120
+ @old_defaults = Paperclip::Attachment.default_options.dup
1121
+ Paperclip::Attachment.default_options.merge!(
1122
+ path: ":rails_root/:attachment/:class/:style/:id/:basename.:extension"
1123
+ )
1124
+ FileUtils.rm_rf("tmp")
1125
+ rebuild_model
1126
+ @instance = Dummy.new
1127
+ allow(@instance).to receive(:id).and_return 123
1128
+ # @attachment = Paperclip::Attachment.new(:avatar, @instance)
1129
+ @attachment = @instance.avatar
1130
+ @file = File.new(fixture_file("5k.png"), "rb")
1131
+ end
1132
+
1133
+ after do
1134
+ @file.close
1135
+ Paperclip::Attachment.default_options.merge!(@old_defaults)
1136
+ end
1137
+
1138
+ it "raises if there are not the correct columns when you try to assign" do
1139
+ @other_attachment = Paperclip::Attachment.new(:not_here, @instance)
1140
+ assert_raises(Paperclip::Error) do
1141
+ @other_attachment.assign(@file)
1142
+ end
1143
+ end
1144
+
1145
+ it "clears out the previous assignment when assigned nil" do
1146
+ @attachment.assign(@file)
1147
+ @attachment.queued_for_write[:original]
1148
+ @attachment.assign(nil)
1149
+ assert_nil @attachment.queued_for_write[:original]
1150
+ end
1151
+
1152
+ it "does not do anything when it is assigned an empty string" do
1153
+ @attachment.assign(@file)
1154
+ original_file = @attachment.queued_for_write[:original]
1155
+ @attachment.assign("")
1156
+ assert_equal original_file, @attachment.queued_for_write[:original]
1157
+ end
1158
+
1159
+ it "returns nil as path when no file assigned" do
1160
+ assert_equal nil, @attachment.path
1161
+ assert_equal nil, @attachment.path(:blah)
1162
+ end
1163
+
1164
+ context "with a file assigned but not saved yet" do
1165
+ it "clears out any attached files" do
1166
+ @attachment.assign(@file)
1167
+ assert @attachment.queued_for_write.present?
1168
+ @attachment.clear
1169
+ assert @attachment.queued_for_write.blank?
1170
+ end
1171
+ end
1172
+
1173
+ context "with a file assigned in the database" do
1174
+ before do
1175
+ allow(@attachment).to receive(:instance_read).with(:file_name).and_return("5k.png")
1176
+ allow(@attachment).to receive(:instance_read).with(:content_type).and_return("image/png")
1177
+ allow(@attachment).to receive(:instance_read).with(:file_size).and_return(12345)
1178
+ dtnow = DateTime.now
1179
+ @now = Time.now
1180
+ allow(Time).to receive(:now).and_return(@now)
1181
+ allow(@attachment).to receive(:instance_read).with(:updated_at).and_return(dtnow)
1182
+ end
1183
+
1184
+ it "returns the proper path when filename has a single .'s" do
1185
+ assert_equal File.expand_path("tmp/avatars/dummies/original/#{@instance.id}/5k.png"), File.expand_path(@attachment.path)
1186
+ end
1187
+
1188
+ it "returns the proper path when filename has multiple .'s" do
1189
+ allow(@attachment).to receive(:instance_read).with(:file_name).and_return("5k.old.png")
1190
+ assert_equal File.expand_path("tmp/avatars/dummies/original/#{@instance.id}/5k.old.png"), File.expand_path(@attachment.path)
1191
+ end
1192
+
1193
+ context "when expecting three styles" do
1194
+ before do
1195
+ rebuild_class styles: {
1196
+ large: ["400x400", :png],
1197
+ medium: ["100x100", :gif],
1198
+ small: ["32x32#", :jpg]
1199
+ }
1200
+ @instance = Dummy.new
1201
+ allow(@instance).to receive(:id).and_return 123
1202
+ @file = File.new(fixture_file("5k.png"), "rb")
1203
+ @attachment = @instance.avatar
1204
+ end
1205
+
1206
+ context "and assigned a file" do
1207
+ before do
1208
+ now = Time.now
1209
+ allow(Time).to receive(:now).and_return(now)
1210
+ @attachment.assign(@file)
1211
+ end
1212
+
1213
+ it "is dirty" do
1214
+ assert @attachment.dirty?
1215
+ end
1216
+
1217
+ context "and saved" do
1218
+ before do
1219
+ @attachment.save
1220
+ end
1221
+
1222
+ it "commits the files to disk" do
1223
+ [:large, :medium, :small].each do |style|
1224
+ expect(@attachment.path(style)).to exist
1225
+ end
1226
+ end
1227
+
1228
+ it "saves the files as the right formats and sizes" do
1229
+ [[:large, 400, 61, "PNG"],
1230
+ [:medium, 100, 15, "GIF"],
1231
+ [:small, 32, 32, "JPEG"]].each do |style|
1232
+ cmd = %[identify -format "%w %h %b %m" "#{@attachment.path(style.first)}"]
1233
+ out = `#{cmd}`
1234
+ width, height, _size, format = out.split(" ")
1235
+ assert_equal style[1].to_s, width.to_s
1236
+ assert_equal style[2].to_s, height.to_s
1237
+ assert_equal style[3].to_s, format.to_s
1238
+ end
1239
+ end
1240
+
1241
+ context "and trying to delete" do
1242
+ before do
1243
+ @existing_names = @attachment.styles.keys.map do |style|
1244
+ @attachment.path(style)
1245
+ end
1246
+ end
1247
+
1248
+ it "deletes the files after assigning nil" do
1249
+ expect(@attachment).to receive(:instance_write).with(:file_name, nil)
1250
+ expect(@attachment).to receive(:instance_write).with(:content_type, nil)
1251
+ expect(@attachment).to receive(:instance_write).with(:file_size, nil)
1252
+ expect(@attachment).to receive(:instance_write).with(:fingerprint, nil)
1253
+ expect(@attachment).to receive(:instance_write).with(:updated_at, nil)
1254
+ @attachment.assign nil
1255
+ @attachment.save
1256
+ @existing_names.each { |f| assert_file_not_exists(f) }
1257
+ end
1258
+
1259
+ it "deletes the files when you call #clear and #save" do
1260
+ expect(@attachment).to receive(:instance_write).with(:file_name, nil)
1261
+ expect(@attachment).to receive(:instance_write).with(:content_type, nil)
1262
+ expect(@attachment).to receive(:instance_write).with(:file_size, nil)
1263
+ expect(@attachment).to receive(:instance_write).with(:fingerprint, nil)
1264
+ expect(@attachment).to receive(:instance_write).with(:updated_at, nil)
1265
+ @attachment.clear
1266
+ @attachment.save
1267
+ @existing_names.each { |f| assert_file_not_exists(f) }
1268
+ end
1269
+
1270
+ it "deletes the files when you call #delete" do
1271
+ expect(@attachment).to receive(:instance_write).with(:file_name, nil)
1272
+ expect(@attachment).to receive(:instance_write).with(:content_type, nil)
1273
+ expect(@attachment).to receive(:instance_write).with(:file_size, nil)
1274
+ expect(@attachment).to receive(:instance_write).with(:fingerprint, nil)
1275
+ expect(@attachment).to receive(:instance_write).with(:updated_at, nil)
1276
+ @attachment.destroy
1277
+ @existing_names.each { |f| assert_file_not_exists(f) }
1278
+ end
1279
+
1280
+ context "when keeping old files" do
1281
+ before do
1282
+ @attachment.options[:keep_old_files] = true
1283
+ end
1284
+
1285
+ it "keeps the files after assigning nil" do
1286
+ expect(@attachment).to receive(:instance_write).with(:file_name, nil)
1287
+ expect(@attachment).to receive(:instance_write).with(:content_type, nil)
1288
+ expect(@attachment).to receive(:instance_write).with(:file_size, nil)
1289
+ expect(@attachment).to receive(:instance_write).with(:fingerprint, nil)
1290
+ expect(@attachment).to receive(:instance_write).with(:updated_at, nil)
1291
+ @attachment.assign nil
1292
+ @attachment.save
1293
+ @existing_names.each { |f| assert_file_exists(f) }
1294
+ end
1295
+
1296
+ it "keeps the files when you call #clear and #save" do
1297
+ expect(@attachment).to receive(:instance_write).with(:file_name, nil)
1298
+ expect(@attachment).to receive(:instance_write).with(:content_type, nil)
1299
+ expect(@attachment).to receive(:instance_write).with(:file_size, nil)
1300
+ expect(@attachment).to receive(:instance_write).with(:fingerprint, nil)
1301
+ expect(@attachment).to receive(:instance_write).with(:updated_at, nil)
1302
+ @attachment.clear
1303
+ @attachment.save
1304
+ @existing_names.each { |f| assert_file_exists(f) }
1305
+ end
1306
+
1307
+ it "keeps the files when you call #delete" do
1308
+ expect(@attachment).to receive(:instance_write).with(:file_name, nil)
1309
+ expect(@attachment).to receive(:instance_write).with(:content_type, nil)
1310
+ expect(@attachment).to receive(:instance_write).with(:file_size, nil)
1311
+ expect(@attachment).to receive(:instance_write).with(:fingerprint, nil)
1312
+ expect(@attachment).to receive(:instance_write).with(:updated_at, nil)
1313
+ @attachment.destroy
1314
+ @existing_names.each { |f| assert_file_exists(f) }
1315
+ end
1316
+ end
1317
+ end
1318
+ end
1319
+ end
1320
+ end
1321
+ end
1322
+
1323
+ context "when trying a nonexistant storage type" do
1324
+ before do
1325
+ rebuild_model storage: :not_here
1326
+ end
1327
+
1328
+ it "is not able to find the module" do
1329
+ assert_raises(Paperclip::Errors::StorageMethodNotFound) { Dummy.new.avatar }
1330
+ end
1331
+ end
1332
+ end
1333
+
1334
+ context "An attachment with only a avatar_file_name column" do
1335
+ before do
1336
+ ActiveRecord::Base.connection.create_table :dummies, force: true do |table|
1337
+ table.column :avatar_file_name, :string
1338
+ end
1339
+ rebuild_class
1340
+ @dummy = Dummy.new
1341
+ @file = File.new(fixture_file("5k.png"), "rb")
1342
+ end
1343
+
1344
+ after { @file.close }
1345
+
1346
+ it "does not error when assigned an attachment" do
1347
+ assert_nothing_raised { @dummy.avatar = @file }
1348
+ end
1349
+
1350
+ it "does not return the time when sent #avatar_updated_at" do
1351
+ @dummy.avatar = @file
1352
+ assert_nil @dummy.avatar.updated_at
1353
+ end
1354
+
1355
+ it "returns the right value when sent #avatar_file_size" do
1356
+ @dummy.avatar = @file
1357
+ assert_equal File.size(@file), @dummy.avatar.size
1358
+ end
1359
+
1360
+ context "and avatar_created_at column" do
1361
+ before do
1362
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_created_at, :timestamp
1363
+ rebuild_class
1364
+ @dummy = Dummy.new
1365
+ end
1366
+
1367
+ it "does not error when assigned an attachment" do
1368
+ assert_nothing_raised { @dummy.avatar = @file }
1369
+ end
1370
+
1371
+ it "returns the creation time when sent #avatar_created_at" do
1372
+ now = Time.now
1373
+ allow(Time).to receive(:now).and_return(now)
1374
+ @dummy.avatar = @file
1375
+ assert_equal now.to_i, @dummy.avatar.created_at
1376
+ end
1377
+
1378
+ it "returns the creation time when sent #avatar_created_at and the entry has been updated" do
1379
+ creation = 2.hours.ago
1380
+ now = Time.now
1381
+ allow(Time).to receive(:now).and_return(creation)
1382
+ @dummy.avatar = @file
1383
+ allow(Time).to receive(:now).and_return(now)
1384
+ @dummy.avatar = @file
1385
+ assert_equal creation.to_i, @dummy.avatar.created_at
1386
+ assert_not_equal now.to_i, @dummy.avatar.created_at
1387
+ end
1388
+
1389
+ it "sets changed? to true on attachment assignment" do
1390
+ @dummy.avatar = @file
1391
+ @dummy.save!
1392
+ @dummy.avatar = @file
1393
+ assert @dummy.changed?
1394
+ end
1395
+ end
1396
+
1397
+ context "and avatar_updated_at column" do
1398
+ before do
1399
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_updated_at, :timestamp
1400
+ rebuild_class
1401
+ @dummy = Dummy.new
1402
+ end
1403
+
1404
+ it "does not error when assigned an attachment" do
1405
+ assert_nothing_raised { @dummy.avatar = @file }
1406
+ end
1407
+
1408
+ it "returns the right value when sent #avatar_updated_at" do
1409
+ now = Time.now
1410
+ allow(Time).to receive(:now).and_return(now)
1411
+ @dummy.avatar = @file
1412
+ assert_equal now.to_i, @dummy.avatar.updated_at
1413
+ end
1414
+ end
1415
+
1416
+ it "does not calculate fingerprint" do
1417
+ allow(Digest::MD5).to receive(:file)
1418
+ @dummy.avatar = @file
1419
+ expect(Digest::MD5).not_to have_received(:file)
1420
+ end
1421
+
1422
+ it "does not assign fingerprint" do
1423
+ @dummy.avatar = @file
1424
+ assert_nil @dummy.avatar.fingerprint
1425
+ end
1426
+
1427
+ context "and avatar_content_type column" do
1428
+ before do
1429
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_content_type, :string
1430
+ rebuild_class
1431
+ @dummy = Dummy.new
1432
+ end
1433
+
1434
+ it "does not error when assigned an attachment" do
1435
+ assert_nothing_raised { @dummy.avatar = @file }
1436
+ end
1437
+
1438
+ it "returns the right value when sent #avatar_content_type" do
1439
+ @dummy.avatar = @file
1440
+ assert_equal "image/png", @dummy.avatar.content_type
1441
+ end
1442
+ end
1443
+
1444
+ context "and avatar_file_size column" do
1445
+ before do
1446
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_file_size, :bigint
1447
+ rebuild_class
1448
+ @dummy = Dummy.new
1449
+ end
1450
+
1451
+ it "does not error when assigned an attachment" do
1452
+ assert_nothing_raised { @dummy.avatar = @file }
1453
+ end
1454
+
1455
+ it "returns the right value when sent #avatar_file_size" do
1456
+ @dummy.avatar = @file
1457
+ assert_equal File.size(@file), @dummy.avatar.size
1458
+ end
1459
+
1460
+ it "returns the right value when saved, reloaded, and sent #avatar_file_size" do
1461
+ @dummy.avatar = @file
1462
+ @dummy.save
1463
+ @dummy = Dummy.find(@dummy.id)
1464
+ assert_equal File.size(@file), @dummy.avatar.size
1465
+ end
1466
+ end
1467
+
1468
+ context "and avatar_fingerprint column" do
1469
+ before do
1470
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_fingerprint, :string
1471
+ rebuild_class
1472
+ @dummy = Dummy.new
1473
+ end
1474
+
1475
+ it "does not error when assigned an attachment" do
1476
+ assert_nothing_raised { @dummy.avatar = @file }
1477
+ end
1478
+
1479
+ context "with explicitly set digest" do
1480
+ before do
1481
+ rebuild_class adapter_options: { hash_digest: Digest::SHA256 }
1482
+ @dummy = Dummy.new
1483
+ end
1484
+
1485
+ it "returns the right value when sent #avatar_fingerprint" do
1486
+ @dummy.avatar = @file
1487
+ assert_equal "734016d801a497f5579cdd4ef2ae1d020088c1db754dc434482d76dd5486520a",
1488
+ @dummy.avatar_fingerprint
1489
+ end
1490
+
1491
+ it "returns the right value when saved, reloaded, and sent #avatar_fingerprint" do
1492
+ @dummy.avatar = @file
1493
+ @dummy.save
1494
+ @dummy = Dummy.find(@dummy.id)
1495
+ assert_equal "734016d801a497f5579cdd4ef2ae1d020088c1db754dc434482d76dd5486520a",
1496
+ @dummy.avatar_fingerprint
1497
+ end
1498
+ end
1499
+
1500
+ context "with the default digest" do
1501
+ before do
1502
+ rebuild_class # MD5 is the default
1503
+ @dummy = Dummy.new
1504
+ end
1505
+
1506
+ it "returns the right value when sent #avatar_fingerprint" do
1507
+ @dummy.avatar = @file
1508
+ assert_equal "aec488126c3b33c08a10c3fa303acf27",
1509
+ @dummy.avatar_fingerprint
1510
+ end
1511
+
1512
+ it "returns the right value when saved, reloaded, and sent #avatar_fingerprint" do
1513
+ @dummy.avatar = @file
1514
+ @dummy.save
1515
+ @dummy = Dummy.find(@dummy.id)
1516
+ assert_equal "aec488126c3b33c08a10c3fa303acf27",
1517
+ @dummy.avatar_fingerprint
1518
+ end
1519
+ end
1520
+ end
1521
+ end
1522
+
1523
+ context "an attachment with delete_file option set to false" do
1524
+ before do
1525
+ rebuild_model preserve_files: true
1526
+ @dummy = Dummy.new
1527
+ @file = File.new(fixture_file("5k.png"), "rb")
1528
+ @dummy.avatar = @file
1529
+ @dummy.save!
1530
+ @attachment = @dummy.avatar
1531
+ @path = @attachment.path
1532
+ end
1533
+
1534
+ after { @file.close }
1535
+
1536
+ it "does not delete the files from storage when attachment is destroyed" do
1537
+ @attachment.destroy
1538
+ assert_file_exists(@path)
1539
+ end
1540
+
1541
+ it "clears out attachment data when attachment is destroyed" do
1542
+ @attachment.destroy
1543
+ assert !@attachment.exists?
1544
+ assert_nil @dummy.avatar_file_name
1545
+ end
1546
+
1547
+ it "does not delete the file when model is destroyed" do
1548
+ @dummy.destroy
1549
+ assert_file_exists(@path)
1550
+ end
1551
+ end
1552
+
1553
+ context "An attached file" do
1554
+ before do
1555
+ rebuild_model
1556
+ @dummy = Dummy.new
1557
+ @file = File.new(fixture_file("5k.png"), "rb")
1558
+ @dummy.avatar = @file
1559
+ @dummy.save!
1560
+ @attachment = @dummy.avatar
1561
+ @path = @attachment.path
1562
+ end
1563
+
1564
+ after { @file.close }
1565
+
1566
+ it "is not deleted when the model fails to destroy" do
1567
+ allow(@dummy).to receive(:destroy).and_raise(Exception)
1568
+
1569
+ assert_raises Exception do
1570
+ @dummy.destroy
1571
+ end
1572
+
1573
+ assert_file_exists(@path)
1574
+ end
1575
+
1576
+ it "is deleted when the model is destroyed" do
1577
+ @dummy.destroy
1578
+ assert_file_not_exists(@path)
1579
+ end
1580
+
1581
+ it "is not deleted when transaction rollbacks after model is destroyed" do
1582
+ ActiveRecord::Base.transaction do
1583
+ @dummy.destroy
1584
+ raise ActiveRecord::Rollback
1585
+ end
1586
+
1587
+ assert_file_exists(@path)
1588
+ end
1589
+ end
1590
+ end