Permalink
Browse files

all tests pass

git-svn-id: http://svn.techno-weenie.net/projects/plugins/attachment_fu@2553 567b1171-46fb-0310-a4c9-b4bef9110e78
  • Loading branch information...
1 parent 54b1449 commit f83683b55e5d03c439b9ccaca185eac515bcc909 technoweenie committed Dec 14, 2006
@@ -1,4 +1,5 @@
require File.join(File.dirname(__FILE__), 'attachment_fu', 'backends')
+require File.join(File.dirname(__FILE__), 'attachment_fu', 'processors')
module Technoweenie # :nodoc:
module AttachmentFu # :nodoc:
@@ -46,20 +47,22 @@ def has_attachment(options = {})
unless included_modules.include? InstanceMethods
class_inheritable_accessor :attachment_options
+ options[:processor] ||= :rmagick
options[:storage] ||= options[:file_system_path] ? :file_system : :db_file
options[:file_system_path] ||= File.join("public", table_name)
options[:file_system_path] = options[:file_system_path][1..-1] if options[:file_system_path].first == '/'
- ##with_options :foreign_key => 'parent_id' do |m|
- ## m.has_many :thumbnails, :dependent => :destroy, :class_name => options[:thumbnail_class].to_s
- ## m.belongs_to :parent, :class_name => self.base_class.to_s
- ##end
-
- ##after_save :create_attachment_thumbnails # allows thumbnails with parent_id to be created
+ with_options :foreign_key => 'parent_id' do |m|
+ m.has_many :thumbnails, :dependent => :destroy, :class_name => options[:thumbnail_class].to_s
+ m.belongs_to :parent, :class_name => base_class.to_s
+ end
after_destroy :destroy_file
extend ClassMethods
- include InstanceMethods, Technoweenie::AttachmentFu::const_get("#{options[:storage].to_s.classify}Backend")
+ include InstanceMethods
+ include Technoweenie::AttachmentFu::const_get("#{options[:storage].to_s.classify}Backend")
+ include Technoweenie::AttachmentFu::const_get("#{options[:processor].to_s.classify}Processor")
+ before_save :process_attachment
end
options[:content_type] = [options[:content_type]].flatten.collect { |t| t == :image ? Technoweenie::ActsAsAttachment.content_types : t }.flatten unless options[:content_type].nil?
@@ -81,6 +84,18 @@ def image?(content_type)
content_types.include?(content_type)
end
+ # Callback after an image has been resized.
+ #
+ # class Foo < ActiveRecord::Base
+ # acts_as_attachment
+ # after_resize do |record, img|
+ # record.aspect_ratio = img.columns.to_f / img.rows.to_f
+ # end
+ # end
+ def after_resize(&block)
+ write_inheritable_array(:after_resize, [block])
+ end
+
# Callback after an attachment has been saved either to the file system or the DB.
# Only called if the file has been changed, not necessarily if the record is updated.
#
@@ -93,6 +108,25 @@ def image?(content_type)
def after_attachment_saved(&block)
write_inheritable_array(:after_attachment_saved, [block])
end
+
+ # Callback before a thumbnail is saved. Use this to pass any necessary extra attributes that may be required.
+ #
+ # class Foo < ActiveRecord::Base
+ # acts_as_attachment
+ # before_thumbnail_saved do |record, thumbnail|
+ # ...
+ # end
+ # end
+ def before_thumbnail_saved(&block)
+ write_inheritable_array(:before_thumbnail_saved, [block])
+ end
+
+ # Get the thumbnail class, which is the current attachment class by default.
+ # Configure this with the :thumbnail_class option.
+ def thumbnail_class
+ attachment_options[:thumbnail_class] = attachment_options[:thumbnail_class].constantize unless attachment_options[:thumbnail_class].is_a?(Class)
+ attachment_options[:thumbnail_class]
+ end
end
module InstanceMethods
@@ -152,14 +186,33 @@ def attachment_data=(data)
@attachment_data = nil
@save_attachment = false
self.size = 0
- return nil if data.nil?
if data
self.size = data.length
@save_attachment = true
@attachment_data = data
end
end
+
+ # sets a temporary location to the asset. Use this if the file is already on the local file system
+ # and if you do not need to load it into memory.
+ def attachment_file=(file)
+ @attachment_file = nil
+ @save_attachment = false
+ self.size = 0
+
+ if file && File.file?(file)
+ file_stat = File.stat(file)
+ self.size = file_stat.size
+ @save_attachment = true
+ @attachment_file = file
+ end
+ end
+
+ # Retrieve the temporary attachment file data if it exists, or return nil
+ def attachment_file_data
+ (@attachment_file && File.file?(@attachment_file)) ? File.read(@attachment_file) : nil
+ end
# Sets the content type.
def content_type=(new_type)
@@ -171,6 +224,11 @@ def filename=(new_name)
write_attribute :filename, sanitize_filename(new_name)
end
+ # Returns the width/height in a suitable format for the image_tag helper: (100x100)
+ def image_size
+ [width.to_s, height.to_s] * 'x'
+ end
+
protected
@@filename_basename_regex = /^.*(\\|\/)/
@@filename_character_regex = /[^\w\.\-]/
@@ -193,6 +251,9 @@ def attachment_attributes_valid?
end
end
+ # Stub for a #process_attachment method in a processor
+ def process_attachment() end
+
# Yanked from ActiveRecord::Callbacks, modified so I can pass args to the callbacks besides self.
# Only accept blocks, however
def callback_with_args(method, arg = self)
@@ -11,7 +11,7 @@ def self.included(base) #:nodoc:
def attachment_data
return @attachment_data if @attachment_data
- filename = full_filename
+ filename = @attachment_file || full_filename
File.open(filename, 'rb') do |file|
@attachment_data = file.read
end if File.file?(filename)
@@ -89,13 +89,14 @@ def save_to_storage
# Methods for DB backed attachments
module DbFileBackend
def self.included(base) #:nodoc:
+ Object.const_set(:DbFile, Class.new(ActiveRecord::Base)) unless Object.const_defined?(:DbFile)
base.belongs_to :db_file, :class_name => '::DbFile', :foreign_key => 'db_file_id'
base.before_save :save_to_storage # so the db_file_id can be set
end
# Gets the attachment data
def attachment_data
- @attachment_data ||= db_file.data
+ @attachment_data ||= (attachment_file_data || db_file.data)
end
# Destroys the file. Called in the after_destroy callback
@@ -0,0 +1,106 @@
+module Technoweenie # :nodoc:
+ module AttachmentFu # :nodoc:
+ module RmagickProcessor
+ def self.included(base)
+ begin
+ require 'RMagick'
+ rescue LoadError
+ # boo hoo no rmagick
+ end
+ base.after_save :create_attachment_thumbnails # allows thumbnails with parent_id to be created
+ base.send :extend, ClassMethods
+ end
+
+ module ClassMethods
+ # Yields a block containing an RMagick Image for the given binary data.
+ def with_image(data, &block)
+ begin
+ binary_data = data.is_a?(Magick::Image) ? data : Magick::Image::from_blob(data).first unless !Object.const_defined?(:Magick)
+ rescue
+ # Log the failure to load the image. This should match ::Magick::ImageMagickError
+ # but that would cause acts_as_attachment to require rmagick.
+ logger.debug("Exception working with image: #{$!}")
+ binary_data = nil
+ end
+ block.call binary_data if block && binary_data
+ ensure
+ !binary_data.nil?
+ end
+ end
+
+ # Allows you to work with an RMagick representation of the attachment in a block.
+ #
+ # @attachment.with_image do |img|
+ # self.data = img.thumbnail(100, 100).to_blob
+ # end
+ #
+ def with_image(data = self.attachment_data, &block)
+ self.class.with_image(data, &block)
+ end
+
+ # Creates or updates the thumbnail for the current attachment.
+ def create_or_update_thumbnail(file_name_suffix, *size)
+ thumbnailable? || raise(ThumbnailError.new("Can't create a thumbnail if the content type is not an image or there is no parent_id column"))
+ returning find_or_initialize_thumbnail(file_name_suffix) do |thumb|
+ resized_image = resize_image_to(size)
+ return if resized_image.nil?
+ thumb.attributes = {
+ :content_type => content_type,
+ :filename => thumbnail_name_for(file_name_suffix),
+ :attachment_data => resized_image.to_blob
+ }
+ callback_with_args :before_thumbnail_saved, thumb
+ thumb.save!
+ end
+ end
+
+ # Resizes a thumbnail.
+ def resize_image_to(size)
+ thumb = nil
+ with_image do |img|
+ thumb = thumbnail_for_image(img, size)
+ end
+ thumb
+ end
+
+ protected
+ def create_attachment_thumbnails
+ if thumbnailable? && @save_attachment && !attachment_options[:thumbnails].blank? && parent_id.nil?
+ attachment_options[:thumbnails].each { |suffix, size| create_or_update_thumbnail(suffix, size) }
+ end
+ if @save_attachment
+ @save_attachment = nil
+ callback :after_attachment_saved
+ end
+ end
+
+ # Performs the actual resizing operation for a thumbnail
+ def thumbnail_for_image(img, size)
+ size = size.first if size.is_a?(Array) && size.length == 1 && !size.first.is_a?(Fixnum)
+ if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
+ size = [size, size] if size.is_a?(Fixnum)
+ img.thumbnail(size.first, size[1])
+ else
+ img.change_geometry(size.to_s) { |cols, rows, image| image.resize(cols, rows) }
+ end
+ end
+
+ def find_or_initialize_thumbnail(file_name_suffix)
+ respond_to?(:parent_id) ?
+ thumbnail_class.find_or_initialize_by_thumbnail_and_parent_id(file_name_suffix.to_s, id) :
+ thumbnail_class.find_or_initialize_by_thumbnail(file_name_suffix.to_s)
+ end
+
+ def process_attachment
+ with_image do |img|
+ resized_img = (attachment_options[:resize_to] && (!respond_to?(:parent_id) || parent_id.nil?)) ?
+ thumbnail_for_image(img, attachment_options[:resize_to]) : img
+ self.width = resized_img.columns if respond_to?(:width)
+ self.height = resized_img.rows if respond_to?(:height)
+ self.attachment_data = resized_img.to_blob
+ callback_with_args :after_resize, resized_img
+ end if image?
+ end
+ end
+ end
+end
@@ -0,0 +1,16 @@
+require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
+
+class DbFileTest < Test::Unit::TestCase
+ include BaseAttachmentTests
+ attachment_model Attachment
+
+ def test_should_call_after_attachment_saved(klass = Attachment)
+ attachment_model.saves = 0
+ assert_created do
+ upload_file :filename => '/files/rails.png'
+ end
+ assert_equal 1, attachment_model.saves
+ end
+
+ test_against_subclass :test_should_call_after_attachment_saved, Attachment
+end
@@ -0,0 +1,78 @@
+require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
+
+class FileSystemTest < Test::Unit::TestCase
+ include BaseAttachmentTests
+ attachment_model FileAttachment
+
+ def test_filesystem_size_for_file_attachment(klass = FileAttachment)
+ attachment_model klass
+ assert_created 1 do
+ attachment = upload_file :filename => '/files/rails.png'
+ assert_equal attachment.size, File.open(attachment.full_filename).stat.size
+ end
+ end
+
+ test_against_subclass :test_filesystem_size_for_file_attachment, FileAttachment
+
+ def test_should_not_overwrite_file_attachment(klass = FileAttachment)
+ attachment_model klass
+ assert_created 2 do
+ real = upload_file :filename => '/files/rails.png'
+ assert_valid real
+ assert !real.new_record?, real.errors.full_messages.join("\n")
+ assert !real.size.zero?
+
+ fake = upload_file :filename => '/files/fake/rails.png'
+ assert_valid fake
+ assert !fake.size.zero?
+
+ assert_not_equal File.open(real.full_filename).stat.size, File.open(fake.full_filename).stat.size
+ end
+ end
+
+ test_against_subclass :test_should_not_overwrite_file_attachment, FileAttachment
+
+ def test_should_store_file_attachment_in_filesystem(klass = FileAttachment)
+ attachment_model klass
+ attachment = nil
+ assert_created do
+ attachment = upload_file :filename => '/files/rails.png'
+ assert_valid attachment
+ assert File.exists?(attachment.full_filename), "#{attachment.full_filename} does not exist"
+ end
+ attachment
+ end
+
+ test_against_subclass :test_should_store_file_attachment_in_filesystem, FileAttachment
+
+ def test_should_delete_old_file_when_updating(klass = FileAttachment)
+ attachment_model klass
+ attachment = upload_file :filename => '/files/rails.png'
+ old_filename = attachment.full_filename
+ assert_not_created do
+ attachment.filename = 'rails2.png'
+ attachment.attachment_data = IO.read(File.join(Test::Unit::TestCase.fixture_path, 'files/rails.png'))
+ attachment.save
+ assert File.exists?(attachment.full_filename), "#{attachment.full_filename} does not exist"
+ assert !File.exists?(old_filename), "#{old_filename} still exists"
+ end
+ end
+
+ test_against_subclass :test_should_delete_old_file_when_updating, FileAttachment
+
+ def test_should_delete_old_file_when_renaming(klass = FileAttachment)
+ attachment_model klass
+ attachment = upload_file :filename => '/files/rails.png'
+ old_filename = attachment.full_filename
+ assert_not_created do
+ attachment.filename = 'rails2.png'
+ attachment.save
+ assert File.exists?(attachment.full_filename), "#{attachment.full_filename} does not exist"
+ assert !File.exists?(old_filename), "#{old_filename} still exists"
+ assert !attachment.reload.size.zero?
+ assert_equal 'rails2.png', attachment.filename
+ end
+ end
+
+ test_against_subclass :test_should_delete_old_file_when_renaming, FileAttachment
+end
Oops, something went wrong.

0 comments on commit f83683b

Please sign in to comment.