Permalink
Browse files

Re-refactoring, plus the beginning of lots of tests.

git-svn-id: https://svn.thoughtbot.com/plugins/paperclip/trunk@242 7bbfaf0e-4d1d-0410-9690-a8bb5f8ef2aa
  • Loading branch information...
1 parent 26444ce commit 11086ba6eafdf215f5353e3144ad7ff6c010ad1e jyurek committed Nov 12, 2007
View
4 Rakefile
@@ -8,7 +8,7 @@ task :default => [:clean, :test]
desc 'Test the paperclip plugin.'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib' << 'profile'
- t.pattern = 'test/**/*_test.rb'
+ t.pattern = 'test/**/test_*.rb'
t.verbose = true
end
@@ -28,4 +28,4 @@ task :clean do |t|
FileUtils.rm_rf "tmp"
FileUtils.rm "test/debug.log" rescue nil
FileUtils.rm "test/paperclip.db" rescue nil
-end
+end
View
73 lib/paperclip.rb
@@ -56,8 +56,8 @@ class PaperclipError < StandardError #:nodoc:
class AttachmentDefinition
def self.defaults
@defaults ||= {
- :path => ":rails_root/public/:class/:attachment/:id/:style_:name",
- :url => "/:class/:attachment/:id/:style_:name",
+ :path => ":rails_root/public/:class/:attachment/:id/:style_:filename",
+ :url => "/:class/:attachment/:id/:style_:filename",
:missing_url => "/:class/:attachment/:style_missing.png",
:attachment_type => :image,
:thumbnails => {},
@@ -80,7 +80,7 @@ def styles
end
def thumbnails
- @thumbnails ||= @options[:thumbnails]
+ @thumbnails ||= @options[:thumbnails] || {}
end
def validate thing, *constraints
@@ -90,8 +90,8 @@ def validate thing, *constraints
def validations
@validations ||= @options.inject({}) do |valids, opts|
key, val = opts
- if (m = key.to_s.match(/^validate_(.+)/))
- valids[m[1]] = val
+ if (m = key.to_s.match(/^validates?_(.+)/))
+ valids[m[1].to_sym] = val
end
valids
end
@@ -108,9 +108,9 @@ def method_missing meth, *args
class Attachment
attr_reader :name, :instance, :original_filename, :content_type, :original_file_size, :definition, :errors
- def initialize name, active_record, definition
+ def initialize active_record, name, definition
@instance = active_record
- @definition = defintiion
+ @definition = definition
@name = name
@errors = []
@@ -132,7 +132,7 @@ def assign uploaded_file
self[:original] = uploaded_file
@dirty = true
- if definition.type == :image
+ if definition.attachment_type == :image
make_thumbnails_from uploaded_file
end
end
@@ -147,7 +147,7 @@ def []=(style, data)
def clear_files
@files = {}
- definition.styles.each{|style| @files[style] = nil }
+ definition.styles.each{|style, geo| @files[style] = nil }
@dirty = false
end
@@ -180,17 +180,18 @@ def save
clear_files
end
- def queue_destroy(complain = false)
+ def destroy(complain = false)
returning true do
- @delete_on_save = true
- @complain_on_delete = complain
- self.original_filename = nil
- self.content_type = nil
+ @delete_on_save = true
+ @complain_on_delete = complain
+ self.original_filename = nil
+ self.content_type = nil
+ self.original_file_size = nil
clear_files
end
end
- def destroy
+ def destroy!
delete_attachment if definition.delete_on_destroy
end
@@ -205,7 +206,8 @@ def url style = nil
end
def read style = nil
- self[style] ? self[style].read : IO.read(file_name(style))
+ style ||= definition.default_style
+ self[style] ? (self[style].rewind && self[style].read) : IO.read(file_name(style))
end
def validate_existence *constraints
@@ -296,7 +298,7 @@ def make_thumbnail geometry, data
raise PaperclipError, "could not be thumbnailed."
end
if Paperclip.options[:whiny_thumbnails] && !$?.success?
- raise PaperclipError, "could not be thumbaniled because of an error with 'convert'."
+ raise PaperclipError, "could not be thumbnailed because of an error with 'convert'."
end
thumb
end
@@ -316,14 +318,14 @@ def geometry_for_crop geometry, orig_io
scale_geometry, scale = if dst[0] == dst[1]
if srch
- [ "x#{dst[1]}", src[1] / dst[1] ]
+ [ "x#{dst[1].to_i}", src[1] / dst[1] ]
else
- [ "#{dst[0]}x", src[0] / dst[0] ]
+ [ "#{dst[0].to_i}x", src[0] / dst[0] ]
end
elsif dsth
- [ "#{dst[0]}x", src[0] / dst[0] ]
+ [ "#{dst[0].to_i}x", src[0] / dst[0] ]
else
- [ "x#{dst[1]}", src[1] / dst[1] ]
+ [ "x#{dst[1].to_i}", src[1] / dst[1] ]
end
crop_geometry = if dsth
@@ -348,14 +350,16 @@ def interpolations
:class => lambda{|style| self.instance.class.to_s.underscore.pluralize },
:style => lambda{|style| style.to_s },
:attachment => lambda{|style| self.name.to_s.pluralize },
- :filename => lambda{|style| self.original_filename }
+ :filename => lambda{|style| self.original_filename },
+ :basename => lambda{|style| self.original_filename.gsub(/\..*$/, "") },
+ :extension => lambda{|style| self.original_filename.gsub(/^.*./, "") }
}
end
def interpolate style, source
returning source.dup do |s|
interpolations.each do |key, proc|
- s.gsub!(/:#{key}/){ proc.call(instance, style) }
+ s.gsub!(/:#{key}/){ proc.call(style) }
end
end
end
@@ -395,8 +399,13 @@ def has_attached_file *attachment_names
options = attachment_names.last.is_a?(Hash) ? attachment_names.pop : {}
include InstanceMethods
+ after_save :save_attached_files
+ before_destroy :destroy_attached_files
+
#class_inheritable_hash :attachment_definitions
@attachment_definitions ||= {}
+ @attachment_names ||= []
+ @attachment_names += attachment_names
attachment_names.each do |aname|
whine_about_columns_for aname
@@ -413,7 +422,7 @@ def has_attached_file *attachment_names
end
def attached_files
- @attachment_definitions.keys
+ @attachment_names
end
def attachment_definition_for attachment
@@ -442,7 +451,19 @@ def whine_about_columns_for attachment #:nodoc:
module InstanceMethods #:nodoc:
def attachment_for name
@attachments ||= {}
- @attachments[name] ||= Attachment.new(self, name)
+ @attachments[name] ||= Attachment.new(self, name, self.class.attachment_definition_for(name))
+ end
+
+ def save_attached_files
+ @attachments.each do |name, attachment|
+ attachment.save
+ end
+ end
+
+ def destroy_attached_files
+ @attachments.each do |name, attachment|
+ attachment.destroy!
+ end
end
end
@@ -462,7 +483,7 @@ def content_type
# Returns the file's normal name.
def original_filename
- self.path
+ File.basename(self.path)
end
# Returns the size of the file.
View
245 lib/paperclip/attachment.rb
@@ -1,245 +0,0 @@
-module Thoughtbot
- module Paperclip
-
- class Attachment
-
- attr_reader :name, :instance, :original_filename, :content_type, :original_file_size, :definition, :errors
-
- def initialize name, active_record
- @instance = active_record
- @definition = @instance.class.attachment_definitions[name]
- @name = name
- @errors = []
-
- self.original_filename = @instance["#{name}_file_name"]
- self.content_type = @instance["#{name}_content_type"]
- self.original_file_size = @instance["#{name}_file_size"]
- @files = {}
- @dirty = false
-
- self.class.send :include, definition.storage_module
- end
-
- def assign uploaded_file
- uploaded_file = fetch_uri(uploaded_file) if uploaded_file.is_a? URI
- return queue_destroy if uploaded_file.nil?
- return unless is_a_file? uploaded_file
-
- self.original_filename = sanitize_filename(uploaded_file.original_filename)
- self.content_type = uploaded_file.content_type
- self.original_file_size = uploaded_file.size
- self[:original] = uploaded_file
- @dirty = true
-
- if definition.type == :image
- make_thumbnails_from uploaded_file
- end
- end
-
- def [](style)
- @files ||= {}
- @files[style]
- end
- alias_method :file_for, :[]
-
- def []=(style, data)
- @files ||= {}
- @files[style] = data
- end
-
- def clear_files
- @files = nil
- @dirty = false
- end
-
- def for_attached_files
- @files.each do |style, data|
- if data
- data.rewind if data.respond_to? :rewind
- yield style, (data.respond_to?(:read) ? data.read : data)
- end
- end
- end
-
- def dirty?
- @dirty
- end
-
- # Validations
-
- def valid?
- definition.validations.each do |validation, constraints|
- send("validate_#{validation}", *constraints)
- end
- errors.empty?
- end
-
- # ActiveRecord Callbacks
-
- def save
- write_attachment if dirty?
- delete_attachment if @delete_on_save
- @delete_on_save = false
- clear_files
- end
-
- def queue_destroy(complain = false)
- returning true do
- @delete_on_save = true
- @complain_on_delete = complain
- self.original_filename = nil
- self.content_type = nil
- clear_files
- end
- end
-
- def destroy
- delete_attachment if definition.delete_on_destroy
- end
-
- # Image Methods
-
- def make_thumbnails_from data
- begin
- definition.styles.each do |style, geometry|
- self[style] = make_thumbnail geometry, data
- end
- rescue PaperclipError => e
- errors << e.message
- clear_files
- self[:original] = data
- end
- end
-
- def make_thumbnail geometry, data
- return data if geometry.nil?
-
- operator = geometry[-1,1]
- begin
- geometry, crop_geometry = geometry_for_crop(geometry, data) if operator == '#'
- convert = Thoughtbot::Paperclip.path_for_command("convert")
- command = "#{convert} - -scale '#{geometry}' #{operator == '#' ? "-crop '#{crop_geometry}'" : ""} - 2>/dev/null"
- thumb = IO.popen(command, "w+") do |io|
- data.rewind
- io.write(data.read)
- io.close_write
- StringIO.new(io.read)
- end
- rescue Errno::EPIPE => e
- raise PaperclipError.new(self), "could not be thumbnailed. Is ImageMagick or GraphicsMagick installed and available?"
- rescue SystemCallError => e
- raise PaperclipError.new(self), "could not be thumbnailed."
- end
- if Thoughtbot::Paperclip.options[:whiny_thumbnails] && !$?.success?
- raise PaperclipError.new(self), "could not be thumbaniled because of an error with 'convert'."
- end
- thumb
- end
-
- def geometry_for_crop geometry, orig_io
- identify = Thoughtbot::Paperclip.path_for_command("identify")
- IO.popen("#{identify} -", "w+") do |io|
- orig_io.rewind
- io.write(orig_io.read)
- io.close_write
- if match = io.read.split[2].match(/(\d+)x(\d+)/)
- src = match[1,2].map(&:to_f)
- srch = src[0] > src[1]
- dst = geometry.match(/(\d+)x(\d+)/)[1,2].map(&:to_f)
- dsth = dst[0] > dst[1]
- ar = src[0] / src[1]
-
- scale_geometry, scale = if dst[0] == dst[1]
- if srch
- [ "x#{dst[1]}", src[1] / dst[1] ]
- else
- [ "#{dst[0]}x", src[0] / dst[0] ]
- end
- elsif dsth
- [ "#{dst[0]}x", src[0] / dst[0] ]
- else
- [ "x#{dst[1]}", src[1] / dst[1] ]
- end
-
- crop_geometry = if dsth
- "%dx%d+%d+%d" % [ dst[0], dst[1], 0, (src[1] / scale - dst[1]) / 2 ]
- else
- "%dx%d+%d+%d" % [ dst[0], dst[1], (src[0] / scale - dst[0]) / 2, 0 ]
- end
-
- [ scale_geometry, crop_geometry ]
- end
- end
- end
-
- # Helper Methods
-
- def interpolate style, source
- returning source.dup do |s|
- s.gsub!(/:rails_root/, RAILS_ROOT)
- s.gsub!(/:id/, instance.id.to_s) if instance.id
- s.gsub!(/:class/, instance.class.to_s.underscore.pluralize)
- s.gsub!(/:style/, style.to_s)
- s.gsub!(/:attachment/, name.to_s.pluralize)
- if original_filename
- file_bits = original_filename.split(".")
- s.gsub!(/:name/, original_filename)
- s.gsub!(/:base/, [file_bits[0], *file_bits[1..-2]].join("."))
- s.gsub!(/:ext/, file_bits.last )
- end
- end
- end
-
- def original_filename= new_name
- instance["#{name}_file_name"] = @original_filename = new_name
- end
-
- def content_type= new_type
- instance["#{name}_content_type"] = @content_type = new_type
- end
-
- def original_file_size= new_size
- instance["#{name}_file_size"] = @original_file_size = new_size
- end
-
- def fetch_uri uri
- image = if uri.scheme == 'file'
- path = url.gsub(%r{^file://}, '/')
- open(path)
- else
- require 'open-uri'
- uri
- end
- begin
- data = StringIO.new(image.read)
- uri.extend(Upfile)
- class << data
- attr_accessor :original_filename, :content_type
- end
- data.original_filename = uri.original_filename
- data.content_type = uri.content_type
- data
- rescue OpenURI::HTTPError => e
- errors << "The file at #{uri.to_s} could not be found."
- return nil
- end
- end
-
- def is_a_file? data
- [:size, :content_type, :original_filename, :read].map do |meth|
- data.respond_to? meth
- end.all?
- end
-
- def sanitize_filename filename
- File.basename(filename).gsub(/[^\w\.\_]/,'_')
- end
- protected :sanitize_filename
-
- def to_s
- url
- end
-
- end
- end
-end
View
93 lib/paperclip/attachment_definition.rb
@@ -1,93 +0,0 @@
-module Thoughtbot
- module Paperclip
- class AttachmentDefinition
-
- def self.defaults
- {
- :path_prefix => ":rails_root/public",
- :url_prefix => "",
- :path => ":attachment/:id/:style_:name",
- :url => nil,
- :attachment_type => :image,
- :thumbnails => {},
- :delete_on_destroy => true,
- :default_style => :original,
- :missing_url => "",
- :missing_path => "",
- :storage => :filesystem
- }
- end
-
- def initialize name, options
- @name = name
- @options = AttachmentDefinition.defaults.merge options
- end
-
- def name
- @name
- end
-
- def styles
- unless @styles
- @styles = @options[:thumbnails]
- @styles[:original] = nil
- end
- @styles
- end
-
- def validate thing, *constraints
- @options[:"validate_#{thing}"] = (constraints.length == 1 ? constraints.first : constraints)
- end
-
- def validations
- @validations ||= @options.inject({}) do |valids, opts|
- key, val = opts
- if (m = key.to_s.match(/^validate_(.+)/))
- valids[m[1]] = val
- end
- valids
- end
- end
-
- def storage_module
- @storage_module ||= Thoughtbot::Paperclip::Storage.const_get(@options[:storage].to_s.camelize)
- end
-
- def type
- @options[:attachment_type]
- end
-
- def default_style
- @options[:default_style]
- end
-
- def path_prefix
- @options[:path_prefix]
- end
-
- def url_prefix
- @options[:url_prefix]
- end
-
- def path
- @options[:path]
- end
-
- def url
- @options[:url]
- end
-
- def missing_file_name
- @options[:missing_path]
- end
-
- def missing_url
- @options[:missing_url]
- end
-
- def delete_on_destroy
- @options[:delete_on_destroy]
- end
- end
- end
-end
View
79 lib/paperclip/storage/filesystem.rb
@@ -1,79 +0,0 @@
-module Thoughtbot
- module Paperclip
- module Storage
- # == Filesystem
- # Typically, Paperclip stores your files in the filesystem, so that Apache (or whatever your
- # main asset server is) can easily access your files without Rails having to be called all
- # the time.
- module Filesystem
-
- def file_name style = nil
- style ||= definition.default_style
- pattern = if original_filename && instance.id
- File.join(definition.path_prefix, definition.path)
- else
- definition.missing_file_name
- end
- interpolate( style, pattern )
- end
-
- def url style = nil
- style ||= definition.default_style
- pattern = if original_filename && instance.id
- [definition.url_prefix, definition.url || definition.path].compact.join("/")
- else
- definition.missing_url
- end
- interpolate( style, pattern )
- end
-
- def write_attachment
- ensure_directories
- for_attached_files do |style, data|
- File.open( file_name(style), "w" ) do |file|
- file.rewind
- file.write(data)
- end
- end
- end
-
- def delete_attachment complain = false
- definition.styles.keys.each do |style|
- file_path = file_name(style)
- begin
- FileUtils.rm file_path if file_path
- rescue SystemCallError => e
- raise PaperclipError.new(self), "Could not delete thumbnail." if Thoughtbot::Paperclip.options[:whiny_deletes] || complain
- end
- end
- end
-
- def file_exists?(style)
- style ||= definition.default_style
- dirty? ? file_for(style) : File.exists?( file_name(style) )
- end
-
- def validate_existence *constraints
- definition.styles.keys.each do |style|
- errors << "requires a valid #{style} file." unless file_exists?(style)
- end
- end
-
- def validate_size *constraints
- errors << "file too large. Must be under #{constraints.last} bytes." if original_file_size > constraints.last
- errors << "file too small. Must be over #{constraints.first} bytes." if original_file_size <= constraints.first
- end
-
- private
-
- def ensure_directories
- for_attached_files do |style, file|
- dirname = File.dirname( file_name(style) )
- FileUtils.mkdir_p dirname
- end
- end
-
- end
- end
- end
-end
View
147 lib/paperclip/storage/s3.rb
@@ -1,147 +0,0 @@
-module Thoughtbot
- module Paperclip
-
- module ClassMethods
- def has_attached_file_with_s3 *attachment_names
- has_attached_file_without_s3 *attachment_names
-
- access_key = secret_key = ""
- if file_name = s3_credentials_file
- creds = YAML.load_file(file_name)
- creds = creds[RAILS_ENV] || creds if Object.const_defined?("RAILS_ENV")
- access_key = creds['access_key_id']
- secret_key = creds['secret_access_key']
- else
- access_key = Thoughtbot::Paperclip.options[:s3_access_key_id]
- secret_key = Thoughtbot::Paperclip.options[:s3_secret_access_key]
- end
-
- if definition.storage_module == Thoughtbot::Paperclip::Storage::S3
- require 'aws/s3'
- AWS::S3::Base.establish_connection!(
- :access_key_id => access_key,
- :secret_access_key => secret_key,
- :persistent => Thoughtbot::Paperclip.options[:s3_persistent] || true
- )
- end
- end
- alias_method_chain :has_attached_file, :s3
-
- private
- def s3_credentials_file
- [ Thoughtbot::Paperclip.options[:s3_credentials_file], File.join(RAILS_ROOT, "config", "s3.yml") ].compact.each do |f|
- return f if File.exists?(f)
- end
- nil
- end
- end
-
- class AttachmentDefinition
- def s3_access
- @options[:s3_access_privilege]
- end
- end
-
- module Storage
- # == Amazon S3 Storage
- # Paperclip can store your files in Amazon's S3 Web Service. You can keep your keys in an s3.yml
- # file inside the +config+ directory, similarly to your database.yml.
- #
- # access_key_id: 12345
- # secret_access_key: 212434...4656456
- #
- # You can also environment-namespace the entries like you would in your database.yml:
- #
- # development:
- # access_key_id: 12345
- # secret_access_key: 212434...4656456
- # production:
- # access_key_id: abcde
- # secret_access_key: gbkjhg...wgbrtjh
- #
- # The location of this file is configurable. You don't even have to use it if you don't want. Both
- # the file's location or the keys themselves may be located in the Thoughtbot::Paperclip.options
- # hash. The S3-related options in this hash are all prefixed with "s3".
- #
- # Thoughtbot::Paperclip.options = {
- # :s3_persistent => true,
- # :s3_credentials_file => "/home/me/.private/s3.yml"
- # }
- #
- # This configuration is best placed in your +environment.rb+ or env-specific file.
- # The complete list of options is as follows:
- # * s3_access_key_id: The Amazon S3 ID you were given to access your account.
- # * s3_secret_access_key: The secret key supplied by Amazon. This should be kept far away from prying
- # eyes, which is why it's suggested that you keep these keys in a separate file that
- # only you and the database can read.
- # * s3_credentials_file: The path to the file where your credentials are kept in YAML format, as
- # described above.
- # * s3_persistent: Maintains an HTTP connection to the Amazon service if possible.
- module S3
- def file_name style = nil
- style ||= definition.default_style
- pattern = if original_filename && instance.id
- definition.url
- else
- definition.missing_url
- end
- interpolate( style, pattern )
- end
-
- def url style = nil
- "http://s3.amazonaws.com/#{bucket}/#{file_name(style)}"
- end
-
- def write_attachment
- bucket = ensure_bucket
- for_attached_files do |style, data|
- AWS::S3::S3Object.store( file_name(style), data, bucket, :access => definition.s3_access || :public_read )
- end
- end
-
- def delete_attachment complain = false
- for_attached_files do |style, data|
- begin
- AWS::S3::S3Object.delete( file_name(style), bucket )
- rescue AWS::S3::ResponseError => error
- raise
- end
- end
- end
-
- def file_exists?(style)
- style ||= definition.default_style
- dirty? ? file_for(style) : AWS::S3::S3Object.exists?( file_name(style), bucket )
- end
-
- def validate_existence *constraints
- definition.styles.keys.each do |style|
- errors << "requires a valid #{style} file." unless file_exists?(style)
- end
- end
-
- def validate_size *constraints
- errors << "file too large. Must be under #{constraints.last} bytes." if original_file_size > constraints.last
- errors << "file too small. Must be over #{constraints.first} bytes." if original_file_size <= constraints.first
- end
-
- private
-
- def bucket
- interpolate(nil, definition.url_prefix)
- end
-
- def ensure_bucket
- begin
- bucket_name = bucket
- AWS::S3::Bucket.create(bucket_name)
- bucket_name
- rescue AWS::S3::S3Exception => e
- raise Thoughtbot::Paperclip::PaperclipError.new(attachment), "You are not allowed access to the bucket '#{bucket_name}'."
- end
- end
-
- end
- end
- end
-end
View
33 lib/paperclip/upfile.rb
@@ -1,33 +0,0 @@
-module Thoughtbot
- module Paperclip
- # The Upfile module is a convenience module for adding uploaded-file-looking methods
- # to the +File+ class. Useful for testing.
- # f = File.new("test/test_avatar.jpg")
- # f.original_filename # => "test_avatar.jpg"
- # f.content_type # => "image/jpg"
- # user.avatar = f
- module Upfile
- # Infer the MIME-type of the file from the extension.
- def content_type
- type = self.path.match(/\.(\w+)$/)[1]
- case type
- when "jpg", "png", "gif" then "image/#{type}"
- when "txt", "csv", "xml", "html", "htm" then "text/#{type}"
- else "x-application/#{type}"
- end
- end
-
- # Returns the file's normal name.
- def original_filename
- self.path
- end
-
- # Returns the size of the file.
- def size
- File.size(self)
- end
- end
- end
-end
-
-File.send(:include, Thoughtbot::Paperclip::Upfile)
View
81 test/models.rb
@@ -1,81 +0,0 @@
-begin
- ActiveRecord::Base.connection.create_table :foos, :force => true do |table|
- table.column :image_file_name, :string
- table.column :image_content_type, :string
- table.column :image_file_size, :integer
- end
- ActiveRecord::Base.connection.create_table :bars, :force => true do |table|
- table.column :document_file_name, :string
- table.column :document_content_type, :string
- table.column :document_file_size, :integer
- end
- ActiveRecord::Base.connection.create_table :non_standards, :force => true do |table|
- table.column :resume_file_name, :string
- table.column :resume_content_type, :string
- table.column :resume_file_size, :integer
- table.column :avatar_file_name, :string
- table.column :avatar_content_type, :string
- table.column :avatar_file_size, :integer
- end
- ActiveRecord::Base.connection.create_table :ess_threes, :force => true do |table|
- table.column :resume_file_name, :string
- table.column :resume_content_type, :string
- table.column :resume_file_size, :integer
- table.column :avatar_file_name, :string
- table.column :avatar_content_type, :string
- table.column :avatar_file_size, :integer
- end
- ActiveRecord::Base.connection.create_table :negatives, :force => true do |table|
- table.column :this_is_the_wrong_name_file_name, :string
- end
-rescue Exception
-end
-
-class Foo < ActiveRecord::Base
- has_attached_file :image, :attachment_type => :image,
- :thumbnails => { :thumb => "100x100>", :medium => "300x300>" },
- :path_prefix => "./repository"
-end
-
-class Bar < ActiveRecord::Base
- has_attached_file :document, :attachment_type => :document,
- :path_prefix => "./repository"
- validates_attached_file :document
-end
-
-class NonStandard < ActiveRecord::Base
- has_attached_file :resume, :attachment_type => :document,
- :path_prefix => "/tmp",
- :path => ":attachment_:id_:name",
- :missing_url => "/:class/:style/:attachment/404.txt"
- has_attached_file :avatar, :attachment_type => :image,
- :thumbnails => { :cropped => "200x10#",
- :bigger => "1000x1000",
- :smaller => "200x200>",
- :square => "150x150#" },
- :path_prefix => "./repository",
- :path => ":class/:attachment/:id/:style_:name",
- :default_style => :square,
- :missing_url => "/:class/:style/:attachment/404.png"
-end
-
-# class EssThree < ActiveRecord::Base
-# has_attached_file :resume, :attachment_type => :document,
-# :path_prefix => "paperclip/test",
-# :path => ":attachment_:id_:name",
-# :missing_url => "/:class/:style/:attachment/404.txt",
-# :storage => :S3
-# has_attached_file :avatar, :attachment_type => :image,
-# :thumbnails => { :cropped => "200x10#",
-# :bigger => "1000x1000",
-# :smaller => "200x200>",
-# :square => "150x150#" },
-# :path_prefix => "paperclip/test/images",
-# :path => ":class/:attachment/:id/:style_:name",
-# :default_style => :square,
-# :missing_url => "/:class/:style/:attachment/404.png",
-# :storage => :S3
-# end
-
-class Negative < ActiveRecord::Base
-end
View
113 test/test_attachment.rb
@@ -0,0 +1,113 @@
+require 'test/unit'
+require File.dirname(__FILE__) + "/test_helper.rb"
+
+class TestAttachment < Test::Unit::TestCase
+ context "An attachment" do
+ setup do
+ @dummy = {}
+ @definition = Paperclip::AttachmentDefinition.new("thing", {})
+ @attachment = Paperclip::Attachment.new(@dummy, "thing", @definition)
+ end
+
+ should "calculate geometries for cropping images" do
+ @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "test_image.jpg"))
+ assert_equal ["50x", "50x25+0+10"], @attachment.send(:geometry_for_crop, "50x25", @file)
+ assert_equal ["x50", "50x50+2+0"], @attachment.send(:geometry_for_crop, "50x50", @file)
+ end
+ end
+
+ context "The class Foo" do
+ setup do
+ ActiveRecord::Base.connection.create_table :foos, :force => true do |table|
+ table.column :image_file_name, :string
+ table.column :image_content_type, :string
+ table.column :image_file_size, :integer
+
+ table.column :document_file_name, :string
+ table.column :document_content_type, :string
+ table.column :document_file_size, :integer
+ end
+ Object.send(:remove_const, :Foo) rescue nil
+ class ::Foo < ActiveRecord::Base; end
+ end
+
+ context "with an image attached to :image" do
+ setup do
+ assert Foo.has_attached_file(:image)
+ @foo = Foo.new
+ @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "test_image.jpg"))
+ assert_nothing_raised{ @foo.image = @file }
+ end
+
+ should "be able to have a file assigned with :image=" do
+ assert_equal "test_image.jpg", @foo.image.original_filename
+ assert_equal "image/jpg", @foo.image.content_type
+ end
+
+ should "be able to retrieve the data as a blob" do
+ assert_equal @file.read, @foo.image.read
+ end
+
+ context "and saved" do
+ setup do
+ assert @foo.save
+ end
+
+ should "have no errors" do
+ assert @foo.image.errors.blank?
+ assert @foo.errors.blank?
+ end
+
+ should "have a file on the filesystem" do
+ assert @foo.image.send(:file_name)
+ assert File.file?(@foo.image.send(:file_name)), @foo.image.send(:file_name)
+ assert File.size(@foo.image.send(:file_name)) > 0
+ assert_match /405x375/, `identify '#{@foo.image.send(:file_name)}'`
+ assert_equal IO.read(@file.path), @foo.image.read
+ end
+ end
+ end
+
+ context "with an image with thumbnails attached to :image and saved" do
+ setup do
+ assert Foo.has_attached_file(:image, :thumbnails => {:small => "16x16", :medium => "100x100", :large => "250x250", :square => "32x32#"})
+ @foo = Foo.new
+ @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "test_image.jpg"))
+ assert_nothing_raised{ @foo.image = @file }
+ assert @foo.save
+ end
+
+ should "have no errors" do
+ assert @foo.image.errors.blank?
+ assert @foo.errors.blank?
+ end
+
+ [:original, :small, :medium, :large, :square].each do |style|
+ should "have a file for #{style} on the filesystem" do
+ assert @foo.image.send(:file_name)
+ assert File.file?(@foo.image.send(:file_name)), @foo.image.send(:file_name)
+ assert File.size(@foo.image.send(:file_name)) > 0
+ assert_equal IO.read(@file.path), @foo.image.read
+ end
+
+ should "return the correct urls when asked for the #{style} image" do
+ assert_equal "/foos/images/1/#{style}_test_image.jpg", @foo.image.url(style)
+ end
+ end
+
+ should "produce the correct dimensions when each style is identified" do
+ assert_match /16x15/, `identify '#{@foo.image.send(:file_name, :small)}'`
+ assert_match /32x32/, `identify '#{@foo.image.send(:file_name, :square)}'`
+ assert_match /100x93/, `identify '#{@foo.image.send(:file_name, :medium)}'`
+ assert_match /250x231/, `identify '#{@foo.image.send(:file_name, :large)}'`
+ assert_match /405x375/, `identify '#{@foo.image.send(:file_name, :original)}'`
+ end
+ end
+
+ context "with an image with thumbnails attached to :image and a document attached to :document" do
+ end
+
+ context "with an invalid image attached to :image" do
+ end
+ end
+end
View
56 test/test_attachment_definition.rb
@@ -0,0 +1,56 @@
+require 'test/unit'
+require File.dirname(__FILE__) + "/test_helper.rb"
+
+class TestAttachmentDefinition < Test::Unit::TestCase
+ context "Attachment definitions" do
+ should "allow overriding options" do
+ not_expected = Paperclip::AttachmentDefinition.defaults[:path]
+ Paperclip::AttachmentDefinition.defaults[:path] = "123"
+ assert_not_equal not_expected, Paperclip::AttachmentDefinition.defaults[:path]
+ assert_equal "123", Paperclip::AttachmentDefinition.defaults[:path]
+ end
+
+ should "accept options that override defaults" do
+ @def = Paperclip::AttachmentDefinition.new "attachment", :path => "123", :delete_on_destroy => false
+ assert_not_equal Paperclip::AttachmentDefinition.defaults[:path], @def.path
+ assert_not_equal Paperclip::AttachmentDefinition.defaults[:delete_on_destroy], @def.delete_on_destroy
+ assert_equal "123", @def.path
+ assert_equal false, @def.delete_on_destroy
+ end
+ end
+
+ context "An attachment defintion" do
+ setup do
+ @options = {
+ :path => "/home/stuff/place",
+ :url => "/attachments/:attachment/:name",
+ :custom_definition => :boogie!,
+ :thumbnails => {:thumb => "100x100", :large => "300x300>"},
+ :validates_existance => true,
+ :validates_size => [0, 2048]
+ }
+ @def = Paperclip::AttachmentDefinition.new "attachment", @options
+ end
+
+ should "automatically look in the hash for missing methods" do
+ assert ! @def.respond_to?(:custom_defintion)
+ assert_equal :boogie!, @def.custom_definition
+ end
+
+ should "be able to read options using attribute readers" do
+ @options.keys.each do |key|
+ assert_equal @options[key], @def.send(key)
+ end
+ end
+
+ should "return styles as thumbnails plus the original" do
+ assert( (@def.thumbnails.keys + [:original]).map(&:to_s).sort == @def.styles.keys.map(&:to_s).sort )
+ end
+
+ should "return all validations when sent :validations" do
+ assert @def.validations[:existance] == true, @def.validations[:existance]
+ assert @def.validations[:size] == [0, 2048], @def.validations[:size]
+ end
+ end
+
+end
View
3 test/test_helper.rb
@@ -5,6 +5,9 @@
require 'fileutils'
require 'pp'
+require File.dirname(__FILE__) + "/simply_shoulda.rb"
+require File.dirname(__FILE__) + "/../init.rb"
+
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
ActiveRecord::Base.establish_connection(config[ENV['RAILS_ENV'] || 'test'])
View
54 test/paperclip_test.rb → test/test_paperclip.rb
@@ -1,9 +1,7 @@
require 'test/unit'
require File.dirname(__FILE__) + "/test_helper.rb"
-require File.dirname(__FILE__) + "/simply_shoulda.rb"
-require File.dirname(__FILE__) + "/../init.rb"
-class PaperclipTest < Test::Unit::TestCase
+class TestPaperclip < Test::Unit::TestCase
context "Paperclip" do
should "allow overriding options" do
@@ -16,6 +14,38 @@ class PaperclipTest < Test::Unit::TestCase
expected = "/usr/bin/wtf"
Paperclip.options[:image_magick_path] = "/usr/bin"
assert_equal expected, Paperclip.path_for_command("wtf")
+
+ expected = "wtf"
+ Paperclip.options[:image_magick_path] = nil
+ assert_equal expected, Paperclip.path_for_command("wtf")
+ end
+
+ context "being used on class Improper" do
+ setup do
+ ActiveRecord::Base.connection.create_table :impropers, :force => true do |table|
+ end
+ Object.send(:remove_const, :Improper) rescue nil
+ class ::Improper < ActiveRecord::Base; end
+ end
+
+ should "raises an error when an attachment is defined" do
+ assert_raises(Paperclip::PaperclipError){ Improper.has_attached_file :image }
+ end
+
+ [:file_name, :content_type].each do |column|
+ context "which has only the #{column} column" do
+ setup do
+ ActiveRecord::Base.connection.create_table :impropers, :force => true do |table|
+ table.column :"image_#{column}", :string
+ end
+ Object.send(:remove_const, :Improper) rescue nil
+ class ::Improper < ActiveRecord::Base; end
+ end
+ should "raises an error when an attachment is defined" do
+ assert_raises(Paperclip::PaperclipError){ Improper.has_attached_file :image }
+ end
+ end
+ end
end
context "being used on class Foo" do
@@ -36,23 +66,41 @@ class ::Foo < ActiveRecord::Base; end
should "be able to assign a default attachment" do
assert Foo.has_attached_file(:image)
assert_equal [:image], Foo.attached_files
+ foo = Foo.new
+ assert foo.respond_to?(:image)
+ assert foo.image.is_a?(Paperclip::Attachment)
end
should "be able to assign two attachments separately" do
assert Foo.has_attached_file(:image)
assert Foo.has_attached_file(:document)
assert_equal [:image, :document], Foo.attached_files
+ foo = Foo.new
+ assert foo.respond_to?(:image)
+ assert foo.respond_to?(:document)
+ assert foo.image.is_a?(Paperclip::Attachment)
+ assert foo.document.is_a?(Paperclip::Attachment)
+ assert foo.image != foo.document
end
should "be able to assign two attachments simultaneously" do
assert Foo.has_attached_file(:image, :document)
assert_equal [:image, :document], Foo.attached_files
+ foo = Foo.new
+ assert foo.respond_to?(:image)
+ assert foo.respond_to?(:document)
+ assert foo.image.is_a?(Paperclip::Attachment)
+ assert foo.document.is_a?(Paperclip::Attachment)
+ assert foo.image != foo.document
end
should "be able to set options on attachments" do
assert Foo.has_attached_file :image, :thumbnails => {:thumb => "100x100"}
assert_equal [:image], Foo.attached_files
assert_equal( {:thumb => "100x100"}, Foo.attachment_definition_for(:image).thumbnails )
+ foo = Foo.new
+ assert foo.respond_to?(:image)
+ assert foo.image.is_a?(Paperclip::Attachment)
end
end
View
21 test/test_upfile.rb
@@ -0,0 +1,21 @@
+require 'test/unit'
+require File.dirname(__FILE__) + "/test_helper.rb"
+
+class TestUpfile < Test::Unit::TestCase
+ context "Using Upfile" do
+ setup do
+ File.send :include, Paperclip::Upfile
+ @filename = File.join(File.dirname(__FILE__), "fixtures", "test_image.jpg")
+ @file = File.new(@filename)
+ end
+
+ should "allow File objects to respond as uploaded files do" do
+ assert_respond_to @file, :original_filename
+ assert_respond_to @file, :content_type
+ assert_respond_to @file, :size
+ assert_equal "test_image.jpg", @file.original_filename
+ assert_equal "image/jpg", @file.content_type
+ assert_equal @file.stat.size, @file.size
+ end
+ end
+end

2 comments on commit 11086ba

@mnutt

Is there a reason that assign-by-URI support was removed? Was it infeasible, or just an issue with the implementation?

@thoughtbot

It wasn’t so much infeasible as it was not terribly useful. Cool, but not useful from the standpoint of assigning directly from the hash like just another attribute.

Please sign in to comment.