Commit 1b8bb3c3 by Joost Baaij

Merge branch 'master' of git://github.com/thoughtbot/paperclip

parents 289d9cfb e23d7f51
...@@ -5,3 +5,6 @@ test/s3.yml ...@@ -5,3 +5,6 @@ test/s3.yml
public public
paperclip*.gem paperclip*.gem
capybara*.html capybara*.html
*.rbc
.bundle
*SPIKE*
appraise "rails2" do
gem "rails", "~>2.3.0"
end
appraise "rails3" do
gem "rails", "~>3.0.0"
end
source "http://rubygems.org"
gem "shoulda"
gem "mocha"
gem "rake"
gem "ruby-debug"
gem "aws-s3", :require => "aws/s3"
gem "sqlite3-ruby", "~>1.3.0"
gem "appraisal"
GEM
remote: http://rubygems.org/
specs:
appraisal (0.1)
bundler
rake
aws-s3 (0.6.2)
builder
mime-types
xml-simple
builder (3.0.0)
columnize (0.3.2)
linecache (0.43)
mime-types (1.16)
mocha (0.9.9)
rake
rake (0.8.7)
ruby-debug (0.10.4)
columnize (>= 0.1)
ruby-debug-base (~> 0.10.4.0)
ruby-debug-base (0.10.4)
linecache (>= 0.3)
shoulda (2.11.3)
sqlite3-ruby (1.3.2)
xml-simple (1.0.12)
PLATFORMS
ruby
DEPENDENCIES
appraisal
aws-s3
mocha
rake
ruby-debug
shoulda
sqlite3-ruby (~> 1.3.0)
...@@ -15,7 +15,13 @@ useful defaults. ...@@ -15,7 +15,13 @@ useful defaults.
See the documentation for +has_attached_file+ in Paperclip::ClassMethods for See the documentation for +has_attached_file+ in Paperclip::ClassMethods for
more detailed options. more detailed options.
The complete RDoc[http://rdoc.info/projects/thoughtbot/paperclip] is online. The complete RDoc[http://rdoc.info/gems/paperclip] is online.
==Installation
Include the gem in your Gemfile:
gem "paperclip", "~> 2.3"
==Installation ==Installation
......
require 'rubygems'
require 'appraisal'
require 'bundler/setup'
require 'rake' require 'rake'
require 'rake/testtask' require 'rake/testtask'
require 'rake/rdoctask' require 'rake/rdoctask'
...@@ -6,11 +10,11 @@ $LOAD_PATH << File.join(File.dirname(__FILE__), 'lib') ...@@ -6,11 +10,11 @@ $LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
require 'paperclip' require 'paperclip'
desc 'Default: run unit tests.' desc 'Default: run unit tests.'
task :default => [:clean, :test] task :default => [:clean, :all]
desc 'Test the paperclip plugin under all supported Rails versions.' desc 'Test the paperclip plugin under all supported Rails versions.'
task :all do |t| task :all do |t|
exec('rake RAILS_VERSION=2.1 && rake RAILS_VERSION=2.3 && rake RAILS_VERSION=3.0') exec('rake appraisal test')
end end
desc 'Test the paperclip plugin.' desc 'Test the paperclip plugin.'
......
# This file was generated by Appraisal
source "http://rubygems.org"
gem "ruby-debug"
gem "rails", "~>2.3.0"
gem "rake"
gem "sqlite3-ruby", "~>1.3.0"
gem "shoulda"
gem "mocha"
gem "aws-s3", {:require=>"aws/s3"}
gem "appraisal"
\ No newline at end of file
GEM
remote: http://rubygems.org/
specs:
actionmailer (2.3.10)
actionpack (= 2.3.10)
actionpack (2.3.10)
activesupport (= 2.3.10)
rack (~> 1.1.0)
activerecord (2.3.10)
activesupport (= 2.3.10)
activeresource (2.3.10)
activesupport (= 2.3.10)
activesupport (2.3.10)
appraisal (0.1)
bundler
rake
aws-s3 (0.6.2)
builder
mime-types
xml-simple
builder (3.0.0)
columnize (0.3.2)
linecache (0.43)
mime-types (1.16)
mocha (0.9.9)
rake
rack (1.1.0)
rails (2.3.10)
actionmailer (= 2.3.10)
actionpack (= 2.3.10)
activerecord (= 2.3.10)
activeresource (= 2.3.10)
activesupport (= 2.3.10)
rake (>= 0.8.3)
rake (0.8.7)
ruby-debug (0.10.4)
columnize (>= 0.1)
ruby-debug-base (~> 0.10.4.0)
ruby-debug-base (0.10.4)
linecache (>= 0.3)
shoulda (2.11.3)
sqlite3-ruby (1.3.2)
xml-simple (1.0.12)
PLATFORMS
ruby
DEPENDENCIES
appraisal
aws-s3
mocha
rails (~> 2.3.0)
rake
ruby-debug
shoulda
sqlite3-ruby (~> 1.3.0)
# This file was generated by Appraisal
source "http://rubygems.org"
gem "ruby-debug"
gem "rails", ">=3.0.3"
gem "rake"
gem "sqlite3-ruby", "~>1.3.0"
gem "shoulda"
gem "mocha"
gem "aws-s3", {:require=>"aws/s3"}
gem "appraisal"
\ No newline at end of file
GEM
remote: http://rubygems.org/
specs:
abstract (1.0.0)
actionmailer (3.0.3)
actionpack (= 3.0.3)
mail (~> 2.2.9)
actionpack (3.0.3)
activemodel (= 3.0.3)
activesupport (= 3.0.3)
builder (~> 2.1.2)
erubis (~> 2.6.6)
i18n (~> 0.4)
rack (~> 1.2.1)
rack-mount (~> 0.6.13)
rack-test (~> 0.5.6)
tzinfo (~> 0.3.23)
activemodel (3.0.3)
activesupport (= 3.0.3)
builder (~> 2.1.2)
i18n (~> 0.4)
activerecord (3.0.3)
activemodel (= 3.0.3)
activesupport (= 3.0.3)
arel (~> 2.0.2)
tzinfo (~> 0.3.23)
activeresource (3.0.3)
activemodel (= 3.0.3)
activesupport (= 3.0.3)
activesupport (3.0.3)
appraisal (0.1)
bundler
rake
arel (2.0.4)
aws-s3 (0.6.2)
builder
mime-types
xml-simple
builder (2.1.2)
columnize (0.3.2)
erubis (2.6.6)
abstract (>= 1.0.0)
i18n (0.4.2)
linecache (0.43)
mail (2.2.10)
activesupport (>= 2.3.6)
i18n (~> 0.4.1)
mime-types (~> 1.16)
treetop (~> 1.4.8)
mime-types (1.16)
mocha (0.9.9)
rake
polyglot (0.3.1)
rack (1.2.1)
rack-mount (0.6.13)
rack (>= 1.0.0)
rack-test (0.5.6)
rack (>= 1.0)
rails (3.0.3)
actionmailer (= 3.0.3)
actionpack (= 3.0.3)
activerecord (= 3.0.3)
activeresource (= 3.0.3)
activesupport (= 3.0.3)
bundler (~> 1.0)
railties (= 3.0.3)
railties (3.0.3)
actionpack (= 3.0.3)
activesupport (= 3.0.3)
rake (>= 0.8.7)
thor (~> 0.14.4)
rake (0.8.7)
ruby-debug (0.10.4)
columnize (>= 0.1)
ruby-debug-base (~> 0.10.4.0)
ruby-debug-base (0.10.4)
linecache (>= 0.3)
shoulda (2.11.3)
sqlite3-ruby (1.3.2)
thor (0.14.6)
treetop (1.4.9)
polyglot (>= 0.3.1)
tzinfo (0.3.23)
xml-simple (1.0.12)
PLATFORMS
ruby
DEPENDENCIES
appraisal
aws-s3
mocha
rails (>= 3.0.3)
rake
ruby-debug
shoulda
sqlite3-ruby (~> 1.3.0)
...@@ -37,6 +37,7 @@ require 'paperclip/thumbnail' ...@@ -37,6 +37,7 @@ require 'paperclip/thumbnail'
require 'paperclip/interpolations' require 'paperclip/interpolations'
require 'paperclip/style' require 'paperclip/style'
require 'paperclip/attachment' require 'paperclip/attachment'
require 'paperclip/storage'
require 'paperclip/callback_compatability' require 'paperclip/callback_compatability'
require 'paperclip/command_line' require 'paperclip/command_line'
require 'paperclip/railtie' require 'paperclip/railtie'
...@@ -103,15 +104,6 @@ module Paperclip ...@@ -103,15 +104,6 @@ module Paperclip
CommandLine.new(cmd, *params).run CommandLine.new(cmd, *params).run
end end
def included base #:nodoc:
base.extend ClassMethods
if base.respond_to?("set_callback")
base.send :include, Paperclip::CallbackCompatability::Rails3
else
base.send :include, Paperclip::CallbackCompatability::Rails21
end
end
def processor name #:nodoc: def processor name #:nodoc:
name = name.to_s.camelize name = name.to_s.camelize
processor = Paperclip.const_get(name) processor = Paperclip.const_get(name)
...@@ -121,6 +113,12 @@ module Paperclip ...@@ -121,6 +113,12 @@ module Paperclip
processor processor
end end
def each_instance_with_attachment(klass, name)
Object.const_get(klass).all.each do |instance|
yield(instance) if instance.send(:"#{name}?")
end
end
# Log a paperclip-specific line. Uses ActiveRecord::Base.logger # Log a paperclip-specific line. Uses ActiveRecord::Base.logger
# by default. Set Paperclip.options[:log] to false to turn off. # by default. Set Paperclip.options[:log] to false to turn off.
def log message def log message
...@@ -159,6 +157,17 @@ module Paperclip ...@@ -159,6 +157,17 @@ module Paperclip
class InfiniteInterpolationError < PaperclipError #:nodoc: class InfiniteInterpolationError < PaperclipError #:nodoc:
end end
module Glue
def self.included base #:nodoc:
base.extend ClassMethods
if base.respond_to?("set_callback")
base.send :include, Paperclip::CallbackCompatability::Rails3
else
base.send :include, Paperclip::CallbackCompatability::Rails21
end
end
end
module ClassMethods module ClassMethods
# +has_attached_file+ gives the class it is called on an attribute that maps to a file. This # +has_attached_file+ gives the class it is called on an attribute that maps to a file. This
# is typically a file stored somewhere on the filesystem and has been uploaded by a user. # is typically a file stored somewhere on the filesystem and has been uploaded by a user.
......
...@@ -4,6 +4,7 @@ module Paperclip ...@@ -4,6 +4,7 @@ module Paperclip
# when the model saves, deletes when the model is destroyed, and processes # when the model saves, deletes when the model is destroyed, and processes
# the file upon assignment. # the file upon assignment.
class Attachment class Attachment
include IOStream
def self.default_options def self.default_options
@default_options ||= { @default_options ||= {
...@@ -88,11 +89,11 @@ module Paperclip ...@@ -88,11 +89,11 @@ module Paperclip
return nil if uploaded_file.nil? return nil if uploaded_file.nil?
@queued_for_write[:original] = uploaded_file.to_tempfile @queued_for_write[:original] = to_tempfile(uploaded_file)
instance_write(:file_name, uploaded_file.original_filename.strip) instance_write(:file_name, uploaded_file.original_filename.strip)
instance_write(:content_type, uploaded_file.content_type.to_s.strip) instance_write(:content_type, uploaded_file.content_type.to_s.strip)
instance_write(:file_size, uploaded_file.size.to_i) instance_write(:file_size, uploaded_file.size.to_i)
instance_write(:fingerprint, uploaded_file.fingerprint) instance_write(:fingerprint, generate_fingerprint(uploaded_file))
instance_write(:updated_at, Time.now) instance_write(:updated_at, Time.now)
@dirty = true @dirty = true
...@@ -101,7 +102,7 @@ module Paperclip ...@@ -101,7 +102,7 @@ module Paperclip
# Reset the file size if the original file was reprocessed. # Reset the file size if the original file was reprocessed.
instance_write(:file_size, @queued_for_write[:original].size.to_i) instance_write(:file_size, @queued_for_write[:original].size.to_i)
instance_write(:fingerprint, @queued_for_write[:original].fingerprint) instance_write(:fingerprint, generate_fingerprint(@queued_for_write[:original]))
ensure ensure
uploaded_file.close if close_uploaded_file uploaded_file.close if close_uploaded_file
end end
...@@ -180,7 +181,7 @@ module Paperclip ...@@ -180,7 +181,7 @@ module Paperclip
# Returns the hash of the file as originally assigned, and lives in the # Returns the hash of the file as originally assigned, and lives in the
# <attachment>_fingerprint attribute of the model. # <attachment>_fingerprint attribute of the model.
def fingerprint def fingerprint
instance_read(:fingerprint) || (@queued_for_write[:original] && @queued_for_write[:original].fingerprint) instance_read(:fingerprint) || (@queued_for_write[:original] && generate_fingerprint(@queued_for_write[:original]))
end end
# Returns the content_type of the file as originally assigned, and lives # Returns the content_type of the file as originally assigned, and lives
...@@ -196,6 +197,12 @@ module Paperclip ...@@ -196,6 +197,12 @@ module Paperclip
time && time.to_f.to_i time && time.to_f.to_i
end end
def generate_fingerprint(source)
data = source.read
source.rewind if source.respond_to?(:rewind)
Digest::MD5.hexdigest(data)
end
# Paths and URLs can have a number of variables interpolated into them # Paths and URLs can have a number of variables interpolated into them
# to vary the storage location based on name, id, style, class, etc. # to vary the storage location based on name, id, style, class, etc.
# This method is a deprecated access into supplying and retrieving these # This method is a deprecated access into supplying and retrieving these
...@@ -277,13 +284,12 @@ module Paperclip ...@@ -277,13 +284,12 @@ module Paperclip
end end
def initialize_storage #:nodoc: def initialize_storage #:nodoc:
storage_name = @storage.to_s.capitalize storage_class_name = @storage.to_s.capitalize
begin begin
require "paperclip/storage/#{@storage}" @storage_module = Paperclip::Storage.const_get(storage_class_name)
rescue MissingSourceFile rescue NameError
raise StorageMethodNotFound, "Cannot find the '#{@storage}' storage adapter." raise StorageMethodNotFound, "Cannot load storage module '#{storage_class_name}'"
end end
@storage_module = Paperclip::Storage.const_get(storage_name)
self.extend(@storage_module) self.extend(@storage_module)
end end
......
...@@ -8,7 +8,7 @@ module Paperclip ...@@ -8,7 +8,7 @@ module Paperclip
@binary = binary.dup @binary = binary.dup
@params = params.dup @params = params.dup
@options = options.dup @options = options.dup
@swallow_stderr = @options.delete(:swallow_stderr) @swallow_stderr = @options.has_key?(:swallow_stderr) ? @options.delete(:swallow_stderr) : Paperclip.options[:swallow_stderr]
@expected_outcodes = @options.delete(:expected_outcodes) @expected_outcodes = @options.delete(:expected_outcodes)
@expected_outcodes ||= [0] @expected_outcodes ||= [0]
end end
......
...@@ -41,8 +41,9 @@ module Paperclip ...@@ -41,8 +41,9 @@ module Paperclip
# Returns the interpolated URL. Will raise an error if the url itself # Returns the interpolated URL. Will raise an error if the url itself
# contains ":url" to prevent infinite recursion. This interpolation # contains ":url" to prevent infinite recursion. This interpolation
# is used in the default :path to ease default specifications. # is used in the default :path to ease default specifications.
RIGHT_HERE = "#{__FILE__.gsub(%r{^\./}, "")}:#{__LINE__ + 3}"
def url attachment, style_name def url attachment, style_name
raise InfiniteInterpolationError if caller.any?{|b| b.index("#{__FILE__}:#{__LINE__ + 1}") } raise InfiniteInterpolationError if caller.any?{|b| b.index(RIGHT_HERE) }
attachment.url(style_name, false) attachment.url(style_name, false)
end end
......
# Provides method that can be included on File-type objects (IO, StringIO, Tempfile, etc) to allow stream copying # Provides method that can be included on File-type objects (IO, StringIO, Tempfile, etc) to allow stream copying
# and Tempfile conversion. # and Tempfile conversion.
module IOStream module IOStream
# Returns a Tempfile containing the contents of the readable object. # Returns a Tempfile containing the contents of the readable object.
def to_tempfile def to_tempfile(object)
name = respond_to?(:original_filename) ? original_filename : (respond_to?(:path) ? path : "stream") return object.to_tempfile if object.respond_to?(:to_tempfile)
tempfile = Paperclip::Tempfile.new("stream" + File.extname(name)) name = object.respond_to?(:original_filename) ? object.original_filename : (object.respond_to?(:path) ? object.path : "stream")
tempfile = Paperclip::Tempfile.new(["stream", File.extname(name)])
tempfile.binmode tempfile.binmode
self.stream_to(tempfile) stream_to(object, tempfile)
end end
# Copies one read-able object from one place to another in blocks, obviating the need to load # Copies one read-able object from one place to another in blocks, obviating the need to load
# the whole thing into memory. Defaults to 8k blocks. If this module is included in both # the whole thing into memory. Defaults to 8k blocks. Returns a File if a String is passed
# StringIO and Tempfile, then either can have its data copied anywhere else without typing # in as the destination and returns the IO or Tempfile as passed in if one is sent as the destination.
# worries or memory overhead worries. Returns a File if a String is passed in as the destination def stream_to object, path_or_file, in_blocks_of = 8192
# and returns the IO or Tempfile as passed in if one is sent as the destination.
def stream_to path_or_file, in_blocks_of = 8192
dstio = case path_or_file dstio = case path_or_file
when String then File.new(path_or_file, "wb+") when String then File.new(path_or_file, "wb+")
when IO then path_or_file when IO then path_or_file
when Tempfile then path_or_file when Tempfile then path_or_file
end end
buffer = "" buffer = ""
self.rewind object.rewind
while self.read(in_blocks_of, buffer) do while object.read(in_blocks_of, buffer) do
dstio.write(buffer) dstio.write(buffer)
end end
dstio.rewind dstio.rewind
...@@ -31,18 +29,6 @@ module IOStream ...@@ -31,18 +29,6 @@ module IOStream
end end
end end
class IO #:nodoc:
include IOStream
end
%w( Tempfile StringIO ).each do |klass|
if Object.const_defined? klass
Object.const_get(klass).class_eval do
include IOStream
end
end
end
# Corrects a bug in Windows when asking for Tempfile size. # Corrects a bug in Windows when asking for Tempfile size.
if defined? Tempfile if defined? Tempfile
class Tempfile class Tempfile
......
...@@ -40,10 +40,19 @@ module Paperclip ...@@ -40,10 +40,19 @@ module Paperclip
# on this blog post: # on this blog post:
# http://marsorange.com/archives/of-mogrify-ruby-tempfile-dynamic-class-definitions # http://marsorange.com/archives/of-mogrify-ruby-tempfile-dynamic-class-definitions
class Tempfile < ::Tempfile class Tempfile < ::Tempfile
# Replaces Tempfile's +make_tmpname+ with one that honors file extensions. # This is Ruby 1.8.7's implementation.
def make_tmpname(basename, n) if RUBY_VERSION <= "1.8.6"
extension = File.extname(basename) def make_tmpname(basename, n)
sprintf("%s,%d,%d%s", File.basename(basename, extension), $$, n.to_i, extension) case basename
when Array
prefix, suffix = *basename
else
prefix, suffix = basename, ''
end
t = Time.now.strftime("%y%m%d")
path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}-#{n}#{suffix}"
end
end end
end end
end end
...@@ -17,7 +17,7 @@ module Paperclip ...@@ -17,7 +17,7 @@ module Paperclip
class Railtie class Railtie
def self.insert def self.insert
ActiveRecord::Base.send(:include, Paperclip) ActiveRecord::Base.send(:include, Paperclip::Glue)
File.send(:include, Paperclip::Upfile) File.send(:include, Paperclip::Upfile)
end end
end end
......
require "paperclip/storage/filesystem"
require "paperclip/storage/s3"
...@@ -56,6 +56,7 @@ module Paperclip ...@@ -56,6 +56,7 @@ module Paperclip
while(true) while(true)
path = File.dirname(path) path = File.dirname(path)
FileUtils.rmdir(path) FileUtils.rmdir(path)
break if File.exists?(path) # Ruby 1.9.2 does not raise if the removal failed.
end end
rescue Errno::EEXIST, Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL, Errno::ENOTDIR rescue Errno::EEXIST, Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL, Errno::ENOTDIR
# Stop trying to remove parent directories # Stop trying to remove parent directories
......
...@@ -127,12 +127,20 @@ module Paperclip ...@@ -127,12 +127,20 @@ module Paperclip
# style, in the format most representative of the current storage. # style, in the format most representative of the current storage.
def to_file style = default_style def to_file style = default_style
return @queued_for_write[style] if @queued_for_write[style] return @queued_for_write[style] if @queued_for_write[style]
file = Tempfile.new(path(style)) filename = path(style)
extname = File.extname(filename)
basename = File.basename(filename, extname)
file = Tempfile.new([basename, extname])
file.binmode
file.write(AWS::S3::S3Object.value(path(style), bucket_name)) file.write(AWS::S3::S3Object.value(path(style), bucket_name))
file.rewind file.rewind
return file return file
end end
def create_bucket
AWS::S3::Bucket.create(bucket_name)
end
def flush_writes #:nodoc: def flush_writes #:nodoc:
@queued_for_write.each do |style, file| @queued_for_write.each do |style, file|
begin begin
...@@ -143,6 +151,9 @@ module Paperclip ...@@ -143,6 +151,9 @@ module Paperclip
{:content_type => instance_read(:content_type), {:content_type => instance_read(:content_type),
:access => @s3_permissions, :access => @s3_permissions,
}.merge(@s3_headers)) }.merge(@s3_headers))
rescue AWS::S3::NoSuchBucket => e
create_bucket
retry
rescue AWS::S3::ResponseError => e rescue AWS::S3::ResponseError => e
raise raise
end end
......
...@@ -45,7 +45,7 @@ module Paperclip ...@@ -45,7 +45,7 @@ module Paperclip
# that contains the new image. # that contains the new image.
def make def make
src = @file src = @file
dst = Tempfile.new([@basename, @format].compact.join(".")) dst = Tempfile.new([@basename, @format ? ".#{@format}" : ''])
dst.binmode dst.binmode
begin begin
......
...@@ -32,11 +32,6 @@ module Paperclip ...@@ -32,11 +32,6 @@ module Paperclip
def size def size
File.size(self) File.size(self)
end end
# Returns the hash of the file.
def fingerprint
Digest::MD5.hexdigest(self.read)
end
end end
end end
......
module Paperclip module Paperclip
VERSION = "2.3.3" unless defined? Paperclip::VERSION VERSION = "2.3.8" unless defined? Paperclip::VERSION
end end
def obtain_class def obtain_class
class_name = ENV['CLASS'] || ENV['class'] class_name = ENV['CLASS'] || ENV['class']
raise "Must specify CLASS" unless class_name raise "Must specify CLASS" unless class_name
@klass = Object.const_get(class_name) class_name
end end
def obtain_attachments def obtain_attachments(klass)
klass = Object.const_get(klass.to_s)
name = ENV['ATTACHMENT'] || ENV['attachment'] name = ENV['ATTACHMENT'] || ENV['attachment']
raise "Class #{@klass.name} has no attachments specified" unless @klass.respond_to?(:attachment_definitions) raise "Class #{klass.name} has no attachments specified" unless klass.respond_to?(:attachment_definitions)
if !name.blank? && @klass.attachment_definitions.keys.include?(name) if !name.blank? && klass.attachment_definitions.keys.include?(name)
[ name ] [ name ]
else else
@klass.attachment_definitions.keys klass.attachment_definitions.keys
end end
end end
def for_all_attachments
klass = obtain_class
names = obtain_attachments
ids = klass.connection.select_values(klass.send(:construct_finder_sql, :select => 'id'))
ids.each do |id|
instance = klass.find(id)
names.each do |name|
result = if instance.send("#{ name }?")
yield(instance, name)
else
true
end
print result ? "." : "x"; $stdout.flush
end
end
puts " Done."
end
namespace :paperclip do namespace :paperclip do
desc "Refreshes both metadata and thumbnails." desc "Refreshes both metadata and thumbnails."
task :refresh => ["paperclip:refresh:metadata", "paperclip:refresh:thumbnails"] task :refresh => ["paperclip:refresh:metadata", "paperclip:refresh:thumbnails"]
...@@ -41,24 +23,31 @@ namespace :paperclip do ...@@ -41,24 +23,31 @@ namespace :paperclip do
desc "Regenerates thumbnails for a given CLASS (and optional ATTACHMENT)." desc "Regenerates thumbnails for a given CLASS (and optional ATTACHMENT)."
task :thumbnails => :environment do task :thumbnails => :environment do
errors = [] errors = []
for_all_attachments do |instance, name| klass = obtain_class
result = instance.send(name).reprocess! names = obtain_attachments(klass)
errors << [instance.id, instance.errors] unless instance.errors.blank? names.each do |name|
result Paperclip.each_instance_with_attachment(klass, name) do |instance|
result = instance.send(name).reprocess!
errors << [instance.id, instance.errors] unless instance.errors.blank?
end
end end
errors.each{|e| puts "#{e.first}: #{e.last.full_messages.inspect}" } errors.each{|e| puts "#{e.first}: #{e.last.full_messages.inspect}" }
end end
desc "Regenerates content_type/size metadata for a given CLASS (and optional ATTACHMENT)." desc "Regenerates content_type/size metadata for a given CLASS (and optional ATTACHMENT)."
task :metadata => :environment do task :metadata => :environment do
for_all_attachments do |instance, name| klass = obtain_class
if file = instance.send(name).to_file names = obtain_attachments(klass)
instance.send("#{name}_file_name=", instance.send("#{name}_file_name").strip) names.each do |name|
instance.send("#{name}_content_type=", file.content_type.strip) Paperclip.each_instance_with_attachment(klass, name) do |instance|
instance.send("#{name}_file_size=", file.size) if instance.respond_to?("#{name}_file_size") if file = instance.send(name).to_file
instance.save(false) instance.send("#{name}_file_name=", instance.send("#{name}_file_name").strip)
else instance.send("#{name}_content_type=", file.content_type.strip)
true instance.send("#{name}_file_size=", file.size) if instance.respond_to?("#{name}_file_size")
instance.save(false)
else
true
end
end end
end end
end end
...@@ -66,13 +55,17 @@ namespace :paperclip do ...@@ -66,13 +55,17 @@ namespace :paperclip do
desc "Cleans out invalid attachments. Useful after you've added new validations." desc "Cleans out invalid attachments. Useful after you've added new validations."
task :clean => :environment do task :clean => :environment do
for_all_attachments do |instance, name| klass = obtain_class
instance.send(name).send(:validate) names = obtain_attachments(klass)
if instance.send(name).valid? names.each do |name|
true Paperclip.each_instance_with_attachment(klass, name) do |instance|
else instance.send(name).send(:validate)
instance.send("#{name}=", nil) if instance.send(name).valid?
instance.save true
else
instance.send("#{name}=", nil)
instance.save
end
end end
end end
end end
......
...@@ -28,6 +28,7 @@ spec = Gem::Specification.new do |s| ...@@ -28,6 +28,7 @@ spec = Gem::Specification.new do |s|
s.add_dependency 'activerecord' s.add_dependency 'activerecord'
s.add_dependency 'activesupport' s.add_dependency 'activesupport'
s.add_development_dependency 'shoulda' s.add_development_dependency 'shoulda'
s.add_development_dependency 'appraisal'
s.add_development_dependency 'mocha' s.add_development_dependency 'mocha'
s.add_development_dependency 'aws-s3' s.add_development_dependency 'aws-s3'
s.add_development_dependency 'sqlite3-ruby' s.add_development_dependency 'sqlite3-ruby'
......
# encoding: utf-8 # encoding: utf-8
require 'test/helper' require './test/helper'
class Dummy class Dummy
# This is a dummy class # This is a dummy class
...@@ -463,14 +463,11 @@ class AttachmentTest < Test::Unit::TestCase ...@@ -463,14 +463,11 @@ class AttachmentTest < Test::Unit::TestCase
setup do setup do
rebuild_model rebuild_model
@not_file = mock @not_file = mock("not_file")
@tempfile = mock @tempfile = mock("tempfile")
@not_file.stubs(:nil?).returns(false) @not_file.stubs(:nil?).returns(false)
@not_file.stubs(:fingerprint).returns('bd94545193321376b70136f8b223abf8')
@tempfile.stubs(:fingerprint).returns('bd94545193321376b70136f8b223abf8')
@not_file.expects(:size).returns(10) @not_file.expects(:size).returns(10)
@tempfile.expects(:size).returns(10) @tempfile.expects(:size).returns(10)
@not_file.expects(:to_tempfile).returns(@tempfile)
@not_file.expects(:original_filename).returns("sheep_say_bæ.png\r\n") @not_file.expects(:original_filename).returns("sheep_say_bæ.png\r\n")
@not_file.expects(:content_type).returns("image/png\r\n") @not_file.expects(:content_type).returns("image/png\r\n")
...@@ -479,6 +476,9 @@ class AttachmentTest < Test::Unit::TestCase ...@@ -479,6 +476,9 @@ class AttachmentTest < Test::Unit::TestCase
@attachment.expects(:valid_assignment?).with(@not_file).returns(true) @attachment.expects(:valid_assignment?).with(@not_file).returns(true)
@attachment.expects(:queue_existing_for_delete) @attachment.expects(:queue_existing_for_delete)
@attachment.expects(:post_process) @attachment.expects(:post_process)
@attachment.expects(:to_tempfile).returns(@tempfile)
@attachment.expects(:generate_fingerprint).with(@tempfile).returns("12345")
@attachment.expects(:generate_fingerprint).with(@not_file).returns("12345")
@dummy.avatar = @not_file @dummy.avatar = @not_file
end end
...@@ -606,7 +606,7 @@ class AttachmentTest < Test::Unit::TestCase ...@@ -606,7 +606,7 @@ class AttachmentTest < Test::Unit::TestCase
[:large, :medium, :small].each do |style| [:large, :medium, :small].each do |style|
io = @attachment.to_file(style) io = @attachment.to_file(style)
# p "in commit to disk test, io is #{io.inspect} and @instance.id is #{@instance.id}" # p "in commit to disk test, io is #{io.inspect} and @instance.id is #{@instance.id}"
assert File.exists?(io) assert File.exists?(io.path)
assert ! io.is_a?(::Tempfile) assert ! io.is_a?(::Tempfile)
io.close io.close
end end
...@@ -703,7 +703,7 @@ class AttachmentTest < Test::Unit::TestCase ...@@ -703,7 +703,7 @@ class AttachmentTest < Test::Unit::TestCase
now = Time.now now = Time.now
Time.stubs(:now).returns(now) Time.stubs(:now).returns(now)
@dummy.avatar = @file @dummy.avatar = @file
assert now, @dummy.avatar.updated_at assert_equal now.to_i, @dummy.avatar.updated_at.to_i
end end
should "return nil when reloaded and sent #avatar_updated_at" do should "return nil when reloaded and sent #avatar_updated_at" do
......
require 'test/helper' require './test/helper'
class CommandLineTest < Test::Unit::TestCase class CommandLineTest < Test::Unit::TestCase
def setup def setup
...@@ -7,13 +7,13 @@ class CommandLineTest < Test::Unit::TestCase ...@@ -7,13 +7,13 @@ class CommandLineTest < Test::Unit::TestCase
end end
should "take a command and parameters and produce a shell command for bash" do should "take a command and parameters and produce a shell command for bash" do
cmd = Paperclip::CommandLine.new("convert", "a.jpg b.png") cmd = Paperclip::CommandLine.new("convert", "a.jpg b.png", :swallow_stderr => false)
assert_equal "convert a.jpg b.png", cmd.command assert_equal "convert a.jpg b.png", cmd.command
end end
should "be able to set a path and produce commands with that path" do should "be able to set a path and produce commands with that path" do
Paperclip::CommandLine.path = "/opt/bin" Paperclip::CommandLine.path = "/opt/bin"
cmd = Paperclip::CommandLine.new("convert", "a.jpg b.png") cmd = Paperclip::CommandLine.new("convert", "a.jpg b.png", :swallow_stderr => false)
assert_equal "/opt/bin/convert a.jpg b.png", cmd.command assert_equal "/opt/bin/convert a.jpg b.png", cmd.command
end end
...@@ -21,7 +21,8 @@ class CommandLineTest < Test::Unit::TestCase ...@@ -21,7 +21,8 @@ class CommandLineTest < Test::Unit::TestCase
cmd = Paperclip::CommandLine.new("convert", cmd = Paperclip::CommandLine.new("convert",
":one :{two}", ":one :{two}",
:one => "a.jpg", :one => "a.jpg",
:two => "b.png") :two => "b.png",
:swallow_stderr => false)
assert_equal "convert 'a.jpg' 'b.png'", cmd.command assert_equal "convert 'a.jpg' 'b.png'", cmd.command
end end
...@@ -30,7 +31,8 @@ class CommandLineTest < Test::Unit::TestCase ...@@ -30,7 +31,8 @@ class CommandLineTest < Test::Unit::TestCase
cmd = Paperclip::CommandLine.new("convert", cmd = Paperclip::CommandLine.new("convert",
":one :{two}", ":one :{two}",
:one => "a.jpg", :one => "a.jpg",
:two => "b.png") :two => "b.png",
:swallow_stderr => false)
assert_equal 'convert "a.jpg" "b.png"', cmd.command assert_equal 'convert "a.jpg" "b.png"', cmd.command
end end
...@@ -38,7 +40,8 @@ class CommandLineTest < Test::Unit::TestCase ...@@ -38,7 +40,8 @@ class CommandLineTest < Test::Unit::TestCase
cmd = Paperclip::CommandLine.new("convert", cmd = Paperclip::CommandLine.new("convert",
":one :two", ":one :two",
:one => "`rm -rf`.jpg", :one => "`rm -rf`.jpg",
:two => "ha'ha.png") :two => "ha'ha.png",
:swallow_stderr => false)
assert_equal "convert '`rm -rf`.jpg' 'ha'\\''ha.png'", cmd.command assert_equal "convert '`rm -rf`.jpg' 'ha'\\''ha.png'", cmd.command
end end
...@@ -47,7 +50,8 @@ class CommandLineTest < Test::Unit::TestCase ...@@ -47,7 +50,8 @@ class CommandLineTest < Test::Unit::TestCase
cmd = Paperclip::CommandLine.new("convert", cmd = Paperclip::CommandLine.new("convert",
":one :two", ":one :two",
:one => "`rm -rf`.jpg", :one => "`rm -rf`.jpg",
:two => "ha'ha.png") :two => "ha'ha.png",
:swallow_stderr => false)
assert_equal %{convert "`rm -rf`.jpg" "ha'ha.png"}, cmd.command assert_equal %{convert "`rm -rf`.jpg" "ha'ha.png"}, cmd.command
end end
...@@ -80,7 +84,7 @@ class CommandLineTest < Test::Unit::TestCase ...@@ -80,7 +84,7 @@ class CommandLineTest < Test::Unit::TestCase
end end
should "run the #command it's given and return the output" do should "run the #command it's given and return the output" do
cmd = Paperclip::CommandLine.new("convert", "a.jpg b.png") cmd = Paperclip::CommandLine.new("convert", "a.jpg b.png", :swallow_stderr => false)
cmd.class.stubs(:"`").with("convert a.jpg b.png").returns(:correct_value) cmd.class.stubs(:"`").with("convert a.jpg b.png").returns(:correct_value)
with_exitstatus_returning(0) do with_exitstatus_returning(0) do
assert_equal :correct_value, cmd.run assert_equal :correct_value, cmd.run
...@@ -88,7 +92,7 @@ class CommandLineTest < Test::Unit::TestCase ...@@ -88,7 +92,7 @@ class CommandLineTest < Test::Unit::TestCase
end end
should "raise a PaperclipCommandLineError if the result code isn't expected" do should "raise a PaperclipCommandLineError if the result code isn't expected" do
cmd = Paperclip::CommandLine.new("convert", "a.jpg b.png") cmd = Paperclip::CommandLine.new("convert", "a.jpg b.png", :swallow_stderr => false)
cmd.class.stubs(:"`").with("convert a.jpg b.png").returns(:correct_value) cmd.class.stubs(:"`").with("convert a.jpg b.png").returns(:correct_value)
with_exitstatus_returning(1) do with_exitstatus_returning(1) do
assert_raises(Paperclip::PaperclipCommandLineError) do assert_raises(Paperclip::PaperclipCommandLineError) do
...@@ -100,7 +104,8 @@ class CommandLineTest < Test::Unit::TestCase ...@@ -100,7 +104,8 @@ class CommandLineTest < Test::Unit::TestCase
should "not raise a PaperclipCommandLineError if the result code is expected" do should "not raise a PaperclipCommandLineError if the result code is expected" do
cmd = Paperclip::CommandLine.new("convert", cmd = Paperclip::CommandLine.new("convert",
"a.jpg b.png", "a.jpg b.png",
:expected_outcodes => [0, 1]) :expected_outcodes => [0, 1],
:swallow_stderr => false)
cmd.class.stubs(:"`").with("convert a.jpg b.png").returns(:correct_value) cmd.class.stubs(:"`").with("convert a.jpg b.png").returns(:correct_value)
with_exitstatus_returning(1) do with_exitstatus_returning(1) do
assert_nothing_raised do assert_nothing_raised do
...@@ -110,7 +115,7 @@ class CommandLineTest < Test::Unit::TestCase ...@@ -110,7 +115,7 @@ class CommandLineTest < Test::Unit::TestCase
end end
should "log the command" do should "log the command" do
cmd = Paperclip::CommandLine.new("convert", "a.jpg b.png") cmd = Paperclip::CommandLine.new("convert", "a.jpg b.png", :swallow_stderr => false)
cmd.class.stubs(:'`') cmd.class.stubs(:'`')
Paperclip.expects(:log).with("convert a.jpg b.png") Paperclip.expects(:log).with("convert a.jpg b.png")
cmd.run cmd.run
......
require 'test/helper' require './test/helper'
class GeometryTest < Test::Unit::TestCase class GeometryTest < Test::Unit::TestCase
context "Paperclip::Geometry" do context "Paperclip::Geometry" do
......
...@@ -5,18 +5,6 @@ require 'test/unit' ...@@ -5,18 +5,6 @@ require 'test/unit'
require 'shoulda' require 'shoulda'
require 'mocha' require 'mocha'
case ENV['RAILS_VERSION']
when '2.1' then
gem 'activerecord', '~>2.1.0'
gem 'activesupport', '~>2.1.0'
when '3.0' then
gem 'activerecord', '~>3.0.0'
gem 'activesupport', '~>3.0.0'
else
gem 'activerecord', '~>2.3.0'
gem 'activesupport', '~>2.3.0'
end
require 'active_record' require 'active_record'
require 'active_record/version' require 'active_record/version'
require 'active_support' require 'active_support'
...@@ -53,7 +41,7 @@ $LOAD_PATH << File.join(ROOT, 'lib', 'paperclip') ...@@ -53,7 +41,7 @@ $LOAD_PATH << File.join(ROOT, 'lib', 'paperclip')
require File.join(ROOT, 'lib', 'paperclip.rb') require File.join(ROOT, 'lib', 'paperclip.rb')
require 'shoulda_macros/paperclip' require './shoulda_macros/paperclip'
FIXTURES_DIR = File.join(File.dirname(__FILE__), "fixtures") FIXTURES_DIR = File.join(File.dirname(__FILE__), "fixtures")
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml')) config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
...@@ -61,10 +49,10 @@ ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new(File.dirname(__FIL ...@@ -61,10 +49,10 @@ ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new(File.dirname(__FIL
ActiveRecord::Base.establish_connection(config['test']) ActiveRecord::Base.establish_connection(config['test'])
def reset_class class_name def reset_class class_name
ActiveRecord::Base.send(:include, Paperclip) ActiveRecord::Base.send(:include, Paperclip::Glue)
Object.send(:remove_const, class_name) rescue nil Object.send(:remove_const, class_name) rescue nil
klass = Object.const_set(class_name, Class.new(ActiveRecord::Base)) klass = Object.const_set(class_name, Class.new(ActiveRecord::Base))
klass.class_eval{ include Paperclip } klass.class_eval{ include Paperclip::Glue }
klass klass
end end
...@@ -90,11 +78,11 @@ def rebuild_model options = {} ...@@ -90,11 +78,11 @@ def rebuild_model options = {}
end end
def rebuild_class options = {} def rebuild_class options = {}
ActiveRecord::Base.send(:include, Paperclip) ActiveRecord::Base.send(:include, Paperclip::Glue)
Object.send(:remove_const, "Dummy") rescue nil Object.send(:remove_const, "Dummy") rescue nil
Object.const_set("Dummy", Class.new(ActiveRecord::Base)) Object.const_set("Dummy", Class.new(ActiveRecord::Base))
Dummy.class_eval do Dummy.class_eval do
include Paperclip include Paperclip::Glue
has_attached_file :avatar, options has_attached_file :avatar, options
end end
end end
......
require 'test/helper' require './test/helper'
class IntegrationTest < Test::Unit::TestCase class IntegrationTest < Test::Unit::TestCase
context "Many models at once" do context "Many models at once" do
......
require 'test/helper' require './test/helper'
class InterpolationsTest < Test::Unit::TestCase class InterpolationsTest < Test::Unit::TestCase
should "return all methods but the infrastructure when sent #all" do should "return all methods but the infrastructure when sent #all" do
......
require 'test/helper' require './test/helper'
class IOStreamTest < Test::Unit::TestCase class IOStreamTest < Test::Unit::TestCase
context "IOStream" do include IOStream
should "be included in IO, File, Tempfile, and StringIO" do
[IO, File, Tempfile, StringIO].each do |klass|
assert klass.included_modules.include?(IOStream), "Not in #{klass}"
end
end
end
context "A file" do context "A file" do
setup do setup do
@file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb') @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
...@@ -21,7 +14,7 @@ class IOStreamTest < Test::Unit::TestCase ...@@ -21,7 +14,7 @@ class IOStreamTest < Test::Unit::TestCase
context "and given a String" do context "and given a String" do
setup do setup do
FileUtils.mkdir_p(File.join(ROOT, 'tmp')) FileUtils.mkdir_p(File.join(ROOT, 'tmp'))
assert @result = @file.stream_to(File.join(ROOT, 'tmp', 'iostream.string.test')) assert @result = stream_to(@file, File.join(ROOT, 'tmp', 'iostream.string.test'))
end end
should "return a File" do should "return a File" do
...@@ -38,7 +31,7 @@ class IOStreamTest < Test::Unit::TestCase ...@@ -38,7 +31,7 @@ class IOStreamTest < Test::Unit::TestCase
setup do setup do
tempfile = Tempfile.new('iostream.test') tempfile = Tempfile.new('iostream.test')
tempfile.binmode tempfile.binmode
assert @result = @file.stream_to(tempfile) assert @result = stream_to(@file, tempfile)
end end
should "return a Tempfile" do should "return a Tempfile" do
...@@ -53,9 +46,9 @@ class IOStreamTest < Test::Unit::TestCase ...@@ -53,9 +46,9 @@ class IOStreamTest < Test::Unit::TestCase
end end
context "that is sent #to_tempfile" do context "that is converted #to_tempfile" do
setup do setup do
assert @tempfile = @file.to_tempfile assert @tempfile = to_tempfile(@file)
end end
should "convert it to a Paperclip Tempfile" do should "convert it to a Paperclip Tempfile" do
......
require 'test/helper' require './test/helper'
class HaveAttachedFileMatcherTest < Test::Unit::TestCase class HaveAttachedFileMatcherTest < Test::Unit::TestCase
context "have_attached_file" do context "have_attached_file" do
......
require 'test/helper' require './test/helper'
class ValidateAttachmentContentTypeMatcherTest < Test::Unit::TestCase class ValidateAttachmentContentTypeMatcherTest < Test::Unit::TestCase
context "validate_attachment_content_type" do context "validate_attachment_content_type" do
......
require 'test/helper' require './test/helper'
class ValidateAttachmentPresenceMatcherTest < Test::Unit::TestCase class ValidateAttachmentPresenceMatcherTest < Test::Unit::TestCase
context "validate_attachment_presence" do context "validate_attachment_presence" do
......
require 'test/helper' require './test/helper'
class ValidateAttachmentSizeMatcherTest < Test::Unit::TestCase class ValidateAttachmentSizeMatcherTest < Test::Unit::TestCase
context "validate_attachment_size" do context "validate_attachment_size" do
......
require 'test/helper' require './test/helper'
class PaperclipTest < Test::Unit::TestCase class PaperclipTest < Test::Unit::TestCase
context "Calling Paperclip.run" do context "Calling Paperclip.run" do
...@@ -43,6 +43,23 @@ class PaperclipTest < Test::Unit::TestCase ...@@ -43,6 +43,23 @@ class PaperclipTest < Test::Unit::TestCase
end end
end end
context "Paperclip.each_instance_with_attachment" do
setup do
@file = File.new(File.join(FIXTURES_DIR, "5k.png"), 'rb')
d1 = Dummy.create(:avatar => @file)
d2 = Dummy.create
d3 = Dummy.create(:avatar => @file)
@expected = [d1, d3]
end
should "yield every instance of a model that has an attachment" do
actual = []
Paperclip.each_instance_with_attachment("Dummy", "avatar") do |instance|
actual << instance
end
assert_same_elements @expected, actual
end
end
should "raise when sent #processor and the name of a class that exists but isn't a subclass of Processor" do should "raise when sent #processor and the name of a class that exists but isn't a subclass of Processor" do
assert_raises(Paperclip::PaperclipError){ Paperclip.processor(:attachment) } assert_raises(Paperclip::PaperclipError){ Paperclip.processor(:attachment) }
end end
...@@ -172,6 +189,12 @@ class PaperclipTest < Test::Unit::TestCase ...@@ -172,6 +189,12 @@ class PaperclipTest < Test::Unit::TestCase
end end
end end
should "not have Attachment in the ActiveRecord::Base namespace" do
assert_raises(NameError) do
ActiveRecord::Base::Attachment
end
end
def self.should_validate validation, options, valid_file, invalid_file def self.should_validate validation, options, valid_file, invalid_file
context "with #{validation} validation and #{options.inspect} options" do context "with #{validation} validation and #{options.inspect} options" do
setup do setup do
......
require 'test/helper' require './test/helper'
class ProcessorTest < Test::Unit::TestCase class ProcessorTest < Test::Unit::TestCase
should "instantiate and call #make when sent #make to the class" do should "instantiate and call #make when sent #make to the class" do
......
require 'test/helper' require './test/helper'
require 'aws/s3' require 'aws/s3'
class StorageTest < Test::Unit::TestCase class StorageTest < Test::Unit::TestCase
...@@ -185,6 +185,21 @@ class StorageTest < Test::Unit::TestCase ...@@ -185,6 +185,21 @@ class StorageTest < Test::Unit::TestCase
end end
end end
context "and saved without a bucket" do
setup do
class AWS::S3::NoSuchBucket < AWS::S3::ResponseError
# Force the class to be created as a proper subclass of ResponseError thanks to AWS::S3's autocreation of exceptions
end
AWS::S3::Bucket.expects(:create).with("testing")
AWS::S3::S3Object.stubs(:store).raises(AWS::S3::NoSuchBucket.new(:message, :response)).then.returns(true)
@dummy.save
end
should "succeed" do
assert true
end
end
context "and remove" do context "and remove" do
setup do setup do
AWS::S3::S3Object.stubs(:exists?).returns(true) AWS::S3::S3Object.stubs(:exists?).returns(true)
...@@ -336,6 +351,11 @@ class StorageTest < Test::Unit::TestCase ...@@ -336,6 +351,11 @@ class StorageTest < Test::Unit::TestCase
should "be on S3" do should "be on S3" do
assert true assert true
end end
should "generate a tempfile with the right name" do
file = @dummy.avatar.to_file
assert_match /^original.*\.png$/, File.basename(file.path)
end
end end
end end
end end
......
# encoding: utf-8 # encoding: utf-8
require 'test/helper' require './test/helper'
class StyleTest < Test::Unit::TestCase class StyleTest < Test::Unit::TestCase
......
require 'test/helper' require './test/helper'
class ThumbnailTest < Test::Unit::TestCase class ThumbnailTest < Test::Unit::TestCase
context "A Paperclip Tempfile" do context "A Paperclip Tempfile" do
setup do setup do
@tempfile = Paperclip::Tempfile.new("file.jpg") @tempfile = Paperclip::Tempfile.new(["file", ".jpg"])
end end
should "have its path contain a real extension" do should "have its path contain a real extension" do
......
require 'test/helper' require './test/helper'
class UpfileTest < Test::Unit::TestCase class UpfileTest < Test::Unit::TestCase
{ %w(jpg jpe jpeg) => 'image/jpeg', { %w(jpg jpe jpeg) => 'image/jpeg',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment