Skip to content
This repository has been archived by the owner on Jul 13, 2023. It is now read-only.

Commit

Permalink
Adds IO adapters to abstract the things that can be assigned.
Browse files Browse the repository at this point in the history
Needs work for S3 Attachments.
  • Loading branch information
Jon Yurek committed Mar 30, 2012
1 parent 6388652 commit 89c8d11
Show file tree
Hide file tree
Showing 42 changed files with 723 additions and 511 deletions.
Binary file added .DS_Store
Binary file not shown.
38 changes: 19 additions & 19 deletions Gemfile.lock
Expand Up @@ -19,13 +19,10 @@ GEM
activesupport (= 3.2.2)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
activerecord-jdbc-adapter (1.2.2)
activerecord-jdbcsqlite3-adapter (1.2.2)
activerecord-jdbc-adapter (~> 1.2.2)
jdbc-sqlite3 (~> 3.7.2)
activesupport (3.2.2)
i18n (~> 0.6)
multi_json (~> 1.0)
addressable (2.2.7)
appraisal (0.4.1)
bundler
rake
Expand All @@ -40,7 +37,8 @@ GEM
json (~> 1.4)
nokogiri (<= 1.5.0)
uuidtools (~> 2.1)
bouncy-castle-java (1.5.0146.1)
bourne (1.1.2)
mocha (= 0.10.5)
builder (3.0.0)
capybara (1.1.2)
mime-types (>= 1.16)
Expand All @@ -52,6 +50,7 @@ GEM
childprocess (0.3.1)
ffi (~> 1.0.6)
cocaine (0.2.1)
coderay (1.0.5)
cucumber (1.1.9)
builder (>= 2.1.2)
diff-lcs (>= 1.1.2)
Expand All @@ -62,7 +61,6 @@ GEM
excon (0.6.6)
fakeweb (1.3.0)
ffi (1.0.11)
ffi (1.0.11-java)
fog (0.9.0)
builder
excon (~> 0.6.1)
Expand All @@ -74,31 +72,30 @@ GEM
nokogiri (>= 1.4.4)
ruby-hmac
formatador (0.2.1)
gherkin (2.9.1)
json (>= 1.4.6)
gherkin (2.9.1-java)
gherkin (2.9.3)
json (>= 1.4.6)
httparty (0.8.1)
multi_json
multi_xml
i18n (0.6.0)
jdbc-sqlite3 (3.7.2)
jruby-openssl (0.7.6.1)
bouncy-castle-java (>= 1.5.0146.1)
json (1.6.5)
json (1.6.5-java)
json (1.6.6)
launchy (2.1.0)
addressable (~> 2.2.6)
metaclass (0.0.1)
method_source (0.7.1)
mime-types (1.18)
mocha (0.10.5)
metaclass (~> 0.0.1)
multi_json (1.1.0)
multi_json (1.2.0)
multi_xml (0.4.2)
net-scp (1.0.4)
net-ssh (>= 1.99.1)
net-ssh (2.3.0)
nokogiri (1.4.7)
nokogiri (1.4.7-java)
weakling (>= 0.0.3)
pry (0.9.8.4)
coderay (~> 1.0.5)
method_source (~> 0.7.1)
slop (>= 2.4.4, < 3)
rack (1.4.1)
rack-test (0.6.1)
rack (>= 1.0)
Expand All @@ -123,32 +120,35 @@ GEM
shoulda-matchers (~> 1.0.0)
shoulda-context (1.0.0)
shoulda-matchers (1.0.0)
slop (2.4.4)
sqlite3 (1.3.5)
term-ansicolor (1.0.7)
tzinfo (0.3.32)
uuidtools (2.1.2)
weakling (0.0.4-java)
xpath (0.1.4)
nokogiri (~> 1.3)

PLATFORMS
java
ruby

DEPENDENCIES
activerecord-jdbcsqlite3-adapter
appraisal (~> 0.4.0)
aruba
aws-sdk (~> 1.3.8)
bourne
bundler
capybara
cocaine (~> 0.2)
cucumber (~> 1.1.0)
fakeweb
fog
jruby-openssl
launchy
mocha
nokogiri (~> 1.4.7)
paperclip!
pry
rake
shoulda
sqlite3
4 changes: 4 additions & 0 deletions features/step_definitions/rails_steps.rb
@@ -1,3 +1,7 @@
When /^I print "([^\"]*)"$/ do |whatever|
puts whatever
end

Given /^I generate a new rails application$/ do
steps %{
When I run `bundle exec #{new_application_command} #{APP_NAME}`
Expand Down
4 changes: 4 additions & 0 deletions features/support/env.rb
@@ -1,6 +1,10 @@
require 'aruba/cucumber'
require 'capybara/cucumber'
require 'test/unit/assertions'
require 'pry'

$CUCUMBER=1

World(Test::Unit::Assertions)

Before do
Expand Down
21 changes: 21 additions & 0 deletions images.rake
@@ -0,0 +1,21 @@
namespace :images do
desc "Regenerate images"
task :regenerate => :environment do
require 'open-uri'
OpportunityPhoto.all.each do |photo|
begin
old_name = photo.image_file_name
new_image = open(photo.image.url(:original, escape: false))
class << new_image
def original_filename; @original_filename; end
def original_filename=(name); @original_filename = name; end
end
new_image.original_filename = old_name
photo.image = new_image
photo.save
rescue => e
puts "ERROR: #{e.message} while processing #{photo.id}"
end
end
end
end
Binary file added lib/.DS_Store
Binary file not shown.
12 changes: 10 additions & 2 deletions lib/paperclip.rb
Expand Up @@ -29,8 +29,6 @@
require 'digest'
require 'tempfile'
require 'paperclip/version'
require 'paperclip/upfile'
require 'paperclip/iostream'
require 'paperclip/geometry'
require 'paperclip/processor'
require 'paperclip/tempfile'
Expand All @@ -49,6 +47,7 @@
require 'paperclip/logger'
require 'paperclip/helpers'
require 'paperclip/railtie'
require 'mime/types'
require 'logger'
require 'cocaine'

Expand Down Expand Up @@ -203,3 +202,12 @@ def attachment_definitions
end
end
end

# This stuff needs to be run after Paperclip is defined.
require 'paperclip/io_adapters/registry'
require 'paperclip/io_adapters/identity_adapter'
require 'paperclip/io_adapters/file_adapter'
require 'paperclip/io_adapters/stringio_adapter'
require 'paperclip/io_adapters/nil_adapter'
require 'paperclip/io_adapters/attachment_adapter'
require 'paperclip/io_adapters/uploaded_file_adapter'
Binary file added lib/paperclip/.DS_Store
Binary file not shown.
84 changes: 29 additions & 55 deletions lib/paperclip/attachment.rb
Expand Up @@ -7,8 +7,6 @@ module Paperclip
# when the model saves, deletes when the model is destroyed, and processes
# the file upon assignment.
class Attachment
include IOStream

def self.default_options
@default_options ||= {
:convert_options => {},
Expand Down Expand Up @@ -90,40 +88,25 @@ def initialize(name, instance, options = {})
# new_user.avatar = old_user.avatar
def assign uploaded_file
ensure_required_accessors!
file = Paperclip.io_adapters.for(uploaded_file)

if uploaded_file.is_a?(Paperclip::Attachment)
uploaded_filename = uploaded_file.original_filename
uploaded_file = uploaded_file.to_file(:original)
close_uploaded_file = uploaded_file.respond_to?(:close)
else
instance_write(:uploaded_file, uploaded_file) if uploaded_file
end

return nil unless valid_assignment?(uploaded_file)

uploaded_file.binmode if uploaded_file.respond_to? :binmode
self.clear
return nil if file.nil?

return nil if uploaded_file.nil?

uploaded_filename ||= uploaded_file.original_filename
stores_fingerprint = @instance.respond_to?("#{name}_fingerprint".to_sym)
@queued_for_write[:original] = to_tempfile(uploaded_file)
instance_write(:file_name, cleanup_filename(uploaded_filename.strip))
instance_write(:content_type, uploaded_file.content_type.to_s.strip)
instance_write(:file_size, uploaded_file.size.to_i)
instance_write(:fingerprint, generate_fingerprint(uploaded_file)) if stores_fingerprint
@queued_for_write[:original] = file
instance_write(:file_name, cleanup_filename(file.original_filename))
instance_write(:content_type, file.content_type)

This comment has been minimized.

Copy link
@yury

yury Mar 31, 2012

this line should be instance_write(:content_type, file.content_type.to_s)
some times content_type is Mime::Type and that couse errors with postgresql.

f = File.open("spec/fixtures/big_good_file.xls") 
f.content_type
=> application/vnd.ms-excel
f.content_type.class
=> MIME::Type

This comment has been minimized.

Copy link
@masterkain

masterkain Apr 1, 2012

why this has to break everytime. everytime paperclip gets upgraded something like this break, are there no specs to catch this?

This comment has been minimized.

Copy link
@sikachu

sikachu Apr 4, 2012

Contributor

We offload those stuff to the IOAdapter, so removing from here does make sense. However, I'm not sure why it returns the content type as a MIME::Type object though.

irb(main):001:0> File.open('log/development.log').content_type
=> "text/plain"
irb(main):002:0> File.open('log/development.log').content_type.class
=> String

I'll make sure to call #to_s in #805 though.

This comment has been minimized.

Copy link
@sikachu

sikachu Apr 4, 2012

Contributor

Interesting.

irb(main):002:0> File.open('/Users/sikachu/Downloads/foo.xls').content_type
=> application/vnd.ms-excel
irb(main):003:0> File.open('/Users/sikachu/Downloads/foo.xls').content_type.class
=> MIME::Type

I like how sneaky this is. I'll definitely fix this. Sorry for the trouble, and thanks for reporting :D

This comment has been minimized.

Copy link
@jyurek

jyurek Apr 24, 2012

I've made sure to add a new test for this to prevent further regressions in 8b778b3

instance_write(:file_size, file.size)
instance_write(:fingerprint, file.fingerprint) if instance_respond_to?(:fingerprint)
instance_write(:updated_at, Time.now)

@dirty = true

post_process(*@options[:only_process]) if post_processing

# Reset the file size if the original file was reprocessed.
instance_write(:file_size, @queued_for_write[:original].size.to_i)
instance_write(:fingerprint, generate_fingerprint(@queued_for_write[:original])) if stores_fingerprint
ensure
uploaded_file.close if close_uploaded_file
instance_write(:file_size, @queued_for_write[:original].size)
instance_write(:fingerprint, @queued_for_write[:original].fingerprint) if instance_respond_to?(:fingerprint)
end

# Returns the public URL of the attachment with a given style. This does
Expand Down Expand Up @@ -252,16 +235,10 @@ def size
instance_read(:file_size) || (@queued_for_write[:original] && @queued_for_write[:original].size)
end

# Returns the hash of the file as originally assigned, and lives in the
# <attachment>_fingerprint attribute of the model.
# Returns the fingerprint of the file, if one's defined. The fingerprint is
# stored in the <attachment>_fingerpring attribute of the model.
def fingerprint
if instance_read(:fingerprint)
instance_read(:fingerprint)
elsif @instance.respond_to?("#{name}_fingerprint".to_sym)
@queued_for_write[:original] && generate_fingerprint(@queued_for_write[:original])
else
nil
end
instance_read(:fingerprint)
end

# Returns the content_type of the file as originally assigned, and lives
Expand Down Expand Up @@ -307,26 +284,16 @@ def generate_fingerprint(source)
# thumbnails forcefully, by reobtaining the original file and going through
# the post-process again.
def reprocess!(*style_args)
new_original = Tempfile.new("paperclip-reprocess")
new_original.binmode
if old_original = to_file(:original)
new_original.write( old_original.respond_to?(:get) ? old_original.get : old_original.read )
new_original.rewind

@queued_for_write = { :original => new_original }
instance_write(:updated_at, Time.now)
post_process(*style_args)

old_original.close if old_original.respond_to?(:close)
old_original.unlink if old_original.respond_to?(:unlink)

saved_only_process, @options[:only_process] = @options[:only_process], style_args
begin
assign(self)
save
else
true
rescue Errno::EACCES => e
warn "#{e} - skipping file."
false
ensure
@options[:only_process] = saved_only_process
end
rescue Errno::EACCES => e
warn "#{e} - skipping file"
false
end

# Returns true if a file has been assigned.
Expand All @@ -336,6 +303,12 @@ def file?

alias :present? :file?

# Determines whether the instance responds to this attribute. Used to prevent
# calculations on fields we won't even store.
def instance_respond_to?(attr)
instance.respond_to?(:"#{name}_#{attr}")
end

# Writes the attachment-specific attribute on the instance. For example,
# instance_write(:file_name, "me.jpg") will write "me.jpg" to the instance's
# "avatar_file_name" field (assuming the attachment is called avatar).
Expand Down Expand Up @@ -428,6 +401,7 @@ def post_process_style(name, style) #:nodoc:
@queued_for_write[name] = style.processors.inject(@queued_for_write[:original]) do |file, processor|
Paperclip.processor(processor).make(file, style.processor_options, self)
end
@queued_for_write[name] = Paperclip.io_adapters.for(@queued_for_write[name])
rescue Paperclip::Error => e
log("An error was received while processing: #{e.inspect}")
(@errors[:processing] ||= []) << e.message if @options[:whiny]
Expand Down Expand Up @@ -463,8 +437,8 @@ def flush_errors #:nodoc:
# called by storage after the writes are flushed and before @queued_for_writes is cleared
def after_flush_writes
@queued_for_write.each do |style, file|
file.close unless file.closed?
file.unlink if file.respond_to?(:unlink) && file.path.present? && File.exist?(file.path)
# file.close unless file.closed?
# file.unlink if file.respond_to?(:unlink) && file.path.present? && File.exist?(file.path)
end
end

Expand Down
61 changes: 61 additions & 0 deletions lib/paperclip/io_adapters/attachment_adapter.rb
@@ -0,0 +1,61 @@
module Paperclip
class AttachmentAdapter

def initialize(target)
@target = target
cache_current_values
end

def original_filename
@original_filename
end

def content_type
@content_type
end

def size
@size
end

def nil?
false
end

def fingerprint
@fingerprint ||= Digest::MD5.file(path).to_s
end

def read(length = nil, buffer = nil)
@tempfile.read(length, buffer)
end

def eof?
@tempfile.eof?
end

def path
@tempfile.path
end

private

def cache_current_values
@tempfile = copy_to_tempfile(@target)
@original_filename = @target.original_filename
@content_type = @target.content_type
@size = @tempfile.size || @target.size
end

def copy_to_tempfile(src)
dest = Tempfile.new(src.original_filename)
FileUtils.cp(src.path(:original), dest.path)
dest
end

end
end

Paperclip.io_adapters.register Paperclip::AttachmentAdapter do |target|
Paperclip::Attachment === target
end

1 comment on commit 89c8d11

@edison
Copy link
Contributor

@edison edison commented on 89c8d11 Mar 31, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that something in this commit are causing this error for me:

NoMethodError: undefined method `tempfile' for #<Tempfile:0x000001030b1ce8>
    /Users/edison/.rvm/gems/ruby-1.9.2-p290/gems/rack-test-0.6.1/lib/rack/test/uploaded_file.rb:40:in `method_missing'
    /Users/edison/.rvm/gems/ruby-1.9.2-p290/gems/paperclip-3.0.1/lib/paperclip/io_adapters/uploaded_file_adapter.rb:5:in `initialize'
    /Users/edison/.rvm/gems/ruby-1.9.2-p290/gems/paperclip-3.0.1/lib/paperclip/io_adapters/registry.rb:29:in `new'
    /Users/edison/.rvm/gems/ruby-1.9.2-p290/gems/paperclip-3.0.1/lib/paperclip/io_adapters/registry.rb:29:in `for'
    /Users/edison/.rvm/gems/ruby-1.9.2-p290/gems/paperclip-3.0.1/lib/paperclip/attachment.rb:91:in `assign'
    /Users/edison/.rvm/gems/ruby-1.9.2-p290/gems/paperclip-3.0.1/lib/paperclip.rb:193:in `block in has_attached_file'
    ...

Please sign in to comment.