futuresinc-attachment_fu 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. data/VERSION.yml +1 -1
  2. data/attachment_fu.gemspec +11 -11
  3. data/init.rb +1 -1
  4. data/lib/attachment_fu/backends/cloud_file_backend.rb +209 -0
  5. data/lib/attachment_fu/backends/db_file_backend.rb +37 -0
  6. data/lib/attachment_fu/backends/file_system_backend.rb +124 -0
  7. data/lib/attachment_fu/backends/s3_backend.rb +392 -0
  8. data/lib/attachment_fu/processors/core_image_processor.rb +55 -0
  9. data/lib/attachment_fu/processors/gd2_processor.rb +53 -0
  10. data/lib/attachment_fu/processors/image_science_processor.rb +60 -0
  11. data/lib/attachment_fu/processors/mini_magick_processor.rb +131 -0
  12. data/lib/attachment_fu/processors/rmagick_processor.rb +56 -0
  13. data/lib/attachment_fu.rb +543 -0
  14. data/test/backends/remote/cloudfiles_test.rb +3 -3
  15. data/test/backends/remote/s3_test.rb +3 -3
  16. data/test/basic_test.rb +2 -2
  17. data/test/extra_attachment_test.rb +2 -2
  18. metadata +11 -11
  19. data/lib/technoweenie/attachment_fu/backends/cloud_file_backend.rb +0 -211
  20. data/lib/technoweenie/attachment_fu/backends/db_file_backend.rb +0 -39
  21. data/lib/technoweenie/attachment_fu/backends/file_system_backend.rb +0 -126
  22. data/lib/technoweenie/attachment_fu/backends/s3_backend.rb +0 -394
  23. data/lib/technoweenie/attachment_fu/processors/core_image_processor.rb +0 -59
  24. data/lib/technoweenie/attachment_fu/processors/gd2_processor.rb +0 -54
  25. data/lib/technoweenie/attachment_fu/processors/image_science_processor.rb +0 -61
  26. data/lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb +0 -132
  27. data/lib/technoweenie/attachment_fu/processors/rmagick_processor.rb +0 -57
  28. data/lib/technoweenie/attachment_fu.rb +0 -545
@@ -0,0 +1,56 @@
1
+ require 'RMagick'
2
+
3
+ module AttachmentFu # :nodoc:
4
+ module Processors
5
+ module RmagickProcessor
6
+ def self.included(base)
7
+ base.send :extend, ClassMethods
8
+ base.alias_method_chain :process_attachment, :processing
9
+ end
10
+
11
+ module ClassMethods
12
+ # Yields a block containing an RMagick Image for the given binary data.
13
+ def with_image(file, &block)
14
+ begin
15
+ binary_data = file.is_a?(Magick::Image) ? file : Magick::Image.read(file).first unless !Object.const_defined?(:Magick)
16
+ rescue
17
+ # Log the failure to load the image. This should match ::Magick::ImageMagickError
18
+ # but that would cause acts_as_attachment to require rmagick.
19
+ logger.debug("Exception working with image: #{$!}")
20
+ binary_data = nil
21
+ end
22
+ block.call binary_data if block && binary_data
23
+ ensure
24
+ !binary_data.nil?
25
+ end
26
+ end
27
+
28
+ protected
29
+ def process_attachment_with_processing
30
+ return unless process_attachment_without_processing
31
+ with_image do |img|
32
+ resize_image_or_thumbnail! img
33
+ self.width = img.columns if respond_to?(:width)
34
+ self.height = img.rows if respond_to?(:height)
35
+ callback_with_args :after_resize, img
36
+ end if image?
37
+ end
38
+
39
+ # Performs the actual resizing operation for a thumbnail
40
+ def resize_image(img, size)
41
+ size = size.first if size.is_a?(Array) && size.length == 1 && !size.first.is_a?(Fixnum)
42
+ if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
43
+ size = [size, size] if size.is_a?(Fixnum)
44
+ img.thumbnail!(*size)
45
+ elsif size.is_a?(String) && size =~ /^c.*$/ # Image cropping - example geometry string: c75x75
46
+ dimensions = size[1..size.size].split("x")
47
+ img.crop_resized!(dimensions[0].to_i, dimensions[1].to_i)
48
+ else
49
+ img.change_geometry(size.to_s) { |cols, rows, image| image.resize!(cols<1 ? 1 : cols, rows<1 ? 1 : rows) }
50
+ end
51
+ img.strip! unless attachment_options[:keep_profile]
52
+ temp_paths.unshift write_to_temp_file(img.to_blob)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,543 @@
1
+ require 'tempfile'
2
+ require 'geometry'
3
+
4
+ Tempfile.class_eval do
5
+ # overwrite so tempfiles use the extension of the basename. important for rmagick and image science
6
+ def make_tmpname(basename, n)
7
+ ext = nil
8
+ sprintf("%s%d-%d%s", basename.to_s.gsub(/\.\w+$/) { |s| ext = s; '' }, $$, n, ext)
9
+ end
10
+ end
11
+
12
+ module AttachmentFu # :nodoc:
13
+ @@default_processors = %w(ImageScience Rmagick MiniMagick Gd2 CoreImage)
14
+ @@tempfile_path = File.join(RAILS_ROOT, 'tmp', 'attachment_fu')
15
+ @@content_types = [
16
+ 'image/jpeg',
17
+ 'image/pjpeg',
18
+ 'image/jpg',
19
+ 'image/gif',
20
+ 'image/png',
21
+ 'image/x-png',
22
+ 'image/jpg',
23
+ 'image/x-ms-bmp',
24
+ 'image/bmp',
25
+ 'image/x-bmp',
26
+ 'image/x-bitmap',
27
+ 'image/x-xbitmap',
28
+ 'image/x-win-bitmap',
29
+ 'image/x-windows-bmp',
30
+ 'image/ms-bmp',
31
+ 'application/bmp',
32
+ 'application/x-bmp',
33
+ 'application/x-win-bitmap',
34
+ 'application/preview',
35
+ 'image/jp_',
36
+ 'application/jpg',
37
+ 'application/x-jpg',
38
+ 'image/pipeg',
39
+ 'image/vnd.swiftview-jpeg',
40
+ 'image/x-xbitmap',
41
+ 'application/png',
42
+ 'application/x-png',
43
+ 'image/gi_',
44
+ 'image/x-citrix-pjpeg'
45
+ ]
46
+ mattr_reader :content_types, :tempfile_path, :default_processors
47
+ mattr_writer :tempfile_path
48
+
49
+ class ThumbnailError < StandardError; end
50
+ class AttachmentError < StandardError; end
51
+
52
+ module ActMethods
53
+ # Options:
54
+ # * <tt>:content_type</tt> - Allowed content types. Allows all by default. Use :image to allow all standard image types.
55
+ # * <tt>:min_size</tt> - Minimum size allowed. 1 byte is the default.
56
+ # * <tt>:max_size</tt> - Maximum size allowed. 1.megabyte is the default.
57
+ # * <tt>:size</tt> - Range of sizes allowed. (1..1.megabyte) is the default. This overrides the :min_size and :max_size options.
58
+ # * <tt>:resize_to</tt> - Used by RMagick to resize images. Pass either an array of width/height, or a geometry string.
59
+ # * <tt>:thumbnails</tt> - Specifies a set of thumbnails to generate. This accepts a hash of filename suffixes and RMagick resizing options.
60
+ # * <tt>:thumbnail_class</tt> - Set what class to use for thumbnails. This attachment class is used by default.
61
+ # * <tt>:path_prefix</tt> - path to store the uploaded files. Uses public/#{table_name} by default for the filesystem, and just #{table_name}
62
+ # for the S3 backend. Setting this sets the :storage to :file_system.
63
+
64
+ # * <tt>:storage</tt> - Use :file_system to specify the attachment data is stored with the file system. Defaults to :db_system.
65
+ # * <tt>:cloundfront</tt> - Set to true if you are using S3 storage and want to serve the files through CloudFront. You will need to
66
+ # set a distribution domain in the amazon_s3.yml config file. Defaults to false
67
+ # * <tt>:bucket_key</tt> - Use this to specify a different bucket key other than :bucket_name in the amazon_s3.yml file. This allows you to use
68
+ # different buckets for different models. An example setting would be :image_bucket and the you would need to define the name of the corresponding
69
+ # bucket in the amazon_s3.yml file.
70
+
71
+ # * <tt>:keep_profile</tt> By default image EXIF data will be stripped to minimize image size. For small thumbnails this proivides important savings. Picture quality is not affected. Set to false if you want to keep the image profile as is. ImageScience will allways keep EXIF data.
72
+ #
73
+ # Examples:
74
+ # has_attachment :max_size => 1.kilobyte
75
+ # has_attachment :size => 1.megabyte..2.megabytes
76
+ # has_attachment :content_type => 'application/pdf'
77
+ # has_attachment :content_type => ['application/pdf', 'application/msword', 'text/plain']
78
+ # has_attachment :content_type => :image, :resize_to => [50,50]
79
+ # has_attachment :content_type => ['application/pdf', :image], :resize_to => 'x50'
80
+ # has_attachment :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
81
+ # has_attachment :storage => :file_system, :path_prefix => 'public/files'
82
+ # has_attachment :storage => :file_system, :path_prefix => 'public/files',
83
+ # :content_type => :image, :resize_to => [50,50]
84
+ # has_attachment :storage => :file_system, :path_prefix => 'public/files',
85
+ # :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
86
+ # has_attachment :storage => :s3
87
+ def has_attachment(options = {})
88
+ # this allows you to redefine the acts' options for each subclass, however
89
+ options[:min_size] ||= 1
90
+ options[:max_size] ||= 1.megabyte
91
+ options[:size] ||= (options[:min_size]..options[:max_size])
92
+ options[:thumbnails] ||= {}
93
+ options[:thumbnail_class] ||= self
94
+ options[:s3_access] ||= :public_read
95
+ options[:cloudfront] ||= false
96
+ options[:content_type] = [options[:content_type]].flatten.collect! { |t| t == :image ? AttachmentFu.content_types : t }.flatten unless options[:content_type].nil?
97
+
98
+ unless options[:thumbnails].is_a?(Hash)
99
+ raise ArgumentError, ":thumbnails option should be a hash: e.g. :thumbnails => { :foo => '50x50' }"
100
+ end
101
+
102
+ extend ClassMethods unless (class << self; included_modules; end).include?(ClassMethods)
103
+ include InstanceMethods unless included_modules.include?(InstanceMethods)
104
+
105
+ parent_options = attachment_options || {}
106
+ # doing these shenanigans so that #attachment_options is available to processors and backends
107
+ self.attachment_options = options
108
+
109
+ attr_accessor :thumbnail_resize_options
110
+
111
+ attachment_options[:storage] ||= (attachment_options[:file_system_path] || attachment_options[:path_prefix]) ? :file_system : :db_file
112
+ attachment_options[:storage] ||= parent_options[:storage]
113
+ attachment_options[:path_prefix] ||= attachment_options[:file_system_path]
114
+ if attachment_options[:path_prefix].nil?
115
+ attachment_options[:path_prefix] = case attachment_options[:storage]
116
+ when :s3 then table_name
117
+ when :cloud_files then table_name
118
+ else File.join("public", table_name)
119
+ end
120
+ end
121
+ attachment_options[:path_prefix] = attachment_options[:path_prefix][1..-1] if options[:path_prefix].first == '/'
122
+
123
+ association_options = { :foreign_key => 'parent_id' }
124
+ if attachment_options[:association_options]
125
+ association_options.merge!(attachment_options[:association_options])
126
+ end
127
+ with_options(association_options) do |m|
128
+ m.has_many :thumbnails, :class_name => "::#{attachment_options[:thumbnail_class]}"
129
+ m.belongs_to :parent, :class_name => "::#{base_class}" unless options[:thumbnails].empty?
130
+ end
131
+
132
+ storage_mod = AttachmentFu::Backends.const_get("#{options[:storage].to_s.classify}Backend")
133
+ include storage_mod unless included_modules.include?(storage_mod)
134
+
135
+ case attachment_options[:processor]
136
+ when :none, nil
137
+ processors = AttachmentFu.default_processors.dup
138
+ begin
139
+ if processors.any?
140
+ attachment_options[:processor] = processors.first
141
+ processor_mod = AttachmentFu::Processors.const_get("#{attachment_options[:processor].to_s.classify}Processor")
142
+ include processor_mod unless included_modules.include?(processor_mod)
143
+ end
144
+ rescue Object, Exception
145
+ raise unless load_related_exception?($!)
146
+
147
+ processors.shift
148
+ retry
149
+ end
150
+ else
151
+ begin
152
+ processor_mod = AttachmentFu::Processors.const_get("#{attachment_options[:processor].to_s.classify}Processor")
153
+ include processor_mod unless included_modules.include?(processor_mod)
154
+ rescue Object, Exception
155
+ raise unless load_related_exception?($!)
156
+
157
+ puts "Problems loading #{options[:processor]}Processor: #{$!}"
158
+ end
159
+ end unless parent_options[:processor] # Don't let child override processor
160
+ end
161
+
162
+ def load_related_exception?(e) #:nodoc: implementation specific
163
+ case
164
+ when e.kind_of?(LoadError), e.kind_of?(MissingSourceFile), $!.class.name == "CompilationError"
165
+ # We can't rescue CompilationError directly, as it is part of the RubyInline library.
166
+ # We must instead rescue RuntimeError, and check the class' name.
167
+ true
168
+ else
169
+ false
170
+ end
171
+ end
172
+ private :load_related_exception?
173
+ end
174
+
175
+ module ClassMethods
176
+ delegate :content_types, :to => AttachmentFu
177
+
178
+ # Performs common validations for attachment models.
179
+ def validates_as_attachment
180
+ validates_presence_of :size, :content_type, :filename
181
+ validate :attachment_attributes_valid?
182
+ end
183
+
184
+ # Returns true or false if the given content type is recognized as an image.
185
+ def image?(content_type)
186
+ content_types.include?(content_type)
187
+ end
188
+
189
+ def self.extended(base)
190
+ base.class_inheritable_accessor :attachment_options
191
+ base.before_destroy :destroy_thumbnails
192
+ base.before_validation :set_size_from_temp_path
193
+ base.after_save :after_process_attachment
194
+ base.after_destroy :destroy_file
195
+ base.after_validation :process_attachment
196
+ if defined?(::ActiveSupport::Callbacks)
197
+ base.define_callbacks :after_resize, :after_attachment_saved, :before_thumbnail_saved
198
+ end
199
+ end
200
+
201
+ unless defined?(::ActiveSupport::Callbacks)
202
+ # Callback after an image has been resized.
203
+ #
204
+ # class Foo < ActiveRecord::Base
205
+ # acts_as_attachment
206
+ # after_resize do |record, img|
207
+ # record.aspect_ratio = img.columns.to_f / img.rows.to_f
208
+ # end
209
+ # end
210
+ def after_resize(&block)
211
+ write_inheritable_array(:after_resize, [block])
212
+ end
213
+
214
+ # Callback after an attachment has been saved either to the file system or the DB.
215
+ # Only called if the file has been changed, not necessarily if the record is updated.
216
+ #
217
+ # class Foo < ActiveRecord::Base
218
+ # acts_as_attachment
219
+ # after_attachment_saved do |record|
220
+ # ...
221
+ # end
222
+ # end
223
+ def after_attachment_saved(&block)
224
+ write_inheritable_array(:after_attachment_saved, [block])
225
+ end
226
+
227
+ # Callback before a thumbnail is saved. Use this to pass any necessary extra attributes that may be required.
228
+ #
229
+ # class Foo < ActiveRecord::Base
230
+ # acts_as_attachment
231
+ # before_thumbnail_saved do |thumbnail|
232
+ # record = thumbnail.parent
233
+ # ...
234
+ # end
235
+ # end
236
+ def before_thumbnail_saved(&block)
237
+ write_inheritable_array(:before_thumbnail_saved, [block])
238
+ end
239
+ end
240
+
241
+ # Get the thumbnail class, which is the current attachment class by default.
242
+ # Configure this with the :thumbnail_class option.
243
+ def thumbnail_class
244
+ attachment_options[:thumbnail_class] = attachment_options[:thumbnail_class].constantize unless attachment_options[:thumbnail_class].is_a?(Class)
245
+ attachment_options[:thumbnail_class]
246
+ end
247
+
248
+ # Copies the given file path to a new tempfile, returning the closed tempfile.
249
+ def copy_to_temp_file(file, temp_base_name)
250
+ returning Tempfile.new(temp_base_name, AttachmentFu.tempfile_path) do |tmp|
251
+ tmp.close
252
+ FileUtils.cp file, tmp.path
253
+ end
254
+ end
255
+
256
+ # Writes the given data to a new tempfile, returning the closed tempfile.
257
+ def write_to_temp_file(data, temp_base_name)
258
+ returning Tempfile.new(temp_base_name, AttachmentFu.tempfile_path) do |tmp|
259
+ tmp.binmode
260
+ tmp.write data
261
+ tmp.close
262
+ end
263
+ end
264
+ end
265
+
266
+ module InstanceMethods
267
+ def self.included(base)
268
+ base.define_callbacks *[:after_resize, :after_attachment_saved, :before_thumbnail_saved] if base.respond_to?(:define_callbacks)
269
+ end
270
+
271
+ # Checks whether the attachment's content type is an image content type
272
+ def image?
273
+ self.class.image?(content_type)
274
+ end
275
+
276
+ # Returns true/false if an attachment is thumbnailable. A thumbnailable attachment has an image content type and the parent_id attribute.
277
+ def thumbnailable?
278
+ image? && respond_to?(:parent_id) && parent_id.nil?
279
+ end
280
+
281
+ # Returns the class used to create new thumbnails for this attachment.
282
+ def thumbnail_class
283
+ self.class.thumbnail_class
284
+ end
285
+
286
+ # Gets the thumbnail name for a filename. 'foo.jpg' becomes 'foo_thumbnail.jpg'
287
+ def thumbnail_name_for(thumbnail = nil)
288
+ return filename if thumbnail.blank?
289
+ ext = nil
290
+ basename = filename.gsub /\.\w+$/ do |s|
291
+ ext = s; ''
292
+ end
293
+ # ImageScience doesn't create gif thumbnails, only pngs
294
+ ext.sub!(/gif$/, 'png') if attachment_options[:processor] == "ImageScience"
295
+ "#{basename}_#{thumbnail}#{ext}"
296
+ end
297
+
298
+ # Creates or updates the thumbnail for the current attachment.
299
+ def create_or_update_thumbnail(temp_file, file_name_suffix, *size)
300
+ thumbnailable? || raise(ThumbnailError.new("Can't create a thumbnail if the content type is not an image or there is no parent_id column"))
301
+ returning find_or_initialize_thumbnail(file_name_suffix) do |thumb|
302
+ thumb.temp_paths.unshift temp_file
303
+ thumb.send(:'attributes=', {
304
+ :content_type => content_type,
305
+ :filename => thumbnail_name_for(file_name_suffix),
306
+ :thumbnail_resize_options => size
307
+ }, false)
308
+ callback_with_args :before_thumbnail_saved, thumb
309
+ thumb.save!
310
+ end
311
+ end
312
+
313
+ # Sets the content type.
314
+ def content_type=(new_type)
315
+ write_attribute :content_type, new_type.to_s.strip
316
+ end
317
+
318
+ # Sanitizes a filename.
319
+ def filename=(new_name)
320
+ write_attribute :filename, sanitize_filename(new_name)
321
+ end
322
+
323
+ # Returns the width/height in a suitable format for the image_tag helper: (100x100)
324
+ def image_size
325
+ [width.to_s, height.to_s] * 'x'
326
+ end
327
+
328
+ # Returns true if the attachment data will be written to the storage system on the next save
329
+ def save_attachment?
330
+ File.file?(temp_path.to_s)
331
+ end
332
+
333
+ # nil placeholder in case this field is used in a form.
334
+ def uploaded_data() nil; end
335
+
336
+ # This method handles the uploaded file object. If you set the field name to uploaded_data, you don't need
337
+ # any special code in your controller.
338
+ #
339
+ # <% form_for :attachment, :html => { :multipart => true } do |f| -%>
340
+ # <p><%= f.file_field :uploaded_data %></p>
341
+ # <p><%= submit_tag :Save %>
342
+ # <% end -%>
343
+ #
344
+ # @attachment = Attachment.create! params[:attachment]
345
+ #
346
+ # TODO: Allow it to work with Merb tempfiles too.
347
+ def uploaded_data=(file_data)
348
+ if file_data.respond_to?(:content_type)
349
+ return nil if file_data.size == 0
350
+ self.content_type = detect_mimetype(file_data)
351
+ self.filename = file_data.original_filename if respond_to?(:filename)
352
+ else
353
+ return nil if file_data.blank? || file_data['size'] == 0
354
+ self.content_type = file_data['content_type']
355
+ self.filename = file_data['filename']
356
+ file_data = file_data['tempfile']
357
+ end
358
+ if file_data.is_a?(StringIO)
359
+ file_data.rewind
360
+ set_temp_data file_data.read
361
+ else
362
+ self.temp_paths.unshift file_data
363
+ end
364
+ end
365
+
366
+ # Gets the latest temp path from the collection of temp paths. While working with an attachment,
367
+ # multiple Tempfile objects may be created for various processing purposes (resizing, for example).
368
+ # An array of all the tempfile objects is stored so that the Tempfile instance is held on to until
369
+ # it's not needed anymore. The collection is cleared after saving the attachment.
370
+ def temp_path
371
+ p = temp_paths.first
372
+ p.respond_to?(:path) ? p.path : p.to_s
373
+ end
374
+
375
+ # Gets an array of the currently used temp paths. Defaults to a copy of #full_filename.
376
+ def temp_paths
377
+ @temp_paths ||= (new_record? || !respond_to?(:full_filename) || !File.exist?(full_filename) ?
378
+ [] : [copy_to_temp_file(full_filename)])
379
+ end
380
+
381
+ # Gets the data from the latest temp file. This will read the file into memory.
382
+ def temp_data
383
+ save_attachment? ? File.read(temp_path) : nil
384
+ end
385
+
386
+ # Writes the given data to a Tempfile and adds it to the collection of temp files.
387
+ def set_temp_data(data)
388
+ temp_paths.unshift write_to_temp_file(data) unless data.nil?
389
+ end
390
+
391
+ # Copies the given file to a randomly named Tempfile.
392
+ def copy_to_temp_file(file)
393
+ self.class.copy_to_temp_file file, random_tempfile_filename
394
+ end
395
+
396
+ # Writes the given file to a randomly named Tempfile.
397
+ def write_to_temp_file(data)
398
+ self.class.write_to_temp_file data, random_tempfile_filename
399
+ end
400
+
401
+ # Stub for creating a temp file from the attachment data. This should be defined in the backend module.
402
+ def create_temp_file() end
403
+
404
+ # Allows you to work with a processed representation (RMagick, ImageScience, etc) of the attachment in a block.
405
+ #
406
+ # @attachment.with_image do |img|
407
+ # self.data = img.thumbnail(100, 100).to_blob
408
+ # end
409
+ #
410
+ def with_image(&block)
411
+ self.class.with_image(temp_path, &block)
412
+ end
413
+
414
+ protected
415
+ # Generates a unique filename for a Tempfile.
416
+ def random_tempfile_filename
417
+ "#{rand Time.now.to_i}#{filename || 'attachment'}"
418
+ end
419
+
420
+ def sanitize_filename(filename)
421
+ return unless filename
422
+ returning filename.strip do |name|
423
+ # NOTE: File.basename doesn't work right with Windows paths on Unix
424
+ # get only the filename, not the whole path
425
+ name.gsub! /^.*(\\|\/)/, ''
426
+
427
+ # Replace all non alphanumeric, underscore or periods with underscore
428
+ name.gsub! /[^A-Za-z0-9\.\-]/, '_'
429
+
430
+ # Remove multiple underscores
431
+ name.gsub! /\_+/, '_'
432
+
433
+ # Finally, downcase result including extension
434
+ name.downcase!
435
+ end
436
+ end
437
+
438
+ # before_validation callback.
439
+ def set_size_from_temp_path
440
+ self.size = File.size(temp_path) if save_attachment?
441
+ end
442
+
443
+ # validates the size and content_type attributes according to the current model's options
444
+ def attachment_attributes_valid?
445
+ [:size, :content_type].each do |attr_name|
446
+ enum = attachment_options[attr_name]
447
+ if Object.const_defined?(:I18n) # Rails >= 2.2
448
+ errors.add attr_name, I18n.translate("activerecord.errors.messages.inclusion", attr_name => enum) unless enum.nil? || enum.include?(send(attr_name))
449
+ else
450
+ errors.add attr_name, ActiveRecord::Errors.default_error_messages[:inclusion] unless enum.nil? || enum.include?(send(attr_name))
451
+ end
452
+ end
453
+ end
454
+
455
+ # Initializes a new thumbnail with the given suffix.
456
+ def find_or_initialize_thumbnail(file_name_suffix)
457
+ respond_to?(:parent_id) ?
458
+ thumbnail_class.find_or_initialize_by_thumbnail_and_parent_id(file_name_suffix.to_s, id) :
459
+ thumbnail_class.find_or_initialize_by_thumbnail(file_name_suffix.to_s)
460
+ end
461
+
462
+ # Stub for a #process_attachment method in a processor
463
+ def process_attachment
464
+ @saved_attachment = save_attachment?
465
+ end
466
+
467
+ # Cleans up after processing. Thumbnails are created, the attachment is stored to the backend, and the temp_paths are cleared.
468
+ def after_process_attachment
469
+ if @saved_attachment
470
+ if respond_to?(:process_attachment_with_processing) && thumbnailable? && !attachment_options[:thumbnails].blank? && parent_id.nil?
471
+ temp_file = temp_path || create_temp_file
472
+ attachment_options[:thumbnails].each { |suffix, size| create_or_update_thumbnail(temp_file, suffix, *size) }
473
+ end
474
+ save_to_storage
475
+ @temp_paths.clear
476
+ @saved_attachment = nil
477
+ callback :after_attachment_saved
478
+ end
479
+ end
480
+
481
+ # Resizes the given processed img object with either the attachment resize options or the thumbnail resize options.
482
+ def resize_image_or_thumbnail!(img)
483
+ if (!respond_to?(:parent_id) || parent_id.nil?) && attachment_options[:resize_to] # parent image
484
+ resize_image(img, attachment_options[:resize_to])
485
+ elsif thumbnail_resize_options # thumbnail
486
+ resize_image(img, thumbnail_resize_options)
487
+ end
488
+ end
489
+
490
+ # Yanked from ActiveRecord::Callbacks, modified so I can pass args to the callbacks besides self.
491
+ # Only accept blocks, however
492
+ if ActiveSupport.const_defined?(:Callbacks)
493
+ # Rails 2.1 and beyond!
494
+ def callback_with_args(method, arg = self)
495
+ notify(method)
496
+
497
+ result = run_callbacks(method, { :object => [self, arg] }) { |result, object| result == false }
498
+
499
+ if result != false && respond_to_without_attributes?(method)
500
+ result = send(method)
501
+ end
502
+
503
+ result
504
+ end
505
+
506
+ def run_callbacks(kind, options = {}, &block)
507
+ options.reverse_merge!( :object => self )
508
+ self.class.send("#{kind}_callback_chain").run(options[:object], options, &block)
509
+ end
510
+ else
511
+ # Rails 2.0
512
+ def callback_with_args(method, arg = self)
513
+ notify(method)
514
+
515
+ result = nil
516
+ callbacks_for(method).each do |callback|
517
+ result = callback.call(self, arg)
518
+ return false if result == false
519
+ end
520
+ result
521
+ end
522
+ end
523
+
524
+ # Removes the thumbnails for the attachment, if it has any
525
+ def destroy_thumbnails
526
+ self.thumbnails.each { |thumbnail| thumbnail.destroy } if thumbnailable?
527
+ end
528
+
529
+ def detect_mimetype(file_data)
530
+ if file_data.content_type.strip == "application/octet-stream"
531
+ return File.mime_type?(file_data.original_filename)
532
+ else
533
+ return file_data.content_type
534
+ end
535
+ end
536
+ end
537
+ end
538
+
539
+ ActiveRecord::Base.send(:extend, AttachmentFu::ActMethods)
540
+ AttachmentFu.tempfile_path = ATTACHMENT_FU_TEMPFILE_PATH if Object.const_defined?(:ATTACHMENT_FU_TEMPFILE_PATH)
541
+ FileUtils.mkdir_p AttachmentFu.tempfile_path
542
+
543
+ $:.unshift(File.dirname(__FILE__) + '../../vendor')
@@ -84,15 +84,15 @@ class CloudfilesTest < ActiveSupport::TestCase
84
84
  end
85
85
 
86
86
  def s3_protocol
87
- Technoweenie::AttachmentFu::Backends::S3Backend.protocol
87
+ AttachmentFu::Backends::S3Backend.protocol
88
88
  end
89
89
 
90
90
  def s3_hostname
91
- Technoweenie::AttachmentFu::Backends::S3Backend.hostname
91
+ AttachmentFu::Backends::S3Backend.hostname
92
92
  end
93
93
 
94
94
  def s3_port_string
95
- Technoweenie::AttachmentFu::Backends::S3Backend.port_string
95
+ AttachmentFu::Backends::S3Backend.port_string
96
96
  end
97
97
  else
98
98
  def test_flunk_s3
@@ -101,15 +101,15 @@ class S3Test < ActiveSupport::TestCase
101
101
  end
102
102
 
103
103
  def s3_protocol
104
- Technoweenie::AttachmentFu::Backends::S3Backend.protocol
104
+ AttachmentFu::Backends::S3Backend.protocol
105
105
  end
106
106
 
107
107
  def s3_hostname
108
- Technoweenie::AttachmentFu::Backends::S3Backend.hostname
108
+ AttachmentFu::Backends::S3Backend.hostname
109
109
  end
110
110
 
111
111
  def s3_port_string
112
- Technoweenie::AttachmentFu::Backends::S3Backend.port_string
112
+ AttachmentFu::Backends::S3Backend.port_string
113
113
  end
114
114
  else
115
115
  def test_flunk_s3
data/test/basic_test.rb CHANGED
@@ -24,8 +24,8 @@ class BasicTest < ActiveSupport::TestCase
24
24
  def test_should_normalize_content_types_to_array
25
25
  assert_equal %w(pdf), PdfAttachment.attachment_options[:content_type]
26
26
  assert_equal %w(pdf doc txt), DocAttachment.attachment_options[:content_type]
27
- assert_equal Technoweenie::AttachmentFu.content_types, ImageAttachment.attachment_options[:content_type]
28
- assert_equal ['pdf'] + Technoweenie::AttachmentFu.content_types, ImageOrPdfAttachment.attachment_options[:content_type]
27
+ assert_equal AttachmentFu.content_types, ImageAttachment.attachment_options[:content_type]
28
+ assert_equal ['pdf'] + AttachmentFu.content_types, ImageOrPdfAttachment.attachment_options[:content_type]
29
29
  end
30
30
 
31
31
  def test_should_sanitize_content_type
@@ -48,7 +48,7 @@ class OrphanAttachmentTest < ActiveSupport::TestCase
48
48
  def test_should_create_thumbnail
49
49
  attachment = upload_file :filename => '/files/rails.png'
50
50
 
51
- assert_raise Technoweenie::AttachmentFu::ThumbnailError do
51
+ assert_raise AttachmentFu::ThumbnailError do
52
52
  attachment.create_or_update_thumbnail(attachment.create_temp_file, 'thumb', 50, 50)
53
53
  end
54
54
  end
@@ -56,7 +56,7 @@ class OrphanAttachmentTest < ActiveSupport::TestCase
56
56
  def test_should_create_thumbnail_with_geometry_string
57
57
  attachment = upload_file :filename => '/files/rails.png'
58
58
 
59
- assert_raise Technoweenie::AttachmentFu::ThumbnailError do
59
+ assert_raise AttachmentFu::ThumbnailError do
60
60
  attachment.create_or_update_thumbnail(attachment.create_temp_file, 'thumb', 'x50')
61
61
  end
62
62
  end