lib/dm-paperclip.rb in dm-paperclip-2.1.4 vs lib/dm-paperclip.rb in dm-paperclip-2.3.0
- old
+ new
@@ -24,66 +24,220 @@
# # => "/users/avatars/4/thumb_me.jpg"
#
# See the +has_attached_file+ documentation for more details.
require 'tempfile'
-require File.join(File.dirname(__FILE__), 'dm-paperclip', 'upfile')
-require File.join(File.dirname(__FILE__), 'dm-paperclip', 'iostream')
-require File.join(File.dirname(__FILE__), 'dm-paperclip', 'geometry')
-require File.join(File.dirname(__FILE__), 'dm-paperclip', 'thumbnail')
-require File.join(File.dirname(__FILE__), 'dm-paperclip', 'storage')
-require File.join(File.dirname(__FILE__), 'dm-paperclip', 'attachment')
-# Only include validations if dm-validations is loaded
-require File.join(File.dirname(__FILE__), 'dm-paperclip', 'validations') unless defined?(DataMapper::Validate).nil?
+require 'dm-core'
+require 'dm-paperclip/upfile'
+require 'dm-paperclip/iostream'
+require 'dm-paperclip/geometry'
+require 'dm-paperclip/processor'
+require 'dm-paperclip/thumbnail'
+require 'dm-paperclip/storage'
+require 'dm-paperclip/interpolations'
+require 'dm-paperclip/attachment'
+
+# The base module that gets included in ActiveRecord::Base. See the
+# documentation for Paperclip::ClassMethods for more useful information.
module Paperclip
- VERSION = "2.1.4"
+
+ VERSION = "2.3.0"
+
+ # To configure Paperclip, put this code in an initializer, Rake task, or wherever:
+ #
+ # Paperclip.configure do |config|
+ # config.root = Rails.root # the application root to anchor relative urls (defaults to Dir.pwd)
+ # config.env = Rails.env # server env support, defaults to ENV['RACK_ENV'] or 'development'
+ # config.use_dm_validations = true # validate attachment sizes and such, defaults to false
+ # config.processors_path = 'lib/pc' # relative path to look for processors, defaults to 'lib/paperclip_processors'
+ # end
+ #
+ def self.configure
+ yield @config = Configuration.new
+ Paperclip.config = @config
+ end
+
+ def self.config=(config)
+ @config = config
+ end
+
+ def self.config
+ @config ||= Configuration.new
+ end
+
+ def self.require_processors
+ return if @processors_already_required
+ Dir.glob(File.expand_path("#{Paperclip.config.processors_path}/*.rb")).sort.each do |processor|
+ require processor
+ end
+ @processors_already_required = true
+ end
+
+ class Configuration
+
+ DEFAULT_PROCESSORS_PATH = 'lib/paperclip_processors'
+
+ attr_writer :root, :env
+ attr_accessor :use_dm_validations
+
+ def root
+ @root ||= Dir.pwd
+ end
+
+ def env
+ @env ||= (ENV['RACK_ENV'] || 'development')
+ end
+
+ def processors_path=(path)
+ @processors_path = File.expand_path(path, root)
+ end
+
+ def processors_path
+ @processors_path ||= File.expand_path("../#{DEFAULT_PROCESSORS_PATH}", root)
+ end
+
+ end
+
class << self
+
# Provides configurability to Paperclip. There are a number of options available, such as:
- # * whiny_thumbnails: Will raise an error if Paperclip cannot process thumbnails of
+ # * whiny: 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+
+ # * log: Logs progress to the Rails log. Uses ActiveRecord's logger, so honors
+ # log levels, etc. Defaults to true.
+ # * command_path: Defines the path at which to find the command line
# 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.
+ # nil, which uses the first executable found in the user's search path.
+ # * image_magick_path: Deprecated alias of command_path.
def options
@options ||= {
- :whiny_thumbnails => true,
- :image_magick_path => nil
+ :whiny => true,
+ :image_magick_path => nil,
+ :command_path => nil,
+ :log => true,
+ :log_command => false,
+ :swallow_stderr => true
}
end
def path_for_command command #:nodoc:
- path = [options[:image_magick_path], command].compact
+ if options[:image_magick_path]
+ warn("[DEPRECATION] :image_magick_path is deprecated and will be removed. Use :command_path instead")
+ end
+ path = [options[:command_path] || options[:image_magick_path], command].compact
File.join(*path)
end
+
+ def interpolates key, &block
+ Paperclip::Interpolations[key] = block
+ end
+
+ # The run method takes a command to execute and a string of parameters
+ # that get passed to it. The command is prefixed with the :command_path
+ # option from Paperclip.options. If you have many commands to run and
+ # they are in different paths, the suggested course of action is to
+ # symlink them so they are all in the same directory.
+ #
+ # If the command returns with a result code that is not one of the
+ # expected_outcodes, a PaperclipCommandLineError will be raised. Generally
+ # a code of 0 is expected, but a list of codes may be passed if necessary.
+ #
+ # This method can log the command being run when
+ # Paperclip.options[:log_command] is set to true (defaults to false). This
+ # will only log if logging in general is set to true as well.
+ def run cmd, params = "", expected_outcodes = 0
+ command = %Q<#{%Q[#{path_for_command(cmd)} #{params}].gsub(/\s+/, " ")}>
+ command = "#{command} 2>#{bit_bucket}" if Paperclip.options[:swallow_stderr]
+ Paperclip.log(command) if Paperclip.options[:log_command]
+ output = `#{command}`
+ unless [expected_outcodes].flatten.include?($?.exitstatus)
+ raise PaperclipCommandLineError, "Error while running #{cmd}"
+ end
+ output
+ end
+
+ def bit_bucket #:nodoc:
+ File.exists?("/dev/null") ? "/dev/null" : "NUL"
+ end
+
+ def included base #:nodoc:
+ base.extend ClassMethods
+ unless base.respond_to?(:define_callbacks)
+ base.send(:include, Paperclip::CallbackCompatability)
+ end
+ end
+
+ def processor name #:nodoc:
+ name = name.to_s.camel_case
+ processor = Paperclip.const_get(name)
+ unless processor.ancestors.include?(Paperclip::Processor)
+ raise PaperclipError.new("[paperclip] Processor #{name} was not found")
+ end
+ processor
+ end
+
+ # Log a paperclip-specific line. Uses ActiveRecord::Base.logger
+ # by default. Set Paperclip.options[:log] to false to turn off.
+ def log message
+ logger.info("[paperclip] #{message}") if logging?
+ end
+
+ def logger #:nodoc:
+ DataMapper.logger
+ end
+
+ def logging? #:nodoc:
+ options[:log]
+ end
end
class PaperclipError < StandardError #:nodoc:
end
- class NotIdentifiedByImageMagickError < PaperclipError #:nodoc:
+ class PaperclipCommandLineError < StandardError #:nodoc:
end
+ class NotIdentifiedByImageMagickError < PaperclipError #:nodoc:
+ end
+
+ class InfiniteInterpolationError < PaperclipError #:nodoc:
+ end
+
module Resource
+
def self.included(base)
+
base.class_eval <<-RUBY, __FILE__, __LINE__ + 1
class_variable_set(:@@attachment_definitions,nil) unless class_variable_defined?(:@@attachment_definitions)
def self.attachment_definitions
@@attachment_definitions
end
def self.attachment_definitions=(obj)
@@attachment_definitions = obj
end
RUBY
+
base.extend Paperclip::ClassMethods
+
+ # Done at this time to ensure that the user
+ # had a chance to configure the app in an initializer
+ if Paperclip.config.use_dm_validations
+ require 'dm-validations'
+ require 'dm-paperclip/validations'
+ base.extend Paperclip::Validate::ClassMethods
+ end
+
+ Paperclip.require_processors
+
end
+
end
module ClassMethods
-
# +has_attached_file+ gives the class it is called on an attribute that maps to a file. This
# is typically a file stored somewhere on the filesystem and has been uploaded by a user.
# The attribute returns a Paperclip::Attachment object which handles the management of
# that file. The intent is to make the attachment as much like a normal attribute. The
# thumbnails will be created when the new file is assigned, but they will *not* be saved
@@ -143,17 +297,21 @@
self.attachment_definitions = {} if self.attachment_definitions.nil?
self.attachment_definitions[name] = {:validations => []}.merge(options)
property_options = options.delete_if { |k,v| ![ :public, :protected, :private, :accessor, :reader, :writer ].include?(key) }
- property "#{name}_file_name".to_sym, String, property_options
- property "#{name}_content_type".to_sym, String, property_options
- property "#{name}_file_size".to_sym, Integer, property_options
- property "#{name}_updated_at".to_sym, DateTime, property_options
+ property :"#{name}_file_name", String, property_options.merge(:length => 255)
+ property :"#{name}_content_type", String, property_options.merge(:length => 255)
+ property :"#{name}_file_size", Integer, property_options
+ property :"#{name}_updated_at", DateTime, property_options
after :save, :save_attached_files
before :destroy, :destroy_attached_files
+
+ # not needed with extlib just do before :post_process, or after :post_process
+ # define_callbacks :before_post_process, :after_post_process
+ # define_callbacks :"before_#{name}_post_process", :"after_#{name}_post_process"
define_method name do |*args|
a = attachment_for(name)
(args.length > 0) ? a.to_s(args.first) : a
end
@@ -164,78 +322,46 @@
define_method "#{name}?" do
! attachment_for(name).original_filename.blank?
end
- unless defined?(DataMapper::Validate).nil?
+ if Paperclip.config.use_dm_validations
add_validator_to_context(opts_from_validator_args([name]), [name], Paperclip::Validate::CopyAttachmentErrors)
end
- end
- unless defined?(DataMapper::Validate).nil?
-
- # Places ActiveRecord-style validations on the size of the file assigned. The
- # possible options are:
- # * +in+: a Range of bytes (i.e. +1..1.megabyte+),
- # * +less_than+: equivalent to :in => 0..options[:less_than]
- # * +greater_than+: equivalent to :in => options[:greater_than]..Infinity
- # * +message+: error message to display, use :min and :max as replacements
- def validates_attachment_size(*fields)
- opts = opts_from_validator_args(fields)
- add_validator_to_context(opts, fields, Paperclip::Validate::SizeValidator)
end
- # Adds errors if thumbnail creation fails. The same as specifying :whiny_thumbnails => true.
- def validates_attachment_thumbnails name, options = {}
- self.attachment_definitions[name][:whiny_thumbnails] = true
+ # Returns the attachment definitions defined by each call to
+ # has_attached_file.
+ def attachment_definitions
+ read_inheritable_attribute(:attachment_definitions)
end
-
- # Places ActiveRecord-style validations on the presence of a file.
- def validates_attachment_presence(*fields)
- opts = opts_from_validator_args(fields)
- add_validator_to_context(opts, fields, Paperclip::Validate::RequiredFieldValidator)
- 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. Allows all by default.
- # * +message+: The message to display when the uploaded file has an invalid content type.
- def validates_attachment_content_type(*fields)
- opts = opts_from_validator_args(fields)
- add_validator_to_context(opts, fields, Paperclip::Validate::ContentTypeValidator)
- end
-
- end
-
end
module InstanceMethods #:nodoc:
def attachment_for name
@attachments ||= {}
@attachments[name] ||= Attachment.new(name, self, self.class.attachment_definitions[name])
end
-
+
def each_attachment
self.class.attachment_definitions.each do |name, definition|
yield(name, attachment_for(name))
end
end
def save_attached_files
- #logger.info("[paperclip] Saving attachments.")
+ Paperclip.log("Saving attachments.")
each_attachment do |name, attachment|
attachment.send(:save)
end
end
def destroy_attached_files
- #logger.info("[paperclip] Deleting attachments.")
+ Paperclip.log("Deleting attachments.")
each_attachment do |name, attachment|
attachment.send(:queue_existing_for_delete)
attachment.send(:flush_deletes)
end
end
end
-
end
-
-File.send(:include, Paperclip::Upfile)