paperclip 3.4.2 → 3.5.0

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

Potentially problematic release.


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

Files changed (50) hide show
  1. checksums.yaml +15 -0
  2. data/.travis.yml +0 -1
  3. data/Appraisals +4 -4
  4. data/NEWS +16 -0
  5. data/README.md +21 -2
  6. data/features/step_definitions/rails_steps.rb +9 -0
  7. data/features/step_definitions/s3_steps.rb +1 -1
  8. data/features/support/fakeweb.rb +4 -1
  9. data/lib/paperclip.rb +6 -42
  10. data/lib/paperclip/attachment.rb +25 -27
  11. data/lib/paperclip/filename_cleaner.rb +16 -0
  12. data/lib/paperclip/glue.rb +0 -1
  13. data/lib/paperclip/has_attached_file.rb +86 -0
  14. data/lib/paperclip/io_adapters/abstract_adapter.rb +8 -0
  15. data/lib/paperclip/io_adapters/data_uri_adapter.rb +27 -0
  16. data/lib/paperclip/io_adapters/empty_string_adapter.rb +18 -0
  17. data/lib/paperclip/io_adapters/stringio_adapter.rb +3 -3
  18. data/lib/paperclip/matchers/have_attached_file_matcher.rb +1 -5
  19. data/lib/paperclip/missing_attachment_styles.rb +11 -16
  20. data/lib/paperclip/storage/filesystem.rb +7 -3
  21. data/lib/paperclip/storage/s3.rb +2 -0
  22. data/lib/paperclip/tasks/attachments.rb +59 -0
  23. data/lib/paperclip/validators.rb +21 -2
  24. data/lib/paperclip/validators/attachment_content_type_validator.rb +9 -2
  25. data/lib/paperclip/validators/attachment_presence_validator.rb +8 -8
  26. data/lib/paperclip/validators/attachment_size_validator.rb +12 -7
  27. data/lib/paperclip/version.rb +1 -1
  28. data/lib/tasks/paperclip.rake +24 -6
  29. data/paperclip.gemspec +5 -3
  30. data/test/attachment_processing_test.rb +69 -15
  31. data/test/attachment_test.rb +49 -0
  32. data/test/filename_cleaner_test.rb +14 -0
  33. data/test/has_attached_file_test.rb +109 -0
  34. data/test/helper.rb +1 -1
  35. data/test/integration_test.rb +1 -65
  36. data/test/io_adapters/abstract_adapter_test.rb +8 -0
  37. data/test/io_adapters/data_uri_adapter_test.rb +60 -0
  38. data/test/io_adapters/empty_string_adapter_test.rb +17 -0
  39. data/test/paperclip_missing_attachment_styles_test.rb +4 -8
  40. data/test/paperclip_test.rb +0 -5
  41. data/test/rake_test.rb +103 -0
  42. data/test/storage/s3_test.rb +13 -1
  43. data/test/tasks/attachments_test.rb +77 -0
  44. data/test/validators/attachment_content_type_validator_test.rb +26 -0
  45. data/test/validators/attachment_size_validator_test.rb +10 -0
  46. data/test/validators_test.rb +8 -1
  47. metadata +56 -77
  48. data/lib/paperclip/attachment_options.rb +0 -9
  49. data/lib/paperclip/instance_methods.rb +0 -35
  50. data/test/attachment_options_test.rb +0 -27
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YjZlZGIxNTY3NDRlZTkyZjE2NmU4NTk3NDg0YTc5YjczODhjYzA2OA==
5
+ data.tar.gz: !binary |-
6
+ NjI1NGVkNTkxNDE2MGZjYWJkYzVhMjZhMWNhNzFhYmYzNTUxNzZhMA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ Y2M4M2UyYjBlZTk4MzNlOGMwODdhM2I0YjIyYmI0YmQxODE1MjMwZTY0YTc5
10
+ ZWUxNDAyNjlhOGNiNGRkOGE4YzNiZjNiMmQ2Mzg2ZmE2OGJhNDEwMDU4MTll
11
+ MWUyYzkxOThiMmY0MzFkZjFlYzk1YWFhMWVhZTQwZjIyOWZhMjU=
12
+ data.tar.gz: !binary |-
13
+ Njk5MTE2NDhkYzk3OWFlOWM2ODUwMjQ2MTIxMmQ1YWIyZDcyZDg5OWQyNTEw
14
+ ZDczODg2NmRkMzMyMWJmMDY0ZWI3NDljNmJmMTQ0ODJkYTNiMWIyN2EyZTM4
15
+ YTYyODEwMTE1NzA2ZWFmMjIzMDAwOTZkMjZmYjVjYjZkNDIwYTI=
@@ -17,4 +17,3 @@ matrix:
17
17
  allow_failures:
18
18
  - rvm: jruby-19mode
19
19
  - rvm: rbx-19mode
20
- - rvm: 2.0.0
data/Appraisals CHANGED
@@ -1,7 +1,7 @@
1
- # appraise "3.0" do
2
- # gem "rails", "~> 3.0.15"
3
- # gem "paperclip", :path => "../"
4
- # end
1
+ appraise "3.0" do
2
+ gem "rails", "~> 3.0.15"
3
+ gem "paperclip", :path => "../"
4
+ end
5
5
 
6
6
  appraise "3.1" do
7
7
  gem "rails", "~> 3.1.6"
data/NEWS CHANGED
@@ -1,3 +1,19 @@
1
+ New in 3.5.0:
2
+
3
+ * Feature: Handle Base64-encoded data URIs as uploads
4
+ * Feature: Add a FilenameCleaner class to allow custom filename sanitation
5
+ * Improvement: Satisfied Mocha deprecation warnings
6
+ * Bug Fix: Allow empty string to be submitted and ignored, as some forms do this
7
+ * Improvement: Make #expiring_url behavior consistent with #url
8
+ * Bug Fix: "Validate" attachments without invoking AR's validations
9
+ * Improvement: Various refactorings for a cleaner codebase
10
+ * Improvement: Be agnostic, use ActiveModel when appropriate
11
+ * Improvement: Add validation errors to the base attachment attribute
12
+ * Improvement: Handle errors in rake tasks
13
+ * Improvement: Largely refactor has_attached_file into a new class
14
+ * Improvement: Added Ruby 2.0.0 as a supported platform and removed 1.8.7
15
+ * Improvement: Fixed some incompatabilities in the test suite
16
+
1
17
  New in 3.4.2:
2
18
 
3
19
  * Improvement: Use https for Gemfile urls
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Paperclip
2
2
  =========
3
3
 
4
- [![Build Status](https://secure.travis-ci.org/thoughtbot/paperclip.png?branch=master)](http://travis-ci.org/thoughtbot/paperclip) [![Dependency Status](https://gemnasium.com/thoughtbot/paperclip.png?travis)](https://gemnasium.com/thoughtbot/paperclip) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/thoughtbot/paperclip)
4
+ [![Build Status](https://secure.travis-ci.org/thoughtbot/paperclip.png?branch=master)](http://travis-ci.org/thoughtbot/paperclip) [![Dependency Status](https://gemnasium.com/thoughtbot/paperclip.png?travis)](https://gemnasium.com/thoughtbot/paperclip) [![Code Climate](https://codeclimate.com/github/thoughtbot/paperclip.png)](https://codeclimate.com/github/thoughtbot/paperclip)
5
5
 
6
6
  Paperclip is intended as an easy file attachment library for Active Record. The
7
7
  intent behind it was to keep setup as easy as possible and to treat files as
@@ -257,7 +257,7 @@ Defaults
257
257
  --------
258
258
  Global defaults for all your paperclip attachments can be defined by changing the Paperclip::Attachment.default_options Hash, this can be useful for setting your default storage settings per example so you won't have to define them in every has_attached_file definition.
259
259
 
260
- If you're using Rails you can define a Hash with default options in config/application.rb or in any of the config/environments/*.rb files on config.paperclip_defaults, these well get merged into Paperclip::Attachment.default_options as your Rails app boots. An example:
260
+ If you're using Rails you can define a Hash with default options in config/application.rb or in any of the config/environments/*.rb files on config.paperclip_defaults, these will get merged into Paperclip::Attachment.default_options as your Rails app boots. An example:
261
261
 
262
262
  ```ruby
263
263
  module YourApp
@@ -657,6 +657,25 @@ Paperclip provides rspec-compatible matchers for testing attachments. See the
657
657
  documentation on [Paperclip::Shoulda::Matchers](http://rubydoc.info/gems/paperclip/Paperclip/Shoulda/Matchers)
658
658
  for more information.
659
659
 
660
+ **Parallel Tests**
661
+
662
+ Because of the default `path` for Paperclip storage, if you try to run tests in
663
+ parallel, you may find that files get overwritten because the same path is being
664
+ calculated for them in each test process. While this fix works for
665
+ parallel_tests, a similar concept should be used for any other mechanism for
666
+ running tests concurrently.
667
+
668
+ ```ruby
669
+ if ENV['PARALLEL_TEST_GROUPS']
670
+ Paperclip::Attachment.default_options[:path] = ":rails_root/public/system/:rails_env/#{ENV['TEST_ENV_NUMBER'].to_i}/:class/:attachment/:id_partition/:filename"
671
+ else
672
+ Paperclip::Attachment.default_options[:path] = ":rails_root/public/system/:rails_env/:class/:attachment/:id_partition/:filename"
673
+ end
674
+ ```
675
+
676
+ The important part here being the inclusion of `ENV['TEST_ENV_NUMBER']`, or the
677
+ similar mechanism for whichever parallel testing library you use.
678
+
660
679
  Contributing
661
680
  ------------
662
681
 
@@ -3,6 +3,7 @@ Given /^I generate a new rails application$/ do
3
3
  When I run `bundle exec #{new_application_command} #{APP_NAME} --skip-bundle`
4
4
  And I cd to "#{APP_NAME}"
5
5
  And I turn off class caching
6
+ And I fix the application.rb for 3.0.12
6
7
  And I write to "Gemfile" with:
7
8
  """
8
9
  source "http://rubygems.org"
@@ -20,6 +21,14 @@ Given /^I generate a new rails application$/ do
20
21
  }
21
22
  end
22
23
 
24
+ Given "I fix the application.rb for 3.0.12" do
25
+ in_current_dir do
26
+ File.open("config/application.rb", "a") do |f|
27
+ f << "ActionController::Base.config.relative_url_root = ''"
28
+ end
29
+ end
30
+ end
31
+
23
32
  Given /^I run a rails generator to generate a "([^"]*)" scaffold with "([^"]*)"$/ do |model_name, attributes|
24
33
  step %[I successfully run `bundle exec #{generator_command} scaffold #{model_name} #{attributes}`]
25
34
  end
@@ -1,5 +1,5 @@
1
1
  When /^I attach the file "([^"]*)" to "([^"]*)" on S3$/ do |file_path, field|
2
- definition = User.attachment_definitions[field.downcase.to_sym]
2
+ definition = Paperclip::Tasks::Attachments.definitions_for(User)[field.downcase.to_sym]
3
3
  path = "https://paperclip.s3.amazonaws.com#{definition[:path]}"
4
4
  path.gsub!(':filename', File.basename(file_path))
5
5
  path.gsub!(/:([^\/\.]+)/) do |match|
@@ -4,7 +4,10 @@ FakeWeb.allow_net_connect = false
4
4
 
5
5
  module FakeWeb
6
6
  class StubSocket
7
- def read_timeout=(ignored)
7
+ def read_timeout=(_ignored)
8
+ end
9
+
10
+ def continue_timeout=(_ignored)
8
11
  end
9
12
  end
10
13
  end
@@ -40,7 +40,6 @@ require 'paperclip/interpolations'
40
40
  require 'paperclip/tempfile_factory'
41
41
  require 'paperclip/style'
42
42
  require 'paperclip/attachment'
43
- require 'paperclip/attachment_options'
44
43
  require 'paperclip/storage'
45
44
  require 'paperclip/callbacks'
46
45
  require 'paperclip/file_command_content_type_detector'
@@ -49,9 +48,11 @@ require 'paperclip/glue'
49
48
  require 'paperclip/errors'
50
49
  require 'paperclip/missing_attachment_styles'
51
50
  require 'paperclip/validators'
52
- require 'paperclip/instance_methods'
53
51
  require 'paperclip/logger'
54
52
  require 'paperclip/helpers'
53
+ require 'paperclip/has_attached_file'
54
+ require 'paperclip/tasks/attachments'
55
+ require 'paperclip/filename_cleaner'
55
56
  require 'mime/types'
56
57
  require 'logger'
57
58
  require 'cocaine'
@@ -173,46 +174,7 @@ module Paperclip
173
174
  # end
174
175
  # end
175
176
  def has_attached_file(name, options = {})
176
- include InstanceMethods
177
-
178
- if attachment_definitions.nil?
179
- self.attachment_definitions = {}
180
- else
181
- self.attachment_definitions = self.attachment_definitions.dup
182
- end
183
-
184
- attachment_definitions[name] = Paperclip::AttachmentOptions.new(options)
185
- Paperclip.classes_with_attachments << self.name
186
-
187
- after_save :save_attached_files
188
- before_destroy :prepare_for_destroy
189
- after_destroy :destroy_attached_files
190
-
191
- define_paperclip_callbacks :post_process, :"#{name}_post_process"
192
-
193
- define_method name do |*args|
194
- a = attachment_for(name)
195
- (args.length > 0) ? a.to_s(args.first) : a
196
- end
197
-
198
- define_method "#{name}=" do |file|
199
- attachment_for(name).assign(file)
200
- end
201
-
202
- define_method "#{name}?" do
203
- attachment_for(name).file?
204
- end
205
-
206
- validates_each(name) do |record, attr, value|
207
- attachment = record.attachment_for(name)
208
- attachment.send(:flush_errors)
209
- end
210
- end
211
-
212
- # Returns the attachment definitions defined by each call to
213
- # has_attached_file.
214
- def attachment_definitions
215
- self.attachment_definitions
177
+ HasAttachedFile.define_on(self, name, options)
216
178
  end
217
179
  end
218
180
  end
@@ -220,9 +182,11 @@ end
220
182
  # This stuff needs to be run after Paperclip is defined.
221
183
  require 'paperclip/io_adapters/registry'
222
184
  require 'paperclip/io_adapters/abstract_adapter'
185
+ require 'paperclip/io_adapters/empty_string_adapter'
223
186
  require 'paperclip/io_adapters/identity_adapter'
224
187
  require 'paperclip/io_adapters/file_adapter'
225
188
  require 'paperclip/io_adapters/stringio_adapter'
189
+ require 'paperclip/io_adapters/data_uri_adapter'
226
190
  require 'paperclip/io_adapters/nil_adapter'
227
191
  require 'paperclip/io_adapters/attachment_adapter'
228
192
  require 'paperclip/io_adapters/uploaded_file_adapter'
@@ -14,6 +14,7 @@ module Paperclip
14
14
  :default_url => "/:attachment/:style/missing.png",
15
15
  :escape_url => true,
16
16
  :restricted_characters => /[&$+,\/:;=?@<>\[\]\{\}\|\\\^~%# ]/,
17
+ :filename_cleaner => nil,
17
18
  :hash_data => ":class/:attachment/:id/:style/:updated_at",
18
19
  :hash_digest => "SHA1",
19
20
  :interpolator => Paperclip::Interpolations,
@@ -28,7 +29,8 @@ module Paperclip
28
29
  :url_generator => Paperclip::UrlGenerator,
29
30
  :use_default_time_zone => true,
30
31
  :use_timestamp => true,
31
- :whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails]
32
+ :whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails],
33
+ :check_validity_before_processing => true
32
34
  }
33
35
  end
34
36
 
@@ -37,7 +39,7 @@ module Paperclip
37
39
  attr_accessor :post_processing
38
40
 
39
41
  # Creates an Attachment object. +name+ is the name of the attachment,
40
- # +instance+ is the ActiveRecord object instance it's attached to, and
42
+ # +instance+ is the model object instance it's attached to, and
41
43
  # +options+ is the same as the hash passed to +has_attached_file+.
42
44
  #
43
45
  # Options include:
@@ -59,6 +61,7 @@ module Paperclip
59
61
  # +source_file_options+ - flags passed to the +convert+ command that controls how the file is read
60
62
  # +processors+ - classes that transform the attachment. Defaults to [:thumbnail]
61
63
  # +preserve_files+ - whether to keep files on the filesystem when deleting or clearing the attachment. Defaults to false
64
+ # +filename_cleaner+ - An object that responds to #call(filename) that will strip unacceptable charcters from filename
62
65
  # +interpolator+ - the object used to interpolate filenames and URLs. Defaults to Paperclip::Interpolations
63
66
  # +url_generator+ - the object used to generate URLs, using the interpolator. Defaults to Paperclip::UrlGenerator
64
67
  # +escape_url+ - Perform URI escaping to URLs. Defaults to true
@@ -92,6 +95,7 @@ module Paperclip
92
95
  ensure_required_accessors!
93
96
  file = Paperclip.io_adapters.for(uploaded_file)
94
97
 
98
+ return nil if not file.assignment?
95
99
  self.clear(*only_process)
96
100
  return nil if file.nil?
97
101
 
@@ -105,7 +109,7 @@ module Paperclip
105
109
 
106
110
  @dirty = true
107
111
 
108
- post_process(*only_process) if post_processing && valid_assignment?
112
+ post_process(*only_process) if post_processing
109
113
 
110
114
  # Reset the file size if the original file was reprocessed.
111
115
  instance_write(:file_size, @queued_for_write[:original].size)
@@ -376,16 +380,6 @@ module Paperclip
376
380
  Paperclip.log(message)
377
381
  end
378
382
 
379
- def valid_assignment? #:nodoc:
380
- if instance.valid?
381
- true
382
- else
383
- instance.errors.none? do |attr, message|
384
- attr.to_s.start_with?(@name.to_s)
385
- end
386
- end
387
- end
388
-
389
383
  def initialize_storage #:nodoc:
390
384
  storage_class_name = @options[:storage].to_s.downcase.camelize
391
385
  begin
@@ -397,18 +391,17 @@ module Paperclip
397
391
  end
398
392
 
399
393
  def extra_options_for(style) #:nodoc:
400
- all_options = @options[:convert_options][:all]
401
- all_options = all_options.call(instance) if all_options.respond_to?(:call)
402
- style_options = @options[:convert_options][style]
403
- style_options = style_options.call(instance) if style_options.respond_to?(:call)
404
-
405
- [ style_options, all_options ].compact.join(" ")
394
+ process_options(:convert_options, style)
406
395
  end
407
396
 
408
397
  def extra_source_file_options_for(style) #:nodoc:
409
- all_options = @options[:source_file_options][:all]
398
+ process_options(:source_file_options, style)
399
+ end
400
+
401
+ def process_options(options_type, style) #:nodoc:
402
+ all_options = @options[options_type][:all]
410
403
  all_options = all_options.call(instance) if all_options.respond_to?(:call)
411
- style_options = @options[:source_file_options][style]
404
+ style_options = @options[options_type][style]
412
405
  style_options = style_options.call(instance) if style_options.respond_to?(:call)
413
406
 
414
407
  [ style_options, all_options ].compact.join(" ")
@@ -419,7 +412,9 @@ module Paperclip
419
412
 
420
413
  instance.run_paperclip_callbacks(:post_process) do
421
414
  instance.run_paperclip_callbacks(:"#{name}_post_process") do
422
- post_process_styles(*style_args)
415
+ unless @options[:check_validity_before_processing] && instance.errors.any?
416
+ post_process_styles(*style_args)
417
+ end
423
418
  end
424
419
  end
425
420
  end
@@ -487,12 +482,15 @@ module Paperclip
487
482
  end
488
483
  end
489
484
 
485
+ # You can either specifiy :restricted_characters or you can define your own
486
+ # :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.
488
+ def filename_cleaner
489
+ @options[:filename_cleaner] || FilenameCleaner.new(@options[:restricted_characters])
490
+ end
491
+
490
492
  def cleanup_filename(filename)
491
- if @options[:restricted_characters]
492
- filename.gsub(@options[:restricted_characters], '_')
493
- else
494
- filename
495
- end
493
+ filename_cleaner.call(filename)
496
494
  end
497
495
 
498
496
  # Check if attachment database table has a created_at field
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+ module Paperclip
3
+ class FilenameCleaner
4
+ def initialize(invalid_character_regex)
5
+ @invalid_character_regex = invalid_character_regex
6
+ end
7
+
8
+ def call(filename)
9
+ if @invalid_character_regex
10
+ filename.gsub(@invalid_character_regex, "_")
11
+ else
12
+ filename
13
+ end
14
+ end
15
+ end
16
+ end
@@ -9,7 +9,6 @@ module Paperclip
9
9
  base.send :include, Callbacks
10
10
  base.send :include, Validators
11
11
  base.send :include, Schema if defined? ActiveRecord
12
- base.class_attribute :attachment_definitions
13
12
 
14
13
  locale_path = Dir.glob(File.dirname(__FILE__) + "/locales/*.{rb,yml}")
15
14
  I18n.load_path += locale_path unless I18n.load_path.include?(locale_path)
@@ -0,0 +1,86 @@
1
+ module Paperclip
2
+ class HasAttachedFile
3
+ def self.define_on(klass, name, options)
4
+ new(klass, name, options).define
5
+ end
6
+
7
+ def initialize(klass, name, options)
8
+ @klass = klass
9
+ @name = name
10
+ @options = options
11
+ end
12
+
13
+ def define
14
+ define_flush_errors
15
+ define_getter
16
+ define_setter
17
+ define_query
18
+ register_with_rake_tasks
19
+ add_active_record_callbacks
20
+ add_paperclip_callbacks
21
+ end
22
+
23
+ private
24
+
25
+ def define_flush_errors
26
+ @klass.send(:validates_each, @name) do |record, attr, value|
27
+ attachment = record.send(@name)
28
+ attachment.send(:flush_errors)
29
+ end
30
+ end
31
+
32
+ def define_getter
33
+ name = @name
34
+ options = @options
35
+
36
+ @klass.send :define_method, @name do |*args|
37
+ ivar = "@attachment_#{name}"
38
+ attachment = instance_variable_get(ivar)
39
+
40
+ if attachment.nil?
41
+ attachment = Attachment.new(name, self, options)
42
+ instance_variable_set(ivar, attachment)
43
+ end
44
+
45
+ if args.length > 0
46
+ attachment.to_s(args.first)
47
+ else
48
+ attachment
49
+ end
50
+ end
51
+ end
52
+
53
+ def define_setter
54
+ name = @name
55
+
56
+ @klass.send :define_method, "#{@name}=" do |file|
57
+ send(name).assign(file)
58
+ end
59
+ end
60
+
61
+ def define_query
62
+ name = @name
63
+
64
+ @klass.send :define_method, "#{@name}?" do
65
+ send(name).file?
66
+ end
67
+ end
68
+
69
+ def register_with_rake_tasks
70
+ Paperclip::Tasks::Attachments.add(@klass, @name, @options)
71
+ end
72
+
73
+ def add_active_record_callbacks
74
+ name = @name
75
+ @klass.send(:after_save) { send(name).send(:save) }
76
+ @klass.send(:before_destroy) { send(name).send(:queue_all_for_delete) }
77
+ @klass.send(:after_destroy) { send(name).send(:flush_deletes) }
78
+ end
79
+
80
+ def add_paperclip_callbacks
81
+ @klass.send(
82
+ :define_paperclip_callbacks,
83
+ :post_process, :"#{@name}_post_process")
84
+ end
85
+ end
86
+ end