Permalink
Browse files

Did some APIish stuff.

git-svn-id: https://svn.thoughtbot.com/plugins/paperclip/trunk@230 7bbfaf0e-4d1d-0410-9690-a8bb5f8ef2aa
  • Loading branch information...
1 parent 8911e40 commit d46eea5099d111906df107945ac6d8ed897dae98 jyurek committed Oct 26, 2007
View
@@ -3,7 +3,7 @@ require 'rake/testtask'
require 'rake/rdoctask'
desc 'Default: run unit tests.'
-task :default => :test
+task :default => [:clean, :test]
desc 'Test the paperclip plugin.'
Rake::TestTask.new(:test) do |t|
@@ -3,15 +3,15 @@ def self.up
<% 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
+ add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_size, :integer
<% end -%>
end
def self.down
<% 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
+ remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_size
<% end -%>
end
end
View
@@ -133,11 +133,11 @@ module ClassMethods
#
# == 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
+ # and +foo_content_type+ column, as a type of +string+, and a +foo_file_size+ column as type +integer+.
+ # 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.
+ # To access the name as stored in the database, you can use Attachment#original_filename.
#
# == Event Triggers
# When an attachment is set by using he setter (+model.attachment=+), the thumbnails are created and held in
@@ -154,7 +154,10 @@ def has_attached_file *attachment_names
class << self
attr_accessor :attachment_definitions
end
+
include InstanceMethods
+ after_save :save_attachments
+ before_destroy :destroy_attachments
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) }
@@ -191,18 +194,6 @@ class << self
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
@@ -221,6 +212,18 @@ def after_initialize_with_paperclip
end
alias_method_chain :after_initialize, :paperclip
+ def save_attachments
+ @attachments.each do |name, attachment|
+ attachment.save
+ end
+ end
+
+ def destroy_attachments
+ @attachments.each do |name, attachment|
+ attachment.destroy
+ end
+ end
+
def attachment_for name
@attachments[name]
end
@@ -230,17 +233,27 @@ def attachment_names
@attachment_definitions.keys
end
+ # Paperclip always validates whether or not file creation was successful, but does not validate
+ # the presence or size of the file unless told. You can specify validations either in the
+ # has_attached_file call or with a separate validates_attached_file call, with a syntax similar
+ # to has_attached_file. If no options are given, the existence of the file is validated.
+ #
+ # validates_attached_file :avatar, :existence => true, :size => 0..(500.kilobytes)
def validates_attached_file *attachment_names
+ options = attachment_names.pop if attachment_names.last.is_a? Hash
+ options ||= { :existence => true }
attachment_names.each do |name|
- @attachment_definitions[name].validate :existence
+ options.each do |key, value|
+ @attachment_definitions[name].validate key, value
+ end
end
end
def whine_about_columns_for name #:nodoc:
- [ "#{name}_file_name", "#{name}_content_type", "#{name}_size" ].each do |column|
+ [ "#{name}_file_name", "#{name}_content_type", "#{name}_file_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)"
+ "(#{name}_file_name, #{name}_content_type, and #{name}_file_size)"
end
end
end
@@ -265,12 +278,20 @@ def whine_about_columns_for name #:nodoc:
# * 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.
+ # When writing a storage system, your code will be mixed into the Attachment that the file represents. You
+ # will therefore have access to all of the methods available to Attachments. Some methods of note are:
+ # * definition: Returns the AttachmentDefintion object created by has_attached_file. Useful for getting
+ # style definitions and flags that you want to set. You should open the AttachmentDefinition class to
+ # add getters for any options you want to be able to set.
+ # * instance: Returns the ActiveRecord object that the Attachment is attached to. Can be used to obtain ids.
+ # * original_filename: Returns the original_filename, which is the same as the attachment_file_name column
+ # in the database. Should be nil if there is no attachment.
+ # * original_file_size: Returns the size of the original file, as passed to Attachment#assign.
+ # * interpolate: Given a style and a pattern, this will interpolate variables like :rails_root, :name,
+ # and :id. See documentation for has_attached_file for more info on interpolation.
+ # * for_attached_files: Iterates over the collection of files for this attachment, passing the style name
+ # and the data to the block. Will not call the block if the data is nil.
+ # * dirty?: Returns true if a new file has been assigned with Attachment#assign, false otherwise.
#
# == Validations
# Storage systems provide their own validations, since the manner of checking the status of them is usually
@@ -8,31 +8,28 @@ module Storage
module Filesystem
def file_name style = nil
- style ||= @definition.default_style
+ style ||= definition.default_style
pattern = if original_filename && instance.id
- File.join(@definition.path_prefix, @definition.path)
+ File.join(definition.path_prefix, definition.path)
else
- @definition.missing_file_name
+ definition.missing_file_name
end
interpolate( style, pattern )
end
def url style = nil
- style ||= @definition.default_style
+ style ||= definition.default_style
pattern = if original_filename && instance.id
- [@definition.url_prefix, @definition.url || @definition.path].compact.join("/")
+ [definition.url_prefix, definition.url || definition.path].compact.join("/")
else
- @definition.missing_url
+ definition.missing_url
end
interpolate( style, pattern )
end
def write_attachment
- return if @files.blank?
ensure_directories
- @files.each do |style, data|
- data.rewind
- data = data.read
+ for_attached_files do |style, data|
File.open( file_name(style), "w" ) do |file|
file.rewind
file.write(data)
@@ -41,7 +38,7 @@ def write_attachment
end
def delete_attachment complain = false
- @definition.styles.keys.each do |style|
+ definition.styles.keys.each do |style|
file_path = file_name(style)
begin
FileUtils.rm file_path if file_path
@@ -51,13 +48,14 @@ def delete_attachment complain = false
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|
- if @dirty
- errors << "requires a valid #{style} file." unless @files && @files[style]
- else
- errors << "requires a valid #{style} file." unless File.exists?( file_name(style) )
- end
+ definition.styles.keys.each do |style|
+ errors << "requires a valid #{style} file." unless file_exists?(style)
end
end
@@ -69,7 +67,7 @@ def validate_size *constraints
private
def ensure_directories
- @files.each do |style, file|
+ for_attached_files do |style, file|
dirname = File.dirname( file_name(style) )
FileUtils.mkdir_p dirname
end
@@ -1,7 +1,7 @@
module Thoughtbot
module Paperclip
- module ClassMethods #:nodoc:
+ module ClassMethods
def has_attached_file_with_s3 *attachment_names
has_attached_file_without_s3 *attachment_names
@@ -16,7 +16,7 @@ def has_attached_file_with_s3 *attachment_names
secret_key = Thoughtbot::Paperclip.options[:s3_secret_access_key]
end
- if @definition.storage_module == Thoughtbot::Paperclip::Storage::S3
+ if definition.storage_module == Thoughtbot::Paperclip::Storage::S3
require 'aws/s3'
AWS::S3::Base.establish_connection!(
:access_key_id => access_key,
@@ -79,11 +79,11 @@ module Storage
# * s3_persistent: Maintains an HTTP connection to the Amazon service if possible.
module S3
def file_name style = nil
- style ||= @definition.default_style
+ style ||= definition.default_style
pattern = if original_filename && instance.id
- @definition.url
+ definition.url
else
- @definition.missing_url
+ definition.missing_url
end
interpolate( style, pattern )
end
@@ -93,16 +93,14 @@ def url style = nil
end
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 )
+ 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
- (attachment[:thumbnails].keys + [:original]).each do |style|
+ for_attached_files do |style, data|
begin
AWS::S3::S3Object.delete( file_name(style), bucket )
rescue AWS::S3::ResponseError => error
@@ -111,13 +109,14 @@ def delete_attachment complain = false
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|
- if @dirty
- errors << "requires a valid #{style} file." unless @files && @files[style]
- else
- errors << "requires a valid #{style} file." unless AWS::S3::S3Object.exists?( file_name(style), bucket )
- end
+ definition.styles.keys.each do |style|
+ errors << "requires a valid #{style} file." unless file_exists?(style)
end
end
@@ -129,7 +128,7 @@ def validate_size *constraints
private
def bucket
- interpolate(nil, @definition.url_prefix)
+ interpolate(nil, definition.url_prefix)
end
def ensure_bucket
View
@@ -2,28 +2,28 @@
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
+ 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_size, :integer
+ 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_size, :integer
+ table.column :resume_file_size, :integer
table.column :avatar_file_name, :string
table.column :avatar_content_type, :string
- table.column :avatar_size, :integer
+ 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_size, :integer
+ table.column :resume_file_size, :integer
table.column :avatar_file_name, :string
table.column :avatar_content_type, :string
- table.column :avatar_size, :integer
+ 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
@@ -53,9 +53,9 @@ def test_should_default_to_the_assigned_default_style_for_path_and_url
def test_should_delete_files_on_destroy
assert @ns.save
assert File.exists?( @ns.resume_file_name ), @ns.resume_file_name
- assert File.exists?( @ns.avatar_file_name(:original) ), @ns.avatar_file_name(:original)
- assert File.exists?( @ns.avatar_file_name(:bigger) )
- assert File.exists?( @ns.avatar_file_name(:cropped) )
+ [:original, :bigger, :cropped].each do |style|
+ assert File.exists?( @ns.avatar_file_name(style) ), @ns.avatar_file_name(style)
+ end
resume_file_name = @ns.resume_file_name
avatar_file_names = [:original, :bigger, :cropped].map{|style| @ns.avatar_file_name(style) }

0 comments on commit d46eea5

Please sign in to comment.