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 ...@@ -3,6 +3,7 @@ class <%= migration_name %> < ActiveRecord::Migration
<% attachments.each do |attachment| -%> <% attachments.each do |attachment| -%>
add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_name, :string 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 %>_content_type, :string
add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_size, :integer
<% end -%> <% end -%>
end end
...@@ -10,6 +11,7 @@ class <%= migration_name %> < ActiveRecord::Migration ...@@ -10,6 +11,7 @@ class <%= migration_name %> < ActiveRecord::Migration
<% attachments.each do |attachment| -%> <% attachments.each do |attachment| -%>
remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_name 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 %>_content_type
remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_size
<% end -%> <% end -%>
end end
end end
module Thoughtbot module Thoughtbot
module Paperclip 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 file_name style = nil
def has_attached_file_with_filesystem *attachment_names style ||= @definition.default_style
has_attached_file_without_filesystem *attachment_names pattern = if original_filename && instance.id
File.join(@definition.path_prefix, @definition.path)
else
@definition.missing_file_name
end end
alias_method_chain :has_attached_file, :filesystem interpolate( style, pattern )
end end
class Storage #:nodoc: def url style = nil
class Filesystem < Storage #:nodoc: style ||= @definition.default_style
def path_for attachment, style = nil pattern = if original_filename && instance.id
style ||= attachment[:default_style] [@definition.url_prefix, @definition.url || @definition.path].compact.join("/")
file = attachment[:instance]["#{attachment[:name]}_file_name"] else
return nil unless file && attachment[:instance].id @definition.missing_url
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
end end
interpolate( style, pattern )
end end
def write_attachment attachment def write_attachment
return if attachment[:files].blank? return if @files.blank?
ensure_directories_for attachment ensure_directories
attachment[:files].each do |style, atch| @files.each do |style, data|
atch.rewind data.rewind
data = atch.read data = data.read
File.open( path_for(attachment, style), "w" ) do |file| File.open( file_name(style), "w" ) do |file|
file.rewind file.rewind
file.write(data) file.write(data)
end end
end end
attachment[:files] = nil
attachment[:dirty] = false
end end
def delete_attachment attachment, complain = false def delete_attachment complain = false
(attachment[:thumbnails].keys + [:original]).each do |style| @definition.styles.keys.each do |style|
file_path = path_for(attachment, style) file_path = file_name(style)
begin begin
FileUtils.rm file_path if file_path FileUtils.rm file_path if file_path
rescue SystemCallError => e 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 end
end end
def attachment_valid? attachment def validate_existence *constraints
attachment[:thumbnails].merge(:original => nil).all? do |style, geometry| @definition.styles.keys.each do |style|
if attachment[:instance]["#{attachment[:name]}_file_name"] if @dirty
if attachment[:dirty] errors << "requires a valid #{style} file." unless @files && @files[style]
!attachment[:files][style].blank? && attachment[:errors].empty?
else else
File.file?( path_for(attachment, style) ) errors << "requires a valid #{style} file." unless File.exists?( file_name(style) )
end end
else
false
end end
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
end end
end end
......
module Thoughtbot module Thoughtbot
module Paperclip module Paperclip
module ClassMethods module ClassMethods #:nodoc:
def has_attached_file_with_s3 *attachment_names 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 = "" access_key = secret_key = ""
if file_name = s3_credentials_file if file_name = s3_credentials_file
...@@ -16,7 +16,7 @@ module Thoughtbot ...@@ -16,7 +16,7 @@ module Thoughtbot
secret_key = Thoughtbot::Paperclip.options[:s3_secret_access_key] secret_key = Thoughtbot::Paperclip.options[:s3_secret_access_key]
end end
if options[:storage].to_s.downcase == "s3" if @definition.storage_module == Thoughtbot::Paperclip::Storage::S3
require 'aws/s3' require 'aws/s3'
AWS::S3::Base.establish_connection!( AWS::S3::Base.establish_connection!(
:access_key_id => access_key, :access_key_id => access_key,
...@@ -36,65 +36,112 @@ module Thoughtbot ...@@ -36,65 +36,112 @@ module Thoughtbot
end end
end end
class Storage #:nodoc: class AttachmentDefinition
class S3 < Storage #:nodoc: def s3_access
def path_for attachment, style = nil @options[:s3_access_privilege]
style ||= attachment[:default_style]
file = attachment[:instance]["#{attachment[:name]}_file_name"]
return nil unless file && attachment[:instance].id
interpolate attachment, attachment[:path], style
end end
def url_for attachment, style = nil
"http://s3.amazonaws.com/#{bucket_for(attachment)}/#{path_for(attachment, style)}"
end end
def bucket_for attachment module Storage
bucket_name = interpolate attachment, attachment[:url_prefix], nil # == 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 end
interpolate( style, pattern )
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}'."
end end
def url style = nil
"http://s3.amazonaws.com/#{bucket}/#{file_name(style)}"
end end
def write_attachment attachment def write_attachment
return if attachment[:files].blank? return if @files.blank?
bucket = ensure_bucket_for attachment bucket = ensure_bucket
attachment[:files].each do |style, atch| @files.each do |style, data|
atch.rewind data.rewind
AWS::S3::S3Object.store( path_for(attachment, style), atch, bucket, :access => attachment[:access] || :public_read ) AWS::S3::S3Object.store( file_name(style), data, bucket, :access => @definition.s3_access || :public_read )
end end
attachment[:files] = nil
attachment[:dirty] = false
end end
def delete_attachment attachment, complain = false def delete_attachment complain = false
(attachment[:thumbnails].keys + [:original]).each do |style| (attachment[:thumbnails].keys + [:original]).each do |style|
file_path = path_for(attachment, style) begin
AWS::S3::S3Object.delete( file_path, bucket_for(attachment) ) AWS::S3::S3Object.delete( file_name(style), bucket )
rescue AWS::S3::ResponseError => error
raise
end
end end
end end
def attachment_valid? attachment def validate_existence *constraints
attachment[:thumbnails].merge(:original => nil).all? do |style, geometry| @definition.styles.keys.each do |style|
if attachment[:instance]["#{attachment[:name]}_file_name"] if @dirty
if attachment[:dirty] errors << "requires a valid #{style} file." unless @files && @files[style]
!attachment[:files][style].blank? && attachment[:errors].empty?
else 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 end
else
false
end end
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 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
end end
......
...@@ -2,22 +2,28 @@ begin ...@@ -2,22 +2,28 @@ begin
ActiveRecord::Base.connection.create_table :foos, :force => true do |table| ActiveRecord::Base.connection.create_table :foos, :force => true do |table|
table.column :image_file_name, :string table.column :image_file_name, :string
table.column :image_content_type, :string table.column :image_content_type, :string
table.column :image_size, :integer
end end
ActiveRecord::Base.connection.create_table :bars, :force => true do |table| ActiveRecord::Base.connection.create_table :bars, :force => true do |table|
table.column :document_file_name, :string table.column :document_file_name, :string
table.column :document_content_type, :string table.column :document_content_type, :string
table.column :document_size, :integer
end end
ActiveRecord::Base.connection.create_table :non_standards, :force => true do |table| ActiveRecord::Base.connection.create_table :non_standards, :force => true do |table|
table.column :resume_file_name, :string table.column :resume_file_name, :string
table.column :resume_content_type, :string table.column :resume_content_type, :string
table.column :resume_size, :integer
table.column :avatar_file_name, :string table.column :avatar_file_name, :string
table.column :avatar_content_type, :string table.column :avatar_content_type, :string
table.column :avatar_size, :integer
end end
ActiveRecord::Base.connection.create_table :ess_threes, :force => true do |table| ActiveRecord::Base.connection.create_table :ess_threes, :force => true do |table|
table.column :resume_file_name, :string table.column :resume_file_name, :string
table.column :resume_content_type, :string table.column :resume_content_type, :string
table.column :resume_size, :integer
table.column :avatar_file_name, :string table.column :avatar_file_name, :string
table.column :avatar_content_type, :string table.column :avatar_content_type, :string
table.column :avatar_size, :integer
end end
ActiveRecord::Base.connection.create_table :negatives, :force => true do |table| ActiveRecord::Base.connection.create_table :negatives, :force => true do |table|
table.column :this_is_the_wrong_name_file_name, :string table.column :this_is_the_wrong_name_file_name, :string
......
...@@ -22,7 +22,7 @@ class PaperclipImagesTest < Test::Unit::TestCase ...@@ -22,7 +22,7 @@ class PaperclipImagesTest < Test::Unit::TestCase
end end
def test_should_save_the_file_and_its_thumbnails 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(: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(:medium) ), @foo.image_file_name(:medium)
assert File.exists?( @foo.image_file_name(:thumb) ), @foo.image_file_name(:thumb) assert File.exists?( @foo.image_file_name(:thumb) ), @foo.image_file_name(:thumb)
...@@ -43,7 +43,7 @@ class PaperclipImagesTest < Test::Unit::TestCase ...@@ -43,7 +43,7 @@ class PaperclipImagesTest < Test::Unit::TestCase
def test_should_ensure_that_file_are_accessible_after_reload def test_should_ensure_that_file_are_accessible_after_reload
assert @foo.save assert @foo.save
assert @foo.image_valid? assert @foo.image_valid?
assert @foo.valid? assert @foo.valid?, @foo.errors.full_messages.inspect
@foo2 = Foo.find @foo.id @foo2 = Foo.find @foo.id
assert @foo.image_valid? assert @foo.image_valid?
...@@ -72,7 +72,7 @@ class PaperclipImagesTest < Test::Unit::TestCase ...@@ -72,7 +72,7 @@ class PaperclipImagesTest < Test::Unit::TestCase
end end
assert @foo.destroy_image assert @foo.destroy_image
assert @foo.save assert @foo.save, @foo.errors.full_messages.inspect
mappings.each do |style, file, url| mappings.each do |style, file, url|
assert_not_equal file, @foo.image_file_name(style) assert_not_equal file, @foo.image_file_name(style)
assert_equal "", @foo.image_file_name(style) assert_equal "", @foo.image_file_name(style)
...@@ -128,8 +128,8 @@ class PaperclipImagesTest < Test::Unit::TestCase ...@@ -128,8 +128,8 @@ class PaperclipImagesTest < Test::Unit::TestCase
Thoughtbot::Paperclip.options[:image_magick_path] = "/does/not/exist" Thoughtbot::Paperclip.options[:image_magick_path] = "/does/not/exist"
assert_nothing_raised{ @foo.image = @file } assert_nothing_raised{ @foo.image = @file }
assert !@foo.save
assert !@foo.valid? assert !@foo.valid?
assert !@foo.save, @foo.errors.full_messages.inspect
assert @foo.errors.length > 0 assert @foo.errors.length > 0
assert @foo.errors.on(:image) assert @foo.errors.on(:image)
[@foo.errors.on(:image)].flatten.each do |err| [@foo.errors.on(:image)].flatten.each do |err|
......
...@@ -15,8 +15,8 @@ class PaperclipTest < Test::Unit::TestCase ...@@ -15,8 +15,8 @@ class PaperclipTest < Test::Unit::TestCase
end end
def test_should_validate_before_save def test_should_validate_before_save
assert @bar.document_valid? assert @bar.document_valid?, @bar.errors.full_messages.inspect
assert @bar.valid? assert @bar.valid?, @bar.errors.full_messages.inspect
end end
def test_should_save_the_created_file_to_the_final_asset_directory def test_should_save_the_created_file_to_the_final_asset_directory
...@@ -25,9 +25,9 @@ class PaperclipTest < Test::Unit::TestCase ...@@ -25,9 +25,9 @@ class PaperclipTest < Test::Unit::TestCase
end end
def test_should_validate def test_should_validate
assert @bar.save assert @bar.save, @bar.errors.full_messages.inspect
assert @bar.document_valid? assert @bar.document_valid?, @bar.errors.full_messages.inspect
assert @bar.valid? assert @bar.valid?, @bar.errors.full_messages.inspect
end end
def test_should_default_to_original_for_path_and_url def test_should_default_to_original_for_path_and_url
...@@ -47,7 +47,7 @@ class PaperclipTest < Test::Unit::TestCase ...@@ -47,7 +47,7 @@ class PaperclipTest < Test::Unit::TestCase
def test_should_put_on_errors_if_no_file_exists def test_should_put_on_errors_if_no_file_exists
assert @bar.save assert @bar.save
@bar.document = nil @bar.document = nil
assert !@bar.document_valid? assert !@bar.document_valid?, @bar.errors.full_messages.inspect
assert !@bar.save assert !@bar.save
assert @bar.errors.length > 0 assert @bar.errors.length > 0
assert @bar.errors.on(:document) 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