paperclip 2.4.2 → 2.4.3

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/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'rubygems'
2
- require 'appraisal'
3
2
  require 'bundler/setup'
3
+ require 'appraisal'
4
4
 
5
5
  require 'rake'
6
6
  require 'rake/testtask'
@@ -28,6 +28,7 @@
28
28
  require 'erb'
29
29
  require 'digest'
30
30
  require 'tempfile'
31
+ require 'paperclip/options'
31
32
  require 'paperclip/version'
32
33
  require 'paperclip/upfile'
33
34
  require 'paperclip/iostream'
@@ -137,8 +138,10 @@ module Paperclip
137
138
  @known_processors[name.to_s] = processor
138
139
  end
139
140
 
141
+ # Find all instances of the given Active Record model +klass+ with attachment +name+.
142
+ # This method is used by the refresh rake tasks.
140
143
  def each_instance_with_attachment(klass, name)
141
- class_for(klass).all.each do |instance|
144
+ class_for(klass).find(:all, :order => 'id').each do |instance|
142
145
  yield(instance) if instance.send(:"#{name}?")
143
146
  end
144
147
  end
@@ -317,7 +320,7 @@ module Paperclip
317
320
  end
318
321
 
319
322
  attachment_definitions[name] = {:validations => []}.merge(options)
320
- Paperclip.classes_with_attachments << self unless Paperclip.classes_with_attachments.include?(self)
323
+ Paperclip.classes_with_attachments << self.name
321
324
  Paperclip.check_for_url_clash(name,attachment_definitions[name][:url],self.name)
322
325
 
323
326
  after_save :save_attached_files
@@ -29,7 +29,7 @@ module Paperclip
29
29
  }
30
30
  end
31
31
 
32
- attr_reader :name, :instance, :default_style, :convert_options, :queued_for_write, :whiny, :options, :source_file_options, :interpolator
32
+ attr_reader :name, :instance, :default_style, :convert_options, :queued_for_write, :whiny, :options, :interpolator
33
33
  attr_accessor :post_processing
34
34
 
35
35
  # Creates an Attachment object. +name+ is the name of the attachment,
@@ -62,27 +62,7 @@ module Paperclip
62
62
 
63
63
  options = self.class.default_options.merge(options)
64
64
 
65
- @url = options[:url]
66
- @url = @url.call(self) if @url.is_a?(Proc)
67
- @path = options[:path]
68
- @path = @path.call(self) if @path.is_a?(Proc)
69
- @styles = options[:styles]
70
- @only_process = options[:only_process]
71
- @normalized_styles = nil
72
- @default_url = options[:default_url]
73
- @default_style = options[:default_style]
74
- @storage = options[:storage]
75
- @use_timestamp = options[:use_timestamp]
76
- @whiny = options[:whiny_thumbnails] || options[:whiny]
77
- @use_default_time_zone = options[:use_default_time_zone]
78
- @hash_digest = options[:hash_digest]
79
- @hash_data = options[:hash_data]
80
- @hash_secret = options[:hash_secret]
81
- @convert_options = options[:convert_options]
82
- @source_file_options = options[:source_file_options]
83
- @processors = options[:processors]
84
- @preserve_files = options[:preserve_files]
85
- @options = options
65
+ @options = Paperclip::Options.new(self, options)
86
66
  @post_processing = true
87
67
  @queued_for_delete = []
88
68
  @queued_for_write = {}
@@ -93,19 +73,13 @@ module Paperclip
93
73
  initialize_storage
94
74
  end
95
75
 
96
- def styles
97
- if @styles.respond_to?(:call) || !@normalized_styles
98
- @normalized_styles = ActiveSupport::OrderedHash.new
99
- (@styles.respond_to?(:call) ? @styles.call(self) : @styles).each do |name, args|
100
- @normalized_styles[name] = Paperclip::Style.new(name, args.dup, self)
101
- end
102
- end
103
- @normalized_styles
104
- end
105
-
106
- def processors
107
- @processors.respond_to?(:call) ? @processors.call(instance) : @processors
108
- end
76
+ # [:url, :path, :only_process, :normalized_styles, :default_url, :default_style,
77
+ # :storage, :use_timestamp, :whiny, :use_default_time_zone, :hash_digest, :hash_secret,
78
+ # :convert_options, :preserve_files].each do |field|
79
+ # define_method field do
80
+ # @options.send(field)
81
+ # end
82
+ # end
109
83
 
110
84
  # What gets called when you call instance.attachment = File. It clears
111
85
  # errors, assigns attributes, and processes the file. It
@@ -139,7 +113,7 @@ module Paperclip
139
113
 
140
114
  @dirty = true
141
115
 
142
- post_process(*@only_process) if post_processing
116
+ post_process(*@options.only_process) if post_processing
143
117
 
144
118
  # Reset the file size if the original file was reprocessed.
145
119
  instance_write(:file_size, @queued_for_write[:original].size.to_i)
@@ -154,9 +128,9 @@ module Paperclip
154
128
  # grained security. This is not recommended if you don't need the
155
129
  # security, however, for performance reasons. Set use_timestamp to false
156
130
  # if you want to stop the attachment update time appended to the url
157
- def url(style_name = default_style, use_timestamp = @use_timestamp)
158
- default_url = @default_url.is_a?(Proc) ? @default_url.call(self) : @default_url
159
- url = original_filename.nil? ? interpolate(default_url, style_name) : interpolate(@url, style_name)
131
+ def url(style_name = default_style, use_timestamp = @options.use_timestamp)
132
+ default_url = @options.default_url.is_a?(Proc) ? @options.default_url.call(self) : @options.default_url
133
+ url = original_filename.nil? ? interpolate(default_url, style_name) : interpolate(@options.url, style_name)
160
134
  URI.escape(use_timestamp && updated_at ? [url, updated_at].compact.join(url.include?("?") ? "&" : "?") : url)
161
135
  end
162
136
 
@@ -165,7 +139,7 @@ module Paperclip
165
139
  # on disk. If the file is stored in S3, the path is the "key" part of the
166
140
  # URL, and the :bucket option refers to the S3 bucket.
167
141
  def path(style_name = default_style)
168
- original_filename.nil? ? nil : interpolate(@path, style_name)
142
+ original_filename.nil? ? nil : interpolate(@options.path, style_name)
169
143
  end
170
144
 
171
145
  # Alias to +url+
@@ -173,6 +147,14 @@ module Paperclip
173
147
  url(style_name)
174
148
  end
175
149
 
150
+ def default_style
151
+ @options.default_style
152
+ end
153
+
154
+ def styles
155
+ @options.styles
156
+ end
157
+
176
158
  # Returns an array containing the errors on this attachment.
177
159
  def errors
178
160
  @errors
@@ -205,7 +187,7 @@ module Paperclip
205
187
  # nil to the attachment *and saving*. This is permanent. If you wish to
206
188
  # wipe out the existing attachment but not save, use #clear.
207
189
  def destroy
208
- unless @preserve_files
190
+ unless @options.preserve_files
209
191
  clear
210
192
  save
211
193
  end
@@ -245,16 +227,16 @@ module Paperclip
245
227
  # The time zone to use for timestamp interpolation. Using the default
246
228
  # time zone ensures that results are consistent across all threads.
247
229
  def time_zone
248
- @use_default_time_zone ? Time.zone_default : Time.zone
230
+ @options.use_default_time_zone ? Time.zone_default : Time.zone
249
231
  end
250
232
 
251
233
  # Returns a unique hash suitable for obfuscating the URL of an otherwise
252
234
  # publicly viewable attachment.
253
235
  def hash(style_name = default_style)
254
- raise ArgumentError, "Unable to generate hash without :hash_secret" unless @hash_secret
236
+ raise ArgumentError, "Unable to generate hash without :hash_secret" unless @options.hash_secret
255
237
  require 'openssl' unless defined?(OpenSSL)
256
- data = interpolate(@hash_data, style_name)
257
- OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@hash_digest).new, @hash_secret, data)
238
+ data = interpolate(@options.hash_data, style_name)
239
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@options.hash_digest).new, @options.hash_secret, data)
258
240
  end
259
241
 
260
242
  def generate_fingerprint(source)
@@ -352,28 +334,28 @@ module Paperclip
352
334
  end
353
335
 
354
336
  def initialize_storage #:nodoc:
355
- storage_class_name = @storage.to_s.downcase.camelize
337
+ storage_class_name = @options.storage.to_s.downcase.camelize
356
338
  begin
357
- @storage_module = Paperclip::Storage.const_get(storage_class_name)
339
+ storage_module = Paperclip::Storage.const_get(storage_class_name)
358
340
  rescue NameError
359
341
  raise StorageMethodNotFound, "Cannot load storage module '#{storage_class_name}'"
360
342
  end
361
- self.extend(@storage_module)
343
+ self.extend(storage_module)
362
344
  end
363
345
 
364
346
  def extra_options_for(style) #:nodoc:
365
- all_options = convert_options[:all]
347
+ all_options = @options.convert_options[:all]
366
348
  all_options = all_options.call(instance) if all_options.respond_to?(:call)
367
- style_options = convert_options[style]
349
+ style_options = @options.convert_options[style]
368
350
  style_options = style_options.call(instance) if style_options.respond_to?(:call)
369
351
 
370
352
  [ style_options, all_options ].compact.join(" ")
371
353
  end
372
354
 
373
355
  def extra_source_file_options_for(style) #:nodoc:
374
- all_options = source_file_options[:all]
356
+ all_options = @options.source_file_options[:all]
375
357
  all_options = all_options.call(instance) if all_options.respond_to?(:call)
376
- style_options = source_file_options[style]
358
+ style_options = @options.source_file_options[style]
377
359
  style_options = style_options.call(instance) if style_options.respond_to?(:call)
378
360
 
379
361
  [ style_options, all_options ].compact.join(" ")
@@ -389,7 +371,7 @@ module Paperclip
389
371
  end
390
372
 
391
373
  def post_process_styles(*style_args) #:nodoc:
392
- styles.each do |name, style|
374
+ @options.styles.each do |name, style|
393
375
  begin
394
376
  if style_args.empty? || style_args.include?(name)
395
377
  raise RuntimeError.new("Style #{name} has no processors defined.") if style.processors.blank?
@@ -399,7 +381,7 @@ module Paperclip
399
381
  end
400
382
  rescue PaperclipError => e
401
383
  log("An error was received while processing: #{e.inspect}")
402
- (@errors[:processing] ||= []) << e.message if @whiny
384
+ (@errors[:processing] ||= []) << e.message if @options.whiny
403
385
  end
404
386
  end
405
387
  end
@@ -409,8 +391,8 @@ module Paperclip
409
391
  end
410
392
 
411
393
  def queue_existing_for_delete #:nodoc:
412
- return unless (file? && @preserve_files==false)
413
- @queued_for_delete += [:original, *styles.keys].uniq.map do |style|
394
+ return if @options.preserve_files || !file?
395
+ @queued_for_delete += [:original, *@options.styles.keys].uniq.map do |style|
414
396
  path(style) if exists?(style)
415
397
  end.compact
416
398
  instance_write(:file_name, nil)
@@ -13,18 +13,20 @@ module Paperclip
13
13
 
14
14
  # Uses ImageMagick to determing the dimensions of a file, passed in as either a
15
15
  # File or path.
16
+ # NOTE: (race cond) Do not reassign the 'file' variable inside this method as it is likely to be
17
+ # a Tempfile object, which would be eligible for file deletion when no longer referenced.
16
18
  def self.from_file file
17
- file = file.path if file.respond_to? "path"
18
- raise(Paperclip::NotIdentifiedByImageMagickError.new("Cannot find the geometry of a file with a blank name")) if file.blank?
19
+ file_path = file.respond_to?(:path) ? file.path : file
20
+ raise(Paperclip::NotIdentifiedByImageMagickError.new("Cannot find the geometry of a file with a blank name")) if file_path.blank?
19
21
  geometry = begin
20
- Paperclip.run("identify", "-format %wx%h :file", :file => "#{file}[0]")
22
+ Paperclip.run("identify", "-format %wx%h :file", :file => "#{file_path}[0]")
21
23
  rescue Cocaine::ExitStatusError
22
24
  ""
23
25
  rescue Cocaine::CommandNotFoundError => e
24
26
  raise Paperclip::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.")
25
27
  end
26
28
  parse(geometry) ||
27
- raise(NotIdentifiedByImageMagickError.new("#{file} is not recognized by the 'identify' command."))
29
+ raise(NotIdentifiedByImageMagickError.new("#{file_path} is not recognized by the 'identify' command."))
28
30
  end
29
31
 
30
32
  # Parses a "WxH" formatted string, where W is the width and H is the height.
@@ -38,7 +38,8 @@ module Paperclip
38
38
  # }
39
39
  def self.current_attachments_styles
40
40
  Hash.new.tap do |current_styles|
41
- Paperclip.classes_with_attachments.each do |klass|
41
+ Paperclip.classes_with_attachments.each do |klass_name|
42
+ klass = Paperclip.class_for(klass_name)
42
43
  klass.attachment_definitions.each do |attachment_name, attachment_attributes|
43
44
  # TODO: is it even possible to take into account Procs?
44
45
  next if attachment_attributes[:styles].kind_of?(Proc)
@@ -0,0 +1,78 @@
1
+ module Paperclip
2
+ class Options
3
+
4
+ attr_accessor :url, :path, :only_process, :normalized_styles, :default_url, :default_style,
5
+ :storage, :use_timestamp, :whiny, :use_default_time_zone, :hash_digest, :hash_secret,
6
+ :convert_options, :source_file_options, :preserve_files, :http_proxy
7
+
8
+ attr_accessor :s3_credentials, :s3_host_name, :s3_options, :s3_permissions, :s3_protocol,
9
+ :s3_headers, :s3_host_alias, :bucket
10
+
11
+ attr_accessor :fog_directory, :fog_credentials, :fog_host, :fog_public, :fog_file
12
+
13
+ def initialize(attachment, hash)
14
+ @attachment = attachment
15
+
16
+ @url = hash[:url]
17
+ @url = @url.call(@attachment) if @url.is_a?(Proc)
18
+ @path = hash[:path]
19
+ @path = @path.call(@attachment) if @path.is_a?(Proc)
20
+ @styles = hash[:styles]
21
+ @only_process = hash[:only_process]
22
+ @normalized_styles = nil
23
+ @default_url = hash[:default_url]
24
+ @default_style = hash[:default_style]
25
+ @storage = hash[:storage]
26
+ @use_timestamp = hash[:use_timestamp]
27
+ @whiny = hash[:whiny_thumbnails] || hash[:whiny]
28
+ @use_default_time_zone = hash[:use_default_time_zone]
29
+ @hash_digest = hash[:hash_digest]
30
+ @hash_data = hash[:hash_data]
31
+ @hash_secret = hash[:hash_secret]
32
+ @convert_options = hash[:convert_options]
33
+ @source_file_options = hash[:source_file_options]
34
+ @processors = hash[:processors]
35
+ @preserve_files = hash[:preserve_files]
36
+ @http_proxy = hash[:http_proxy]
37
+
38
+ #s3 options
39
+ @s3_credentials = hash[:s3_credentials]
40
+ @s3_host_name = hash[:s3_host_name]
41
+ @bucket = hash[:bucket]
42
+ @s3_options = hash[:s3_options]
43
+ @s3_permissions = hash[:s3_permissions]
44
+ @s3_protocol = hash[:s3_protocol]
45
+ @s3_headers = hash[:s3_headers]
46
+ @s3_host_alias = hash[:s3_host_alias]
47
+
48
+ #fog options
49
+ @fog_directory = hash[:fog_directory]
50
+ @fog_credentials = hash[:fog_credentials]
51
+ @fog_host = hash[:fog_host]
52
+ @fog_public = hash[:fog_public]
53
+ @fog_file = hash[:fog_file]
54
+ end
55
+
56
+ def method_missing(method, *args, &blk)
57
+ if method.to_s[-1] == "="
58
+ instance_variable_set("@#{method[0..-2]}", args[0])
59
+ else
60
+ instance_variable_get("@#{method}")
61
+ end
62
+ end
63
+
64
+ def processors
65
+ @processors.respond_to?(:call) ? @processors.call(@attachment.instance) : @processors
66
+ end
67
+
68
+ def styles
69
+ if @styles.respond_to?(:call) || !@normalized_styles
70
+ @normalized_styles = ActiveSupport::OrderedHash.new
71
+ (@styles.respond_to?(:call) ? @styles.call(@attachment) : @styles).each do |name, args|
72
+ normalized_styles[name] = Paperclip::Style.new(name, args.dup, @attachment)
73
+ end
74
+ end
75
+ @normalized_styles
76
+ end
77
+ end
78
+ end
@@ -41,15 +41,9 @@ module Paperclip
41
41
  end unless defined?(Fog)
42
42
 
43
43
  base.instance_eval do
44
- @fog_directory = @options[:fog_directory]
45
- @fog_credentials = parse_credentials(@options[:fog_credentials])
46
- @fog_host = @options[:fog_host]
47
- @fog_public = @options.key?(:fog_public) ? @options[:fog_public] : true
48
- @fog_file = @options[:fog_file] || {}
49
-
50
- unless @url.to_s.match(/^:fog.*url$/)
51
- @path = @path.gsub(/:url/, @url)
52
- @url = ':fog_public_url'
44
+ unless @options.url.to_s.match(/^:fog.*url$/)
45
+ @options.path = @options.path.gsub(/:url/, @options.url)
46
+ @options.url = ':fog_public_url'
53
47
  end
54
48
  Paperclip.interpolates(:fog_public_url) do |attachment, style|
55
49
  attachment.public_url(style)
@@ -65,15 +59,27 @@ module Paperclip
65
59
  end
66
60
  end
67
61
 
62
+ def fog_credentials
63
+ @fog_credentials ||= parse_credentials(@options.fog_credentials)
64
+ end
65
+
66
+ def fog_file
67
+ @fog_file ||= @options.fog_file || {}
68
+ end
69
+
70
+ def fog_public
71
+ @fog_public ||= @options.fog_public || true
72
+ end
73
+
68
74
  def flush_writes
69
75
  for style, file in @queued_for_write do
70
76
  log("saving #{path(style)}")
71
77
  retried = false
72
78
  begin
73
- directory.files.create(@fog_file.merge(
79
+ directory.files.create(fog_file.merge(
74
80
  :body => file,
75
81
  :key => path(style),
76
- :public => @fog_public
82
+ :public => fog_public
77
83
  ))
78
84
  rescue Excon::Errors::NotFound
79
85
  raise if retried
@@ -115,8 +121,8 @@ module Paperclip
115
121
  end
116
122
 
117
123
  def public_url(style = default_style)
118
- if @fog_host
119
- host = (@fog_host =~ /%d/) ? @fog_host % (path(style).hash % 4) : @fog_host
124
+ if @options.fog_host
125
+ host = (@options.fog_host =~ /%d/) ? @options.fog_host % (path(style).hash % 4) : @options.fog_host
120
126
  "#{host}/#{path(style)}"
121
127
  else
122
128
  directory.files.new(:key => path(style)).public_url
@@ -145,11 +151,11 @@ module Paperclip
145
151
  end
146
152
 
147
153
  def connection
148
- @connection ||= ::Fog::Storage.new(@fog_credentials)
154
+ @connection ||= ::Fog::Storage.new(fog_credentials)
149
155
  end
150
156
 
151
157
  def directory
152
- @directory ||= connection.directories.new(:key => @fog_directory)
158
+ @directory ||= connection.directories.new(:key => @options.fog_directory)
153
159
  end
154
160
  end
155
161
  end
@@ -77,33 +77,28 @@ module Paperclip
77
77
  end unless defined?(AWS::S3)
78
78
 
79
79
  base.instance_eval do
80
- @s3_credentials = parse_credentials(@options[:s3_credentials])
81
- @s3_host_name = @options[:s3_host_name] || @s3_credentials[:s3_host_name]
82
- @bucket = @options[:bucket] || @s3_credentials[:bucket]
83
- @bucket = @bucket.call(self) if @bucket.is_a?(Proc)
84
- @s3_options = @options[:s3_options] || {}
85
- @s3_permissions = set_permissions(@options[:s3_permissions])
86
- @s3_protocol = @options[:s3_protocol] ||
80
+ @s3_options = @options.s3_options || {}
81
+ @s3_permissions = set_permissions(@options.s3_permissions)
82
+ @s3_protocol = @options.s3_protocol ||
87
83
  Proc.new do |style|
88
84
  (@s3_permissions[style.to_sym] || @s3_permissions[:default]) == :public_read ? 'http' : 'https'
89
85
  end
90
- @s3_headers = @options[:s3_headers] || {}
91
- @s3_host_alias = @options[:s3_host_alias]
92
- @s3_host_alias = @s3_host_alias.call(self) if @s3_host_alias.is_a?(Proc)
93
- unless @url.to_s.match(/^:s3.*url$/)
94
- @path = @path.gsub(/:url/, @url)
95
- @url = ":s3_path_url"
86
+ @s3_headers = @options.s3_headers || {}
87
+
88
+ unless @options.url.to_s.match(/^:s3.*url$/) || @options.url == ":asset_host"
89
+ @options.path = @options.path.gsub(/:url/, @options.url)
90
+ @options.url = ":s3_path_url"
96
91
  end
97
- @url = ":asset_host" if @options[:url].to_s == ":asset_host"
92
+ @options.url = @options.url.inspect if @options.url.is_a?(Symbol)
98
93
 
99
- @http_proxy = @options[:http_proxy] || nil
94
+ @http_proxy = @options.http_proxy || nil
100
95
  if @http_proxy
101
96
  @s3_options.merge!({:proxy => @http_proxy})
102
97
  end
103
98
 
104
99
  AWS::S3::Base.establish_connection!( @s3_options.merge(
105
- :access_key_id => @s3_credentials[:access_key_id],
106
- :secret_access_key => @s3_credentials[:secret_access_key]
100
+ :access_key_id => s3_credentials[:access_key_id],
101
+ :secret_access_key => s3_credentials[:secret_access_key]
107
102
  ))
108
103
  end
109
104
  Paperclip.interpolates(:s3_alias_url) do |attachment, style|
@@ -124,7 +119,23 @@ module Paperclip
124
119
  AWS::S3::S3Object.url_for(path(style_name), bucket_name, :expires_in => time, :use_ssl => (s3_protocol(style_name) == 'https'))
125
120
  end
126
121
 
122
+ def s3_credentials
123
+ @s3_credentials ||= parse_credentials(@options.s3_credentials)
124
+ end
125
+
126
+ def s3_host_name
127
+ @options.s3_host_name || s3_credentials[:s3_host_name] || "s3.amazonaws.com"
128
+ end
129
+
130
+ def s3_host_alias
131
+ @s3_host_alias = @options.s3_host_alias
132
+ @s3_host_alias = @s3_host_alias.call(self) if @s3_host_alias.is_a?(Proc)
133
+ @s3_host_alias
134
+ end
135
+
127
136
  def bucket_name
137
+ @bucket = @options.bucket || s3_credentials[:bucket]
138
+ @bucket = @bucket.call(self) if @bucket.is_a?(Proc)
128
139
  @bucket
129
140
  end
130
141
 
@@ -148,10 +159,6 @@ module Paperclip
148
159
  using_http_proxy? ? @http_proxy[:password] : nil
149
160
  end
150
161
 
151
- def s3_host_name
152
- @s3_host_name || "s3.amazonaws.com"
153
- end
154
-
155
162
  def set_permissions permissions
156
163
  if permissions.is_a?(Hash)
157
164
  permissions[:default] = permissions[:default] || :public_read
@@ -161,10 +168,6 @@ module Paperclip
161
168
  permissions
162
169
  end
163
170
 
164
- def s3_host_alias
165
- @s3_host_alias
166
- end
167
-
168
171
  def parse_credentials creds
169
172
  creds = find_credentials(creds).stringify_keys
170
173
  env = Object.const_defined?(:Rails) ? Rails.env : nil