Commit 8911e40e by jyurek

New hotness. Holy crap.

git-svn-id: https://svn.thoughtbot.com/plugins/paperclip/trunk@229 7bbfaf0e-4d1d-0410-9690-a8bb5f8ef2aa
parent 72c4e738
......@@ -3,6 +3,7 @@ class <%= migration_name %> < ActiveRecord::Migration
<% attachments.each do |attachment| -%>
add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_name, :string
add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_content_type, :string
add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_size, :integer
<% end -%>
end
......@@ -10,6 +11,7 @@ class <%= migration_name %> < ActiveRecord::Migration
<% attachments.each do |attachment| -%>
remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_name
remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_content_type
remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_size
<% end -%>
end
end
......@@ -10,8 +10,272 @@
#
# See the +has_attached_file+ documentation for more details.
require 'paperclip/paperclip'
require 'paperclip/storage'
require 'paperclip/upfile'
require 'paperclip/attachment'
require 'paperclip/attachment_definition'
require 'paperclip/storage/filesystem'
require 'paperclip/storage/s3'
module Thoughtbot #:nodoc:
# Paperclip defines an attachment as any file, though it makes special considerations
# for image files. You can declare that a model has an attached file with the
# +has_attached_file+ method:
#
# class User < ActiveRecord::Base
# has_attached_file :avatar, :thumbnails => { :thumb => "100x100" }
# end
#
# See the +has_attached_file+ documentation for more details.
module Paperclip
class << self
# Provides configurability to Paperclip. There are a number of options available, such as:
# * whiny_deletes: Will raise an error if Paperclip is unable to delete an attachment. Defaults to false.
# * whiny_thumbnails: Will raise an error if Paperclip cannot process thumbnails of an uploaded image. Defaults to true.
# * image_magick_path: Defines the path at which to find the +convert+ and +identify+ programs if they are
# not visible to Rails the system's search path. Defaults to nil, which uses the first executable found
# in the search path.
def options
@options ||= {
:whiny_deletes => false,
:whiny_thumbnails => true,
:image_magick_path => nil
}
end
def path_for_command command #:nodoc:
path = [options[:image_magick_path], command].compact
File.join(*path)
end
end
class PaperclipError < StandardError #:nodoc:
attr_accessor :attachment
def initialize attachment
@attachment = attachment
end
end
module ClassMethods
# == Methods
# +has_attached_file+ attaches a file (or files) with a given name to a model. It creates seven instance
# methods using the attachment name (where "attachment" in the following is the name
# passed in to +has_attached_file+):
# * attachment: Returns the name of the file that was attached, with no path information.
# * attachment?: Alias for _attachment_ for clarity in determining if the attachment exists.
# * attachment=(file): Sets the attachment to the file and creates the thumbnails (if necessary).
# +file+ can be anything normally accepted as an upload (+StringIO+ or +Tempfile+) or a +File+
# if it has had the +Upfile+ module included.
# Note this does not save the attachments.
# user.avatar = File.new("~/pictures/me.png")
# user.avatar = params[:user][:avatar] # When :avatar is a file_field
# * attachment_file_name(style): The name of the file, including path information. Pass in the
# name of a thumbnail to get the path to that thumbnail.
# user.avatar_file_name(:thumb) # => "public/users/44/thumb/me.png"
# user.avatar_file_name # => "public/users/44/original/me.png"
# * attachment_url(style): The public URL of the attachment, suitable for passing to +image_tag+
# or +link_to+. Pass in the name of a thumbnail to get the url to that thumbnail.
# user.avatar_url(:thumb) # => "http://assethost.com/users/44/thumb/me.png"
# user.avatar_url # => "http://assethost.com/users/44/original/me.png"
# * attachment_valid?: If unsaved, returns true if all thumbnails have data (that is,
# they were successfully made). If saved, returns true if all expected files exist and are
# of nonzero size.
# * destroy_attachment(complain = false): Deletes the attachment and all thumbnails. Sets the +attachment_file_name+
# column and +attachment_content_type+ column to +nil+. Set +complain+ to true to override
# the +whiny_deletes+ option.
#
# == Options
# There are a number of options you can set to change the behavior of Paperclip.
# * +path_prefix+: The location of the repository of attachments on disk. See Interpolation below
# for more control over where the files are located.
# :path_prefix => ":rails_root/public"
# :path_prefix => "/var/app/repository"
# * +url_prefix+: The root URL of where the attachment is publically accessible. See Interpolation below
# for more control over where the files are located.
# :url_prefix => "/"
# :url_prefix => "/user_files"
# :url_prefix => "http://some.other.host/stuff"
# * +path+: Where the files are stored underneath the +path_prefix+ directory and underneath the +url_prefix+ URL.
# See Interpolation below for more control over where the files are located.
# :path => ":class/:style/:id/:name" # => "users/original/13/picture.gif"
# * +attachment_type+: If this is set to :image (which it is, by default), Paperclip will attempt to make thumbnails.
# * +thumbnails+: A hash of thumbnail styles and their geometries. You can find more about geometry strings
# at the ImageMagick website (http://www.imagemagick.org/script/command-line-options.php#resize). Paperclip
# also adds the "#" option, which will resize the image to fit maximally inside the dimensions and then crop
# the rest off (weighted at the center).
# * +delete_on_destroy+: When records are deleted, the attachment that goes with it is also deleted. Set
# this to +false+ to prevent the file from being deleted.
# * +default_style+: The thumbnail style that will be used by default for +attachment_file_name+ and +attachment_url+
# Defaults to +original+.
# has_attached_file :avatar, :thumbnails => { :normal => "100x100#" },
# :default_style => :normal
# user.avatar_url # => "/avatars/23/normal_me.png"
# * +missing_url+: The URL that will be returned if there is no attachment assigned. It should be an absolute
# URL, not relative to the +url_prefix+. This field is interpolated.
# has_attached_file :avatar, :missing_url => "/images/default_:style_avatar.png"
# User.new.avatar_url(:small) # => "/images/default_small_avatar.png"
#
# == Interpolation
# The +path_prefix+, +url_prefix+, and +path+ options can have dynamic interpolation done so that the
# locations of the files can vary depending on a variety of factors. Each variable looks like a Ruby symbol
# and is searched for with +gsub+, so a variety of effects can be achieved. The list of possible variables
# follows:
# * +rails_root+: The value of the +RAILS_ROOT+ constant for your app. Typically used when putting your
# attachments into the public directory. Probably not useful in the +path+ definition.
# * +class+: The underscored, pluralized version of the class in which the attachment is defined.
# * +attachment+: The pluralized name of the attachment as given to +has_attached_file+
# * +style+: The name of the thumbnail style for the current thumbnail. If no style is given, "original" is used.
# * +id+: The record's id.
# * +name+: The file's name, as stored in the attachment_file_name column.
#
# When interpolating, you are not confined to making any one of these into its own directory. This is
# perfectly valid:
# :path => ":attachment/:style/:id-:name" # => "avatars/thumb/44-me.png"
#
# == Model Requirements
# For any given attachment _foo_, the model the attachment is in needs to have both a +foo_file_name+
# and +foo_content_type+ column, as a type of +string+. The +foo_file_name+ column contains only the name
# of the file and none of the path information. However, the +foo_file_name+ column accessor is overwritten
# by the one (defined above) which returns the full path to whichever style thumbnail is passed in.
# In a pinch, you can either use +read_attribute+ or the plain +foo+ accessor, which returns the database's
# +foo_file_name+ column.
#
# == Event Triggers
# When an attachment is set by using he setter (+model.attachment=+), the thumbnails are created and held in
# memory. They are not saved until the +after_save+ trigger fires, at which point the attachment and all
# thumbnails are written to disk.
#
# Attached files are destroyed when the associated record is destroyed in a +before_destroy+ trigger. Set
# the +delete_on_destroy+ option to +false+ to prevent this behavior. Also note that using the ActiveRecord's
# +delete+ method instead of the +destroy+ method will prevent the +before_destroy+ trigger from firing.
def has_attached_file *attachment_names
options = attachment_names.last.is_a?(Hash) ? attachment_names.pop : {}
@attachment_definitions ||= {}
class << self
attr_accessor :attachment_definitions
end
include InstanceMethods
validates_each(*attachment_names) do |record, attr, value|
value.errors.each{|e| record.errors.add(attr, e) unless record.errors.on(attr) && record.errors.on(attr).include?(e) }
end
attachment_names.each do |name|
whine_about_columns_for name
@attachment_definitions[name] = Thoughtbot::Paperclip::AttachmentDefinition.new(name, options)
define_method "#{name}=" do |uploaded_file|
attachment_for(name).assign uploaded_file
end
define_method name do
attachment_for(name)
end
define_method "#{name}?" do
attachment_for(name).original_filename
end
define_method "#{name}_valid?" do
attachment_for(name).valid?
end
define_method "#{name}_file_name" do |*args|
attachment_for(name).file_name(args.first)
end
define_method "#{name}_url" do |*args|
attachment_for(name).url(args.first)
end
define_method "destroy_#{name}" do |*args|
attachment_for(name).queue_destroy(args.first)
end
define_method "#{name}_after_save" do
attachment_for(name).save
end
private :"#{name}_after_save"
after_save :"#{name}_after_save"
define_method "#{name}_before_destroy" do
attachment_for(name).destroy
end
private :"#{name}_before_destroy"
before_destroy :"#{name}_before_destroy"
end
end
module InstanceMethods #:nodoc:
unless method_defined? :after_initialize
def after_initialize
# We need this, because Rails won't even try this method unless it is specifically defined.
end
end
def after_initialize_with_paperclip
@attachments = {}
self.class.attachment_definitions.keys.each do |name|
@attachments[name] = Thoughtbot::Paperclip::Attachment.new(name, self)
end
end
alias_method_chain :after_initialize, :paperclip
def attachment_for name
@attachments[name]
end
end
def attachment_names
@attachment_definitions.keys
end
def validates_attached_file *attachment_names
attachment_names.each do |name|
@attachment_definitions[name].validate :existence
end
end
def whine_about_columns_for name #:nodoc:
[ "#{name}_file_name", "#{name}_content_type", "#{name}_size" ].each do |column|
unless column_names.include?(column)
raise PaperclipError, "Class #{self.name} does not have all of the necessary columns to have an attachment named #{name}. " +
"(#{name}_file_name, #{name}_content_type, and #{name}_size)"
end
end
end
end
# == Storage Subsystems
# While Paperclip focuses primarily on using the filesystem for data storage, it is possible to allow
# other storage mediums. A module inside the Storage module can be used as the storage provider for
# attachments when has_attached_file is given the +storage+ option. The value of the option should be
# the name of the module, symbol-ized (e.g. :filesystem, :s3). You can look at the Filesystem and S3
# modules for examples of how it typically works.
#
# If you want to implement a storage system, you are required to implement the following methods:
# * file_name(style = nil): Takes a style (i.e. thumbnail name) and should return the canonical name
# for referencing that file or thumbnail. You may define this how you wish. For example, in
# Filesystem, it is the location of the file under the path_prefix. In S3, it returns the path portion
# of the Amazon URL minus the bucket name.
# * url(style = nil): Takes a style and should return the URL at which the attachment should be accessed.
# * write_attachment: Write all the files and thumbnails in this attachment to the storage medium. Should
# return true or false depending on success.
# * delete_attachment: Delete the files and thumbnails from the storage medium. Should return true or false
# depending on success.
#
# When writing files, the @files variable will hold a hash of style names and their data. If @files is nil,
# then no new data has been assigned to the attachment and you should not perform any work.
#
# You will also have access to @definition, which is the AttachmentDefintion object for the attachment. The
# methods in your module will be mixed into an Attachment instance, so you have full access to the
# Attachment itself.
#
# == Validations
# Storage systems provide their own validations, since the manner of checking the status of them is usually
# specific to the means of storage. To provide a validation, define a method starting with "validate_" in
# your module. You are responsible for adding errors to the +errors+ array if validation fails.
module Storage; end
end
end
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
module ClassMethods
def has_attached_file_with_filesystem *attachment_names
has_attached_file_without_filesystem *attachment_names
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
alias_method_chain :has_attached_file, :filesystem
interpolate( style, pattern )
end
class Storage #:nodoc:
class Filesystem < Storage #:nodoc:
def path_for attachment, style = nil
style ||= attachment[:default_style]
file = attachment[:instance]["#{attachment[:name]}_file_name"]
return nil unless file && attachment[:instance].id
prefix = interpolate attachment, "#{attachment[:path_prefix]}/#{attachment[:path]}", style
File.join( prefix.split("/") )
end
def url_for attachment, style = nil
style ||= attachment[:default_style]
file = attachment[:instance]["#{attachment[:name]}_file_name"]
return nil unless file && attachment[:instance].id
interpolate attachment, "#{attachment[:url_prefix]}/#{attachment[:path]}", style
end
def ensure_directories_for attachment
attachment[:files].each do |style, file|
dirname = File.dirname(path_for(attachment, style))
FileUtils.mkdir_p dirname
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 attachment
return if attachment[:files].blank?
ensure_directories_for attachment
attachment[:files].each do |style, atch|
atch.rewind
data = atch.read
File.open( path_for(attachment, style), "w" ) do |file|
def write_attachment
return if @files.blank?
ensure_directories
@files.each do |style, data|
data.rewind
data = data.read
File.open( file_name(style), "w" ) do |file|
file.rewind
file.write(data)
end
end
attachment[:files] = nil
attachment[:dirty] = false
end
def delete_attachment attachment, complain = false
(attachment[:thumbnails].keys + [:original]).each do |style|
file_path = path_for(attachment, style)
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(attachment), "Could not delete thumbnail." if ::Thoughtbot::Paperclip.options[:whiny_deletes] || complain
raise PaperclipError.new(self), "Could not delete thumbnail." if Thoughtbot::Paperclip.options[:whiny_deletes] || complain
end
end
end
def attachment_valid? attachment
attachment[:thumbnails].merge(:original => nil).all? do |style, geometry|
if attachment[:instance]["#{attachment[:name]}_file_name"]
if attachment[:dirty]
!attachment[:files][style].blank? && attachment[:errors].empty?
def validate_existence *constraints
@definition.styles.keys.each do |style|
if @dirty
errors << "requires a valid #{style} file." unless @files && @files[style]
else
File.file?( path_for(attachment, style) )
errors << "requires a valid #{style} file." unless File.exists?( file_name(style) )
end
else
false
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
@files.each do |style, file|
dirname = File.dirname( file_name(style) )
FileUtils.mkdir_p dirname
end
end
end
end
end
......
module Thoughtbot
module Paperclip
module ClassMethods
module ClassMethods #:nodoc:
def has_attached_file_with_s3 *attachment_names
attachments, options = has_attached_file_without_s3 *attachment_names
has_attached_file_without_s3 *attachment_names
access_key = secret_key = ""
if file_name = s3_credentials_file
......@@ -16,7 +16,7 @@ module Thoughtbot
secret_key = Thoughtbot::Paperclip.options[:s3_secret_access_key]
end
if options[:storage].to_s.downcase == "s3"
if @definition.storage_module == Thoughtbot::Paperclip::Storage::S3
require 'aws/s3'
AWS::S3::Base.establish_connection!(
:access_key_id => access_key,
......@@ -36,65 +36,112 @@ module Thoughtbot
end
end
class Storage #:nodoc:
class S3 < Storage #:nodoc:
def path_for attachment, style = nil
style ||= attachment[:default_style]
file = attachment[:instance]["#{attachment[:name]}_file_name"]
return nil unless file && attachment[:instance].id
interpolate attachment, attachment[:path], style
class AttachmentDefinition
def s3_access
@options[:s3_access_privilege]
end
def url_for attachment, style = nil
"http://s3.amazonaws.com/#{bucket_for(attachment)}/#{path_for(attachment, style)}"
end
def bucket_for attachment
bucket_name = interpolate attachment, attachment[:url_prefix], nil
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
def ensure_bucket_for attachment, style = nil
begin
bucket_name = bucket_for attachment
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}'."
interpolate( style, pattern )
end
def url style = nil
"http://s3.amazonaws.com/#{bucket}/#{file_name(style)}"
end
def write_attachment attachment
return if attachment[:files].blank?
bucket = ensure_bucket_for attachment
attachment[:files].each do |style, atch|
atch.rewind
AWS::S3::S3Object.store( path_for(attachment, style), atch, bucket, :access => attachment[:access] || :public_read )
def write_attachment
return if @files.blank?
bucket = ensure_bucket
@files.each do |style, data|
data.rewind
AWS::S3::S3Object.store( file_name(style), data, bucket, :access => @definition.s3_access || :public_read )
end
attachment[:files] = nil
attachment[:dirty] = false
end
def delete_attachment attachment, complain = false
def delete_attachment complain = false
(attachment[:thumbnails].keys + [:original]).each do |style|
file_path = path_for(attachment, style)
AWS::S3::S3Object.delete( file_path, bucket_for(attachment) )
begin
AWS::S3::S3Object.delete( file_name(style), bucket )
rescue AWS::S3::ResponseError => error
raise
end
end
end
def attachment_valid? attachment
attachment[:thumbnails].merge(:original => nil).all? do |style, geometry|
if attachment[:instance]["#{attachment[:name]}_file_name"]
if attachment[:dirty]
!attachment[:files][style].blank? && attachment[:errors].empty?
def validate_existence *constraints
@definition.styles.keys.each do |style|
if @dirty
errors << "requires a valid #{style} file." unless @files && @files[style]
else
AWS::S3::S3Object.exists?( path_for(attachment, style), bucket_for(attachment) )
errors << "requires a valid #{style} file." unless AWS::S3::S3Object.exists?( file_name(style), bucket )
end
else
false
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
......
......@@ -2,22 +2,28 @@ 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_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_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_size, :integer
table.column :avatar_file_name, :string
table.column :avatar_content_type, :string
table.column :avatar_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_size, :integer
table.column :avatar_file_name, :string
table.column :avatar_content_type, :string
table.column :avatar_size, :integer
end
ActiveRecord::Base.connection.create_table :negatives, :force => true do |table|
table.column :this_is_the_wrong_name_file_name, :string
......
......@@ -22,7 +22,7 @@ class PaperclipImagesTest < Test::Unit::TestCase
end
def test_should_save_the_file_and_its_thumbnails
assert @foo.save
assert @foo.save, @foo.errors.full_messages.inspect
assert File.exists?( @foo.image_file_name(:original) ), @foo.image_file_name(:original)
assert File.exists?( @foo.image_file_name(:medium) ), @foo.image_file_name(:medium)
assert File.exists?( @foo.image_file_name(:thumb) ), @foo.image_file_name(:thumb)
......@@ -43,7 +43,7 @@ class PaperclipImagesTest < Test::Unit::TestCase
def test_should_ensure_that_file_are_accessible_after_reload
assert @foo.save
assert @foo.image_valid?
assert @foo.valid?
assert @foo.valid?, @foo.errors.full_messages.inspect
@foo2 = Foo.find @foo.id
assert @foo.image_valid?
......@@ -72,7 +72,7 @@ class PaperclipImagesTest < Test::Unit::TestCase
end
assert @foo.destroy_image
assert @foo.save
assert @foo.save, @foo.errors.full_messages.inspect
mappings.each do |style, file, url|
assert_not_equal file, @foo.image_file_name(style)
assert_equal "", @foo.image_file_name(style)
......@@ -128,8 +128,8 @@ class PaperclipImagesTest < Test::Unit::TestCase
Thoughtbot::Paperclip.options[:image_magick_path] = "/does/not/exist"
assert_nothing_raised{ @foo.image = @file }
assert !@foo.save
assert !@foo.valid?
assert !@foo.save, @foo.errors.full_messages.inspect
assert @foo.errors.length > 0
assert @foo.errors.on(:image)
[@foo.errors.on(:image)].flatten.each do |err|
......
......@@ -15,8 +15,8 @@ class PaperclipTest < Test::Unit::TestCase
end
def test_should_validate_before_save
assert @bar.document_valid?
assert @bar.valid?
assert @bar.document_valid?, @bar.errors.full_messages.inspect
assert @bar.valid?, @bar.errors.full_messages.inspect
end
def test_should_save_the_created_file_to_the_final_asset_directory
......@@ -25,9 +25,9 @@ class PaperclipTest < Test::Unit::TestCase
end
def test_should_validate
assert @bar.save
assert @bar.document_valid?
assert @bar.valid?
assert @bar.save, @bar.errors.full_messages.inspect
assert @bar.document_valid?, @bar.errors.full_messages.inspect
assert @bar.valid?, @bar.errors.full_messages.inspect
end
def test_should_default_to_original_for_path_and_url
......@@ -47,7 +47,7 @@ class PaperclipTest < Test::Unit::TestCase
def test_should_put_on_errors_if_no_file_exists
assert @bar.save
@bar.document = nil
assert !@bar.document_valid?
assert !@bar.document_valid?, @bar.errors.full_messages.inspect
assert !@bar.save
assert @bar.errors.length > 0
assert @bar.errors.on(:document)
......
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