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)