paperclip 2.8.0 → 3.0.2

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 (94) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +9 -7
  3. data/Appraisals +6 -12
  4. data/Gemfile +2 -0
  5. data/NEWS +24 -0
  6. data/README.md +53 -21
  7. data/Rakefile +7 -2
  8. data/UPGRADING +14 -0
  9. data/features/basic_integration.feature +8 -8
  10. data/features/rake_tasks.feature +1 -1
  11. data/features/step_definitions/attachment_steps.rb +11 -2
  12. data/features/step_definitions/rails_steps.rb +17 -79
  13. data/features/support/env.rb +3 -0
  14. data/features/support/file_helpers.rb +24 -0
  15. data/features/support/rails.rb +3 -3
  16. data/gemfiles/{rails3_1.gemfile → 3.0.gemfile} +3 -1
  17. data/gemfiles/{rails2.gemfile → 3.1.gemfile} +3 -1
  18. data/gemfiles/{rails3.gemfile → 3.2.gemfile} +3 -1
  19. data/images.rake +21 -0
  20. data/lib/generators/paperclip/paperclip_generator.rb +1 -2
  21. data/lib/paperclip.rb +48 -319
  22. data/lib/paperclip/attachment.rb +33 -81
  23. data/lib/paperclip/attachment_options.rb +0 -1
  24. data/lib/paperclip/callbacks.rb +30 -0
  25. data/lib/paperclip/errors.rb +27 -0
  26. data/lib/paperclip/geometry.rb +6 -4
  27. data/lib/paperclip/glue.rb +15 -0
  28. data/lib/paperclip/helpers.rb +71 -0
  29. data/lib/paperclip/instance_methods.rb +35 -0
  30. data/lib/paperclip/interpolations.rb +2 -2
  31. data/lib/paperclip/io_adapters/attachment_adapter.rb +62 -0
  32. data/lib/paperclip/io_adapters/file_adapter.rb +81 -0
  33. data/lib/paperclip/io_adapters/identity_adapter.rb +12 -0
  34. data/lib/paperclip/io_adapters/nil_adapter.rb +34 -0
  35. data/lib/paperclip/io_adapters/registry.rb +32 -0
  36. data/lib/paperclip/io_adapters/stringio_adapter.rb +64 -0
  37. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +63 -0
  38. data/lib/paperclip/locales/en.yml +17 -0
  39. data/lib/paperclip/logger.rb +21 -0
  40. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +1 -1
  41. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +2 -2
  42. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +7 -7
  43. data/lib/paperclip/processor.rb +32 -17
  44. data/lib/paperclip/railtie.rb +10 -15
  45. data/lib/paperclip/storage/filesystem.rb +5 -14
  46. data/lib/paperclip/storage/fog.rb +2 -21
  47. data/lib/paperclip/storage/s3.rb +12 -29
  48. data/lib/paperclip/tempfile.rb +41 -0
  49. data/lib/paperclip/thumbnail.rb +2 -3
  50. data/lib/paperclip/validators.rb +45 -0
  51. data/lib/paperclip/validators/attachment_content_type_validator.rb +47 -0
  52. data/lib/paperclip/validators/attachment_presence_validator.rb +26 -0
  53. data/lib/paperclip/validators/attachment_size_validator.rb +102 -0
  54. data/lib/paperclip/version.rb +1 -1
  55. data/lib/tasks/paperclip.rake +3 -11
  56. data/paperclip.gemspec +15 -5
  57. data/test/adapter_registry_test.rb +32 -0
  58. data/test/attachment_adapter_test.rb +48 -0
  59. data/test/attachment_options_test.rb +0 -13
  60. data/test/attachment_test.rb +27 -55
  61. data/test/file_adapter_test.rb +43 -0
  62. data/test/generator_test.rb +78 -0
  63. data/test/geometry_test.rb +5 -5
  64. data/test/helper.rb +9 -11
  65. data/test/identity_adapter_test.rb +8 -0
  66. data/test/integration_test.rb +39 -94
  67. data/test/interpolations_test.rb +8 -1
  68. data/test/matchers/validate_attachment_size_matcher_test.rb +16 -2
  69. data/test/nil_adapter_test.rb +25 -0
  70. data/test/paperclip_test.rb +30 -189
  71. data/test/storage/filesystem_test.rb +0 -14
  72. data/test/storage/fog_test.rb +0 -14
  73. data/test/storage/s3_live_test.rb +22 -9
  74. data/test/storage/s3_test.rb +70 -34
  75. data/test/stringio_adapter_test.rb +42 -0
  76. data/test/style_test.rb +10 -16
  77. data/test/thumbnail_test.rb +16 -10
  78. data/test/uploaded_file_adapter_test.rb +98 -0
  79. data/test/validators/attachment_content_type_validator_test.rb +140 -0
  80. data/test/validators/attachment_presence_validator_test.rb +85 -0
  81. data/test/validators/attachment_size_validator_test.rb +207 -0
  82. data/test/validators_test.rb +25 -0
  83. metadata +152 -30
  84. data/gemfiles/rails3_2.gemfile +0 -9
  85. data/generators/paperclip/USAGE +0 -5
  86. data/generators/paperclip/paperclip_generator.rb +0 -27
  87. data/generators/paperclip/templates/paperclip_migration.rb.erb +0 -19
  88. data/init.rb +0 -4
  89. data/lib/paperclip/callback_compatibility.rb +0 -61
  90. data/lib/paperclip/iostream.rb +0 -45
  91. data/lib/paperclip/upfile.rb +0 -64
  92. data/rails/init.rb +0 -2
  93. data/test/iostream_test.rb +0 -71
  94. data/test/upfile_test.rb +0 -53
@@ -7,8 +7,6 @@ module Paperclip
7
7
  # when the model saves, deletes when the model is destroyed, and processes
8
8
  # the file upon assignment.
9
9
  class Attachment
10
- include IOStream
11
-
12
10
  def self.default_options
13
11
  @default_options ||= {
14
12
  :convert_options => {},
@@ -25,7 +23,7 @@ module Paperclip
25
23
  :source_file_options => {},
26
24
  :storage => :filesystem,
27
25
  :styles => {},
28
- :url => "/system/:attachment/:id/:style/:filename",
26
+ :url => "/system/:class/:attachment/:id_partition/:style/:filename",
29
27
  :url_generator => Paperclip::UrlGenerator,
30
28
  :use_default_time_zone => true,
31
29
  :use_timestamp => true,
@@ -90,29 +88,16 @@ module Paperclip
90
88
  # new_user.avatar = old_user.avatar
91
89
  def assign uploaded_file
92
90
  ensure_required_accessors!
91
+ file = Paperclip.io_adapters.for(uploaded_file)
93
92
 
94
- if uploaded_file.is_a?(Paperclip::Attachment)
95
- uploaded_filename = uploaded_file.original_filename
96
- uploaded_file = uploaded_file.to_file(:original)
97
- close_uploaded_file = uploaded_file.respond_to?(:close)
98
- else
99
- instance_write(:uploaded_file, uploaded_file) if uploaded_file
100
- end
101
-
102
- return nil unless valid_assignment?(uploaded_file)
103
-
104
- uploaded_file.binmode if uploaded_file.respond_to? :binmode
105
93
  self.clear
94
+ return nil if file.nil?
106
95
 
107
- return nil if uploaded_file.nil?
108
-
109
- uploaded_filename ||= uploaded_file.original_filename
110
- stores_fingerprint = @instance.respond_to?("#{name}_fingerprint".to_sym)
111
- @queued_for_write[:original] = to_tempfile(uploaded_file)
112
- instance_write(:file_name, cleanup_filename(uploaded_filename.strip))
113
- instance_write(:content_type, uploaded_file.content_type.to_s.strip)
114
- instance_write(:file_size, uploaded_file.size.to_i)
115
- instance_write(:fingerprint, generate_fingerprint(uploaded_file)) if stores_fingerprint
96
+ @queued_for_write[:original] = file
97
+ instance_write(:file_name, cleanup_filename(file.original_filename))
98
+ instance_write(:content_type, file.content_type)
99
+ instance_write(:file_size, file.size)
100
+ instance_write(:fingerprint, file.fingerprint) if instance_respond_to?(:fingerprint)
116
101
  instance_write(:updated_at, Time.now)
117
102
 
118
103
  @dirty = true
@@ -120,10 +105,8 @@ module Paperclip
120
105
  post_process(*@options[:only_process]) if post_processing
121
106
 
122
107
  # Reset the file size if the original file was reprocessed.
123
- instance_write(:file_size, @queued_for_write[:original].size.to_i)
124
- instance_write(:fingerprint, generate_fingerprint(@queued_for_write[:original])) if stores_fingerprint
125
- ensure
126
- uploaded_file.close if close_uploaded_file
108
+ instance_write(:file_size, @queued_for_write[:original].size)
109
+ instance_write(:fingerprint, @queued_for_write[:original].fingerprint) if instance_respond_to?(:fingerprint)
127
110
  end
128
111
 
129
112
  # Returns the public URL of the attachment with a given style. This does
@@ -252,16 +235,10 @@ module Paperclip
252
235
  instance_read(:file_size) || (@queued_for_write[:original] && @queued_for_write[:original].size)
253
236
  end
254
237
 
255
- # Returns the hash of the file as originally assigned, and lives in the
256
- # <attachment>_fingerprint attribute of the model.
238
+ # Returns the fingerprint of the file, if one's defined. The fingerprint is
239
+ # stored in the <attachment>_fingerpring attribute of the model.
257
240
  def fingerprint
258
- if instance_read(:fingerprint)
259
- instance_read(:fingerprint)
260
- elsif @instance.respond_to?("#{name}_fingerprint".to_sym)
261
- @queued_for_write[:original] && generate_fingerprint(@queued_for_write[:original])
262
- else
263
- nil
264
- end
241
+ instance_read(:fingerprint)
265
242
  end
266
243
 
267
244
  # Returns the content_type of the file as originally assigned, and lives
@@ -292,53 +269,21 @@ module Paperclip
292
269
  OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@options[:hash_digest]).new, @options[:hash_secret], data)
293
270
  end
294
271
 
295
- def generate_fingerprint(source)
296
- if source.respond_to?(:path) && source.path && !source.path.blank?
297
- Digest::MD5.file(source.path).to_s
298
- else
299
- data = source.read
300
- source.rewind if source.respond_to?(:rewind)
301
- Digest::MD5.hexdigest(data)
302
- end
303
- end
304
-
305
- # Paths and URLs can have a number of variables interpolated into them
306
- # to vary the storage location based on name, id, style, class, etc.
307
- # This method is a deprecated access into supplying and retrieving these
308
- # interpolations. Future access should use either Paperclip.interpolates
309
- # or extend the Paperclip::Interpolations module directly.
310
- def self.interpolations
311
- warn('[DEPRECATION] Paperclip::Attachment.interpolations is deprecated ' +
312
- 'and will be removed from future versions. ' +
313
- 'Use Paperclip.interpolates instead')
314
- Paperclip::Interpolations
315
- end
316
-
317
272
  # This method really shouldn't be called that often. It's expected use is
318
273
  # in the paperclip:refresh rake task and that's it. It will regenerate all
319
274
  # thumbnails forcefully, by reobtaining the original file and going through
320
275
  # the post-process again.
321
276
  def reprocess!(*style_args)
322
- new_original = Tempfile.new("paperclip-reprocess")
323
- new_original.binmode
324
- if old_original = to_file(:original)
325
- new_original.write( old_original.respond_to?(:get) ? old_original.get : old_original.read )
326
- new_original.rewind
327
-
328
- @queued_for_write = { :original => new_original }
329
- instance_write(:updated_at, Time.now)
330
- post_process(*style_args)
331
-
332
- old_original.close if old_original.respond_to?(:close)
333
- old_original.unlink if old_original.respond_to?(:unlink)
334
-
277
+ saved_only_process, @options[:only_process] = @options[:only_process], style_args
278
+ begin
279
+ assign(self)
335
280
  save
336
- else
337
- true
281
+ rescue Errno::EACCES => e
282
+ warn "#{e} - skipping file."
283
+ false
284
+ ensure
285
+ @options[:only_process] = saved_only_process
338
286
  end
339
- rescue Errno::EACCES => e
340
- warn "#{e} - skipping file"
341
- false
342
287
  end
343
288
 
344
289
  # Returns true if a file has been assigned.
@@ -348,6 +293,12 @@ module Paperclip
348
293
 
349
294
  alias :present? :file?
350
295
 
296
+ # Determines whether the instance responds to this attribute. Used to prevent
297
+ # calculations on fields we won't even store.
298
+ def instance_respond_to?(attr)
299
+ instance.respond_to?(:"#{name}_#{attr}")
300
+ end
301
+
351
302
  # Writes the attachment-specific attribute on the instance. For example,
352
303
  # instance_write(:file_name, "me.jpg") will write "me.jpg" to the instance's
353
304
  # "avatar_file_name" field (assuming the attachment is called avatar).
@@ -377,7 +328,7 @@ module Paperclip
377
328
  def ensure_required_accessors! #:nodoc:
378
329
  %w(file_name).each do |field|
379
330
  unless @instance.respond_to?("#{name}_#{field}") && @instance.respond_to?("#{name}_#{field}=")
380
- raise PaperclipError.new("#{@instance.class} model missing required attr_accessor for '#{name}_#{field}'")
331
+ raise Paperclip::Error.new("#{@instance.class} model missing required attr_accessor for '#{name}_#{field}'")
381
332
  end
382
333
  end
383
334
  end
@@ -395,7 +346,7 @@ module Paperclip
395
346
  begin
396
347
  storage_module = Paperclip::Storage.const_get(storage_class_name)
397
348
  rescue NameError
398
- raise StorageMethodNotFound, "Cannot load storage module '#{storage_class_name}'"
349
+ raise Errors::StorageMethodNotFound, "Cannot load storage module '#{storage_class_name}'"
399
350
  end
400
351
  self.extend(storage_module)
401
352
  end
@@ -440,7 +391,8 @@ module Paperclip
440
391
  @queued_for_write[name] = style.processors.inject(@queued_for_write[:original]) do |file, processor|
441
392
  Paperclip.processor(processor).make(file, style.processor_options, self)
442
393
  end
443
- rescue PaperclipError => e
394
+ @queued_for_write[name] = Paperclip.io_adapters.for(@queued_for_write[name])
395
+ rescue Paperclip::Error => e
444
396
  log("An error was received while processing: #{e.inspect}")
445
397
  (@errors[:processing] ||= []) << e.message if @options[:whiny]
446
398
  end
@@ -475,8 +427,8 @@ module Paperclip
475
427
  # called by storage after the writes are flushed and before @queued_for_writes is cleared
476
428
  def after_flush_writes
477
429
  @queued_for_write.each do |style, file|
478
- file.close unless file.closed?
479
- file.unlink if file.respond_to?(:unlink) && file.path.present? && File.exist?(file.path)
430
+ # file.close unless file.closed?
431
+ # file.unlink if file.respond_to?(:unlink) && file.path.present? && File.exist?(file.path)
480
432
  end
481
433
  end
482
434
 
@@ -1,7 +1,6 @@
1
1
  module Paperclip
2
2
  class AttachmentOptions < Hash
3
3
  def initialize(options)
4
- options = {:validations => []}.merge(options)
5
4
  options.each do |k, v|
6
5
  self.[]=(k, v)
7
6
  end
@@ -0,0 +1,30 @@
1
+ module Paperclip
2
+ module Callbacks
3
+ def self.included(base)
4
+ base.extend(Defining)
5
+ base.send(:include, Running)
6
+ end
7
+
8
+ module Defining
9
+ def define_paperclip_callbacks(*callbacks)
10
+ define_callbacks *[callbacks, {:terminator => "result == false"}].flatten
11
+ callbacks.each do |callback|
12
+ eval <<-end_callbacks
13
+ def before_#{callback}(*args, &blk)
14
+ set_callback(:#{callback}, :before, *args, &blk)
15
+ end
16
+ def after_#{callback}(*args, &blk)
17
+ set_callback(:#{callback}, :after, *args, &blk)
18
+ end
19
+ end_callbacks
20
+ end
21
+ end
22
+ end
23
+
24
+ module Running
25
+ def run_paperclip_callbacks(callback, opts = nil, &block)
26
+ run_callbacks(callback, opts, &block)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ module Paperclip
2
+ # A base error class for Paperclip. Most of the error that will be thrown
3
+ # from Paperclip will inherits from this class.
4
+ class Error < StandardError
5
+ end
6
+
7
+ module Errors
8
+ # Will be thrown when a storage method is not found.
9
+ class StorageMethodNotFound < Paperclip::Error
10
+ end
11
+
12
+ # Will be thrown when a command or executable is not found.
13
+ class CommandNotFoundError < Paperclip::Error
14
+ end
15
+
16
+ # Will be thrown when ImageMagic cannot determine the uploaded file's
17
+ # metadata, usually this would mean the file is not an image.
18
+ class NotIdentifiedByImageMagickError < Paperclip::Error
19
+ end
20
+
21
+ # Will be thrown if the interpolation is creating an infinite loop. If you
22
+ # are creating an interpolator which might cause an infinite loop, you
23
+ # should be throwing this error upon the infinite loop as well.
24
+ class InfiniteInterpolationError < Paperclip::Error
25
+ end
26
+ end
27
+ end
@@ -17,16 +17,18 @@ module Paperclip
17
17
  # a Tempfile object, which would be eligible for file deletion when no longer referenced.
18
18
  def self.from_file file
19
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?
20
+ raise(Errors::NotIdentifiedByImageMagickError.new("Cannot find the geometry of a file with a blank name")) if file_path.blank?
21
21
  geometry = begin
22
- Paperclip.run("identify", "-format %wx%h :file", :file => "#{file_path}[0]")
22
+ silence_stream(STDERR) do
23
+ Paperclip.run("identify", "-format %wx%h :file", :file => "#{file_path}[0]")
24
+ end
23
25
  rescue Cocaine::ExitStatusError
24
26
  ""
25
27
  rescue Cocaine::CommandNotFoundError => e
26
- raise Paperclip::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.")
28
+ raise Errors::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.")
27
29
  end
28
30
  parse(geometry) ||
29
- raise(NotIdentifiedByImageMagickError.new("#{file_path} is not recognized by the 'identify' command."))
31
+ raise(Errors::NotIdentifiedByImageMagickError.new("#{file_path} is not recognized by the 'identify' command."))
30
32
  end
31
33
 
32
34
  # Parses a "WxH" formatted string, where W is the width and H is the height.
@@ -0,0 +1,15 @@
1
+ require 'paperclip/callbacks'
2
+
3
+ module Paperclip
4
+ module Glue
5
+ def self.included base #:nodoc:
6
+ base.extend ClassMethods
7
+ base.send :include, Callbacks
8
+ base.send :include, Validators
9
+ base.class_attribute :attachment_definitions
10
+
11
+ locale_path = Dir.glob(File.dirname(__FILE__) + "/locales/*.{rb,yml}")
12
+ I18n.load_path += locale_path unless I18n.load_path.include?(locale_path)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,71 @@
1
+ module Paperclip
2
+ module Helpers
3
+ def configure
4
+ yield(self) if block_given?
5
+ end
6
+
7
+ def interpolates key, &block
8
+ Paperclip::Interpolations[key] = block
9
+ end
10
+
11
+ # The run method takes the name of a binary to run, the arguments to that binary
12
+ # and some options:
13
+ #
14
+ # :command_path -> A $PATH-like variable that defines where to look for the binary
15
+ # on the filesystem. Colon-separated, just like $PATH.
16
+ #
17
+ # :expected_outcodes -> An array of integers that defines the expected exit codes
18
+ # of the binary. Defaults to [0].
19
+ #
20
+ # :log_command -> Log the command being run when set to true (defaults to false).
21
+ # This will only log if logging in general is set to true as well.
22
+ #
23
+ # :swallow_stderr -> Set to true if you don't care what happens on STDERR.
24
+ #
25
+ def run(cmd, arguments = "", local_options = {})
26
+ command_path = options[:command_path]
27
+ Cocaine::CommandLine.path = ( Cocaine::CommandLine.path ? [Cocaine::CommandLine.path].flatten | [command_path] : command_path )
28
+ local_options = local_options.merge(:logger => logger) if logging? && (options[:log_command] || local_options[:log_command])
29
+ Cocaine::CommandLine.new(cmd, arguments, local_options).run
30
+ end
31
+
32
+ # Find all instances of the given Active Record model +klass+ with attachment +name+.
33
+ # This method is used by the refresh rake tasks.
34
+ def each_instance_with_attachment(klass, name)
35
+ unscope_method = class_for(klass).respond_to?(:unscoped) ? :unscoped : :with_exclusive_scope
36
+ class_for(klass).send(unscope_method) do
37
+ class_for(klass).find_each(:conditions => "#{name}_file_name is not null") do |instance|
38
+ yield(instance)
39
+ end
40
+ end
41
+ end
42
+
43
+ def class_for(class_name)
44
+ # Ruby 1.9 introduces an inherit argument for Module#const_get and
45
+ # #const_defined? and changes their default behavior.
46
+ # https://github.com/rails/rails/blob/v3.0.9/activesupport/lib/active_support/inflector/methods.rb#L89
47
+ if Module.method(:const_get).arity == 1
48
+ class_name.split('::').inject(Object) do |klass, partial_class_name|
49
+ klass.const_defined?(partial_class_name) ? klass.const_get(partial_class_name) : klass.const_missing(partial_class_name)
50
+ end
51
+ else
52
+ class_name.split('::').inject(Object) do |klass, partial_class_name|
53
+ klass.const_defined?(partial_class_name) ? klass.const_get(partial_class_name, false) : klass.const_missing(partial_class_name)
54
+ end
55
+ end
56
+ end
57
+
58
+ def check_for_url_clash(name,url,klass)
59
+ @names_url ||= {}
60
+ default_url = url || Attachment.default_options[:url]
61
+ if @names_url[name] && @names_url[name][:url] == default_url && @names_url[name][:class] != klass && @names_url[name][:url] !~ /:class/
62
+ log("Duplicate URL for #{name} with #{default_url}. This will clash with attachment defined in #{@names_url[name][:class]} class")
63
+ end
64
+ @names_url[name] = {:url => default_url, :class => klass}
65
+ end
66
+
67
+ def reset_duplicate_clash_check!
68
+ @names_url = nil
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,35 @@
1
+ module Paperclip
2
+ module InstanceMethods #:nodoc:
3
+ def attachment_for name
4
+ @_paperclip_attachments ||= {}
5
+ @_paperclip_attachments[name] ||= Attachment.new(name, self, self.class.attachment_definitions[name])
6
+ end
7
+
8
+ def each_attachment
9
+ self.class.attachment_definitions.each do |name, definition|
10
+ yield(name, attachment_for(name))
11
+ end
12
+ end
13
+
14
+ def save_attached_files
15
+ Paperclip.log("Saving attachments.")
16
+ each_attachment do |name, attachment|
17
+ attachment.send(:save)
18
+ end
19
+ end
20
+
21
+ def destroy_attached_files
22
+ Paperclip.log("Deleting attachments.")
23
+ each_attachment do |name, attachment|
24
+ attachment.send(:flush_deletes)
25
+ end
26
+ end
27
+
28
+ def prepare_for_destroy
29
+ Paperclip.log("Scheduling attachments for deletion.")
30
+ each_attachment do |name, attachment|
31
+ attachment.send(:queue_existing_for_delete)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -46,7 +46,7 @@ module Paperclip
46
46
  # is used in the default :path to ease default specifications.
47
47
  RIGHT_HERE = "#{__FILE__.gsub(%r{^\./}, "")}:#{__LINE__ + 3}"
48
48
  def url attachment, style_name
49
- raise InfiniteInterpolationError if caller.any?{|b| b.index(RIGHT_HERE) }
49
+ raise Errors::InfiniteInterpolationError if caller.any?{|b| b.index(RIGHT_HERE) }
50
50
  attachment.url(style_name, :timestamp => false, :escape => false)
51
51
  end
52
52
 
@@ -93,7 +93,7 @@ module Paperclip
93
93
  # If the style has a format defined, it will return the format instead
94
94
  # of the actual extension.
95
95
  def extension attachment, style_name
96
- ((style = attachment.styles[style_name.to_sym]) && style[:format]) ||
96
+ ((style = attachment.styles[style_name.to_s.to_sym]) && style[:format]) ||
97
97
  File.extname(attachment.original_filename).gsub(/^\.+/, "")
98
98
  end
99
99
 
@@ -0,0 +1,62 @@
1
+ module Paperclip
2
+ class AttachmentAdapter
3
+
4
+ def initialize(target)
5
+ @target = target
6
+ cache_current_values
7
+ end
8
+
9
+ def original_filename
10
+ @original_filename
11
+ end
12
+
13
+ def content_type
14
+ @content_type
15
+ end
16
+
17
+ def size
18
+ @size
19
+ end
20
+
21
+ def nil?
22
+ false
23
+ end
24
+
25
+ def fingerprint
26
+ @fingerprint ||= Digest::MD5.file(path).to_s
27
+ end
28
+
29
+ def read(length = nil, buffer = nil)
30
+ @tempfile.read(length, buffer)
31
+ end
32
+
33
+ def eof?
34
+ @tempfile.eof?
35
+ end
36
+
37
+ def path
38
+ @tempfile.path
39
+ end
40
+
41
+ private
42
+
43
+ def cache_current_values
44
+ @tempfile = copy_to_tempfile(@target)
45
+ @original_filename = @target.original_filename
46
+ @content_type = @target.content_type
47
+ @size = @tempfile.size || @target.size
48
+ end
49
+
50
+ def copy_to_tempfile(src)
51
+ dest = Tempfile.new(src.original_filename)
52
+ dest.binmode
53
+ FileUtils.cp(src.path(:original), dest.path)
54
+ dest
55
+ end
56
+
57
+ end
58
+ end
59
+
60
+ Paperclip.io_adapters.register Paperclip::AttachmentAdapter do |target|
61
+ Paperclip::Attachment === target
62
+ end