paperclip 2.3.16 → 2.4.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.

data/README.md CHANGED
@@ -1,5 +1,4 @@
1
- Paperclip
2
- =========
1
+ # Paperclip [![Build Status](https://secure.travis-ci.org/thoughtbot/paperclip.png?branch=master)](http://travis-ci.org/thoughtbot/paperclip)
3
2
 
4
3
  Paperclip is intended as an easy file attachment library for ActiveRecord. The
5
4
  intent behind it was to keep setup as easy as possible and to treat files as
@@ -26,13 +25,21 @@ that it does, on your command line, run `which convert` (one of the ImageMagick
26
25
  utilities). This will give you the path where that utility is installed. For
27
26
  example, it might return `/usr/local/bin/convert`.
28
27
 
29
- Then, in your environment config file, let Paperclip know to look there by adding that
28
+ Then, in your environment config file, let Paperclip know to look there by adding that
30
29
  directory to its path.
31
30
 
32
31
  In development mode, you might add this line to `config/environments/development.rb)`:
33
32
 
34
33
  Paperclip.options[:command_path] = "/usr/local/bin/"
35
34
 
35
+ If you're on Mac OSX, you'll want to run the following with Homebrew:
36
+
37
+ brew install imagemagick
38
+
39
+ If you are dealing with pdf uploads or running the test suite, also run:
40
+
41
+ brew install gs
42
+
36
43
  Installation
37
44
  ------------
38
45
 
@@ -99,6 +106,11 @@ In your show view:
99
106
  <%= image_tag @user.avatar.url(:medium) %>
100
107
  <%= image_tag @user.avatar.url(:thumb) %>
101
108
 
109
+ To detach a file, simply set the attribute to `nil`:
110
+
111
+ @user.avatar = nil
112
+ @user.save
113
+
102
114
  Usage
103
115
  -----
104
116
 
@@ -219,6 +231,130 @@ Paperclip has an interpolation called `:hash` for obfuscating filenames of publi
219
231
 
220
232
  [https://github.com/thoughtbot/paperclip/pull/416](https://github.com/thoughtbot/paperclip/pull/416)
221
233
 
234
+ MD5 Checksum / Fingerprint
235
+ -------
236
+
237
+ A MD5 checksum of the original file assigned will be placed in the model if it
238
+ has an attribute named fingerprint. Following the user model migration example
239
+ above, the migration would look like the following.
240
+
241
+ class AddAvatarFingerprintColumnToUser < ActiveRecord::Migration
242
+ def self.up
243
+ add_column :users, :avatar_fingerprint, :string
244
+ end
245
+
246
+ def self.down
247
+ remove_column :users, :avatar_fingerprint
248
+ end
249
+ end
250
+
251
+ Custom Attachment Processors
252
+ -------
253
+
254
+ Custom attachment processors can be implemented and their only requirement is
255
+ to inherit from `Paperclip::Processor` (see `lib/paperclip/processor.rb`).
256
+ For example, when `:styles` are specified for an image attachment, the
257
+ thumbnail processor (see `lib/paperclip/thumbnail.rb`) is loaded without having
258
+ to specify it as a `:processor` parameter to `has_attached_file`. When any
259
+ other processor is defined it must be called out in the `:processors`
260
+ parameter if it is to be applied to the attachment. The thumbnail processor
261
+ uses the imagemagick `convert` command to do the work of resizing image
262
+ thumbnails. It would be easy to create a custom processor that watermarks
263
+ an image using imagemagick's `composite` command. Following the
264
+ implementation pattern of the thumbnail processor would be a way to implement a
265
+ watermark processor. All kinds of attachment processors can be created;
266
+ a few utility examples would be compression and encryption processors.
267
+
268
+
269
+ Dynamic Configuration
270
+ ---------------------
271
+
272
+ Callable objects (labdas, Procs) can be used in a number of places for dynamic
273
+ configuration throughout Paperclip. This strategy exists in a number of
274
+ components of the library but is most significant in the possibilities for
275
+ allowing custom styles and processors to be applied for specific model
276
+ instances, rather than applying defined styles and processors across all
277
+ instances.
278
+
279
+ Dynamic Styles:
280
+
281
+ Imagine a user model that had different styles based on the role of the user.
282
+ Perhaps some users are bosses (e.g. a User model instance responds to #boss?)
283
+ and merit a bigger avatar thumbnail than regular users. The configuration to
284
+ determine what style parameters are to be used based on the user role might
285
+ look as follows where a boss will receive a `300x300` thumbnail otherwise a
286
+ `100x100` thumbnail will be created.
287
+
288
+ class User < ActiveRecord::Base
289
+ has_attached_file :avatar, :styles => lambda { |attachment| { :thumb => (attachment.instance.boss? ? "300x300>" : "100x100>") }
290
+ end
291
+
292
+ Dynamic Processors:
293
+
294
+ Another contrived example is a user model that is aware of which file processors
295
+ should be applied to it (beyond the implied `thumbnail` processor invoked when
296
+ `:styles` are defined). Perhaps we have a watermark processor available and it is
297
+ only used on the avatars of certain models. The configuration for this might be
298
+ where the instance is queried for which processors should be applied to it.
299
+ Presumably some users might return `[:thumbnail, :watermark]` for its
300
+ processors, where a defined `watermark` processor is invoked after the
301
+ `thumbnail` processor already defined by Paperclip.
302
+
303
+ class User < ActiveRecord::Base
304
+ has_attached_file :avatar, :processors => lambda { |instance| instance.processors }
305
+ attr_accessor :watermark
306
+ end
307
+
308
+ Deploy
309
+ ------
310
+
311
+ Paperclip is aware of new attachment styles you have added in previous deploy. The only thing you should do after each deployment is to call
312
+ `rake paperclip:refresh:missing_styles`. It will store current attachment styles in `RAILS_ROOT/public/system/paperclip_attachments.yml`
313
+ by default. You can change it by:
314
+
315
+ Paperclip.registered_attachments_styles_path = '/tmp/config/paperclip_attachments.yml'
316
+
317
+ Here is an example for Capistrano:
318
+
319
+ namespace :deploy do
320
+ desc "build missing paperclip styles"
321
+ task :build_missing_paperclip_styles, :roles => :app do
322
+ run "cd #{release_path}; RAILS_ENV=production bundle exec rake paperclip:refresh:missing_styles"
323
+ end
324
+ end
325
+
326
+ after("deploy:update_code", "deploy:build_missing_paperclip_styles")
327
+
328
+ Now you don't have to remember to refresh thumbnails in production everytime you add new style.
329
+ Unfortunately it does not work with dynamic styles - it just ignores them.
330
+
331
+ If you already have working app and don't want `rake paperclip:refresh:missing_styles` to refresh old pictures, you need to tell
332
+ Paperclip about existing styles. Simply create paperclip_attachments.yml file by hand. For example:
333
+
334
+ class User < ActiveRecord::Base
335
+ has_attached_file :avatar, :styles => {:thumb => 'x100', :croppable => '600x600>', :big => '1000x1000>'}
336
+ end
337
+
338
+ class Book < ActiveRecord::Base
339
+ has_attached_file :cover, :styles => {:small => 'x100', :large => '1000x1000>'}
340
+ has_attached_file :sample, :styles => {:thumb => 'x100'}
341
+ end
342
+
343
+ Then in `RAILS_ROOT/public/system/paperclip_attachments.yml`:
344
+
345
+ ---
346
+ :User:
347
+ :avatar:
348
+ - :thumb
349
+ - :croppable
350
+ - :big
351
+ :Book:
352
+ :cover:
353
+ - :small
354
+ - :large
355
+ :sample:
356
+ - :thumb
357
+
222
358
  Testing
223
359
  -------
224
360
 
@@ -233,7 +369,7 @@ If you'd like to contribute a feature or bugfix: Thanks! To make sure your
233
369
  fix/feature has a high chance of being included, please read the following
234
370
  guidelines:
235
371
 
236
- 1. Ask on the mailing list[http://groups.google.com/group/paperclip-plugin], or
372
+ 1. Ask on the mailing list[http://groups.google.com/group/paperclip-plugin], or
237
373
  post a new GitHub Issue[http://github.com/thoughtbot/paperclip/issues].
238
374
  2. Make sure there are tests! We will not accept any patch that is not tested.
239
375
  It's a rare time when explicit tests aren't needed. If you have questions
@@ -39,7 +39,9 @@ require 'paperclip/style'
39
39
  require 'paperclip/attachment'
40
40
  require 'paperclip/storage'
41
41
  require 'paperclip/callback_compatibility'
42
+ require 'paperclip/missing_attachment_styles'
42
43
  require 'paperclip/railtie'
44
+ require 'logger'
43
45
  require 'cocaine'
44
46
 
45
47
  # The base module that gets included in ActiveRecord::Base. See the
@@ -91,7 +93,7 @@ module Paperclip
91
93
  # This method can log the command being run when
92
94
  # Paperclip.options[:log_command] is set to true (defaults to false). This
93
95
  # will only log if logging in general is set to true as well.
94
- def run cmd, *params
96
+ def run(cmd, *params)
95
97
  if options[:image_magick_path]
96
98
  Paperclip.log("[DEPRECATION] :image_magick_path is deprecated and will be removed. Use :command_path instead")
97
99
  end
@@ -99,14 +101,16 @@ module Paperclip
99
101
  Cocaine::CommandLine.new(cmd, *params).run
100
102
  end
101
103
 
102
- def processor name #:nodoc:
103
- name = name.to_s.camelize
104
- load_processor(name) unless Paperclip.const_defined?(name)
105
- processor = Paperclip.const_get(name)
106
- unless processor.ancestors.include?(Paperclip::Processor)
107
- raise PaperclipError.new("Processor #{name} was not found")
104
+ def processor(name) #:nodoc:
105
+ @known_processors ||= {}
106
+ if @known_processors[name.to_s]
107
+ @known_processors[name.to_s]
108
+ else
109
+ name = name.to_s.camelize
110
+ load_processor(name) unless Paperclip.const_defined?(name)
111
+ processor = Paperclip.const_get(name)
112
+ @known_processors[name.to_s] = processor
108
113
  end
109
- processor
110
114
  end
111
115
 
112
116
  def load_processor(name)
@@ -115,6 +119,23 @@ module Paperclip
115
119
  end
116
120
  end
117
121
 
122
+ def clear_processors!
123
+ @known_processors.try(:clear)
124
+ end
125
+
126
+ # You can add your own processor via the Paperclip configuration. Normally
127
+ # Paperclip will load all processors from the
128
+ # Rails.root/lib/paperclip_processors directory, but here you can add any
129
+ # existing class using this mechanism.
130
+ #
131
+ # Paperclip.configure do |c|
132
+ # c.register_processor :watermarker, WatermarkingProcessor.new
133
+ # end
134
+ def register_processor(name, processor)
135
+ @known_processors ||= {}
136
+ @known_processors[name.to_s] = processor
137
+ end
138
+
118
139
  def each_instance_with_attachment(klass, name)
119
140
  class_for(klass).all.each do |instance|
120
141
  yield(instance) if instance.send(:"#{name}?")
@@ -128,7 +149,11 @@ module Paperclip
128
149
  end
129
150
 
130
151
  def logger #:nodoc:
131
- defined?(ActiveRecord::Base) ? ActiveRecord::Base.logger : Rails.logger
152
+ @logger ||= options[:logger] || Logger.new(STDOUT)
153
+ end
154
+
155
+ def logger=(logger)
156
+ @logger = logger
132
157
  end
133
158
 
134
159
  def logging? #:nodoc:
@@ -264,9 +289,11 @@ module Paperclip
264
289
  end
265
290
 
266
291
  attachment_definitions[name] = {:validations => []}.merge(options)
292
+ Paperclip.classes_with_attachments << self
267
293
 
268
294
  after_save :save_attached_files
269
- before_destroy :destroy_attached_files
295
+ before_destroy :prepare_for_destroy
296
+ after_destroy :destroy_attached_files
270
297
 
271
298
  define_paperclip_callbacks :post_process, :"#{name}_post_process"
272
299
 
@@ -328,7 +355,7 @@ module Paperclip
328
355
  # be run is this lambda or method returns true.
329
356
  # * +unless+: Same as +if+ but validates if lambda or method returns false.
330
357
  def validates_attachment_presence name, options = {}
331
- message = options[:message] || "must be set"
358
+ message = options[:message] || :empty
332
359
  validates_presence_of :"#{name}_file_name",
333
360
  :message => message,
334
361
  :if => options[:if],
@@ -402,10 +429,17 @@ module Paperclip
402
429
  def destroy_attached_files
403
430
  Paperclip.log("Deleting attachments.")
404
431
  each_attachment do |name, attachment|
405
- attachment.send(:queue_existing_for_delete)
406
432
  attachment.send(:flush_deletes)
407
433
  end
408
434
  end
435
+
436
+ def prepare_for_destroy
437
+ Paperclip.log("Scheduling attachments for deletion.")
438
+ each_attachment do |name, attachment|
439
+ attachment.send(:queue_existing_for_delete)
440
+ end
441
+ end
442
+
409
443
  end
410
444
 
411
445
  end
@@ -14,6 +14,7 @@ module Paperclip
14
14
  :only_process => [],
15
15
  :processors => [:thumbnail],
16
16
  :convert_options => {},
17
+ :source_file_options => {},
17
18
  :default_url => "/:attachment/:style/missing.png",
18
19
  :default_style => :original,
19
20
  :storage => :filesystem,
@@ -26,12 +27,33 @@ module Paperclip
26
27
  }
27
28
  end
28
29
 
29
- attr_reader :name, :instance, :default_style, :convert_options, :queued_for_write, :whiny, :options
30
+ attr_reader :name, :instance, :default_style, :convert_options, :queued_for_write, :whiny, :options, :source_file_options, :interpolator
30
31
  attr_accessor :post_processing
31
32
 
32
33
  # Creates an Attachment object. +name+ is the name of the attachment,
33
34
  # +instance+ is the ActiveRecord object instance it's attached to, and
34
35
  # +options+ is the same as the hash passed to +has_attached_file+.
36
+ #
37
+ # Options include:
38
+ #
39
+ # +url+ - a relative URL of the attachment. This is interpolated using +interpolator+
40
+ # +path+ - where on the filesystem to store the attachment. This is interpolated using +interpolator+
41
+ # +styles+ - a hash of options for processing the attachment. See +has_attached_file+ for the details
42
+ # +only_process+ - style args to be run through the post-processor. This defaults to the empty list
43
+ # +default_url+ - a URL for the missing image
44
+ # +default_style+ - the style to use when don't specify an argument to e.g. #url, #path
45
+ # +storage+ - the storage mechanism. Defaults to :filesystem
46
+ # +use_timestamp+ - whether to append an anti-caching timestamp to image URLs. Defaults to true
47
+ # +whiny+, +whiny_thumbnails+ - whether to raise when thumbnailing fails
48
+ # +use_default_time_zone+ - related to +use_timestamp+. Defaults to true
49
+ # +hash_digest+ - a string representing a class that will be used to hash URLs for obfuscation
50
+ # +hash_data+ - the relative URL for the hash data. This is interpolated using +interpolator+
51
+ # +hash_secret+ - a secret passed to the +hash_digest+
52
+ # +convert_options+ - flags passed to the +convert+ command for processing
53
+ # +source_file_options+ - flags passed to the +convert+ command that controls how the file is read
54
+ # +processors+ - classes that transform the attachment. Defaults to [:thumbnail]
55
+ # +preserve_files+ - whether to keep files on the filesystem when deleting to clearing the attachment. Defaults to false
56
+ # +interpolator+ - the object used to interpolate filenames and URLs. Defaults to Paperclip::Interpolations
35
57
  def initialize name, instance, options = {}
36
58
  @name = name
37
59
  @instance = instance
@@ -55,6 +77,7 @@ module Paperclip
55
77
  @hash_data = options[:hash_data]
56
78
  @hash_secret = options[:hash_secret]
57
79
  @convert_options = options[:convert_options]
80
+ @source_file_options = options[:source_file_options]
58
81
  @processors = options[:processors]
59
82
  @preserve_files = options[:preserve_files]
60
83
  @options = options
@@ -63,6 +86,7 @@ module Paperclip
63
86
  @queued_for_write = {}
64
87
  @errors = {}
65
88
  @dirty = false
89
+ @interpolator = (options[:interpolator] || Paperclip::Interpolations)
66
90
 
67
91
  initialize_storage
68
92
  end
@@ -111,7 +135,7 @@ module Paperclip
111
135
 
112
136
  @dirty = true
113
137
 
114
- post_process(*@only_process) if @post_processing
138
+ post_process(*@only_process) if post_processing
115
139
 
116
140
  # Reset the file size if the original file was reprocessed.
117
141
  instance_write(:file_size, @queued_for_write[:original].size.to_i)
@@ -136,7 +160,7 @@ module Paperclip
136
160
  # file is stored in the filesystem the path refers to the path of the file
137
161
  # on disk. If the file is stored in S3, the path is the "key" part of the
138
162
  # URL, and the :bucket option refers to the S3 bucket.
139
- def path style_name = default_style
163
+ def path(style_name = default_style)
140
164
  original_filename.nil? ? nil : interpolate(@path, style_name)
141
165
  end
142
166
 
@@ -341,6 +365,15 @@ module Paperclip
341
365
  [ style_options, all_options ].compact.join(" ")
342
366
  end
343
367
 
368
+ def extra_source_file_options_for(style) #:nodoc:
369
+ all_options = source_file_options[:all]
370
+ all_options = all_options.call(instance) if all_options.respond_to?(:call)
371
+ style_options = source_file_options[style]
372
+ style_options = style_options.call(instance) if style_options.respond_to?(:call)
373
+
374
+ [ style_options, all_options ].compact.join(" ")
375
+ end
376
+
344
377
  def post_process(*style_args) #:nodoc:
345
378
  return if @queued_for_write[:original].nil?
346
379
  instance.run_paperclip_callbacks(:post_process) do
@@ -366,8 +399,8 @@ module Paperclip
366
399
  end
367
400
  end
368
401
 
369
- def interpolate pattern, style_name = default_style #:nodoc:
370
- Paperclip::Interpolations.interpolate(pattern, self, style_name)
402
+ def interpolate(pattern, style_name = default_style) #:nodoc:
403
+ interpolator.interpolate(pattern, self, style_name)
371
404
  end
372
405
 
373
406
  def queue_existing_for_delete #:nodoc:
@@ -387,5 +420,13 @@ module Paperclip
387
420
  end
388
421
  end
389
422
 
423
+ # called by storage after the writes are flushed and before @queued_for_writes is cleared
424
+ def after_flush_writes
425
+ @queued_for_write.each do |style, file|
426
+ file.close unless file.closed?
427
+ file.unlink if file.respond_to?(:unlink) && file.path.present? && File.exist?(file.path)
428
+ end
429
+ end
430
+
390
431
  end
391
432
  end
@@ -94,6 +94,31 @@ module Paperclip
94
94
  File.extname(attachment.original_filename).gsub(/^\.+/, "")
95
95
  end
96
96
 
97
+ # Returns an extension based on the content type. e.g. "jpeg" for "image/jpeg".
98
+ # Each mime type generally has multiple extensions associated with it, so
99
+ # if the extension from teh original filename is one of these extensions,
100
+ # that extension is used, otherwise, the first in the list is used.
101
+ def content_type_extension attachment, style_name
102
+ mime_type = MIME::Types[attachment.content_type]
103
+ extensions_for_mime_type = unless mime_type.empty?
104
+ mime_type.first.extensions
105
+ else
106
+ []
107
+ end
108
+
109
+ original_extension = extension(attachment, style_name)
110
+ if extensions_for_mime_type.include? original_extension
111
+ original_extension
112
+ elsif !extensions_for_mime_type.empty?
113
+ extensions_for_mime_type.first
114
+ else
115
+ # It's possible, though unlikely, that the mime type is not in the
116
+ # database, so just use the part after the '/' in the mime type as the
117
+ # extension.
118
+ %r{/([^/]*)$}.match(attachment.content_type)[1]
119
+ end
120
+ end
121
+
97
122
  # Returns the id of the instance.
98
123
  def id attachment, style_name
99
124
  attachment.instance.id
@@ -118,7 +143,11 @@ module Paperclip
118
143
  # Returns the id of the instance in a split path form. e.g. returns
119
144
  # 000/001/234 for an id of 1234.
120
145
  def id_partition attachment, style_name
121
- ("%09d" % attachment.instance.id).scan(/\d{3}/).join("/")
146
+ if (id = attachment.instance.id).is_a?(Integer)
147
+ ("%09d" % id).scan(/\d{3}/).join("/")
148
+ else
149
+ id.scan(/.{3}/).first(3).join("/")
150
+ end
122
151
  end
123
152
 
124
153
  # Returns the pluralized form of the attachment name. e.g.