public
Description: Treat an ActiveRecord model as a file attachment, storing its patch, size, content type, etc.
Homepage: http://weblog.techno-weenie.net
Clone URL: git://github.com/technoweenie/attachment_fu.git
Click here to lend your support to: attachment_fu and make a donation at www.pledgie.com !
comments comments comments

git-svn-id: 
http://svn.techno-weenie.net/projects/plugins/attachment_fu@2576 
567b1171-46fb-0310-a4c9-b4bef9110e78
technoweenie (author)
Sat Dec 23 09:58:15 -0800 2006
commit  d89bfe439f34f3e12fc2b6840d69e1f95be2645f
tree    45e9491434766b04b7d97377f6b3bec8cddf2b86
parent  5dc782d1f24b21b3989a18f97a3a16c2d6ef6c46
...
1
2
3
4
 
5
6
7
...
1
2
3
 
4
5
6
7
0
@@ -1,7 +1,7 @@
0
 require 'tempfile'
0
 
0
 Tempfile.class_eval do
0
- # overwrite so tempfiles keep the basename extension
0
+ # overwrite so tempfiles use the extension of the basename. important for rmagick and image science
0
   def make_tmpname(basename, n)
0
     ext = nil
0
     sprintf("%s%d-%d%s", basename.gsub(/\.\w+$/) { |s| ext = s; '' }, $$, n, ext)
...
142
143
144
 
145
146
147
...
149
150
151
 
152
153
154
...
163
164
165
 
166
167
168
169
 
170
171
172
...
181
182
183
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
185
186
...
202
203
204
205
206
207
208
209
 
 
 
 
210
211
212
213
214
 
215
216
217
218
 
 
 
219
220
221
222
223
 
224
225
226
227
 
228
229
230
231
 
232
233
234
235
 
236
237
238
239
 
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
 
258
259
260
...
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
 
283
284
285
...
297
298
299
 
300
301
302
...
308
309
310
311
 
 
312
313
314
...
320
321
322
 
323
324
325
...
333
334
335
 
336
337
338
...
142
143
144
145
146
147
148
...
150
151
152
153
154
155
156
...
165
166
167
168
169
170
171
172
173
174
175
176
...
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
...
241
242
243
 
 
 
 
 
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
290
291
292
...
295
296
297
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
299
300
301
302
303
...
315
316
317
318
319
320
321
...
327
328
329
 
330
331
332
333
334
...
340
341
342
343
344
345
346
...
354
355
356
357
358
359
360
0
@@ -142,6 +142,7 @@ module Technoweenie # :nodoc:
0
         attachment_options[:thumbnail_class]
0
       end
0
 
0
+ # Copies the given file path to a new tempfile, returning the closed tempfile.
0
       def copy_to_temp_file(file, temp_base_name)
0
         returning Tempfile.new(temp_base_name, Technoweenie::AttachmentFu.tempfile_path) do |tmp|
0
           tmp.close
0
@@ -149,6 +150,7 @@ module Technoweenie # :nodoc:
0
         end
0
       end
0
       
0
+ # Writes the given data to a new tempfile, returning the closed tempfile.
0
       def write_to_temp_file(data, temp_base_name)
0
         returning Tempfile.new(temp_base_name, Technoweenie::AttachmentFu.tempfile_path) do |tmp|
0
           tmp.write data
0
@@ -163,10 +165,12 @@ module Technoweenie # :nodoc:
0
         self.class.image?(content_type)
0
       end
0
       
0
+ # Returns true/false if an attachment is thumbnailable. A thumbnailable attachment has an image content type and the parent_id attribute.
0
       def thumbnailable?
0
         image? && respond_to?(:parent_id)
0
       end
0
 
0
+ # Returns the class used to create new thumbnails for this attachment.
0
       def thumbnail_class
0
         self.class.thumbnail_class
0
       end
0
@@ -181,6 +185,41 @@ module Technoweenie # :nodoc:
0
         "#{basename}_#{thumbnail}#{ext}"
0
       end
0
 
0
+ # Creates or updates the thumbnail for the current attachment.
0
+ def create_or_update_thumbnail(temp_file, file_name_suffix, *size)
0
+ thumbnailable? || raise(ThumbnailError.new("Can't create a thumbnail if the content type is not an image or there is no parent_id column"))
0
+ returning find_or_initialize_thumbnail(file_name_suffix) do |thumb|
0
+ thumb.attributes = {
0
+ :content_type => content_type,
0
+ :filename => thumbnail_name_for(file_name_suffix),
0
+ :temp_path => temp_file,
0
+ :thumbnail_resize_options => size
0
+ }
0
+ callback_with_args :before_thumbnail_saved, thumb
0
+ thumb.save!
0
+ end
0
+ end
0
+
0
+ # Sets the content type.
0
+ def content_type=(new_type)
0
+ write_attribute :content_type, new_type.to_s.strip
0
+ end
0
+
0
+ # Sanitizes a filename.
0
+ def filename=(new_name)
0
+ write_attribute :filename, sanitize_filename(new_name)
0
+ end
0
+
0
+ # Returns the width/height in a suitable format for the image_tag helper: (100x100)
0
+ def image_size
0
+ [width.to_s, height.to_s] * 'x'
0
+ end
0
+
0
+ # Returns true if the attachment data will be written to the storage system on the next save
0
+ def save_attachment?
0
+ File.file?(temp_path.to_s)
0
+ end
0
+
0
       # nil placeholder in case this field is used in a form.
0
       def uploaded_data() nil; end
0
 
0
@@ -202,59 +241,52 @@ module Technoweenie # :nodoc:
0
         self.temp_path = file_data.path
0
       end
0
 
0
- # returns true if the attachment data will be written to the storage system on the next save
0
- def save_attachment?
0
- File.file?(temp_path.to_s)
0
- end
0
-
0
+ # Gets the latest temp path from the collection of temp paths. While working with an attachment,
0
+ # multiple Tempfile objects may be created for various processing purposes (resizing, for example).
0
+ # An array of all the tempfile objects is stored so that the Tempfile instance is held on to until
0
+ # it's not needed anymore. The collection is cleared after saving the attachment.
0
       def temp_path
0
         p = temp_paths.first
0
         p.respond_to?(:path) ? p.path : p.to_s
0
       end
0
       
0
+ # Gets an array of the currently used temp paths.
0
       def temp_paths
0
         @temp_paths ||= []
0
       end
0
       
0
+ # Adds a new temp_path to the array. This should take a string or a Tempfile. This class makes no
0
+ # attempt to remove the files, so Tempfiles should be used. Tempfiles remove themselves when they go out of scope.
0
+ # You can also use string paths for temporary files, such as those used for uploaded files in a web server.
0
       def temp_path=(value)
0
         temp_paths.unshift value
0
         temp_path
0
       end
0
 
0
+ # Gets the data from the latest temp file. This will read the file into memory.
0
       def temp_data
0
         save_attachment? ? File.read(temp_path) : nil
0
       end
0
       
0
+ # Writes the given data to a Tempfile and adds it to the collection of temp files.
0
       def temp_data=(data)
0
         self.temp_path = write_to_temp_file data unless data.nil?
0
       end
0
       
0
+ # Copies the given file to a randomly named Tempfile.
0
       def copy_to_temp_file(file)
0
         self.class.copy_to_temp_file file, random_tempfile_filename
0
       end
0
       
0
+ # Writes the given file to a randomly named Tempfile.
0
       def write_to_temp_file(data)
0
         self.class.write_to_temp_file data, random_tempfile_filename
0
       end
0
       
0
+ # Stub for creating a temp file from the attachment data. This should be defined in the backend module.
0
       def create_temp_file() end
0
 
0
- # Sets the content type.
0
- def content_type=(new_type)
0
- write_attribute :content_type, new_type.to_s.strip
0
- end
0
-
0
- # sanitizes a filename.
0
- def filename=(new_name)
0
- write_attribute :filename, sanitize_filename(new_name)
0
- end
0
-
0
- # Returns the width/height in a suitable format for the image_tag helper: (100x100)
0
- def image_size
0
- [width.to_s, height.to_s] * 'x'
0
- end
0
-
0
- # Allows you to work with an RMagick representation of the attachment in a block.
0
+ # Allows you to work with a processed representation (RMagick, ImageScience, etc) of the attachment in a block.
0
       #
0
       # @attachment.with_image do |img|
0
       # self.data = img.thumbnail(100, 100).to_blob
0
@@ -263,23 +295,9 @@ module Technoweenie # :nodoc:
0
       def with_image(&block)
0
         self.class.with_image(temp_path, &block)
0
       end
0
-
0
- # Creates or updates the thumbnail for the current attachment.
0
- def create_or_update_thumbnail(temp_file, file_name_suffix, *size)
0
- thumbnailable? || raise(ThumbnailError.new("Can't create a thumbnail if the content type is not an image or there is no parent_id column"))
0
- returning find_or_initialize_thumbnail(file_name_suffix) do |thumb|
0
- thumb.attributes = {
0
- :content_type => content_type,
0
- :filename => thumbnail_name_for(file_name_suffix),
0
- :temp_path => temp_file,
0
- :thumbnail_resize_options => size
0
- }
0
- callback_with_args :before_thumbnail_saved, thumb
0
- thumb.save!
0
- end
0
- end
0
 
0
       protected
0
+ # Generates a unique filename for a Tempfile.
0
         def random_tempfile_filename
0
           "#{rand Time.now.to_i}#{filename || 'attachment'}"
0
         end
0
@@ -297,6 +315,7 @@ module Technoweenie # :nodoc:
0
           end
0
         end
0
 
0
+ # before_validation callback.
0
         def set_size_from_temp_path
0
           self.size = File.size(temp_path) if save_attachment?
0
         end
0
@@ -308,7 +327,8 @@ module Technoweenie # :nodoc:
0
             errors.add attr_name, ActiveRecord::Errors.default_error_messages[:inclusion] unless enum.nil? || enum.include?(send(attr_name))
0
           end
0
         end
0
-
0
+
0
+ # Initializes a new thumbnail with the given suffix.
0
         def find_or_initialize_thumbnail(file_name_suffix)
0
           respond_to?(:parent_id) ?
0
             thumbnail_class.find_or_initialize_by_thumbnail_and_parent_id(file_name_suffix.to_s, id) :
0
@@ -320,6 +340,7 @@ module Technoweenie # :nodoc:
0
           @saved_attachment = save_attachment?
0
         end
0
 
0
+ # Cleans up after processing. Thumbnails are created, the attachment is stored to the backend, and the temp_paths are cleared.
0
         def after_process_attachment
0
           if @saved_attachment
0
             if thumbnailable? && !attachment_options[:thumbnails].blank? && parent_id.nil?
0
@@ -333,6 +354,7 @@ module Technoweenie # :nodoc:
0
           end
0
         end
0
 
0
+ # Resizes the given processed img object with either the attachment resize options or the thumbnail resize options.
0
         def resize_image_or_thumbnail!(img)
0
           if (!respond_to?(:parent_id) || parent_id.nil?) && attachment_options[:resize_to] # parent image
0
             resize_image(img, attachment_options[:resize_to])
...
8
9
10
 
11
12
 
13
14
15
16
17
18
19
20
21
22
23
24
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
27
28
29
30
31
32
33
34
35
...
8
9
10
11
12
 
13
14
 
 
 
 
 
 
 
 
 
 
 
 
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 
 
 
 
 
 
36
37
38
0
@@ -8,28 +8,31 @@ module Technoweenie # :nodoc:
0
           base.belongs_to :db_file, :class_name => '::DbFile', :foreign_key => 'db_file_id'
0
         end
0
 
0
+ # Creates a temp file with the current db data.
0
         def create_temp_file
0
- write_to_temp_file db_file.data
0
+ write_to_temp_file current_data
0
         end
0
-
0
- # Destroys the file. Called in the after_destroy callback
0
- def destroy_file
0
- db_file.destroy if db_file
0
- end
0
-
0
- # Saves the data to the DbFile model
0
- def save_to_storage
0
- if save_attachment?
0
- (db_file || build_db_file).data = temp_data
0
- db_file.save!
0
- self.class.update_all ['db_file_id = ?', self.db_file_id = db_file.id], ['id = ?', id]
0
+
0
+ protected
0
+ # Destroys the file. Called in the after_destroy callback
0
+ def destroy_file
0
+ db_file.destroy if db_file
0
+ end
0
+
0
+ # Saves the data to the DbFile model
0
+ def save_to_storage
0
+ if save_attachment?
0
+ (db_file || build_db_file).data = temp_data
0
+ db_file.save!
0
+ self.class.update_all ['db_file_id = ?', self.db_file_id = db_file.id], ['id = ?', id]
0
+ end
0
+ true
0
+ end
0
+
0
+ # Gets the current data from the database
0
+ def current_data
0
+ db_file.data
0
           end
0
- true
0
- end
0
-
0
- def current_data
0
- db_file.data
0
- end
0
       end
0
     end
0
   end
...
40
41
42
43
 
 
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
 
 
 
 
 
59
60
61
62
63
64
65
66
67
68
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
71
72
73
74
75
76
77
78
79
80
...
40
41
42
 
43
44
45
46
47
 
 
 
 
 
 
 
 
 
 
 
 
48
49
50
51
52
53
 
 
 
 
 
 
 
 
 
 
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
 
 
 
 
 
 
 
81
82
83
0
@@ -40,41 +40,44 @@ module Technoweenie # :nodoc:
0
           @old_filename = full_filename unless filename.nil? || @old_filename
0
           write_attribute :filename, sanitize_filename(value)
0
         end
0
-
0
+
0
+ # Creates a temp file from the currently saved file.
0
         def create_temp_file
0
           copy_to_temp_file full_filename
0
         end
0
-
0
- # Destroys the file. Called in the after_destroy callback
0
- def destroy_file
0
- FileUtils.rm full_filename rescue nil
0
- end
0
-
0
- def rename_file
0
- return unless @old_filename && @old_filename != full_filename
0
- if save_attachment? && File.exists?(@old_filename)
0
- FileUtils.rm @old_filename
0
- elsif File.exists?(@old_filename)
0
- FileUtils.mv @old_filename, full_filename
0
+
0
+ protected
0
+ # Destroys the file. Called in the after_destroy callback
0
+ def destroy_file
0
+ FileUtils.rm full_filename rescue nil
0
           end
0
- @old_filename = nil
0
- true
0
- end
0
-
0
- # Saves the file to the file system
0
- def save_to_storage
0
- if save_attachment?
0
- # TODO: This overwrites the file if it exists, maybe have an allow_overwrite option?
0
- FileUtils.mkdir_p(File.dirname(full_filename))
0
- FileUtils.mv temp_path, full_filename
0
+
0
+ # Renames the given file before saving
0
+ def rename_file
0
+ return unless @old_filename && @old_filename != full_filename
0
+ if save_attachment? && File.exists?(@old_filename)
0
+ FileUtils.rm @old_filename
0
+ elsif File.exists?(@old_filename)
0
+ FileUtils.mv @old_filename, full_filename
0
+ end
0
+ @old_filename = nil
0
+ true
0
+ end
0
+
0
+ # Saves the file to the file system
0
+ def save_to_storage
0
+ if save_attachment?
0
+ # TODO: This overwrites the file if it exists, maybe have an allow_overwrite option?
0
+ FileUtils.mkdir_p(File.dirname(full_filename))
0
+ FileUtils.mv temp_path, full_filename
0
+ end
0
+ @old_filename = nil
0
+ true
0
+ end
0
+
0
+ def current_data
0
+ File.file?(full_filename) ? File.read(full_filename) : nil
0
           end
0
- @old_filename = nil
0
- true
0
- end
0
-
0
- def current_data
0
- File.file?(full_filename) ? File.read(full_filename) : nil
0
- end
0
       end
0
     end
0
   end
...
53
54
55
56
 
57
58
59
60
61
62
 
 
63
64
65
...
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
136
137
...
53
54
55
 
56
57
58
59
60
 
 
61
62
63
64
65
...
109
110
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
0
@@ -53,13 +53,13 @@ module Technoweenie # :nodoc:
0
       # To specify S3 as the storage mechanism for a model, set the acts_as_attachment <tt>:storage</tt> option to <tt>:s3</tt>.
0
       #
0
       # class Photo < ActiveRecord::Base
0
- # acts_as_attachment :storage => :s3
0
+ # has_attachment :storage => :s3
0
       # end
0
       #
0
       # Of course, all the usual configuration options apply:
0
       #
0
- # acts_as_attachment :storage => :s3, :content_type => ['application/pdf', :image], :resize_to => 'x50'
0
- # acts_as_attachment :storage => :s3, :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
0
+ # has_attachment :storage => :s3, :content_type => ['application/pdf', :image], :resize_to => 'x50'
0
+ # has_attachment :storage => :s3, :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
0
       module S3
0
         class S3RequiredLibraryNotFound < StandardError; end
0
         class S3ConfigFileNotFound < StandardError; end
0
@@ -109,29 +109,30 @@ module Technoweenie # :nodoc:
0
         def create_temp_file
0
           write_to_temp_file current_data
0
         end
0
-
0
- # Destroys the file. Called in the after_destroy callback
0
- def destroy_file
0
- AWS::S3::S3Object.delete filename, bucket
0
- end
0
-
0
- def rename_file
0
- return unless @old_filename && @old_filename != filename
0
- AWS::S3::S3Object.rename(@old_filename, filename, bucket, :access => :public_read)
0
- @old_filename = nil
0
- true
0
- end
0
-
0
- # Saves the file to the file system
0
- def save_to_storage
0
- AWS::S3::S3Object.store(filename, attachment_data, bucket, :content_type => content_type, :access => attachment_options[:s3_access]) if save_attachment?
0
- @old_filename = nil
0
- true
0
- end
0
-
0
- def current_data
0
- AWS::S3::S3Object.value filename, bucket
0
- end
0
+
0
+ protected
0
+ # Destroys the file. Called in the after_destroy callback
0
+ def destroy_file
0
+ AWS::S3::S3Object.delete filename, bucket
0
+ end
0
+
0
+ def rename_file
0
+ return unless @old_filename && @old_filename != filename
0
+ AWS::S3::S3Object.rename(@old_filename, filename, bucket, :access => :public_read)
0
+ @old_filename = nil
0
+ true
0
+ end
0
+
0
+ # Saves the file to S3
0
+ def save_to_storage
0
+ AWS::S3::S3Object.store(filename, attachment_data, bucket, :content_type => content_type, :access => attachment_options[:s3_access]) if save_attachment?
0
+ @old_filename = nil
0
+ true
0
+ end
0
+
0
+ def current_data
0
+ AWS::S3::S3Object.value filename, bucket
0
+ end
0
       end
0
     end
0
   end
...
22
23
24
25
 
26
27
28
...
22
23
24
 
25
26
27
28
0
@@ -22,7 +22,7 @@ module BaseAttachmentTests
0
       assert attachment.save_attachment?
0
       attachment.save!
0
       
0
- assert_equal 'wtf', attachment_model.find(attachment.id).current_data
0
+ assert_equal 'wtf', attachment_model.find(attachment.id).send(:current_data)
0
     end
0
   end
0
   

Comments

    No one has commented yet.