paperclip 2.1.2 → 2.1.5

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.

@@ -14,17 +14,19 @@ In your model:
14
14
 
15
15
  In your migrations:
16
16
 
17
- class AddAvatarColumsToUser < ActiveRecord::Migration
17
+ class AddAvatarColumnsToUser < ActiveRecord::Migration
18
18
  def self.up
19
- add_column :users, :avatar_file_name, :string
19
+ add_column :users, :avatar_file_name, :string
20
20
  add_column :users, :avatar_content_type, :string
21
- add_column :users, :avatar_file_size, :integer
21
+ add_column :users, :avatar_file_size, :integer
22
+ add_column :users, :avatar_updated_at, :datetime
22
23
  end
23
24
 
24
25
  def self.down
25
26
  remove_column :users, :avatar_file_name
26
27
  remove_column :users, :avatar_content_type
27
28
  remove_column :users, :avatar_file_size
29
+ remove_column :users, :avatar_updated_at
28
30
  end
29
31
  end
30
32
 
@@ -46,3 +48,12 @@ In your show view:
46
48
  <%= image_tag @user.avatar.url(:medium) %>
47
49
  <%= image_tag @user.avatar.url(:thumb) %>
48
50
 
51
+ ==Contributing
52
+
53
+ If you'd like to contribute a feature or bugfix, thanks! To make sure your fix/feature
54
+ has a high chance of being added, please read the following guidelines:
55
+
56
+ 1. Ask on the mailing list, or post a ticket in Lighthouse.
57
+ 2. Make sure there are tests! I will not accept any patch that is not tested.
58
+ It's a rare time when explicit tests aren't needed. If you have questions about
59
+ writing tests for paperclip, please ask the mailing list.
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ task :default => [:clean, :test]
12
12
  desc 'Test the paperclip plugin.'
13
13
  Rake::TestTask.new(:test) do |t|
14
14
  t.libs << 'lib' << 'profile'
15
- t.pattern = 'test/**/test_*.rb'
15
+ t.pattern = 'test/**/*_test.rb'
16
16
  t.verbose = true
17
17
  end
18
18
 
@@ -50,21 +50,24 @@ spec = Gem::Specification.new do |s|
50
50
  s.version = Paperclip::VERSION
51
51
  s.author = "Jon Yurek"
52
52
  s.email = "jyurek@thoughtbot.com"
53
- s.homepage = "http://www.thoughtbot.com/"
53
+ s.homepage = "http://www.thoughtbot.com/projects/paperclip"
54
54
  s.platform = Gem::Platform::RUBY
55
55
  s.summary = "File attachments as attributes for ActiveRecord"
56
- s.files = FileList["README",
56
+ s.files = FileList["README*",
57
57
  "LICENSE",
58
58
  "Rakefile",
59
59
  "init.rb",
60
- "{generators,lib,tasks,test}/**/*"].to_a
60
+ "{generators,lib,tasks,test,shoulda_macros}/**/*"].to_a
61
61
  s.require_path = "lib"
62
62
  s.test_files = FileList["test/**/test_*.rb"].to_a
63
63
  s.rubyforge_project = "paperclip"
64
64
  s.has_rdoc = true
65
- s.extra_rdoc_files = ["README"]
65
+ s.extra_rdoc_files = FileList["README*"].to_a
66
66
  s.rdoc_options << '--line-numbers' << '--inline-source'
67
67
  s.requirements << "ImageMagick"
68
+ s.add_runtime_dependency 'right_aws'
69
+ s.add_development_dependency 'thoughtbot-shoulda'
70
+ s.add_development_dependency 'mocha'
68
71
  end
69
72
 
70
73
  Rake::GemPackageTask.new(spec) do |pkg|
@@ -82,3 +85,10 @@ task :release => [:test, :sync_docs, :gem] do
82
85
  spec.version,
83
86
  File.join("pkg", "#{spec.name}-#{spec.version}.gem")
84
87
  end
88
+
89
+ desc "Generate a gemspec file for GitHub"
90
+ task :gemspec do
91
+ File.open("#{spec.name}.gemspec", 'w') do |f|
92
+ f.write spec.to_ruby
93
+ end
94
+ end
@@ -1,5 +1,5 @@
1
1
  Usage:
2
2
 
3
- script/generate attachment Class attachment1 attachment2
3
+ script/generate paperclip Class attachment1 (attachment2 ...)
4
4
 
5
5
  This will create a migration that will add the proper columns to your class's table.
@@ -10,7 +10,7 @@ class PaperclipGenerator < Rails::Generator::NamedBase
10
10
  file_name = generate_file_name
11
11
  @migration_name = file_name.camelize
12
12
  record do |m|
13
- m.migration_template "paperclip_migration.rb",
13
+ m.migration_template "paperclip_migration.rb.erb",
14
14
  File.join('db', 'migrate'),
15
15
  :migration_file_name => file_name
16
16
  end
@@ -24,4 +24,4 @@ class PaperclipGenerator < Rails::Generator::NamedBase
24
24
  "add_attachments_#{names.join("_")}_to_#{@class_name.underscore}"
25
25
  end
26
26
 
27
- end
27
+ end
@@ -4,6 +4,7 @@ class <%= migration_name %> < ActiveRecord::Migration
4
4
  add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_name, :string
5
5
  add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_content_type, :string
6
6
  add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_size, :integer
7
+ add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_updated_at, :datetime
7
8
  <% end -%>
8
9
  end
9
10
 
@@ -12,6 +13,7 @@ class <%= migration_name %> < ActiveRecord::Migration
12
13
  remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_name
13
14
  remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_content_type
14
15
  remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_size
16
+ remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_updated_at
15
17
  <% end -%>
16
18
  end
17
19
  end
data/lib/paperclip.rb CHANGED
@@ -33,10 +33,11 @@ require 'paperclip/thumbnail'
33
33
  require 'paperclip/storage'
34
34
  require 'paperclip/attachment'
35
35
 
36
- # The base module that gets included in ActiveRecord::Base.
36
+ # The base module that gets included in ActiveRecord::Base. See the
37
+ # documentation for Paperclip::ClassMethods for more useful information.
37
38
  module Paperclip
38
39
 
39
- VERSION = "2.1.2"
40
+ VERSION = "2.1.5"
40
41
 
41
42
  class << self
42
43
  # Provides configurability to Paperclip. There are a number of options available, such as:
@@ -57,6 +58,18 @@ module Paperclip
57
58
  File.join(*path)
58
59
  end
59
60
 
61
+ def run cmd, params = "", expected_outcodes = 0
62
+ output = `#{%Q[#{path_for_command(cmd)} #{params} 2>#{bit_bucket}].gsub(/\s+/, " ")}`
63
+ unless [expected_outcodes].flatten.include?($?.exitstatus)
64
+ raise PaperclipCommandLineError, "Error while running #{cmd}"
65
+ end
66
+ output
67
+ end
68
+
69
+ def bit_bucket
70
+ File.exists?("/dev/null") ? "/dev/null" : "NUL"
71
+ end
72
+
60
73
  def included base #:nodoc:
61
74
  base.extend ClassMethods
62
75
  end
@@ -65,6 +78,9 @@ module Paperclip
65
78
  class PaperclipError < StandardError #:nodoc:
66
79
  end
67
80
 
81
+ class PaperclipCommandLineError < StandardError #:nodoc:
82
+ end
83
+
68
84
  class NotIdentifiedByImageMagickError < PaperclipError #:nodoc:
69
85
  end
70
86
 
@@ -82,7 +98,8 @@ module Paperclip
82
98
  # as easily point to a directory served directly through Apache as it can to an action
83
99
  # that can control permissions. You can specify the full domain and path, but usually
84
100
  # just an absolute path is sufficient. The leading slash must be included manually for
85
- # absolute paths. The default value is "/:class/:attachment/:id/:style_:filename". See
101
+ # absolute paths. The default value is
102
+ # "/:class/:attachment/:id/:style_:basename.:extension". See
86
103
  # Paperclip::Attachment#interpolate for more information on variable interpolaton.
87
104
  # :url => "/:attachment/:id/:style_:basename:extension"
88
105
  # :url => "http://some.other.host/stuff/:class/:id_:extension"
@@ -102,24 +119,33 @@ module Paperclip
102
119
  # has_attached_file :avatar, :styles => { :normal => "100x100#" },
103
120
  # :default_style => :normal
104
121
  # user.avatar.url # => "/avatars/23/normal_me.png"
105
- # * +path+: The location of the repository of attachments on disk. This can be coordinated
106
- # with the value of the +url+ option to allow files to be saved into a place where Apache
107
- # can serve them without hitting your app. Defaults to
108
- # ":rails_root/public/:class/:attachment/:id/:style_:filename".
109
- # By default this places the files in the app's public directory which can be served
110
- # directly. If you are using capistrano for deployment, a good idea would be to
111
- # make a symlink to the capistrano-created system directory from inside your app's
112
- # public directory.
113
- # See Paperclip::Attachment#interpolate for more information on variable interpolaton.
114
- # :path => "/var/app/attachments/:class/:id/:style/:filename"
115
122
  # * +whiny_thumbnails+: Will raise an error if Paperclip cannot process thumbnails of an
116
123
  # uploaded image. This will ovrride the global setting for this attachment.
117
124
  # Defaults to true.
125
+ # * +convert_options+: When creating thumbnails, use this free-form options
126
+ # field to pass in various convert command options. Typical options are "-strip" to
127
+ # remove all Exif data from the image (save space for thumbnails and avatars) or
128
+ # "-depth 8" to specify the bit depth of the resulting conversion. See ImageMagick
129
+ # convert documentation for more options: (http://www.imagemagick.org/script/convert.php)
130
+ # Note that this option takes a hash of options, each of which correspond to the style
131
+ # of thumbnail being generated. You can also specify :all as a key, which will apply
132
+ # to all of the thumbnails being generated. If you specify options for the :original,
133
+ # it would be best if you did not specify destructive options, as the intent of keeping
134
+ # the original around is to regenerate all the thumbnails when requirements change.
135
+ # has_attached_file :avatar, :styles => { :large => "300x300", :negative => "100x100" }
136
+ # :convert_options => {
137
+ # :all => "-strip",
138
+ # :negative => "-negate"
139
+ # }
140
+ # * +storage+: Chooses the storage backend where the files will be stored. The current
141
+ # choices are :filesystem and :s3. The default is :filesystem. Make sure you read the
142
+ # documentation for Paperclip::Storage::Filesystem and Paperclip::Storage::S3
143
+ # for backend-specific options.
118
144
  def has_attached_file name, options = {}
119
145
  include InstanceMethods
120
146
 
121
147
  write_inheritable_attribute(:attachment_definitions, {}) if attachment_definitions.nil?
122
- attachment_definitions[name] = {:validations => []}.merge(options)
148
+ attachment_definitions[name] = {:validations => {}}.merge(options)
123
149
 
124
150
  after_save :save_attached_files
125
151
  before_destroy :destroy_attached_files
@@ -134,11 +160,11 @@ module Paperclip
134
160
  end
135
161
 
136
162
  define_method "#{name}?" do
137
- ! attachment_for(name).original_filename.blank?
163
+ attachment_for(name).file?
138
164
  end
139
165
 
140
166
  validates_each(name) do |record, attr, value|
141
- value.send(:flush_errors)
167
+ value.send(:flush_errors) unless value.valid?
142
168
  end
143
169
  end
144
170
 
@@ -149,22 +175,14 @@ module Paperclip
149
175
  # * +greater_than+: equivalent to :in => options[:greater_than]..Infinity
150
176
  # * +message+: error message to display, use :min and :max as replacements
151
177
  def validates_attachment_size name, options = {}
152
- attachment_definitions[name][:validations] << lambda do |attachment, instance|
153
- unless options[:greater_than].nil?
154
- options[:in] = (options[:greater_than]..(1/0)) # 1/0 => Infinity
155
- end
156
- unless options[:less_than].nil?
157
- options[:in] = (0..options[:less_than])
158
- end
159
- unless attachment.original_filename.blank? || options[:in].include?(instance[:"#{name}_file_size"].to_i)
160
- min = options[:in].first
161
- max = options[:in].last
162
-
163
- if options[:message]
164
- options[:message].gsub(/:min/, min.to_s).gsub(/:max/, max.to_s)
165
- else
166
- "file size is not between #{min} and #{max} bytes."
167
- end
178
+ min = options[:greater_than] || (options[:in] && options[:in].first) || 0
179
+ max = options[:less_than] || (options[:in] && options[:in].last) || (1.0/0)
180
+ range = (min..max)
181
+ message = options[:message] || "file size must be between :min and :max bytes."
182
+
183
+ attachment_definitions[name][:validations][:size] = lambda do |attachment, instance|
184
+ if attachment.file? && !range.include?(attachment.size.to_i)
185
+ message.gsub(/:min/, min.to_s).gsub(/:max/, max.to_s)
168
186
  end
169
187
  end
170
188
  end
@@ -176,26 +194,29 @@ module Paperclip
176
194
 
177
195
  # Places ActiveRecord-style validations on the presence of a file.
178
196
  def validates_attachment_presence name, options = {}
179
- attachment_definitions[name][:validations] << lambda do |attachment, instance|
180
- if attachment.original_filename.blank?
181
- options[:message] || "must be set."
182
- end
197
+ message = options[:message] || "must be set."
198
+ attachment_definitions[name][:validations][:presence] = lambda do |attachment, instance|
199
+ message unless attachment.file?
183
200
  end
184
201
  end
185
202
 
186
203
  # Places ActiveRecord-style validations on the content type of the file assigned. The
187
204
  # possible options are:
188
- # * +content_type+: Allowed content types. Can be a single content type or an array. Allows all by default.
205
+ # * +content_type+: Allowed content types. Can be a single content type or an array.
206
+ # Each type can be a String or a Regexp. It should be noted that Internet Explorer uploads
207
+ # files with content_types that you may not expect. For example, JPEG images are given
208
+ # image/pjpeg and PNGs are image/x-png, so keep that in mind when determining how you match.
209
+ # Allows all by default.
189
210
  # * +message+: The message to display when the uploaded file has an invalid content type.
190
211
  def validates_attachment_content_type name, options = {}
191
- attachment_definitions[name][:validations] << lambda do |attachment, instance|
212
+ attachment_definitions[name][:validations][:content_type] = lambda do |attachment, instance|
192
213
  valid_types = [options[:content_type]].flatten
193
214
 
194
- unless attachment.original_filename.nil?
215
+ unless attachment.original_filename.blank?
195
216
  unless options[:content_type].blank?
196
- content_type = instance[:"#{name}_content_type"]
217
+ content_type = attachment.instance_read(:content_type)
197
218
  unless valid_types.any?{|t| t === content_type }
198
- options[:message] || ActiveRecord::Errors.default_error_messages[:inclusion]
219
+ options[:message] || "is not one of the allowed file types."
199
220
  end
200
221
  end
201
222
  end
@@ -222,12 +243,14 @@ module Paperclip
222
243
  end
223
244
 
224
245
  def save_attached_files
246
+ logger.info("[paperclip] Saving attachments.")
225
247
  each_attachment do |name, attachment|
226
248
  attachment.send(:save)
227
249
  end
228
250
  end
229
251
 
230
252
  def destroy_attached_files
253
+ logger.info("[paperclip] Deleting attachments.")
231
254
  each_attachment do |name, attachment|
232
255
  attachment.send(:queue_existing_for_delete)
233
256
  attachment.send(:flush_deletes)
@@ -10,12 +10,12 @@ module Paperclip
10
10
  :styles => {},
11
11
  :default_url => "/:attachment/:style/missing.png",
12
12
  :default_style => :original,
13
- :validations => [],
13
+ :validations => {},
14
14
  :storage => :filesystem
15
15
  }
16
16
  end
17
17
 
18
- attr_reader :name, :instance, :styles, :default_style
18
+ attr_reader :name, :instance, :styles, :default_style, :convert_options
19
19
 
20
20
  # Creates an Attachment object. +name+ is the name of the attachment, +instance+ is the
21
21
  # ActiveRecord object instance it's attached to, and +options+ is the same as the hash
@@ -34,37 +34,59 @@ module Paperclip
34
34
  @default_style = options[:default_style]
35
35
  @storage = options[:storage]
36
36
  @whiny_thumbnails = options[:whiny_thumbnails]
37
+ @convert_options = options[:convert_options] || {}
37
38
  @options = options
38
39
  @queued_for_delete = []
39
40
  @queued_for_write = {}
40
- @errors = []
41
+ @errors = {}
41
42
  @validation_errors = nil
42
43
  @dirty = false
43
44
 
44
45
  normalize_style_definition
45
46
  initialize_storage
47
+
48
+ logger.info("[paperclip] Paperclip attachment #{name} on #{instance.class} initialized.")
46
49
  end
47
50
 
48
51
  # What gets called when you call instance.attachment = File. It clears errors,
49
52
  # assigns attributes, processes the file, and runs validations. It also queues up
50
53
  # the previous file for deletion, to be flushed away on #save of its host.
54
+ # In addition to form uploads, you can also assign another Paperclip attachment:
55
+ # new_user.avatar = old_user.avatar
51
56
  def assign uploaded_file
57
+ %w(file_name).each do |field|
58
+ unless @instance.class.column_names.include?("#{name}_#{field}")
59
+ raise PaperclipError.new("#{@instance.class} model does not have required column '#{name}_#{field}'")
60
+ end
61
+ end
62
+
63
+ if uploaded_file.is_a?(Paperclip::Attachment)
64
+ uploaded_file = uploaded_file.to_file(:original)
65
+ end
66
+
52
67
  return nil unless valid_assignment?(uploaded_file)
68
+ logger.info("[paperclip] Assigning #{uploaded_file.inspect} to #{name}")
53
69
 
70
+ uploaded_file.binmode if uploaded_file.respond_to? :binmode
54
71
  queue_existing_for_delete
55
- @errors = []
72
+ @errors = {}
56
73
  @validation_errors = nil
57
74
 
58
75
  return nil if uploaded_file.nil?
59
76
 
60
- @queued_for_write[:original] = uploaded_file.to_tempfile
61
- @instance[:"#{@name}_file_name"] = uploaded_file.original_filename
62
- @instance[:"#{@name}_content_type"] = uploaded_file.content_type
63
- @instance[:"#{@name}_file_size"] = uploaded_file.size
77
+ logger.info("[paperclip] Writing attributes for #{name}")
78
+ @queued_for_write[:original] = uploaded_file.to_tempfile
79
+ instance_write(:file_name, uploaded_file.original_filename.strip.gsub(/[^\w\d\.\-]+/, '_'))
80
+ instance_write(:content_type, uploaded_file.content_type.to_s.strip)
81
+ instance_write(:file_size, uploaded_file.size.to_i)
82
+ instance_write(:updated_at, Time.now)
64
83
 
65
84
  @dirty = true
66
85
 
67
86
  post_process
87
+
88
+ # Reset the file size if the original file was reprocessed.
89
+ instance_write(:file_size, uploaded_file.size.to_i)
68
90
  ensure
69
91
  validate
70
92
  end
@@ -75,15 +97,16 @@ module Paperclip
75
97
  # This is not recommended if you don't need the security, however, for
76
98
  # performance reasons.
77
99
  def url style = default_style
78
- original_filename.nil? ? interpolate(@default_url, style) : interpolate(@url, style)
100
+ url = original_filename.nil? ? interpolate(@default_url, style) : interpolate(@url, style)
101
+ updated_at ? [url, updated_at].compact.join(url.include?("?") ? "&" : "?") : url
79
102
  end
80
103
 
81
- # Returns the path of the attachment as defined by the :path optionn. If the
104
+ # Returns the path of the attachment as defined by the :path option. If the
82
105
  # file is stored in the filesystem the path refers to the path of the file on
83
- # disk. If the file is stored in S3, the path is the "key" part of th URL,
106
+ # disk. If the file is stored in S3, the path is the "key" part of the URL,
84
107
  # and the :bucket option refers to the S3 bucket.
85
108
  def path style = nil #:nodoc:
86
- interpolate(@path, style)
109
+ original_filename.nil? ? nil : interpolate(@path, style)
87
110
  end
88
111
 
89
112
  # Alias to +url+
@@ -91,14 +114,15 @@ module Paperclip
91
114
  url(style)
92
115
  end
93
116
 
94
- # Returns true if there are any errors on this attachment.
117
+ # Returns true if there are no errors on this attachment.
95
118
  def valid?
96
- errors.length == 0
119
+ validate
120
+ errors.empty?
97
121
  end
98
122
 
99
123
  # Returns an array containing the errors on this attachment.
100
124
  def errors
101
- @errors.compact.uniq
125
+ @errors
102
126
  end
103
127
 
104
128
  # Returns true if there are changes that need to be saved.
@@ -110,11 +134,13 @@ module Paperclip
110
134
  # the instance's errors and returns false, cancelling the save.
111
135
  def save
112
136
  if valid?
137
+ logger.info("[paperclip] Saving files for #{name}")
113
138
  flush_deletes
114
139
  flush_writes
115
140
  @dirty = false
116
141
  true
117
142
  else
143
+ logger.info("[paperclip] Errors on #{name}. Not saving.")
118
144
  flush_errors
119
145
  false
120
146
  end
@@ -123,7 +149,20 @@ module Paperclip
123
149
  # Returns the name of the file as originally assigned, and as lives in the
124
150
  # <attachment>_file_name attribute of the model.
125
151
  def original_filename
126
- instance[:"#{name}_file_name"]
152
+ instance_read(:file_name)
153
+ end
154
+
155
+ def size
156
+ instance_read(:file_size) || (@queued_for_write[:original] && @queued_for_write[:original].size)
157
+ end
158
+
159
+ def content_type
160
+ instance_read(:content_type)
161
+ end
162
+
163
+ def updated_at
164
+ time = instance_read(:updated_at)
165
+ time && time.to_i
127
166
  end
128
167
 
129
168
  # A hash of procs that are run during the interpolation of a path or url.
@@ -134,11 +173,12 @@ module Paperclip
134
173
  def self.interpolations
135
174
  @interpolations ||= {
136
175
  :rails_root => lambda{|attachment,style| RAILS_ROOT },
176
+ :rails_env => lambda{|attachment,style| RAILS_ENV },
137
177
  :class => lambda do |attachment,style|
138
178
  attachment.instance.class.name.underscore.pluralize
139
179
  end,
140
180
  :basename => lambda do |attachment,style|
141
- attachment.original_filename.gsub(File.extname(attachment.original_filename), "")
181
+ attachment.original_filename.gsub(/#{File.extname(attachment.original_filename)}$/, "")
142
182
  end,
143
183
  :extension => lambda do |attachment,style|
144
184
  ((style = attachment.styles[style]) && style.last) ||
@@ -159,29 +199,58 @@ module Paperclip
159
199
  # again.
160
200
  def reprocess!
161
201
  new_original = Tempfile.new("paperclip-reprocess")
162
- old_original = to_file(:original)
163
- new_original.write( old_original.read )
164
- new_original.rewind
202
+ if old_original = to_file(:original)
203
+ new_original.write( old_original.read )
204
+ new_original.rewind
165
205
 
166
- @queued_for_write = { :original => new_original }
167
- post_process
206
+ @queued_for_write = { :original => new_original }
207
+ post_process
208
+
209
+ old_original.close if old_original.respond_to?(:close)
168
210
 
169
- old_original.close if old_original.respond_to?(:close)
211
+ save
212
+ else
213
+ true
214
+ end
215
+ end
216
+
217
+ def file?
218
+ !original_filename.blank?
219
+ end
220
+
221
+ def instance_write(attr, value)
222
+ setter = :"#{name}_#{attr}="
223
+ responds = instance.respond_to?(setter)
224
+ instance.send(setter, value) if responds || attr.to_s == "file_name"
225
+ end
226
+
227
+ def instance_read(attr)
228
+ getter = :"#{name}_#{attr}"
229
+ responds = instance.respond_to?(getter)
230
+ instance.send(getter) if responds || attr.to_s == "file_name"
170
231
  end
171
232
 
172
233
  private
173
234
 
235
+ def logger
236
+ instance.logger
237
+ end
238
+
174
239
  def valid_assignment? file #:nodoc:
175
240
  file.nil? || (file.respond_to?(:original_filename) && file.respond_to?(:content_type))
176
241
  end
177
242
 
178
243
  def validate #:nodoc:
179
244
  unless @validation_errors
180
- @validation_errors = @validations.collect do |v|
181
- v.call(self, instance)
182
- end.flatten.compact.uniq
183
- @errors += @validation_errors
245
+ @validation_errors = @validations.inject({}) do |errors, validation|
246
+ name, block = validation
247
+ errors[name] = block.call(self, instance) if block
248
+ errors
249
+ end
250
+ @validation_errors.reject!{|k,v| v == nil }
251
+ @errors.merge!(@validation_errors)
184
252
  end
253
+ @validation_errors
185
254
  end
186
255
 
187
256
  def normalize_style_definition
@@ -197,17 +266,24 @@ module Paperclip
197
266
  self.extend(@storage_module)
198
267
  end
199
268
 
269
+ def extra_options_for(style) #:nodoc:
270
+ [ convert_options[style], convert_options[:all] ].compact.join(" ")
271
+ end
272
+
200
273
  def post_process #:nodoc:
201
274
  return if @queued_for_write[:original].nil?
275
+ logger.info("[paperclip] Post-processing #{name}")
202
276
  @styles.each do |name, args|
203
277
  begin
204
278
  dimensions, format = args
279
+ dimensions = dimensions.call(instance) if dimensions.respond_to? :call
205
280
  @queued_for_write[name] = Thumbnail.make(@queued_for_write[:original],
206
281
  dimensions,
207
282
  format,
208
- @whiny_thumnails)
283
+ extra_options_for(name),
284
+ @whiny_thumbnails)
209
285
  rescue PaperclipError => e
210
- @errors << e.message if @whiny_thumbnails
286
+ (@errors[:processing] ||= []) << e.message if @whiny_thumbnails
211
287
  end
212
288
  end
213
289
  end
@@ -223,18 +299,20 @@ module Paperclip
223
299
  end
224
300
 
225
301
  def queue_existing_for_delete #:nodoc:
226
- return if original_filename.blank?
302
+ return unless file?
303
+ logger.info("[paperclip] Queueing the existing files for #{name} for deletion.")
227
304
  @queued_for_delete += [:original, *@styles.keys].uniq.map do |style|
228
305
  path(style) if exists?(style)
229
306
  end.compact
230
- @instance[:"#{@name}_file_name"] = nil
231
- @instance[:"#{@name}_content_type"] = nil
232
- @instance[:"#{@name}_file_size"] = nil
307
+ instance_write(:file_name, nil)
308
+ instance_write(:content_type, nil)
309
+ instance_write(:file_size, nil)
310
+ instance_write(:updated_at, nil)
233
311
  end
234
312
 
235
313
  def flush_errors #:nodoc:
236
- @errors.each do |error|
237
- instance.errors.add(name, error)
314
+ @errors.each do |error, message|
315
+ instance.errors.add(name, message) if message
238
316
  end
239
317
  end
240
318