kt-paperclip 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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