Skip to content

Commit

Permalink
Updated documentation. Also changed the defaults of :path and :url to…
Browse files Browse the repository at this point in the history
… something more sane and defaultable.
  • Loading branch information
Jon Yurek committed Dec 29, 2008
1 parent 9324ba8 commit e345764
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 65 deletions.
6 changes: 1 addition & 5 deletions Rakefile
Expand Up @@ -27,7 +27,7 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = 'Paperclip'
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.rdoc_files.include('README')
rdoc.rdoc_files.include('README*')
rdoc.rdoc_files.include('lib/**/*.rb')
end

Expand Down Expand Up @@ -70,10 +70,6 @@ spec = Gem::Specification.new do |s|
s.add_development_dependency 'mocha'
end

Rake::GemPackageTask.new(spec) do |pkg|
pkg.need_tar = true
end

desc "Release new version"
task :release => [:test, :sync_docs, :gem] do
require 'rubygems'
Expand Down
48 changes: 28 additions & 20 deletions lib/paperclip.rb
Expand Up @@ -63,33 +63,39 @@ def options

def path_for_command command #:nodoc:
if options[:image_magick_path]
ActiveSupport::Deprecation.warn(":image_magick_path is deprecated and will be removed. Use :command_path instead")
ActiveSupport::Deprecation.warn(":image_magick_path is deprecated and "+
"will be removed. Use :command_path "+
"instead")
end
path = [options[:image_magick_path] || options[:command_path], command].compact
File.join(*path)
end

def run cmd, params = "", expected_outcodes = 0
def run cmd, params = "", expected_outcodes = 0 #:nodoc:
output = `#{%Q[#{path_for_command(cmd)} #{params} 2>#{bit_bucket}].gsub(/\s+/, " ")}`
unless [expected_outcodes].flatten.include?($?.exitstatus)
raise PaperclipCommandLineError, "Error while running #{cmd}"
end
output
end

def bit_bucket
def bit_bucket #:nodoc:
File.exists?("/dev/null") ? "/dev/null" : "NUL"
end

def included base #:nodoc:
base.extend ClassMethods
base.send(:include, Paperclip::CallbackCompatability) unless base.respond_to?(:define_callbacks)
unless base.respond_to?(:define_callbacks)
base.send(:include, Paperclip::CallbackCompatability)
end
end

def processor name
def processor name #:nodoc:
name = name.to_s.camelize
processor = Paperclip.const_get(name)
raise PaperclipError.new("Processor #{name} was not found") unless processor.ancestors.include?(Paperclip::Processor)
unless processor.ancestors.include?(Paperclip::Processor)
raise PaperclipError.new("Processor #{name} was not found")
end
processor
end
end
Expand All @@ -116,15 +122,15 @@ module ClassMethods
# * +url+: The full URL of where the attachment is publically accessible. This can just
# as easily point to a directory served directly through Apache as it can to an action
# that can control permissions. You can specify the full domain and path, but usually
# just an absolute path is sufficient. The leading slash must be included manually for
# just an absolute path is sufficient. The leading slash *must* be included manually for
# absolute paths. The default value is
# "/:class/:attachment/:id/:style_:basename.:extension". See
# "/system/:attachment/:id/:style/:basename.:extension". See
# Paperclip::Attachment#interpolate for more information on variable interpolaton.
# :url => "/:attachment/:id/:style_:basename:extension"
# :url => "/:class/:attachment/:id/:style_:basename.:extension"
# :url => "http://some.other.host/stuff/:class/:id_:extension"
# * +default_url+: The URL that will be returned if there is no attachment assigned.
# This field is interpolated just as the url is. The default value is
# "/:class/:attachment/missing_:style.png"
# "/:attachment/:style/missing.png"
# has_attached_file :avatar, :default_url => "/images/default_:style_avatar.png"
# User.new.avatar_url(:small) # => "/images/default_small_avatar.png"
# * +styles+: A hash of thumbnail styles and their geometries. You can find more about
Expand Down Expand Up @@ -222,14 +228,16 @@ def validates_attachment_presence name, options = {}
end
end

# Places ActiveRecord-style validations on the content type of the file assigned. The
# possible options are:
# * +content_type+: Allowed content types. Can be a single content type or an array.
# Each type can be a String or a Regexp. It should be noted that Internet Explorer uploads
# files with content_types that you may not expect. For example, JPEG images are given
# image/pjpeg and PNGs are image/x-png, so keep that in mind when determining how you match.
# Allows all by default.
# * +message+: The message to display when the uploaded file has an invalid content type.
# Places ActiveRecord-style validations on the content type of the file
# assigned. The possible options are:
# * +content_type+: Allowed content types. Can be a single content type
# or an array. Each type can be a String or a Regexp. It should be
# noted that Internet Explorer upload files with content_types that you
# may not expect. For example, JPEG images are given image/pjpeg and
# PNGs are image/x-png, so keep that in mind when determining how you
# match. Allows all by default.
# * +message+: The message to display when the uploaded file has an invalid
# content type.
def validates_attachment_content_type name, options = {}
attachment_definitions[name][:validations][:content_type] = lambda do |attachment, instance|
valid_types = [options[:content_type]].flatten
Expand All @@ -245,11 +253,11 @@ def validates_attachment_content_type name, options = {}
end
end

# Returns the attachment definitions defined by each call to has_attached_file.
# Returns the attachment definitions defined by each call to
# has_attached_file.
def attachment_definitions
read_inheritable_attribute(:attachment_definitions)
end

end

module InstanceMethods #:nodoc:
Expand Down
85 changes: 53 additions & 32 deletions lib/paperclip/attachment.rb
@@ -1,12 +1,13 @@
module Paperclip
# The Attachment class manages the files for a given attachment. It saves when the model saves,
# deletes when the model is destroyed, and processes the file upon assignment.
# The Attachment class manages the files for a given attachment. It saves
# when the model saves, deletes when the model is destroyed, and processes
# the file upon assignment.
class Attachment

def self.default_options
@default_options ||= {
:url => "/:attachment/:id/:style/:basename.:extension",
:path => ":rails_root/public/:attachment/:id/:style/:basename.:extension",
:url => "/system/:attachment/:id/:style/:basename.:extension",
:path => ":rails_root/public/system/:attachment/:id/:style/:basename.:extension",
:styles => {},
:default_url => "/:attachment/:style/missing.png",
:default_style => :original,
Expand All @@ -17,9 +18,9 @@ def self.default_options

attr_reader :name, :instance, :styles, :default_style, :convert_options, :queued_for_write

# Creates an Attachment object. +name+ is the name of the attachment, +instance+ is the
# ActiveRecord object instance it's attached to, and +options+ is the same as the hash
# passed to +has_attached_file+.
# Creates an Attachment object. +name+ is the name of the attachment,
# +instance+ is the ActiveRecord object instance it's attached to, and
# +options+ is the same as the hash passed to +has_attached_file+.
def initialize name, instance, options = {}
@name = name
@instance = instance
Expand All @@ -46,14 +47,17 @@ def initialize name, instance, options = {}
normalize_style_definition
initialize_storage

logger.info("[paperclip] Paperclip attachment #{name} on #{instance.class} initialized.")
log("Paperclip attachment #{name} on #{instance.class} initialized.")
end

# What gets called when you call instance.attachment = File. It clears errors,
# assigns attributes, processes the file, and runs validations. It also queues up
# the previous file for deletion, to be flushed away on #save of its host.
# In addition to form uploads, you can also assign another Paperclip attachment:
# What gets called when you call instance.attachment = File. It clears
# errors, assigns attributes, processes the file, and runs validations. It
# also queues up the previous file for deletion, to be flushed away on
# #save of its host. In addition to form uploads, you can also assign
# another Paperclip attachment:
# new_user.avatar = old_user.avatar
# If the file that is assigned is not valid, the processing (i.e.
# thumbnailing, etc) will NOT be run.
def assign uploaded_file
%w(file_name).each do |field|
unless @instance.class.column_names.include?("#{name}_#{field}")
Expand All @@ -66,7 +70,7 @@ def assign uploaded_file
end

return nil unless valid_assignment?(uploaded_file)
logger.info("[paperclip] Assigning #{uploaded_file.inspect} to #{name}")
log("Assigning #{uploaded_file.inspect} to #{name}")

uploaded_file.binmode if uploaded_file.respond_to? :binmode
queue_existing_for_delete
Expand All @@ -75,7 +79,7 @@ def assign uploaded_file

return nil if uploaded_file.nil?

logger.info("[paperclip] Writing attributes for #{name}")
log("Writing attributes for #{name}")
@queued_for_write[:original] = uploaded_file.to_tempfile
instance_write(:file_name, uploaded_file.original_filename.strip.gsub(/[^\w\d\.\-]+/, '_'))
instance_write(:content_type, uploaded_file.content_type.to_s.strip)
Expand All @@ -92,21 +96,22 @@ def assign uploaded_file
validate
end

# Returns the public URL of the attachment, with a given style. Note that this
# does not necessarily need to point to a file that your web server can access
# and can point to an action in your app, if you need fine grained security.
# This is not recommended if you don't need the security, however, for
# performance reasons.
# set include_updated_timestamp to false if you want to stop the attachment update time appended to the url
# Returns the public URL of the attachment, with a given style. Note that
# this does not necessarily need to point to a file that your web server
# can access and can point to an action in your app, if you need fine
# grained security. This is not recommended if you don't need the
# security, however, for performance reasons. set
# include_updated_timestamp to false if you want to stop the attachment
# update time appended to the url
def url style = default_style, include_updated_timestamp = true
url = original_filename.nil? ? interpolate(@default_url, style) : interpolate(@url, style)
include_updated_timestamp && updated_at ? [url, updated_at].compact.join(url.include?("?") ? "&" : "?") : url
end

# Returns the path of the attachment as defined by the :path option. If the
# file is stored in the filesystem the path refers to the path of the file on
# disk. If the file is stored in S3, the path is the "key" part of the URL,
# and the :bucket option refers to the S3 bucket.
# file is stored in the filesystem the path refers to the path of the file
# on disk. If the file is stored in S3, the path is the "key" part of the
# URL, and the :bucket option refers to the S3 bucket.
def path style = nil #:nodoc:
original_filename.nil? ? nil : interpolate(@path, style)
end
Expand Down Expand Up @@ -136,32 +141,38 @@ def dirty?
# the instance's errors and returns false, cancelling the save.
def save
if valid?
logger.info("[paperclip] Saving files for #{name}")
log("Saving files for #{name}")
flush_deletes
flush_writes
@dirty = false
true
else
logger.info("[paperclip] Errors on #{name}. Not saving.")
log("Errors on #{name}. Not saving.")
flush_errors
false
end
end

# Returns the name of the file as originally assigned, and as lives in the
# Returns the name of the file as originally assigned, and lives in the
# <attachment>_file_name attribute of the model.
def original_filename
instance_read(:file_name)
end

# Returns the size of the file as originally assigned, and lives in the
# <attachment>_file_size attribute of the model.
def size
instance_read(:file_size) || (@queued_for_write[:original] && @queued_for_write[:original].size)
end

# Returns the content_type of the file as originally assigned, and lives
# in the <attachment>_content_type attribute of the model.
def content_type
instance_read(:content_type)
end

# Returns the last modified time of the file as originally assigned, and
# lives in the <attachment>_updated_at attribute of the model.
def updated_at
time = instance_read(:updated_at)
time && time.to_i
Expand Down Expand Up @@ -195,10 +206,10 @@ def self.interpolations
}
end

# This method really shouldn't be called that often. It's expected use is in the
# paperclip:refresh rake task and that's it. It will regenerate all thumbnails
# forcefully, by reobtaining the original file and going through the post-process
# again.
# This method really shouldn't be called that often. It's expected use is
# in the paperclip:refresh rake task and that's it. It will regenerate all
# thumbnails forcefully, by reobtaining the original file and going through
# the post-process again.
def reprocess!
new_original = Tempfile.new("paperclip-reprocess")
if old_original = to_file(:original)
Expand All @@ -216,16 +227,22 @@ def reprocess!
end
end

# Returns true if a file has been assigned.
def file?
!original_filename.blank?
end

# Writes the attachment-specific attribute on the instance. For example,
# instance_write(:file_name, "me.jpg") will write "me.jpg" to the instance's
# "avatar_file_name" field (assuming the attachment is called avatar).
def instance_write(attr, value)
setter = :"#{name}_#{attr}="
responds = instance.respond_to?(setter)
instance.send(setter, value) if responds || attr.to_s == "file_name"
end

# Reads the attachment-specific attribute on the instance. See instance_write
# for more details.
def instance_read(attr)
getter = :"#{name}_#{attr}"
responds = instance.respond_to?(getter)
Expand All @@ -238,6 +255,10 @@ def logger
instance.logger
end

def log message
logger.info("[paperclip] #{message}")
end

def valid_assignment? file #:nodoc:
file.nil? || (file.respond_to?(:original_filename) && file.respond_to?(:content_type))
end
Expand Down Expand Up @@ -290,7 +311,7 @@ def post_process #:nodoc:
return if @queued_for_write[:original].nil?
return if callback(:before_post_process) == false
return if callback(:"before_#{name}_post_process") == false
logger.info("[paperclip] Post-processing #{name}")
log("Post-processing #{name}")
@styles.each do |name, args|
begin
@queued_for_write[name] = @queued_for_write[:original]
Expand Down Expand Up @@ -321,7 +342,7 @@ def interpolate pattern, style = default_style #:nodoc:

def queue_existing_for_delete #:nodoc:
return unless file?
logger.info("[paperclip] Queueing the existing files for #{name} for deletion.")
log("Queueing the existing files for #{name} for deletion.")
@queued_for_delete += [:original, *@styles.keys].uniq.map do |style|
path(style) if exists?(style)
end.compact
Expand Down
4 changes: 2 additions & 2 deletions lib/paperclip/callback_compatability.rb
@@ -1,6 +1,6 @@
module Paperclip
# This module is intended as a compatability shim for the differences in callbacks
# between Rails 2.0 and Rails 2.1.
# This module is intended as a compatability shim for the differences in
# callbacks between Rails 2.0 and Rails 2.1.
module CallbackCompatability
def self.included(base)
base.extend(ClassMethods)
Expand Down
2 changes: 1 addition & 1 deletion lib/paperclip/iostream.rb
Expand Up @@ -30,7 +30,7 @@ def stream_to path_or_file, in_blocks_of = 8192
end
end

class IO
class IO #:nodoc:
include IOStream
end

Expand Down

0 comments on commit e345764

Please sign in to comment.