diff --git a/lib/paperclip.rb b/lib/paperclip.rb index cf7b1c96a..cb29749c5 100644 --- a/lib/paperclip.rb +++ b/lib/paperclip.rb @@ -41,7 +41,12 @@ require 'paperclip/storage' require 'paperclip/callbacks' require 'paperclip/glue' +require 'paperclip/errors' require 'paperclip/missing_attachment_styles' +require 'paperclip/validators' +require 'paperclip/instance_methods' +require 'paperclip/logger' +require 'paperclip/helpers' require 'paperclip/railtie' require 'logger' require 'cocaine' @@ -49,162 +54,27 @@ # The base module that gets included in ActiveRecord::Base. See the # documentation for Paperclip::ClassMethods for more useful information. module Paperclip - - class << self - # Provides configurability to Paperclip. The options available are: - # * whiny: Will raise an error if Paperclip cannot process thumbnails of - # an uploaded image. Defaults to true. - # * 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 user's search path. - def options - @options ||= { - :whiny => true, - :image_magick_path => nil, - :command_path => nil, - :log => true, - :log_command => true, - :swallow_stderr => true - } - end - - def configure - yield(self) if block_given? - end - - def interpolates key, &block - Paperclip::Interpolations[key] = block - end - - # The run method takes the name of a binary to run, the arguments to that binary - # and some options: - # - # :command_path -> A $PATH-like variable that defines where to look for the binary - # on the filesystem. Colon-separated, just like $PATH. - # - # :expected_outcodes -> An array of integers that defines the expected exit codes - # of the binary. Defaults to [0]. - # - # :log_command -> Log the command being run when set to true (defaults to false). - # This will only log if logging in general is set to true as well. - # - # :swallow_stderr -> Set to true if you don't care what happens on STDERR. - # - def run(cmd, arguments = "", local_options = {}) - command_path = options[:command_path] - Cocaine::CommandLine.path = ( Cocaine::CommandLine.path ? [Cocaine::CommandLine.path].flatten | [command_path] : command_path ) - local_options = local_options.merge(:logger => logger) if logging? && (options[:log_command] || local_options[:log_command]) - Cocaine::CommandLine.new(cmd, arguments, local_options).run - end - - def processor(name) #:nodoc: - @known_processors ||= {} - if @known_processors[name.to_s] - @known_processors[name.to_s] - else - name = name.to_s.camelize - load_processor(name) unless Paperclip.const_defined?(name) - processor = Paperclip.const_get(name) - @known_processors[name.to_s] = processor - end - end - - def load_processor(name) - if defined?(Rails.root) && Rails.root - require File.expand_path(Rails.root.join("lib", "paperclip_processors", "#{name.underscore}.rb")) - end - end - - def clear_processors! - @known_processors.try(:clear) - end - - # You can add your own processor via the Paperclip configuration. Normally - # Paperclip will load all processors from the - # Rails.root/lib/paperclip_processors directory, but here you can add any - # existing class using this mechanism. - # - # Paperclip.configure do |c| - # c.register_processor :watermarker, WatermarkingProcessor.new - # end - def register_processor(name, processor) - @known_processors ||= {} - @known_processors[name.to_s] = processor - end - - # Find all instances of the given Active Record model +klass+ with attachment +name+. - # This method is used by the refresh rake tasks. - def each_instance_with_attachment(klass, name) - unscope_method = class_for(klass).respond_to?(:unscoped) ? :unscoped : :with_exclusive_scope - class_for(klass).send(unscope_method) do - class_for(klass).find_each(:conditions => "#{name}_file_name is not null") do |instance| - yield(instance) - end - end - end - - # Log a paperclip-specific line. This will log to STDOUT - # by default. Set Paperclip.options[:log] to false to turn off. - def log message - logger.info("[paperclip] #{message}") if logging? - end - - def logger #:nodoc: - @logger ||= options[:logger] || Logger.new(STDOUT) - end - - def logger=(logger) - @logger = logger - end - - def logging? #:nodoc: - options[:log] - end - - def class_for(class_name) - # Ruby 1.9 introduces an inherit argument for Module#const_get and - # #const_defined? and changes their default behavior. - # https://github.com/rails/rails/blob/v3.0.9/activesupport/lib/active_support/inflector/methods.rb#L89 - if Module.method(:const_get).arity == 1 - class_name.split('::').inject(Object) do |klass, partial_class_name| - klass.const_defined?(partial_class_name) ? klass.const_get(partial_class_name) : klass.const_missing(partial_class_name) - end - else - class_name.split('::').inject(Object) do |klass, partial_class_name| - klass.const_defined?(partial_class_name) ? klass.const_get(partial_class_name, false) : klass.const_missing(partial_class_name) - end - end - end - - def check_for_url_clash(name,url,klass) - @names_url ||= {} - default_url = url || Attachment.default_options[:url] - if @names_url[name] && @names_url[name][:url] == default_url && @names_url[name][:class] != klass && @names_url[name][:url] !~ /:class/ - log("Duplicate URL for #{name} with #{default_url}. This will clash with attachment defined in #{@names_url[name][:class]} class") - end - @names_url[name] = {:url => default_url, :class => klass} - end - - def reset_duplicate_clash_check! - @names_url = nil - end - end - - class PaperclipError < StandardError #:nodoc: - end - - class StorageMethodNotFound < PaperclipError - end - - class CommandNotFoundError < PaperclipError - end - - class NotIdentifiedByImageMagickError < PaperclipError #:nodoc: - end - - class InfiniteInterpolationError < PaperclipError #:nodoc: + extend Helpers + extend Logger + extend ProcessorHelpers + + # Provides configurability to Paperclip. The options available are: + # * whiny: Will raise an error if Paperclip cannot process thumbnails of + # an uploaded image. Defaults to true. + # * 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 user's search path. + def self.options + @options ||= { + :whiny => true, + :image_magick_path => nil, + :command_path => nil, + :log => true, + :log_command => true, + :swallow_stderr => true + } end module ClassMethods @@ -287,7 +157,7 @@ module ClassMethods # "/assets/avatars/default_#{gender}.png" # end # end - def has_attached_file name, options = {} + def has_attached_file(name, options = {}) include InstanceMethods if attachment_definitions.nil? @@ -407,40 +277,4 @@ def attachment_definitions self.attachment_definitions end end - - module InstanceMethods #:nodoc: - def attachment_for name - @_paperclip_attachments ||= {} - @_paperclip_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 - Paperclip.log("Saving attachments.") - each_attachment do |name, attachment| - attachment.send(:save) - end - end - - def destroy_attached_files - Paperclip.log("Deleting attachments.") - each_attachment do |name, attachment| - attachment.send(:flush_deletes) - end - end - - def prepare_for_destroy - Paperclip.log("Scheduling attachments for deletion.") - each_attachment do |name, attachment| - attachment.send(:queue_existing_for_delete) - end - end - - end - end diff --git a/lib/paperclip/attachment.rb b/lib/paperclip/attachment.rb index 69b912260..4286ff4f7 100644 --- a/lib/paperclip/attachment.rb +++ b/lib/paperclip/attachment.rb @@ -365,7 +365,7 @@ def path_option def ensure_required_accessors! #:nodoc: %w(file_name).each do |field| unless @instance.respond_to?("#{name}_#{field}") && @instance.respond_to?("#{name}_#{field}=") - raise PaperclipError.new("#{@instance.class} model missing required attr_accessor for '#{name}_#{field}'") + raise Paperclip::Error.new("#{@instance.class} model missing required attr_accessor for '#{name}_#{field}'") end end end @@ -383,7 +383,7 @@ def initialize_storage #:nodoc: begin storage_module = Paperclip::Storage.const_get(storage_class_name) rescue NameError - raise StorageMethodNotFound, "Cannot load storage module '#{storage_class_name}'" + raise Errors::StorageMethodNotFound, "Cannot load storage module '#{storage_class_name}'" end self.extend(storage_module) end @@ -428,7 +428,7 @@ def post_process_style(name, style) #:nodoc: @queued_for_write[name] = style.processors.inject(@queued_for_write[:original]) do |file, processor| Paperclip.processor(processor).make(file, style.processor_options, self) end - rescue PaperclipError => e + rescue Paperclip::Error => e log("An error was received while processing: #{e.inspect}") (@errors[:processing] ||= []) << e.message if @options[:whiny] end diff --git a/lib/paperclip/errors.rb b/lib/paperclip/errors.rb new file mode 100644 index 000000000..add598f6c --- /dev/null +++ b/lib/paperclip/errors.rb @@ -0,0 +1,27 @@ +module Paperclip + # A base error class for Paperclip. Most of the error that will be thrown + # from Paperclip will inherits from this class. + class Error < StandardError + end + + module Errors + # Will be thrown when a storage method is not found. + class StorageMethodNotFound < Paperclip::Error + end + + # Will be thrown when a command or executable is not found. + class CommandNotFoundError < Paperclip::Error + end + + # Will be thrown when ImageMagic cannot determine the uploaded file's + # metadata, usually this would mean the file is not an image. + class NotIdentifiedByImageMagickError < Paperclip::Error + end + + # Will be thrown if the interpolation is creating an infinite loop. If you + # are creating an interpolator which might cause an infinite loop, you + # should be throwing this error upon the infinite loop as well. + class InfiniteInterpolationError < Paperclip::Error + end + end +end diff --git a/lib/paperclip/geometry.rb b/lib/paperclip/geometry.rb index 4ef716262..f7bbcb9bf 100644 --- a/lib/paperclip/geometry.rb +++ b/lib/paperclip/geometry.rb @@ -17,16 +17,16 @@ def initialize width = nil, height = nil, modifier = nil # a Tempfile object, which would be eligible for file deletion when no longer referenced. def self.from_file file file_path = file.respond_to?(:path) ? file.path : file - raise(Paperclip::NotIdentifiedByImageMagickError.new("Cannot find the geometry of a file with a blank name")) if file_path.blank? + raise(Errors::NotIdentifiedByImageMagickError.new("Cannot find the geometry of a file with a blank name")) if file_path.blank? geometry = begin Paperclip.run("identify", "-format %wx%h :file", :file => "#{file_path}[0]") rescue Cocaine::ExitStatusError "" rescue Cocaine::CommandNotFoundError => e - raise Paperclip::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.") + raise Errors::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.") end parse(geometry) || - raise(NotIdentifiedByImageMagickError.new("#{file_path} is not recognized by the 'identify' command.")) + raise(Errors::NotIdentifiedByImageMagickError.new("#{file_path} is not recognized by the 'identify' command.")) end # Parses a "WxH" formatted string, where W is the width and H is the height. diff --git a/lib/paperclip/helpers.rb b/lib/paperclip/helpers.rb new file mode 100644 index 000000000..6ea603975 --- /dev/null +++ b/lib/paperclip/helpers.rb @@ -0,0 +1,71 @@ +module Paperclip + module Helpers + def configure + yield(self) if block_given? + end + + def interpolates key, &block + Paperclip::Interpolations[key] = block + end + + # The run method takes the name of a binary to run, the arguments to that binary + # and some options: + # + # :command_path -> A $PATH-like variable that defines where to look for the binary + # on the filesystem. Colon-separated, just like $PATH. + # + # :expected_outcodes -> An array of integers that defines the expected exit codes + # of the binary. Defaults to [0]. + # + # :log_command -> Log the command being run when set to true (defaults to false). + # This will only log if logging in general is set to true as well. + # + # :swallow_stderr -> Set to true if you don't care what happens on STDERR. + # + def run(cmd, arguments = "", local_options = {}) + command_path = options[:command_path] + Cocaine::CommandLine.path = ( Cocaine::CommandLine.path ? [Cocaine::CommandLine.path].flatten | [command_path] : command_path ) + local_options = local_options.merge(:logger => logger) if logging? && (options[:log_command] || local_options[:log_command]) + Cocaine::CommandLine.new(cmd, arguments, local_options).run + end + + # Find all instances of the given Active Record model +klass+ with attachment +name+. + # This method is used by the refresh rake tasks. + def each_instance_with_attachment(klass, name) + unscope_method = class_for(klass).respond_to?(:unscoped) ? :unscoped : :with_exclusive_scope + class_for(klass).send(unscope_method) do + class_for(klass).find_each(:conditions => "#{name}_file_name is not null") do |instance| + yield(instance) + end + end + end + + def class_for(class_name) + # Ruby 1.9 introduces an inherit argument for Module#const_get and + # #const_defined? and changes their default behavior. + # https://github.com/rails/rails/blob/v3.0.9/activesupport/lib/active_support/inflector/methods.rb#L89 + if Module.method(:const_get).arity == 1 + class_name.split('::').inject(Object) do |klass, partial_class_name| + klass.const_defined?(partial_class_name) ? klass.const_get(partial_class_name) : klass.const_missing(partial_class_name) + end + else + class_name.split('::').inject(Object) do |klass, partial_class_name| + klass.const_defined?(partial_class_name) ? klass.const_get(partial_class_name, false) : klass.const_missing(partial_class_name) + end + end + end + + def check_for_url_clash(name,url,klass) + @names_url ||= {} + default_url = url || Attachment.default_options[:url] + if @names_url[name] && @names_url[name][:url] == default_url && @names_url[name][:class] != klass && @names_url[name][:url] !~ /:class/ + log("Duplicate URL for #{name} with #{default_url}. This will clash with attachment defined in #{@names_url[name][:class]} class") + end + @names_url[name] = {:url => default_url, :class => klass} + end + + def reset_duplicate_clash_check! + @names_url = nil + end + end +end diff --git a/lib/paperclip/instance_methods.rb b/lib/paperclip/instance_methods.rb new file mode 100644 index 000000000..f7ddd9e26 --- /dev/null +++ b/lib/paperclip/instance_methods.rb @@ -0,0 +1,35 @@ +module Paperclip + module InstanceMethods #:nodoc: + def attachment_for name + @_paperclip_attachments ||= {} + @_paperclip_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 + Paperclip.log("Saving attachments.") + each_attachment do |name, attachment| + attachment.send(:save) + end + end + + def destroy_attached_files + Paperclip.log("Deleting attachments.") + each_attachment do |name, attachment| + attachment.send(:flush_deletes) + end + end + + def prepare_for_destroy + Paperclip.log("Scheduling attachments for deletion.") + each_attachment do |name, attachment| + attachment.send(:queue_existing_for_delete) + end + end + end +end diff --git a/lib/paperclip/interpolations.rb b/lib/paperclip/interpolations.rb index 31ab4e292..1f867b3d6 100644 --- a/lib/paperclip/interpolations.rb +++ b/lib/paperclip/interpolations.rb @@ -46,7 +46,7 @@ def filename attachment, style_name # is used in the default :path to ease default specifications. RIGHT_HERE = "#{__FILE__.gsub(%r{^\./}, "")}:#{__LINE__ + 3}" def url attachment, style_name - raise InfiniteInterpolationError if caller.any?{|b| b.index(RIGHT_HERE) } + raise Errors::InfiniteInterpolationError if caller.any?{|b| b.index(RIGHT_HERE) } attachment.url(style_name, :timestamp => false, :escape => false) end diff --git a/lib/paperclip/logger.rb b/lib/paperclip/logger.rb new file mode 100644 index 000000000..e5320f581 --- /dev/null +++ b/lib/paperclip/logger.rb @@ -0,0 +1,21 @@ +module Paperclip + module Logger + # Log a paperclip-specific line. This will log to STDOUT + # by default. Set Paperclip.options[:log] to false to turn off. + def log message + logger.info("[paperclip] #{message}") if logging? + end + + def logger #:nodoc: + @logger ||= options[:logger] || Logger.new(STDOUT) + end + + def logger=(logger) + @logger = logger + end + + def logging? #:nodoc: + options[:log] + end + end +end diff --git a/lib/paperclip/processor.rb b/lib/paperclip/processor.rb index ac4fd3d47..b801442bc 100644 --- a/lib/paperclip/processor.rb +++ b/lib/paperclip/processor.rb @@ -34,6 +34,43 @@ def self.make file, options = {}, attachment = nil end end + module ProcessorHelpers + def processor(name) #:nodoc: + @known_processors ||= {} + if @known_processors[name.to_s] + @known_processors[name.to_s] + else + name = name.to_s.camelize + load_processor(name) unless Paperclip.const_defined?(name) + processor = Paperclip.const_get(name) + @known_processors[name.to_s] = processor + end + end + + def load_processor(name) + if defined?(Rails.root) && Rails.root + require File.expand_path(Rails.root.join("lib", "paperclip_processors", "#{name.underscore}.rb")) + end + end + + def clear_processors! + @known_processors.try(:clear) + end + + # You can add your own processor via the Paperclip configuration. Normally + # Paperclip will load all processors from the + # Rails.root/lib/paperclip_processors directory, but here you can add any + # existing class using this mechanism. + # + # Paperclip.configure do |c| + # c.register_processor :watermarker, WatermarkingProcessor.new + # end + def register_processor(name, processor) + @known_processors ||= {} + @known_processors[name.to_s] = processor + end + end + # Due to how ImageMagick handles its image format conversion and how Tempfile # handles its naming scheme, it is necessary to override how Tempfile makes # its names so as to allow for file extensions. Idea taken from the comments diff --git a/lib/paperclip/thumbnail.rb b/lib/paperclip/thumbnail.rb index 4a08ebeff..b9852f898 100644 --- a/lib/paperclip/thumbnail.rb +++ b/lib/paperclip/thumbnail.rb @@ -76,9 +76,9 @@ def make success = Paperclip.run("convert", parameters, :source => "#{File.expand_path(src.path)}#{'[0]' unless animated?}", :dest => File.expand_path(dst.path)) rescue Cocaine::ExitStatusError => e - raise PaperclipError, "There was an error processing the thumbnail for #{@basename}" if @whiny + raise Paperclip::Error, "There was an error processing the thumbnail for #{@basename}" if @whiny rescue Cocaine::CommandNotFoundError => e - raise Paperclip::CommandNotFoundError.new("Could not run the `convert` command. Please install ImageMagick.") + raise Paperclip::Errors::CommandNotFoundError.new("Could not run the `convert` command. Please install ImageMagick.") end dst diff --git a/test/attachment_test.rb b/test/attachment_test.rb index d43e8293c..46f270721 100644 --- a/test/attachment_test.rb +++ b/test/attachment_test.rb @@ -512,7 +512,7 @@ class Paperclip::Test < Paperclip::Processor; end setup do rebuild_model :processor => [:thumbnail], :styles => { :small => '' }, :whiny_thumbnails => true @dummy = Dummy.new - Paperclip::Thumbnail.expects(:make).raises(Paperclip::PaperclipError, "cannot be processed.") + Paperclip::Thumbnail.expects(:make).raises(Paperclip::Error, "cannot be processed.") @file = StringIO.new("...") @file.stubs(:to_tempfile).returns(@file) @dummy.avatar = @file @@ -579,7 +579,7 @@ class Paperclip::Test < Paperclip::Processor; end should "convert underscored storage name to camelcase" do rebuild_model :storage => :not_here @dummy = Dummy.new - exception = assert_raises(Paperclip::StorageMethodNotFound) do + exception = assert_raises(Paperclip::Errors::StorageMethodNotFound) do @dummy.avatar end assert exception.message.include?("NotHere") @@ -588,7 +588,7 @@ class Paperclip::Test < Paperclip::Processor; end should "raise an error if you try to include a storage module that doesn't exist" do rebuild_model :storage => :not_here @dummy = Dummy.new - assert_raises(Paperclip::StorageMethodNotFound) do + assert_raises(Paperclip::Errors::StorageMethodNotFound) do @dummy.avatar end end @@ -825,7 +825,7 @@ def do_after_all; end should "raise if there are not the correct columns when you try to assign" do @other_attachment = Paperclip::Attachment.new(:not_here, @instance) - assert_raises(Paperclip::PaperclipError) do + assert_raises(Paperclip::Error) do @other_attachment.assign(@file) end end @@ -1011,7 +1011,7 @@ def do_after_all; end end should "not be able to find the module" do - assert_raise(Paperclip::StorageMethodNotFound){ Dummy.new.avatar } + assert_raise(Paperclip::Errors::StorageMethodNotFound){ Dummy.new.avatar } end end end diff --git a/test/geometry_test.rb b/test/geometry_test.rb index 7063db9ab..bb017ad1c 100644 --- a/test/geometry_test.rb +++ b/test/geometry_test.rb @@ -117,30 +117,30 @@ class GeometryTest < Test::Unit::TestCase should "not generate from a bad file" do file = "/home/This File Does Not Exist.omg" - assert_raise(Paperclip::NotIdentifiedByImageMagickError){ @geo = Paperclip::Geometry.from_file(file) } + assert_raise(Paperclip::Errors::NotIdentifiedByImageMagickError){ @geo = Paperclip::Geometry.from_file(file) } end should "not generate from a blank filename" do file = "" - assert_raise(Paperclip::NotIdentifiedByImageMagickError){ @geo = Paperclip::Geometry.from_file(file) } + assert_raise(Paperclip::Errors::NotIdentifiedByImageMagickError){ @geo = Paperclip::Geometry.from_file(file) } end should "not generate from a nil file" do file = nil - assert_raise(Paperclip::NotIdentifiedByImageMagickError){ @geo = Paperclip::Geometry.from_file(file) } + assert_raise(Paperclip::Errors::NotIdentifiedByImageMagickError){ @geo = Paperclip::Geometry.from_file(file) } end should "not generate from a file with no path" do file = mock("file", :path => "") file.stubs(:respond_to?).with(:path).returns(true) - assert_raise(Paperclip::NotIdentifiedByImageMagickError){ @geo = Paperclip::Geometry.from_file(file) } + assert_raise(Paperclip::Errors::NotIdentifiedByImageMagickError){ @geo = Paperclip::Geometry.from_file(file) } end should "let us know when a command isn't found versus a processing error" do old_path = ENV['PATH'] begin ENV['PATH'] = '' - assert_raises(Paperclip::CommandNotFoundError) do + assert_raises(Paperclip::Errors::CommandNotFoundError) do file = File.join(File.dirname(__FILE__), "fixtures", "5k.png") @geo = Paperclip::Geometry.from_file(file) end diff --git a/test/interpolations_test.rb b/test/interpolations_test.rb index b2d87d9bc..0af7d3131 100644 --- a/test/interpolations_test.rb +++ b/test/interpolations_test.rb @@ -155,7 +155,7 @@ def url(*args) Paperclip::Interpolations.url(self, :style) end end - assert_raises(Paperclip::InfiniteInterpolationError){ Paperclip::Interpolations.url(attachment, :style) } + assert_raises(Paperclip::Errors::InfiniteInterpolationError){ Paperclip::Interpolations.url(attachment, :style) } end should "return the filename as basename.extension" do diff --git a/test/thumbnail_test.rb b/test/thumbnail_test.rb index 0050f3794..9412994c0 100644 --- a/test/thumbnail_test.rb +++ b/test/thumbnail_test.rb @@ -77,7 +77,7 @@ class ThumbnailTest < Test::Unit::TestCase old_path = ENV['PATH'] begin ENV['PATH'] = '' - assert_raises(Paperclip::CommandNotFoundError) do + assert_raises(Paperclip::Errors::CommandNotFoundError) do @thumb.make end ensure @@ -154,7 +154,7 @@ class ThumbnailTest < Test::Unit::TestCase end should "error when trying to create the thumbnail" do - assert_raises(Paperclip::PaperclipError) do + assert_raises(Paperclip::Error) do @thumb.make end end @@ -194,7 +194,7 @@ class ThumbnailTest < Test::Unit::TestCase end should "error when trying to create the thumbnail" do - assert_raises(Paperclip::PaperclipError) do + assert_raises(Paperclip::Error) do @thumb.make end end @@ -203,7 +203,7 @@ class ThumbnailTest < Test::Unit::TestCase old_path = ENV['PATH'] begin ENV['PATH'] = '' - assert_raises(Paperclip::CommandNotFoundError) do + assert_raises(Paperclip::Errors::CommandNotFoundError) do @thumb.make end ensure