paperclip 3.5.2 → 5.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (211) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +17 -0
  3. data/.gitignore +0 -6
  4. data/.hound.yml +1055 -0
  5. data/.rubocop.yml +1 -0
  6. data/.travis.yml +19 -13
  7. data/Appraisals +4 -16
  8. data/CONTRIBUTING.md +29 -13
  9. data/Gemfile +10 -7
  10. data/LICENSE +1 -3
  11. data/NEWS +226 -23
  12. data/README.md +494 -152
  13. data/RELEASING.md +17 -0
  14. data/Rakefile +6 -8
  15. data/UPGRADING +12 -9
  16. data/features/basic_integration.feature +27 -8
  17. data/features/migration.feature +0 -24
  18. data/features/step_definitions/attachment_steps.rb +36 -28
  19. data/features/step_definitions/html_steps.rb +2 -2
  20. data/features/step_definitions/rails_steps.rb +68 -37
  21. data/features/step_definitions/s3_steps.rb +2 -2
  22. data/features/step_definitions/web_steps.rb +1 -103
  23. data/features/support/env.rb +3 -2
  24. data/features/support/file_helpers.rb +2 -2
  25. data/features/support/fixtures/gemfile.txt +1 -1
  26. data/features/support/paths.rb +1 -1
  27. data/features/support/rails.rb +2 -25
  28. data/gemfiles/4.2.gemfile +17 -0
  29. data/gemfiles/5.0.gemfile +17 -0
  30. data/lib/generators/paperclip/paperclip_generator.rb +0 -2
  31. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +1 -1
  32. data/lib/paperclip/attachment.rb +160 -47
  33. data/lib/paperclip/attachment_registry.rb +4 -1
  34. data/lib/paperclip/callbacks.rb +13 -1
  35. data/lib/paperclip/content_type_detector.rb +26 -24
  36. data/lib/paperclip/errors.rb +8 -1
  37. data/lib/paperclip/file_command_content_type_detector.rb +6 -8
  38. data/lib/paperclip/geometry_detector_factory.rb +10 -3
  39. data/lib/paperclip/geometry_parser_factory.rb +1 -1
  40. data/lib/paperclip/glue.rb +1 -1
  41. data/lib/paperclip/has_attached_file.rb +17 -1
  42. data/lib/paperclip/helpers.rb +14 -10
  43. data/lib/paperclip/interpolations/plural_cache.rb +6 -5
  44. data/lib/paperclip/interpolations.rb +27 -14
  45. data/lib/paperclip/io_adapters/abstract_adapter.rb +28 -4
  46. data/lib/paperclip/io_adapters/attachment_adapter.rb +13 -8
  47. data/lib/paperclip/io_adapters/data_uri_adapter.rb +11 -16
  48. data/lib/paperclip/io_adapters/empty_string_adapter.rb +5 -4
  49. data/lib/paperclip/io_adapters/file_adapter.rb +12 -6
  50. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +8 -8
  51. data/lib/paperclip/io_adapters/identity_adapter.rb +12 -6
  52. data/lib/paperclip/io_adapters/nil_adapter.rb +8 -5
  53. data/lib/paperclip/io_adapters/registry.rb +6 -2
  54. data/lib/paperclip/io_adapters/stringio_adapter.rb +15 -16
  55. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +10 -6
  56. data/lib/paperclip/io_adapters/uri_adapter.rb +41 -19
  57. data/lib/paperclip/locales/en.yml +1 -0
  58. data/lib/paperclip/matchers/have_attached_file_matcher.rb +2 -1
  59. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +4 -4
  60. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +2 -1
  61. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +2 -1
  62. data/lib/paperclip/media_type_spoof_detector.rb +89 -0
  63. data/lib/paperclip/processor.rb +5 -41
  64. data/lib/paperclip/processor_helpers.rb +50 -0
  65. data/lib/paperclip/rails_environment.rb +25 -0
  66. data/lib/paperclip/schema.rb +9 -7
  67. data/lib/paperclip/storage/filesystem.rb +14 -3
  68. data/lib/paperclip/storage/fog.rb +47 -22
  69. data/lib/paperclip/storage/s3.rb +144 -73
  70. data/lib/paperclip/style.rb +8 -2
  71. data/lib/paperclip/tempfile_factory.rb +6 -4
  72. data/lib/paperclip/thumbnail.rb +26 -14
  73. data/lib/paperclip/url_generator.rb +25 -14
  74. data/lib/paperclip/validators/attachment_content_type_validator.rb +4 -0
  75. data/lib/paperclip/validators/attachment_file_name_validator.rb +80 -0
  76. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +29 -0
  77. data/lib/paperclip/validators/attachment_presence_validator.rb +4 -0
  78. data/lib/paperclip/validators/attachment_size_validator.rb +5 -3
  79. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +27 -0
  80. data/lib/paperclip/validators.rb +12 -3
  81. data/lib/paperclip/version.rb +3 -1
  82. data/lib/paperclip.rb +31 -11
  83. data/lib/tasks/paperclip.rake +34 -5
  84. data/paperclip.gemspec +18 -17
  85. data/shoulda_macros/paperclip.rb +13 -3
  86. data/{test → spec}/database.yml +0 -0
  87. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  88. data/{test/attachment_processing_test.rb → spec/paperclip/attachment_processing_spec.rb} +17 -20
  89. data/spec/paperclip/attachment_registry_spec.rb +158 -0
  90. data/{test/attachment_test.rb → spec/paperclip/attachment_spec.rb} +524 -400
  91. data/{test/content_type_detector_test.rb → spec/paperclip/content_type_detector_spec.rb} +17 -19
  92. data/{test/file_command_content_type_detector_test.rb → spec/paperclip/file_command_content_type_detector_spec.rb} +7 -6
  93. data/spec/paperclip/filename_cleaner_spec.rb +14 -0
  94. data/spec/paperclip/geometry_detector_spec.rb +39 -0
  95. data/{test/geometry_parser_test.rb → spec/paperclip/geometry_parser_spec.rb} +27 -27
  96. data/{test/geometry_test.rb → spec/paperclip/geometry_spec.rb} +50 -52
  97. data/spec/paperclip/glue_spec.rb +44 -0
  98. data/spec/paperclip/has_attached_file_spec.rb +158 -0
  99. data/{test/integration_test.rb → spec/paperclip/integration_spec.rb} +141 -133
  100. data/{test/interpolations_test.rb → spec/paperclip/interpolations_spec.rb} +70 -46
  101. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +101 -0
  102. data/{test/io_adapters/attachment_adapter_test.rb → spec/paperclip/io_adapters/attachment_adapter_spec.rb} +38 -34
  103. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +89 -0
  104. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  105. data/spec/paperclip/io_adapters/file_adapter_spec.rb +131 -0
  106. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +121 -0
  107. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
  108. data/{test/io_adapters/nil_adapter_test.rb → spec/paperclip/io_adapters/nil_adapter_spec.rb} +7 -7
  109. data/{test/io_adapters/registry_test.rb → spec/paperclip/io_adapters/registry_spec.rb} +12 -9
  110. data/{test/io_adapters/stringio_adapter_test.rb → spec/paperclip/io_adapters/stringio_adapter_spec.rb} +21 -18
  111. data/{test/io_adapters/uploaded_file_adapter_test.rb → spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb} +46 -46
  112. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +172 -0
  113. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
  114. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +109 -0
  115. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
  116. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
  117. data/spec/paperclip/media_type_spoof_detector_spec.rb +79 -0
  118. data/spec/paperclip/meta_class_spec.rb +30 -0
  119. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +84 -0
  120. data/{test/paperclip_test.rb → spec/paperclip/paperclip_spec.rb} +46 -71
  121. data/spec/paperclip/plural_cache_spec.rb +37 -0
  122. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  123. data/{test/processor_test.rb → spec/paperclip/processor_spec.rb} +5 -5
  124. data/spec/paperclip/rails_environment_spec.rb +33 -0
  125. data/{test/rake_test.rb → spec/paperclip/rake_spec.rb} +15 -15
  126. data/spec/paperclip/schema_spec.rb +248 -0
  127. data/{test/storage/filesystem_test.rb → spec/paperclip/storage/filesystem_spec.rb} +18 -18
  128. data/spec/paperclip/storage/fog_spec.rb +561 -0
  129. data/spec/paperclip/storage/s3_live_spec.rb +188 -0
  130. data/spec/paperclip/storage/s3_spec.rb +1693 -0
  131. data/spec/paperclip/style_spec.rb +255 -0
  132. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  133. data/spec/paperclip/tempfile_spec.rb +35 -0
  134. data/{test/thumbnail_test.rb → spec/paperclip/thumbnail_spec.rb} +150 -131
  135. data/spec/paperclip/url_generator_spec.rb +222 -0
  136. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
  137. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +160 -0
  138. data/{test/validators/attachment_presence_validator_test.rb → spec/paperclip/validators/attachment_presence_validator_spec.rb} +20 -20
  139. data/{test/validators/attachment_size_validator_test.rb → spec/paperclip/validators/attachment_size_validator_spec.rb} +77 -64
  140. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +52 -0
  141. data/spec/paperclip/validators_spec.rb +164 -0
  142. data/spec/spec_helper.rb +47 -0
  143. data/spec/support/assertions.rb +82 -0
  144. data/spec/support/conditional_filter_helper.rb +5 -0
  145. data/spec/support/fake_model.rb +25 -0
  146. data/spec/support/fake_rails.rb +12 -0
  147. data/{test → spec/support}/fixtures/12k.png +0 -0
  148. data/{test → spec/support}/fixtures/50x50.png +0 -0
  149. data/{test → spec/support}/fixtures/5k.png +0 -0
  150. data/{test → spec/support}/fixtures/animated +0 -0
  151. data/{test → spec/support}/fixtures/animated.gif +0 -0
  152. data/{test → spec/support}/fixtures/animated.unknown +0 -0
  153. data/{test → spec/support}/fixtures/bad.png +0 -0
  154. data/spec/support/fixtures/empty.html +1 -0
  155. data/spec/support/fixtures/empty.xlsx +0 -0
  156. data/{test → spec/support}/fixtures/fog.yml +0 -0
  157. data/{test → spec/support}/fixtures/rotated.jpg +0 -0
  158. data/{test → spec/support}/fixtures/s3.yml +0 -0
  159. data/spec/support/fixtures/spaced file.jpg +0 -0
  160. data/{test → spec/support}/fixtures/spaced file.png +0 -0
  161. data/{test → spec/support}/fixtures/text.txt +0 -0
  162. data/{test → spec/support}/fixtures/twopage.pdf +0 -0
  163. data/{test → spec/support}/fixtures/uppercase.PNG +0 -0
  164. data/spec/support/matchers/accept.rb +5 -0
  165. data/spec/support/matchers/exist.rb +5 -0
  166. data/spec/support/matchers/have_column.rb +23 -0
  167. data/{test → spec}/support/mock_attachment.rb +2 -0
  168. data/{test → spec}/support/mock_interpolator.rb +0 -0
  169. data/{test → spec}/support/mock_url_generator_builder.rb +2 -2
  170. data/spec/support/model_reconstruction.rb +68 -0
  171. data/spec/support/reporting.rb +11 -0
  172. data/spec/support/test_data.rb +13 -0
  173. data/spec/support/version_helper.rb +9 -0
  174. metadata +262 -297
  175. data/RUNNING_TESTS.md +0 -4
  176. data/cucumber/paperclip_steps.rb +0 -6
  177. data/gemfiles/3.0.gemfile +0 -11
  178. data/gemfiles/3.1.gemfile +0 -11
  179. data/gemfiles/3.2.gemfile +0 -11
  180. data/gemfiles/4.0.gemfile +0 -11
  181. data/test/attachment_definitions_test.rb +0 -12
  182. data/test/attachment_registry_test.rb +0 -77
  183. data/test/filename_cleaner_test.rb +0 -14
  184. data/test/generator_test.rb +0 -80
  185. data/test/geometry_detector_test.rb +0 -24
  186. data/test/has_attached_file_test.rb +0 -125
  187. data/test/helper.rb +0 -215
  188. data/test/io_adapters/abstract_adapter_test.rb +0 -58
  189. data/test/io_adapters/data_uri_adapter_test.rb +0 -67
  190. data/test/io_adapters/empty_string_adapter_test.rb +0 -17
  191. data/test/io_adapters/file_adapter_test.rb +0 -119
  192. data/test/io_adapters/http_url_proxy_adapter_test.rb +0 -102
  193. data/test/io_adapters/identity_adapter_test.rb +0 -8
  194. data/test/io_adapters/uri_adapter_test.rb +0 -102
  195. data/test/matchers/have_attached_file_matcher_test.rb +0 -24
  196. data/test/matchers/validate_attachment_content_type_matcher_test.rb +0 -110
  197. data/test/matchers/validate_attachment_presence_matcher_test.rb +0 -69
  198. data/test/matchers/validate_attachment_size_matcher_test.rb +0 -86
  199. data/test/meta_class_test.rb +0 -32
  200. data/test/paperclip_missing_attachment_styles_test.rb +0 -90
  201. data/test/plural_cache_test.rb +0 -36
  202. data/test/schema_test.rb +0 -200
  203. data/test/storage/fog_test.rb +0 -453
  204. data/test/storage/s3_live_test.rb +0 -179
  205. data/test/storage/s3_test.rb +0 -1348
  206. data/test/style_test.rb +0 -213
  207. data/test/support/mock_model.rb +0 -2
  208. data/test/tempfile_factory_test.rb +0 -13
  209. data/test/url_generator_test.rb +0 -187
  210. data/test/validators/attachment_content_type_validator_test.rb +0 -323
  211. data/test/validators_test.rb +0 -32
@@ -103,107 +103,5 @@ When /^(?:|I )attach the file "([^"]*)" to "([^"]*)"$/ do |path, field|
103
103
  end
104
104
 
105
105
  Then /^(?:|I )should see "([^"]*)"$/ do |text|
106
- if page.respond_to? :should
107
- page.should have_content(text)
108
- else
109
- assert page.has_content?(text)
110
- end
111
- end
112
-
113
- Then /^(?:|I )should see \/([^\/]*)\/$/ do |regexp|
114
- regexp = Regexp.new(regexp)
115
-
116
- if page.respond_to? :should
117
- page.should have_xpath('//*', :text => regexp)
118
- else
119
- assert page.has_xpath?('//*', :text => regexp)
120
- end
121
- end
122
-
123
- Then /^(?:|I )should not see "([^"]*)"$/ do |text|
124
- if page.respond_to? :should
125
- page.should have_no_content(text)
126
- else
127
- assert page.has_no_content?(text)
128
- end
129
- end
130
-
131
- Then /^(?:|I )should not see \/([^\/]*)\/$/ do |regexp|
132
- regexp = Regexp.new(regexp)
133
-
134
- if page.respond_to? :should
135
- page.should have_no_xpath('//*', :text => regexp)
136
- else
137
- assert page.has_no_xpath?('//*', :text => regexp)
138
- end
139
- end
140
-
141
- Then /^the "([^"]*)" field(?: within (.*))? should contain "([^"]*)"$/ do |field, parent, value|
142
- with_scope(parent) do
143
- field = find_field(field)
144
- if field.value.respond_to? :should
145
- field.value.should =~ /#{value}/
146
- else
147
- assert_match(/#{value}/, field.value)
148
- end
149
- end
150
- end
151
-
152
- Then /^the "([^"]*)" field(?: within (.*))? should not contain "([^"]*)"$/ do |field, parent, value|
153
- with_scope(parent) do
154
- field = find_field(field)
155
- if field.value.respond_to? :should_not
156
- field.value.should_not =~ /#{value}/
157
- else
158
- assert_no_match(/#{value}/, field.value)
159
- end
160
- end
161
- end
162
-
163
- Then /^the "([^"]*)" checkbox(?: within (.*))? should be checked$/ do |label, parent|
164
- with_scope(parent) do
165
- field_checked = find_field(label)['checked']
166
- if field_checked.respond_to? :should
167
- field_checked.should be_true
168
- else
169
- assert field_checked
170
- end
171
- end
172
- end
173
-
174
- Then /^the "([^"]*)" checkbox(?: within (.*))? should not be checked$/ do |label, parent|
175
- with_scope(parent) do
176
- field_checked = find_field(label)['checked']
177
- if field_checked.respond_to? :should
178
- field_checked.should be_false
179
- else
180
- assert !field_checked
181
- end
182
- end
183
- end
184
-
185
- Then /^(?:|I )should be on (.+)$/ do |page_name|
186
- current_path = URI.parse(current_url).path
187
- if current_path.respond_to? :should
188
- current_path.should == path_to(page_name)
189
- else
190
- assert_equal path_to(page_name), current_path
191
- end
192
- end
193
-
194
- Then /^(?:|I )should have the following query string:$/ do |expected_pairs|
195
- query = URI.parse(current_url).query
196
- actual_params = query ? CGI.parse(query) : {}
197
- expected_params = {}
198
- expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')}
199
-
200
- if actual_params.respond_to? :should
201
- actual_params.should == expected_params
202
- else
203
- assert_equal expected_params, actual_params
204
- end
205
- end
206
-
207
- Then /^show me the page$/ do
208
- save_and_open_page
106
+ expect(page).to have_content(text)
209
107
  end
@@ -1,11 +1,12 @@
1
1
  require 'aruba/cucumber'
2
2
  require 'capybara/cucumber'
3
- require 'test/unit/assertions'
3
+ require 'rspec/matchers'
4
4
 
5
5
  $CUCUMBER=1
6
6
 
7
- World(Test::Unit::Assertions)
7
+ World(RSpec::Matchers)
8
8
 
9
9
  Before do
10
+ aruba.config.command_launcher = ENV.fetch("DEBUG", nil) ? :debug : :spawn
10
11
  @aruba_timeout_seconds = 120
11
12
  end
@@ -1,6 +1,6 @@
1
1
  module FileHelpers
2
2
  def append_to(path, contents)
3
- in_current_dir do
3
+ cd(".") do
4
4
  File.open(path, "a") do |file|
5
5
  file.puts
6
6
  file.puts contents
@@ -13,7 +13,7 @@ module FileHelpers
13
13
  end
14
14
 
15
15
  def comment_out_gem_in_gemfile(gemname)
16
- in_current_dir do
16
+ cd(".") do
17
17
  gemfile = File.read("Gemfile")
18
18
  gemfile.sub!(/^(\s*)(gem\s*['"]#{gemname})/, "\\1# \\2")
19
19
  File.open("Gemfile", 'w'){ |file| file.write(gemfile) }
@@ -2,4 +2,4 @@ source "http://rubygems.org"
2
2
 
3
3
  gem "rails", "RAILS_VERSION"
4
4
  gem "rdoc"
5
- gem "sqlite3"
5
+ gem "sqlite3", "1.3.8"
@@ -17,7 +17,7 @@ module NavigationHelpers
17
17
  page_name =~ /the (.*) page/
18
18
  path_components = $1.split(/\s+/)
19
19
  self.send(path_components.push('path').join('_').to_sym)
20
- rescue Object => e
20
+ rescue Object
21
21
  raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
22
22
  "Now, go and add a mapping in #{__FILE__}"
23
23
  end
@@ -6,7 +6,8 @@ ORIGINAL_BUNDLE_VARS = Hash[ENV.select{ |key,value| BUNDLE_ENV_VARS.include?(key
6
6
  ENV['RAILS_ENV'] = 'test'
7
7
 
8
8
  Before do
9
- ENV['BUNDLE_GEMFILE'] = File.join(Dir.pwd, ENV['BUNDLE_GEMFILE']) unless ENV['BUNDLE_GEMFILE'].start_with?(Dir.pwd)
9
+ gemfile = ENV['BUNDLE_GEMFILE'].to_s
10
+ ENV['BUNDLE_GEMFILE'] = File.join(Dir.pwd, gemfile) unless gemfile.start_with?(Dir.pwd)
10
11
  @framework_version = nil
11
12
  end
12
13
 
@@ -34,29 +35,5 @@ module RailsCommandHelpers
34
35
  def framework_major_version
35
36
  framework_version.split(".").first.to_i
36
37
  end
37
-
38
- def using_protected_attributes?
39
- framework_major_version < 4
40
- end
41
-
42
- def new_application_command
43
- "rails new"
44
- end
45
-
46
- def generator_command
47
- if framework_major_version >= 4
48
- "rails generate"
49
- else
50
- "script/rails generate"
51
- end
52
- end
53
-
54
- def runner_command
55
- if framework_major_version >= 4
56
- "rails runner"
57
- else
58
- "script/rails runner"
59
- end
60
- end
61
38
  end
62
39
  World(RailsCommandHelpers)
@@ -0,0 +1,17 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sqlite3", "~> 1.3.8", :platforms => :ruby
6
+ gem "pry"
7
+ gem "rails", "~> 4.2.0"
8
+
9
+ group :development, :test do
10
+ gem "activerecord-import"
11
+ gem "mime-types"
12
+ gem "builder"
13
+ gem "rubocop", :require => false
14
+ gem "rspec"
15
+ end
16
+
17
+ gemspec :path => "../"
@@ -0,0 +1,17 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sqlite3", "~> 1.3.8", :platforms => :ruby
6
+ gem "pry"
7
+ gem "rails", "~> 5.0.0"
8
+
9
+ group :development, :test do
10
+ gem "activerecord-import"
11
+ gem "mime-types"
12
+ gem "builder"
13
+ gem "rubocop", :require => false
14
+ gem "rspec"
15
+ end
16
+
17
+ gemspec :path => "../"
@@ -16,8 +16,6 @@ class PaperclipGenerator < ActiveRecord::Generators::Base
16
16
  migration_template "paperclip_migration.rb.erb", "db/migrate/#{migration_file_name}"
17
17
  end
18
18
 
19
- protected
20
-
21
19
  def migration_name
22
20
  "add_attachment_#{attachment_names.join("_")}_to_#{name.underscore.pluralize}"
23
21
  end
@@ -9,7 +9,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration
9
9
 
10
10
  def self.down
11
11
  <% attachment_names.each do |attachment| -%>
12
- drop_attached_file :<%= table_name %>, :<%= attachment %>
12
+ remove_attachment :<%= table_name %>, :<%= attachment %>
13
13
  <% end -%>
14
14
  end
15
15
  end
@@ -1,6 +1,8 @@
1
1
  # encoding: utf-8
2
2
  require 'uri'
3
3
  require 'paperclip/url_generator'
4
+ require 'active_support/deprecation'
5
+ require 'active_support/core_ext/string/inflections'
4
6
 
5
7
  module Paperclip
6
8
  # The Attachment class manages the files for a given attachment. It saves
@@ -30,12 +32,14 @@ module Paperclip
30
32
  :use_default_time_zone => true,
31
33
  :use_timestamp => true,
32
34
  :whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails],
35
+ :validate_media_type => true,
36
+ :adapter_options => { hash_digest: Digest::MD5 },
33
37
  :check_validity_before_processing => true
34
38
  }
35
39
  end
36
40
 
37
41
  attr_reader :name, :instance, :default_style, :convert_options, :queued_for_write, :whiny,
38
- :options, :interpolator, :source_file_options, :whiny
42
+ :options, :interpolator, :source_file_options
39
43
  attr_accessor :post_processing
40
44
 
41
45
  # Creates an Attachment object. +name+ is the name of the attachment,
@@ -47,7 +51,8 @@ module Paperclip
47
51
  # +url+ - a relative URL of the attachment. This is interpolated using +interpolator+
48
52
  # +path+ - where on the filesystem to store the attachment. This is interpolated using +interpolator+
49
53
  # +styles+ - a hash of options for processing the attachment. See +has_attached_file+ for the details
50
- # +only_process+ - style args to be run through the post-processor. This defaults to the empty list
54
+ # +only_process+ - style args to be run through the post-processor. This defaults to the empty list (which is
55
+ # a special case that indicates all styles should be processed)
51
56
  # +default_url+ - a URL for the missing image
52
57
  # +default_style+ - the style to use when an argument is not specified e.g. #url, #path
53
58
  # +storage+ - the storage mechanism. Defaults to :filesystem
@@ -66,10 +71,11 @@ module Paperclip
66
71
  # +url_generator+ - the object used to generate URLs, using the interpolator. Defaults to Paperclip::UrlGenerator
67
72
  # +escape_url+ - Perform URI escaping to URLs. Defaults to true
68
73
  def initialize(name, instance, options = {})
69
- @name = name
74
+ @name = name.to_sym
75
+ @name_string = name.to_s
70
76
  @instance = instance
71
77
 
72
- options = self.class.default_options.merge(options)
78
+ options = self.class.default_options.deep_merge(options)
73
79
 
74
80
  @options = options
75
81
  @post_processing = true
@@ -78,7 +84,7 @@ module Paperclip
78
84
  @errors = {}
79
85
  @dirty = false
80
86
  @interpolator = options[:interpolator]
81
- @url_generator = options[:url_generator].new(self, @options)
87
+ @url_generator = options[:url_generator].new(self)
82
88
  @source_file_options = options[:source_file_options]
83
89
  @whiny = options[:whiny]
84
90
 
@@ -86,36 +92,30 @@ module Paperclip
86
92
  end
87
93
 
88
94
  # What gets called when you call instance.attachment = File. It clears
89
- # errors, assigns attributes, and processes the file. It
90
- # also queues up the previous file for deletion, to be flushed away on
91
- # #save of its host. In addition to form uploads, you can also assign
92
- # another Paperclip attachment:
95
+ # errors, assigns attributes, and processes the file. It also queues up the
96
+ # previous file for deletion, to be flushed away on #save of its host. In
97
+ # addition to form uploads, you can also assign another Paperclip
98
+ # attachment:
93
99
  # new_user.avatar = old_user.avatar
94
- def assign uploaded_file
100
+ def assign(uploaded_file)
101
+ @file = Paperclip.io_adapters.for(uploaded_file,
102
+ @options[:adapter_options])
95
103
  ensure_required_accessors!
96
- file = Paperclip.io_adapters.for(uploaded_file)
104
+ ensure_required_validations!
97
105
 
98
- return nil if not file.assignment?
99
- self.clear(*only_process)
100
- return nil if file.nil?
106
+ if @file.assignment?
107
+ clear(*only_process)
101
108
 
102
- @queued_for_write[:original] = file
103
- instance_write(:file_name, cleanup_filename(file.original_filename))
104
- instance_write(:content_type, file.content_type.to_s.strip)
105
- instance_write(:file_size, file.size)
106
- instance_write(:fingerprint, file.fingerprint) if instance_respond_to?(:fingerprint)
107
- instance_write(:created_at, Time.now) if has_enabled_but_unset_created_at?
108
- instance_write(:updated_at, Time.now)
109
-
110
- @dirty = true
111
-
112
- post_process(*only_process) if post_processing
113
-
114
- # Reset the file size if the original file was reprocessed.
115
- instance_write(:file_size, @queued_for_write[:original].size)
116
- instance_write(:fingerprint, @queued_for_write[:original].fingerprint) if instance_respond_to?(:fingerprint)
117
- updater = :"#{name}_file_name_will_change!"
118
- instance.send updater if instance.respond_to? updater
109
+ if @file.nil?
110
+ nil
111
+ else
112
+ assign_attributes
113
+ post_process_file
114
+ reset_file_if_original_reprocessed
115
+ end
116
+ else
117
+ nil
118
+ end
119
119
  end
120
120
 
121
121
  # Returns the public URL of the attachment with a given style. This does
@@ -140,9 +140,8 @@ module Paperclip
140
140
  #
141
141
  # +#new(Paperclip::Attachment, options_hash)+
142
142
  # +#for(style_name, options_hash)+
143
- def url(style_name = default_style, options = {})
144
- default_options = {:timestamp => @options[:use_timestamp], :escape => @options[:escape_url]}
145
143
 
144
+ def url(style_name = default_style, options = {})
146
145
  if options == true || options == false # Backwards compatibility.
147
146
  @url_generator.for(style_name, default_options.merge(:timestamp => options))
148
147
  else
@@ -150,6 +149,13 @@ module Paperclip
150
149
  end
151
150
  end
152
151
 
152
+ def default_options
153
+ {
154
+ :timestamp => @options[:use_timestamp],
155
+ :escape => @options[:escape_url]
156
+ }
157
+ end
158
+
153
159
  # Alias to +url+ that allows using the expiring_url method provided by the cloud
154
160
  # storage implementations, but keep using filesystem storage for development and
155
161
  # testing.
@@ -166,6 +172,18 @@ module Paperclip
166
172
  path.respond_to?(:unescape) ? path.unescape : path
167
173
  end
168
174
 
175
+ # :nodoc:
176
+ def staged_path(style_name = default_style)
177
+ if staged?
178
+ @queued_for_write[style_name].path
179
+ end
180
+ end
181
+
182
+ # :nodoc:
183
+ def staged?
184
+ ! @queued_for_write.empty?
185
+ end
186
+
169
187
  # Alias to +url+
170
188
  def to_s style_name = default_style
171
189
  url(style_name)
@@ -222,6 +240,10 @@ module Paperclip
222
240
  # the instance's errors and returns false, cancelling the save.
223
241
  def save
224
242
  flush_deletes unless @options[:keep_old_files]
243
+ process = only_process
244
+ if process.any? && !process.include?(:original)
245
+ @queued_for_write.except!(:original)
246
+ end
225
247
  flush_writes
226
248
  @dirty = false
227
249
  true
@@ -308,12 +330,16 @@ module Paperclip
308
330
  OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@options[:hash_digest]).new, @options[:hash_secret], data)
309
331
  end
310
332
 
311
- # This method really shouldn't be called that often. It's expected use is
333
+ # This method really shouldn't be called that often. Its expected use is
312
334
  # in the paperclip:refresh rake task and that's it. It will regenerate all
313
335
  # thumbnails forcefully, by reobtaining the original file and going through
314
336
  # the post-process again.
337
+ # NOTE: Calling reprocess WILL NOT delete existing files. This is due to
338
+ # inconsistencies in timing of S3 commands. It's possible that calling
339
+ # #reprocess! will lose data if the files are not kept.
315
340
  def reprocess!(*style_args)
316
341
  saved_only_process, @options[:only_process] = @options[:only_process], style_args
342
+ saved_preserve_files, @options[:preserve_files] = @options[:preserve_files], true
317
343
  begin
318
344
  assign(self)
319
345
  save
@@ -323,12 +349,13 @@ module Paperclip
323
349
  false
324
350
  ensure
325
351
  @options[:only_process] = saved_only_process
352
+ @options[:preserve_files] = saved_preserve_files
326
353
  end
327
354
  end
328
355
 
329
356
  # Returns true if a file has been assigned.
330
357
  def file?
331
- !original_filename.blank?
358
+ original_filename.present?
332
359
  end
333
360
 
334
361
  alias :present? :file?
@@ -347,7 +374,7 @@ module Paperclip
347
374
  # instance_write(:file_name, "me.jpg") will write "me.jpg" to the instance's
348
375
  # "avatar_file_name" field (assuming the attachment is called avatar).
349
376
  def instance_write(attr, value)
350
- setter = :"#{name}_#{attr}="
377
+ setter = :"#{@name_string}_#{attr}="
351
378
  if instance.respond_to?(setter)
352
379
  instance.send(setter, value)
353
380
  end
@@ -356,7 +383,7 @@ module Paperclip
356
383
  # Reads the attachment-specific attribute on the instance. See instance_write
357
384
  # for more details.
358
385
  def instance_read(attr)
359
- getter = :"#{name}_#{attr}"
386
+ getter = :"#{@name_string}_#{attr}"
360
387
  if instance.respond_to?(getter)
361
388
  instance.send(getter)
362
389
  end
@@ -368,10 +395,24 @@ module Paperclip
368
395
  @options[:path].respond_to?(:call) ? @options[:path].call(self) : @options[:path]
369
396
  end
370
397
 
398
+ def active_validator_classes
399
+ @instance.class.validators.map(&:class)
400
+ end
401
+
402
+ def missing_required_validator?
403
+ (active_validator_classes.flat_map(&:ancestors) & Paperclip::REQUIRED_VALIDATORS).empty?
404
+ end
405
+
406
+ def ensure_required_validations!
407
+ if missing_required_validator?
408
+ raise Paperclip::Errors::MissingRequiredValidatorError
409
+ end
410
+ end
411
+
371
412
  def ensure_required_accessors! #:nodoc:
372
413
  %w(file_name).each do |field|
373
- unless @instance.respond_to?("#{name}_#{field}") && @instance.respond_to?("#{name}_#{field}=")
374
- raise Paperclip::Error.new("#{@instance.class} model missing required attr_accessor for '#{name}_#{field}'")
414
+ unless @instance.respond_to?("#{@name_string}_#{field}") && @instance.respond_to?("#{@name_string}_#{field}=")
415
+ raise Paperclip::Error.new("#{@instance.class} model missing required attr_accessor for '#{@name_string}_#{field}'")
375
416
  end
376
417
  end
377
418
  end
@@ -390,6 +431,61 @@ module Paperclip
390
431
  self.extend(storage_module)
391
432
  end
392
433
 
434
+ def assign_attributes
435
+ @queued_for_write[:original] = @file
436
+ assign_file_information
437
+ assign_fingerprint { @file.fingerprint }
438
+ assign_timestamps
439
+ end
440
+
441
+ def assign_file_information
442
+ instance_write(:file_name, cleanup_filename(@file.original_filename))
443
+ instance_write(:content_type, @file.content_type.to_s.strip)
444
+ instance_write(:file_size, @file.size)
445
+ end
446
+
447
+ def assign_fingerprint
448
+ if instance_respond_to?(:fingerprint)
449
+ instance_write(:fingerprint, yield)
450
+ end
451
+ end
452
+
453
+ def assign_timestamps
454
+ if has_enabled_but_unset_created_at?
455
+ instance_write(:created_at, Time.now)
456
+ end
457
+
458
+ instance_write(:updated_at, Time.now)
459
+ end
460
+
461
+ def post_process_file
462
+ dirty!
463
+
464
+ if post_processing
465
+ post_process(*only_process)
466
+ end
467
+ end
468
+
469
+ def dirty!
470
+ @dirty = true
471
+ end
472
+
473
+ def reset_file_if_original_reprocessed
474
+ instance_write(:file_size, @queued_for_write[:original].size)
475
+ assign_fingerprint { @queued_for_write[:original].fingerprint }
476
+ reset_updater
477
+ end
478
+
479
+ def reset_updater
480
+ if instance.respond_to?(updater)
481
+ instance.send(updater)
482
+ end
483
+ end
484
+
485
+ def updater
486
+ :"#{name}_file_name_will_change!"
487
+ end
488
+
393
489
  def extra_options_for(style) #:nodoc:
394
490
  process_options(:convert_options, style)
395
491
  end
@@ -412,7 +508,7 @@ module Paperclip
412
508
 
413
509
  instance.run_paperclip_callbacks(:post_process) do
414
510
  instance.run_paperclip_callbacks(:"#{name}_post_process") do
415
- unless @options[:check_validity_before_processing] && instance.errors.any?
511
+ if !@options[:check_validity_before_processing] || !instance.errors.any?
416
512
  post_process_styles(*style_args)
417
513
  end
418
514
  end
@@ -429,13 +525,26 @@ module Paperclip
429
525
  def post_process_style(name, style) #:nodoc:
430
526
  begin
431
527
  raise RuntimeError.new("Style #{name} has no processors defined.") if style.processors.blank?
432
- @queued_for_write[name] = style.processors.inject(@queued_for_write[:original]) do |file, processor|
433
- Paperclip.processor(processor).make(file, style.processor_options, self)
528
+ intermediate_files = []
529
+ original = @queued_for_write[:original]
530
+
531
+ @queued_for_write[name] = style.processors.
532
+ reduce(original) do |file, processor|
533
+ file = Paperclip.processor(processor).make(file, style.processor_options, self)
534
+ intermediate_files << file unless file == @queued_for_write[:original]
535
+ file
434
536
  end
435
- @queued_for_write[name] = Paperclip.io_adapters.for(@queued_for_write[name])
436
- rescue Paperclip::Error => e
537
+
538
+ unadapted_file = @queued_for_write[name]
539
+ @queued_for_write[name] = Paperclip.io_adapters.
540
+ for(@queued_for_write[name], @options[:adapter_options])
541
+ unadapted_file.close if unadapted_file.respond_to?(:close)
542
+ @queued_for_write[name]
543
+ rescue Paperclip::Errors::NotIdentifiedByImageMagickError => e
437
544
  log("An error was received while processing: #{e.inspect}")
438
545
  (@errors[:processing] ||= []) << e.message if @options[:whiny]
546
+ ensure
547
+ unlink_files(intermediate_files)
439
548
  end
440
549
  end
441
550
 
@@ -474,9 +583,13 @@ module Paperclip
474
583
  end
475
584
  end
476
585
 
477
- # called by storage after the writes are flushed and before @queued_for_writes is cleared
586
+ # called by storage after the writes are flushed and before @queued_for_write is cleared
478
587
  def after_flush_writes
479
- @queued_for_write.each do |style, file|
588
+ unlink_files(@queued_for_write.values)
589
+ end
590
+
591
+ def unlink_files(files)
592
+ Array(files).each do |file|
480
593
  file.close unless file.closed?
481
594
  file.unlink if file.respond_to?(:unlink) && file.path.present? && File.exist?(file.path)
482
595
  end
@@ -484,7 +597,7 @@ module Paperclip
484
597
 
485
598
  # You can either specifiy :restricted_characters or you can define your own
486
599
  # :filename_cleaner object. This object needs to respond to #call and takes
487
- # the filename that will be cleaned. It should return the cleaned filenme.
600
+ # the filename that will be cleaned. It should return the cleaned filename.
488
601
  def filename_cleaner
489
602
  @options[:filename_cleaner] || FilenameCleaner.new(@options[:restricted_characters])
490
603
  end
@@ -51,7 +51,10 @@ module Paperclip
51
51
  end
52
52
 
53
53
  def definitions_for(klass)
54
- @attachments[klass]
54
+ parent_classes = klass.ancestors.reverse
55
+ parent_classes.each_with_object({}) do |ancestor, inherited_definitions|
56
+ inherited_definitions.deep_merge! @attachments[ancestor]
57
+ end
55
58
  end
56
59
  end
57
60
  end
@@ -7,7 +7,7 @@ module Paperclip
7
7
 
8
8
  module Defining
9
9
  def define_paperclip_callbacks(*callbacks)
10
- define_callbacks *[callbacks, {:terminator => "result == false"}].flatten
10
+ define_callbacks(*[callbacks, { terminator: hasta_la_vista_baby }].flatten)
11
11
  callbacks.each do |callback|
12
12
  eval <<-end_callbacks
13
13
  def before_#{callback}(*args, &blk)
@@ -19,6 +19,18 @@ module Paperclip
19
19
  end_callbacks
20
20
  end
21
21
  end
22
+
23
+ private
24
+
25
+ def hasta_la_vista_baby
26
+ lambda do |_, result|
27
+ if result.respond_to?(:call)
28
+ result.call == false
29
+ else
30
+ result == false
31
+ end
32
+ end
33
+ end
22
34
  end
23
35
 
24
36
  module Running