tristandunn-paperclip 2.3.1 → 2.3.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -60,7 +60,7 @@ exclude_file_globs = ["test/s3.yml",
60
60
  "test/tmp",
61
61
  "test/tmp/*"]
62
62
  spec = Gem::Specification.new do |s|
63
- s.name = "paperclip"
63
+ s.name = "tristandunn-paperclip"
64
64
  s.version = Paperclip::VERSION
65
65
  s.author = "Jon Yurek"
66
66
  s.email = "jyurek@thoughtbot.com"
@@ -75,8 +75,12 @@ spec = Gem::Specification.new do |s|
75
75
  s.extra_rdoc_files = FileList["README*"].to_a
76
76
  s.rdoc_options << '--line-numbers' << '--inline-source'
77
77
  s.requirements << "ImageMagick"
78
- s.add_development_dependency 'thoughtbot-shoulda'
79
- s.add_development_dependency 'mocha'
78
+ s.add_development_dependency 'shoulda'
79
+ s.add_development_dependency 'jferris-mocha', '>= 0.9.5.0.1241126838'
80
+ s.add_development_dependency 'aws-s3'
81
+ s.add_development_dependency 'sqlite3-ruby'
82
+ s.add_development_dependency 'activerecord'
83
+ s.add_development_dependency 'activesupport'
80
84
  end
81
85
 
82
86
  desc "Print a list of the files to be put into the gem"
data/lib/paperclip.rb CHANGED
@@ -25,6 +25,7 @@
25
25
  #
26
26
  # See the +has_attached_file+ documentation for more details.
27
27
 
28
+ require 'erb'
28
29
  require 'tempfile'
29
30
  require 'paperclip/upfile'
30
31
  require 'paperclip/iostream'
@@ -33,6 +34,7 @@ require 'paperclip/processor'
33
34
  require 'paperclip/thumbnail'
34
35
  require 'paperclip/storage'
35
36
  require 'paperclip/interpolations'
37
+ require 'paperclip/style'
36
38
  require 'paperclip/attachment'
37
39
  if defined? RAILS_ROOT
38
40
  Dir.glob(File.join(File.expand_path(RAILS_ROOT), "lib", "paperclip_processors", "*.rb")).each do |processor|
@@ -44,7 +46,7 @@ end
44
46
  # documentation for Paperclip::ClassMethods for more useful information.
45
47
  module Paperclip
46
48
 
47
- VERSION = "2.3.1"
49
+ VERSION = "2.3.1.1"
48
50
 
49
51
  class << self
50
52
  # Provides configurability to Paperclip. There are a number of options available, such as:
@@ -238,7 +240,7 @@ module Paperclip
238
240
 
239
241
  validates_each(name) do |record, attr, value|
240
242
  attachment = record.attachment_for(name)
241
- attachment.send(:flush_errors) unless attachment.valid?
243
+ attachment.send(:flush_errors)
242
244
  end
243
245
  end
244
246
 
@@ -256,11 +258,13 @@ module Paperclip
256
258
  max = options[:less_than] || (options[:in] && options[:in].last) || (1.0/0)
257
259
  range = (min..max)
258
260
  message = options[:message] || "file size must be between :min and :max bytes."
261
+ message = message.gsub(/:min/, min.to_s).gsub(/:max/, max.to_s)
259
262
 
260
- attachment_definitions[name][:validations] << [:size, {:range => range,
261
- :message => message,
262
- :if => options[:if],
263
- :unless => options[:unless]}]
263
+ validates_inclusion_of :"#{name}_file_size",
264
+ :in => range,
265
+ :message => message,
266
+ :if => options[:if],
267
+ :unless => options[:unless]
264
268
  end
265
269
 
266
270
  # Adds errors if thumbnail creation fails. The same as specifying :whiny_thumbnails => true.
@@ -278,9 +282,10 @@ module Paperclip
278
282
  # * +unless+: Same as +if+ but validates if lambda or method returns false.
279
283
  def validates_attachment_presence name, options = {}
280
284
  message = options[:message] || "must be set."
281
- attachment_definitions[name][:validations] << [:presence, {:message => message,
282
- :if => options[:if],
283
- :unless => options[:unless]}]
285
+ validates_presence_of :"#{name}_file_name",
286
+ :message => message,
287
+ :if => options[:if],
288
+ :unless => options[:unless]
284
289
  end
285
290
 
286
291
  # Places ActiveRecord-style validations on the content type of the file
@@ -300,10 +305,12 @@ module Paperclip
300
305
  # model, content_type validation will work _ONLY upon assignment_ and
301
306
  # re-validation after the instance has been reloaded will always succeed.
302
307
  def validates_attachment_content_type name, options = {}
303
- attachment_definitions[name][:validations] << [:content_type, {:content_type => options[:content_type],
304
- :message => options[:message],
305
- :if => options[:if],
306
- :unless => options[:unless]}]
308
+ types = [options.delete(:content_type)].flatten
309
+ validates_each(:"#{name}_content_type", options) do |record, attr, value|
310
+ unless types.any?{|t| t === value }
311
+ record.errors.add(:"#{name}_content_type", :inclusion, :default => options[:message], :value => value)
312
+ end
313
+ end
307
314
  end
308
315
 
309
316
  # Returns the attachment definitions defined by each call to
@@ -7,18 +7,19 @@ module Paperclip
7
7
 
8
8
  def self.default_options
9
9
  @default_options ||= {
10
- :url => "/system/:attachment/:id/:style/:filename",
11
- :path => ":rails_root/public:url",
12
- :styles => {},
13
- :default_url => "/:attachment/:style/missing.png",
14
- :default_style => :original,
15
- :validations => [],
16
- :storage => :filesystem,
17
- :whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails]
10
+ :url => "/system/:attachment/:id/:style/:filename",
11
+ :path => ":rails_root/public:url",
12
+ :styles => {},
13
+ :processors => [:thumbnail],
14
+ :convert_options => {},
15
+ :default_url => "/:attachment/:style/missing.png",
16
+ :default_style => :original,
17
+ :storage => :filesystem,
18
+ :whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails]
18
19
  }
19
20
  end
20
21
 
21
- attr_reader :name, :instance, :styles, :default_style, :convert_options, :queued_for_write, :options
22
+ attr_reader :name, :instance, :default_style, :convert_options, :queued_for_write, :whiny, :options
22
23
 
23
24
  # Creates an Attachment object. +name+ is the name of the attachment,
24
25
  # +instance+ is the ActiveRecord object instance it's attached to, and
@@ -34,33 +35,42 @@ module Paperclip
34
35
  @path = options[:path]
35
36
  @path = @path.call(self) if @path.is_a?(Proc)
36
37
  @styles = options[:styles]
37
- @styles = @styles.call(self) if @styles.is_a?(Proc)
38
+ @normalized_styles = nil
38
39
  @default_url = options[:default_url]
39
- @validations = options[:validations]
40
40
  @default_style = options[:default_style]
41
41
  @storage = options[:storage]
42
42
  @whiny = options[:whiny_thumbnails] || options[:whiny]
43
- @convert_options = options[:convert_options] || {}
44
- @processors = options[:processors] || [:thumbnail]
43
+ @convert_options = options[:convert_options]
44
+ @processors = options[:processors]
45
45
  @options = options
46
46
  @queued_for_delete = []
47
47
  @queued_for_write = {}
48
48
  @errors = {}
49
- @validation_errors = nil
50
49
  @dirty = false
51
50
 
52
- normalize_style_definition
53
51
  initialize_storage
54
52
  end
53
+
54
+ def styles
55
+ unless @normalized_styles
56
+ @normalized_styles = {}
57
+ (@styles.respond_to?(:call) ? @styles.call(self) : @styles).each do |name, args|
58
+ @normalized_styles[name] = Paperclip::Style.new(name, args, self)
59
+ end
60
+ end
61
+ @normalized_styles
62
+ end
63
+
64
+ def processors
65
+ @processors.respond_to?(:call) ? @processors.call(instance) : @processors
66
+ end
55
67
 
56
68
  # What gets called when you call instance.attachment = File. It clears
57
- # errors, assigns attributes, processes the file, and runs validations. It
69
+ # errors, assigns attributes, and processes the file. It
58
70
  # also queues up the previous file for deletion, to be flushed away on
59
71
  # #save of its host. In addition to form uploads, you can also assign
60
72
  # another Paperclip attachment:
61
73
  # new_user.avatar = old_user.avatar
62
- # If the file that is assigned is not valid, the processing (i.e.
63
- # thumbnailing, etc) will NOT be run.
64
74
  def assign uploaded_file
65
75
  ensure_required_accessors!
66
76
 
@@ -84,13 +94,12 @@ module Paperclip
84
94
 
85
95
  @dirty = true
86
96
 
87
- post_process if valid?
97
+ post_process
88
98
 
89
99
  # Reset the file size if the original file was reprocessed.
90
100
  instance_write(:file_size, @queued_for_write[:original].size.to_i)
91
101
  ensure
92
102
  uploaded_file.close if close_uploaded_file
93
- validate
94
103
  end
95
104
 
96
105
  # Returns the public URL of the attachment, with a given style. Note that
@@ -100,8 +109,8 @@ module Paperclip
100
109
  # security, however, for performance reasons. set
101
110
  # include_updated_timestamp to false if you want to stop the attachment
102
111
  # update time appended to the url
103
- def url style = default_style, include_updated_timestamp = true
104
- url = original_filename.nil? ? interpolate(@default_url, style) : interpolate(@url, style)
112
+ def url style_name = default_style, include_updated_timestamp = true
113
+ url = original_filename.nil? ? interpolate(@default_url, style_name) : interpolate(@url, style_name)
105
114
  include_updated_timestamp && updated_at ? [url, updated_at].compact.join(url.include?("?") ? "&" : "?") : url
106
115
  end
107
116
 
@@ -109,19 +118,13 @@ module Paperclip
109
118
  # file is stored in the filesystem the path refers to the path of the file
110
119
  # on disk. If the file is stored in S3, the path is the "key" part of the
111
120
  # URL, and the :bucket option refers to the S3 bucket.
112
- def path style = default_style
113
- original_filename.nil? ? nil : interpolate(@path, style)
121
+ def path style_name = default_style
122
+ original_filename.nil? ? nil : interpolate(@path, style_name)
114
123
  end
115
124
 
116
125
  # Alias to +url+
117
- def to_s style = nil
118
- url(style)
119
- end
120
-
121
- # Returns true if there are no errors on this attachment.
122
- def valid?
123
- validate
124
- errors.empty?
126
+ def to_s style_name = nil
127
+ url(style_name)
125
128
  end
126
129
 
127
130
  # Returns an array containing the errors on this attachment.
@@ -137,15 +140,10 @@ module Paperclip
137
140
  # Saves the file, if there are no errors. If there are, it flushes them to
138
141
  # the instance's errors and returns false, cancelling the save.
139
142
  def save
140
- if valid?
141
- flush_deletes
142
- flush_writes
143
- @dirty = false
144
- true
145
- else
146
- flush_errors
147
- false
148
- end
143
+ flush_deletes
144
+ flush_writes
145
+ @dirty = false
146
+ true
149
147
  end
150
148
 
151
149
  # Clears out the attachment. Has the same effect as previously assigning
@@ -154,7 +152,6 @@ module Paperclip
154
152
  def clear
155
153
  queue_existing_for_delete
156
154
  @errors = {}
157
- @validation_errors = nil
158
155
  end
159
156
 
160
157
  # Destroys the attachment. Has the same effect as previously assigning
@@ -187,7 +184,7 @@ module Paperclip
187
184
  # lives in the <attachment>_updated_at attribute of the model.
188
185
  def updated_at
189
186
  time = instance_read(:updated_at)
190
- time && time.to_i
187
+ time && time.to_f.to_i
191
188
  end
192
189
 
193
190
  # Paths and URLs can have a number of variables interpolated into them
@@ -267,82 +264,6 @@ module Paperclip
267
264
  file.nil? || (file.respond_to?(:original_filename) && file.respond_to?(:content_type))
268
265
  end
269
266
 
270
- def validate #:nodoc:
271
- unless @validation_errors
272
- @validation_errors = @validations.inject({}) do |errors, validation|
273
- name, options = validation
274
- errors[name] = send(:"validate_#{name}", options) if allow_validation?(options)
275
- errors
276
- end
277
- @validation_errors.reject!{|k,v| v == nil }
278
- @errors.merge!(@validation_errors)
279
- end
280
- @validation_errors
281
- end
282
-
283
- def allow_validation? options #:nodoc:
284
- (options[:if].nil? || check_guard(options[:if])) && (options[:unless].nil? || !check_guard(options[:unless]))
285
- end
286
-
287
- def check_guard guard #:nodoc:
288
- if guard.respond_to? :call
289
- guard.call(instance)
290
- elsif ! guard.blank?
291
- instance.send(guard.to_s)
292
- end
293
- end
294
-
295
- def validate_size options #:nodoc:
296
- if file? && !options[:range].include?(size.to_i)
297
- options[:message].gsub(/:min/, options[:min].to_s).gsub(/:max/, options[:max].to_s)
298
- end
299
- end
300
-
301
- def validate_presence options #:nodoc:
302
- options[:message] unless file?
303
- end
304
-
305
- def validate_content_type options #:nodoc:
306
- valid_types = [options[:content_type]].flatten
307
- unless original_filename.blank?
308
- unless valid_types.blank?
309
- content_type = instance_read(:content_type)
310
- unless valid_types.any?{|t| content_type.nil? || t === content_type }
311
- options[:message] || "is not one of the allowed file types."
312
- end
313
- end
314
- end
315
- end
316
-
317
- def normalize_style_definition #:nodoc:
318
- @styles.each do |name, args|
319
- unless args.is_a? Hash
320
- dimensions, format = [args, nil].flatten[0..1]
321
- format = nil if format.blank?
322
- @styles[name] = {
323
- :processors => @processors,
324
- :geometry => dimensions,
325
- :format => format,
326
- :whiny => @whiny,
327
- :convert_options => extra_options_for(name)
328
- }
329
- else
330
- @styles[name] = {
331
- :processors => @processors,
332
- :whiny => @whiny,
333
- :convert_options => extra_options_for(name)
334
- }.merge(@styles[name])
335
- end
336
- end
337
- end
338
-
339
- def solidify_style_definitions #:nodoc:
340
- @styles.each do |name, args|
341
- @styles[name][:geometry] = @styles[name][:geometry].call(instance) if @styles[name][:geometry].respond_to?(:call)
342
- @styles[name][:processors] = @styles[name][:processors].call(instance) if @styles[name][:processors].respond_to?(:call)
343
- end
344
- end
345
-
346
267
  def initialize_storage #:nodoc:
347
268
  @storage_module = Paperclip::Storage.const_get(@storage.to_s.capitalize)
348
269
  self.extend(@storage_module)
@@ -359,7 +280,6 @@ module Paperclip
359
280
 
360
281
  def post_process #:nodoc:
361
282
  return if @queued_for_write[:original].nil?
362
- solidify_style_definitions
363
283
  return if fire_events(:before)
364
284
  post_process_styles
365
285
  return if fire_events(:after)
@@ -375,11 +295,11 @@ module Paperclip
375
295
  end
376
296
 
377
297
  def post_process_styles #:nodoc:
378
- @styles.each do |name, args|
298
+ styles.each do |name, style|
379
299
  begin
380
- raise RuntimeError.new("Style #{name} has no processors defined.") if args[:processors].blank?
381
- @queued_for_write[name] = args[:processors].inject(@queued_for_write[:original]) do |file, processor|
382
- Paperclip.processor(processor).make(file, args, self)
300
+ raise RuntimeError.new("Style #{name} has no processors defined.") if style.processors.blank?
301
+ @queued_for_write[name] = style.processors.inject(@queued_for_write[:original]) do |file, processor|
302
+ Paperclip.processor(processor).make(file, style.processor_options, self)
383
303
  end
384
304
  rescue PaperclipError => e
385
305
  log("An error was received while processing: #{e.inspect}")
@@ -388,13 +308,13 @@ module Paperclip
388
308
  end
389
309
  end
390
310
 
391
- def interpolate pattern, style = default_style #:nodoc:
392
- Paperclip::Interpolations.interpolate(pattern, self, style)
311
+ def interpolate pattern, style_name = default_style #:nodoc:
312
+ Paperclip::Interpolations.interpolate(pattern, self, style_name)
393
313
  end
394
314
 
395
315
  def queue_existing_for_delete #:nodoc:
396
316
  return unless file?
397
- @queued_for_delete += [:original, *@styles.keys].uniq.map do |style|
317
+ @queued_for_delete += [:original, *styles.keys].uniq.map do |style|
398
318
  path(style) if exists?(style)
399
319
  end.compact
400
320
  instance_write(:file_name, nil)
@@ -34,72 +34,75 @@ module Paperclip
34
34
  end
35
35
 
36
36
  # Returns the filename, the same way as ":basename.:extension" would.
37
- def filename attachment, style
38
- "#{basename(attachment, style)}.#{extension(attachment, style)}"
37
+ def filename attachment, style_name
38
+ "#{basename(attachment, style_name)}.#{extension(attachment, style_name)}"
39
39
  end
40
40
 
41
41
  # Returns the interpolated URL. Will raise an error if the url itself
42
42
  # contains ":url" to prevent infinite recursion. This interpolation
43
43
  # is used in the default :path to ease default specifications.
44
- def url attachment, style
44
+ def url attachment, style_name
45
45
  raise InfiniteInterpolationError if attachment.options[:url].include?(":url")
46
- attachment.url(style, false)
46
+ attachment.url(style_name, false)
47
47
  end
48
48
 
49
49
  # Returns the timestamp as defined by the <attachment>_updated_at field
50
- def timestamp attachment, style
50
+ def timestamp attachment, style_name
51
51
  attachment.instance_read(:updated_at).to_s
52
52
  end
53
53
 
54
54
  # Returns the RAILS_ROOT constant.
55
- def rails_root attachment, style
55
+ def rails_root attachment, style_name
56
56
  RAILS_ROOT
57
57
  end
58
58
 
59
59
  # Returns the RAILS_ENV constant.
60
- def rails_env attachment, style
60
+ def rails_env attachment, style_name
61
61
  RAILS_ENV
62
62
  end
63
63
 
64
64
  # Returns the underscored, pluralized version of the class name.
65
65
  # e.g. "users" for the User class.
66
- def class attachment, style
66
+ # NOTE: The arguments need to be optional, because some tools fetch
67
+ # all class names. Calling #class will return the expected class.
68
+ def class attachment = nil, style_name = nil
69
+ return super() if attachment.nil? && style_name.nil?
67
70
  attachment.instance.class.to_s.underscore.pluralize
68
71
  end
69
72
 
70
73
  # Returns the basename of the file. e.g. "file" for "file.jpg"
71
- def basename attachment, style
74
+ def basename attachment, style_name
72
75
  attachment.original_filename.gsub(/#{File.extname(attachment.original_filename)}$/, "")
73
76
  end
74
77
 
75
78
  # Returns the extension of the file. e.g. "jpg" for "file.jpg"
76
79
  # If the style has a format defined, it will return the format instead
77
80
  # of the actual extension.
78
- def extension attachment, style
79
- ((style = attachment.styles[style]) && style[:format]) ||
81
+ def extension attachment, style_name
82
+ ((style = attachment.styles[style_name]) && style[:format]) ||
80
83
  File.extname(attachment.original_filename).gsub(/^\.+/, "")
81
84
  end
82
85
 
83
86
  # Returns the id of the instance.
84
- def id attachment, style
87
+ def id attachment, style_name
85
88
  attachment.instance.id
86
89
  end
87
90
 
88
91
  # Returns the id of the instance in a split path form. e.g. returns
89
92
  # 000/001/234 for an id of 1234.
90
- def id_partition attachment, style
93
+ def id_partition attachment, style_name
91
94
  ("%09d" % attachment.instance.id).scan(/\d{3}/).join("/")
92
95
  end
93
96
 
94
97
  # Returns the pluralized form of the attachment name. e.g.
95
98
  # "avatars" for an attachment of :avatar
96
- def attachment attachment, style
99
+ def attachment attachment, style_name
97
100
  attachment.name.to_s.downcase.pluralize
98
101
  end
99
102
 
100
103
  # Returns the style, or the default style if nil is supplied.
101
- def style attachment, style
102
- style || attachment.default_style
104
+ def style attachment, style_name
105
+ style_name || attachment.default_style
103
106
  end
104
107
  end
105
108
  end