public
Description: Paperclip File Management Plugin
Homepage: http://www.thoughtbot.com/projects/paperclip
Clone URL: git://github.com/thoughtbot/paperclip.git
Did some APIish stuff.

git-svn-id: https://svn.thoughtbot.com/plugins/paperclip/trunk@230 
7bbfaf0e-4d1d-0410-9690-a8bb5f8ef2aa
jyurek (author)
Fri Oct 26 09:16:58 -0700 2007
commit  d46eea5099d111906df107945ac6d8ed897dae98
tree    7d4f7dbe75e0dced1a5c3a07505e0d95a342a4cb
parent  8911e40efb804c39675e13f29b43e1dc396a8e4e
...
3
4
5
6
 
7
8
9
...
3
4
5
 
6
7
8
9
0
@@ -3,7 +3,7 @@ require 'rake/testtask'
0
 require 'rake/rdoctask'
0
 
0
 desc 'Default: run unit tests.'
0
-task :default => :test
0
+task :default => [:clean, :test]
0
 
0
 desc 'Test the paperclip plugin.'
0
 Rake::TestTask.new(:test) do |t|
...
3
4
5
6
 
7
8
9
...
11
12
13
14
 
15
16
17
...
3
4
5
 
6
7
8
9
...
11
12
13
 
14
15
16
17
0
@@ -3,7 +3,7 @@ class <%= migration_name %> < ActiveRecord::Migration
0
 <% attachments.each do |attachment| -%>
0
     add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_name, :string
0
     add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_content_type, :string
0
- add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_size, :integer
0
+ add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_size, :integer
0
 <% end -%>
0
   end
0
 
0
@@ -11,7 +11,7 @@ class <%= migration_name %> < ActiveRecord::Migration
0
 <% attachments.each do |attachment| -%>
0
     remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_name
0
     remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_content_type
0
- remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_size
0
+ remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_size
0
 <% end -%>
0
   end
0
 end
...
133
134
135
136
 
 
137
138
139
140
 
141
142
143
...
154
155
156
 
157
 
 
158
159
160
...
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
...
221
222
223
 
 
 
 
 
 
 
 
 
 
 
 
224
225
226
...
230
231
232
 
 
 
 
 
 
233
 
 
234
235
 
 
 
236
237
238
239
240
 
241
242
243
 
244
245
246
...
265
266
267
268
269
270
271
272
273
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
275
276
...
133
134
135
 
136
137
138
139
 
 
140
141
142
143
...
154
155
156
157
158
159
160
161
162
163
...
194
195
196
 
 
 
 
 
 
 
 
 
 
 
 
197
198
199
...
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
...
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
...
278
279
280
 
 
 
 
 
 
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
0
@@ -133,11 +133,11 @@ module Thoughtbot #:nodoc:
0
       #
0
       # == Model Requirements
0
       # For any given attachment _foo_, the model the attachment is in needs to have both a +foo_file_name+
0
- # and +foo_content_type+ column, as a type of +string+. The +foo_file_name+ column contains only the name
0
+ # and +foo_content_type+ column, as a type of +string+, and a +foo_file_size+ column as type +integer+.
0
+ # The +foo_file_name+ column contains only the name
0
       # of the file and none of the path information. However, the +foo_file_name+ column accessor is overwritten
0
       # by the one (defined above) which returns the full path to whichever style thumbnail is passed in.
0
- # In a pinch, you can either use +read_attribute+ or the plain +foo+ accessor, which returns the database's
0
- # +foo_file_name+ column.
0
+ # To access the name as stored in the database, you can use Attachment#original_filename.
0
       #
0
       # == Event Triggers
0
       # When an attachment is set by using he setter (+model.attachment=+), the thumbnails are created and held in
0
@@ -154,7 +154,10 @@ module Thoughtbot #:nodoc:
0
         class << self
0
           attr_accessor :attachment_definitions
0
         end
0
+
0
         include InstanceMethods
0
+ after_save :save_attachments
0
+ before_destroy :destroy_attachments
0
         
0
         validates_each(*attachment_names) do |record, attr, value|
0
           value.errors.each{|e| record.errors.add(attr, e) unless record.errors.on(attr) && record.errors.on(attr).include?(e) }
0
@@ -191,18 +194,6 @@ module Thoughtbot #:nodoc:
0
           define_method "destroy_#{name}" do |*args|
0
             attachment_for(name).queue_destroy(args.first)
0
           end
0
-
0
- define_method "#{name}_after_save" do
0
- attachment_for(name).save
0
- end
0
- private :"#{name}_after_save"
0
- after_save :"#{name}_after_save"
0
-
0
- define_method "#{name}_before_destroy" do
0
- attachment_for(name).destroy
0
- end
0
- private :"#{name}_before_destroy"
0
- before_destroy :"#{name}_before_destroy"
0
         end
0
       end
0
       
0
@@ -221,6 +212,18 @@ module Thoughtbot #:nodoc:
0
         end
0
         alias_method_chain :after_initialize, :paperclip
0
         
0
+ def save_attachments
0
+ @attachments.each do |name, attachment|
0
+ attachment.save
0
+ end
0
+ end
0
+
0
+ def destroy_attachments
0
+ @attachments.each do |name, attachment|
0
+ attachment.destroy
0
+ end
0
+ end
0
+
0
         def attachment_for name
0
           @attachments[name]
0
         end
0
@@ -230,17 +233,27 @@ module Thoughtbot #:nodoc:
0
         @attachment_definitions.keys
0
       end
0
 
0
+ # Paperclip always validates whether or not file creation was successful, but does not validate
0
+ # the presence or size of the file unless told. You can specify validations either in the
0
+ # has_attached_file call or with a separate validates_attached_file call, with a syntax similar
0
+ # to has_attached_file. If no options are given, the existence of the file is validated.
0
+ #
0
+ # validates_attached_file :avatar, :existence => true, :size => 0..(500.kilobytes)
0
       def validates_attached_file *attachment_names
0
+ options = attachment_names.pop if attachment_names.last.is_a? Hash
0
+ options ||= { :existence => true }
0
         attachment_names.each do |name|
0
- @attachment_definitions[name].validate :existence
0
+ options.each do |key, value|
0
+ @attachment_definitions[name].validate key, value
0
+ end
0
         end
0
       end
0
       
0
       def whine_about_columns_for name #:nodoc:
0
- [ "#{name}_file_name", "#{name}_content_type", "#{name}_size" ].each do |column|
0
+ [ "#{name}_file_name", "#{name}_content_type", "#{name}_file_size" ].each do |column|
0
           unless column_names.include?(column)
0
             raise PaperclipError, "Class #{self.name} does not have all of the necessary columns to have an attachment named #{name}. " +
0
- "(#{name}_file_name, #{name}_content_type, and #{name}_size)"
0
+ "(#{name}_file_name, #{name}_content_type, and #{name}_file_size)"
0
           end
0
         end
0
       end
0
@@ -265,12 +278,20 @@ module Thoughtbot #:nodoc:
0
     # * delete_attachment: Delete the files and thumbnails from the storage medium. Should return true or false
0
     # depending on success.
0
     #
0
- # When writing files, the @files variable will hold a hash of style names and their data. If @files is nil,
0
- # then no new data has been assigned to the attachment and you should not perform any work.
0
- #
0
- # You will also have access to @definition, which is the AttachmentDefintion object for the attachment. The
0
- # methods in your module will be mixed into an Attachment instance, so you have full access to the
0
- # Attachment itself.
0
+ # When writing a storage system, your code will be mixed into the Attachment that the file represents. You
0
+ # will therefore have access to all of the methods available to Attachments. Some methods of note are:
0
+ # * definition: Returns the AttachmentDefintion object created by has_attached_file. Useful for getting
0
+ # style definitions and flags that you want to set. You should open the AttachmentDefinition class to
0
+ # add getters for any options you want to be able to set.
0
+ # * instance: Returns the ActiveRecord object that the Attachment is attached to. Can be used to obtain ids.
0
+ # * original_filename: Returns the original_filename, which is the same as the attachment_file_name column
0
+ # in the database. Should be nil if there is no attachment.
0
+ # * original_file_size: Returns the size of the original file, as passed to Attachment#assign.
0
+ # * interpolate: Given a style and a pattern, this will interpolate variables like :rails_root, :name,
0
+ # and :id. See documentation for has_attached_file for more info on interpolation.
0
+ # * for_attached_files: Iterates over the collection of files for this attachment, passing the style name
0
+ # and the data to the block. Will not call the block if the data is nil.
0
+ # * dirty?: Returns true if a new file has been assigned with Attachment#assign, false otherwise.
0
     #
0
     # == Validations
0
     # Storage systems provide their own validations, since the manner of checking the status of them is usually
...
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
...
41
42
43
44
 
45
46
47
...
51
52
53
 
 
 
 
 
54
55
56
57
58
59
60
 
 
61
62
63
...
69
70
71
72
 
73
74
75
...
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
...
38
39
40
 
41
42
43
44
...
48
49
50
51
52
53
54
55
56
 
 
 
 
 
 
57
58
59
60
61
...
67
68
69
 
70
71
72
73
0
@@ -8,31 +8,28 @@ module Thoughtbot
0
       module Filesystem
0
 
0
         def file_name style = nil
0
- style ||= @definition.default_style
0
+ style ||= definition.default_style
0
           pattern = if original_filename && instance.id
0
- File.join(@definition.path_prefix, @definition.path)
0
+ File.join(definition.path_prefix, definition.path)
0
           else
0
- @definition.missing_file_name
0
+ definition.missing_file_name
0
           end
0
           interpolate( style, pattern )
0
         end
0
 
0
         def url style = nil
0
- style ||= @definition.default_style
0
+ style ||= definition.default_style
0
           pattern = if original_filename && instance.id
0
- [@definition.url_prefix, @definition.url || @definition.path].compact.join("/")
0
+ [definition.url_prefix, definition.url || definition.path].compact.join("/")
0
           else
0
- @definition.missing_url
0
+ definition.missing_url
0
           end
0
           interpolate( style, pattern )
0
         end
0
 
0
         def write_attachment
0
- return if @files.blank?
0
           ensure_directories
0
- @files.each do |style, data|
0
- data.rewind
0
- data = data.read
0
+ for_attached_files do |style, data|
0
             File.open( file_name(style), "w" ) do |file|
0
               file.rewind
0
               file.write(data)
0
@@ -41,7 +38,7 @@ module Thoughtbot
0
         end
0
 
0
         def delete_attachment complain = false
0
- @definition.styles.keys.each do |style|
0
+ definition.styles.keys.each do |style|
0
             file_path = file_name(style)
0
             begin
0
               FileUtils.rm file_path if file_path
0
@@ -51,13 +48,14 @@ module Thoughtbot
0
           end
0
         end
0
         
0
+ def file_exists?(style)
0
+ style ||= definition.default_style
0
+ dirty? ? file_for(style) : File.exists?( file_name(style) )
0
+ end
0
+
0
         def validate_existence *constraints
0
- @definition.styles.keys.each do |style|
0
- if @dirty
0
- errors << "requires a valid #{style} file." unless @files && @files[style]
0
- else
0
- errors << "requires a valid #{style} file." unless File.exists?( file_name(style) )
0
- end
0
+ definition.styles.keys.each do |style|
0
+ errors << "requires a valid #{style} file." unless file_exists?(style)
0
           end
0
         end
0
         
0
@@ -69,7 +67,7 @@ module Thoughtbot
0
         private
0
 
0
         def ensure_directories
0
- @files.each do |style, file|
0
+ for_attached_files do |style, file|
0
             dirname = File.dirname( file_name(style) )
0
             FileUtils.mkdir_p dirname
0
           end
...
1
2
3
4
 
5
6
7
...
16
17
18
19
 
20
21
22
...
79
80
81
82
 
83
84
 
85
86
 
87
88
89
...
93
94
95
96
97
98
99
100
 
 
101
102
103
104
105
 
106
107
108
...
111
112
113
 
 
 
 
 
114
115
116
117
118
119
120
 
 
121
122
123
...
129
130
131
132
 
133
134
135
...
1
2
3
 
4
5
6
7
...
16
17
18
 
19
20
21
22
...
79
80
81
 
82
83
 
84
85
 
86
87
88
89
...
93
94
95
 
96
 
 
 
97
98
99
100
101
102
 
103
104
105
106
...
109
110
111
112
113
114
115
116
117
 
 
 
 
 
 
118
119
120
121
122
...
128
129
130
 
131
132
133
134
0
@@ -1,7 +1,7 @@
0
 module Thoughtbot
0
   module Paperclip
0
     
0
- module ClassMethods #:nodoc:
0
+ module ClassMethods
0
       def has_attached_file_with_s3 *attachment_names
0
         has_attached_file_without_s3 *attachment_names
0
 
0
@@ -16,7 +16,7 @@ module Thoughtbot
0
           secret_key = Thoughtbot::Paperclip.options[:s3_secret_access_key]
0
         end
0
 
0
- if @definition.storage_module == Thoughtbot::Paperclip::Storage::S3
0
+ if definition.storage_module == Thoughtbot::Paperclip::Storage::S3
0
           require 'aws/s3'
0
           AWS::S3::Base.establish_connection!(
0
             :access_key_id => access_key,
0
@@ -79,11 +79,11 @@ module Thoughtbot
0
       # * s3_persistent: Maintains an HTTP connection to the Amazon service if possible.
0
       module S3
0
         def file_name style = nil
0
- style ||= @definition.default_style
0
+ style ||= definition.default_style
0
           pattern = if original_filename && instance.id
0
- @definition.url
0
+ definition.url
0
           else
0
- @definition.missing_url
0
+ definition.missing_url
0
           end
0
           interpolate( style, pattern )
0
         end
0
@@ -93,16 +93,14 @@ module Thoughtbot
0
         end
0
 
0
         def write_attachment
0
- return if @files.blank?
0
           bucket = ensure_bucket
0
- @files.each do |style, data|
0
- data.rewind
0
- AWS::S3::S3Object.store( file_name(style), data, bucket, :access => @definition.s3_access || :public_read )
0
+ for_attached_files do |style, data|
0
+ AWS::S3::S3Object.store( file_name(style), data, bucket, :access => definition.s3_access || :public_read )
0
           end
0
         end
0
 
0
         def delete_attachment complain = false
0
- (attachment[:thumbnails].keys + [:original]).each do |style|
0
+ for_attached_files do |style, data|
0
             begin
0
               AWS::S3::S3Object.delete( file_name(style), bucket )
0
             rescue AWS::S3::ResponseError => error
0
@@ -111,13 +109,14 @@ module Thoughtbot
0
           end
0
         end
0
         
0
+ def file_exists?(style)
0
+ style ||= definition.default_style
0
+ dirty? ? file_for(style) : AWS::S3::S3Object.exists?( file_name(style), bucket )
0
+ end
0
+
0
         def validate_existence *constraints
0
- @definition.styles.keys.each do |style|
0
- if @dirty
0
- errors << "requires a valid #{style} file." unless @files && @files[style]
0
- else
0
- errors << "requires a valid #{style} file." unless AWS::S3::S3Object.exists?( file_name(style), bucket )
0
- end
0
+ definition.styles.keys.each do |style|
0
+ errors << "requires a valid #{style} file." unless file_exists?(style)
0
           end
0
         end
0
         
0
@@ -129,7 +128,7 @@ module Thoughtbot
0
         private
0
         
0
         def bucket
0
- interpolate(nil, @definition.url_prefix)
0
+ interpolate(nil, definition.url_prefix)
0
         end
0
 
0
         def ensure_bucket
...
2
3
4
5
 
6
7
8
9
10
 
11
12
13
14
15
 
16
17
18
 
19
20
21
22
23
 
24
25
26
 
27
28
29
...
2
3
4
 
5
6
7
8
9
 
10
11
12
13
14
 
15
16
17
 
18
19
20
21
22
 
23
24
25
 
26
27
28
29
0
@@ -2,28 +2,28 @@ begin
0
   ActiveRecord::Base.connection.create_table :foos, :force => true do |table|
0
     table.column :image_file_name, :string
0
     table.column :image_content_type, :string
0
- table.column :image_size, :integer
0
+ table.column :image_file_size, :integer
0
   end
0
   ActiveRecord::Base.connection.create_table :bars, :force => true do |table|
0
     table.column :document_file_name, :string
0
     table.column :document_content_type, :string
0
- table.column :document_size, :integer
0
+ table.column :document_file_size, :integer
0
   end
0
   ActiveRecord::Base.connection.create_table :non_standards, :force => true do |table|
0
     table.column :resume_file_name, :string
0
     table.column :resume_content_type, :string
0
- table.column :resume_size, :integer
0
+ table.column :resume_file_size, :integer
0
     table.column :avatar_file_name, :string
0
     table.column :avatar_content_type, :string
0
- table.column :avatar_size, :integer
0
+ table.column :avatar_file_size, :integer
0
   end
0
   ActiveRecord::Base.connection.create_table :ess_threes, :force => true do |table|
0
     table.column :resume_file_name, :string
0
     table.column :resume_content_type, :string
0
- table.column :resume_size, :integer
0
+ table.column :resume_file_size, :integer
0
     table.column :avatar_file_name, :string
0
     table.column :avatar_content_type, :string
0
- table.column :avatar_size, :integer
0
+ table.column :avatar_file_size, :integer
0
   end
0
   ActiveRecord::Base.connection.create_table :negatives, :force => true do |table|
0
     table.column :this_is_the_wrong_name_file_name, :string
...
53
54
55
56
57
58
 
 
 
59
60
61
...
53
54
55
 
 
 
56
57
58
59
60
61
0
@@ -53,9 +53,9 @@ class PaperclipNonStandardTest < Test::Unit::TestCase
0
   def test_should_delete_files_on_destroy
0
     assert @ns.save
0
     assert File.exists?( @ns.resume_file_name ), @ns.resume_file_name
0
- assert File.exists?( @ns.avatar_file_name(:original) ), @ns.avatar_file_name(:original)
0
- assert File.exists?( @ns.avatar_file_name(:bigger) )
0
- assert File.exists?( @ns.avatar_file_name(:cropped) )
0
+ [:original, :bigger, :cropped].each do |style|
0
+ assert File.exists?( @ns.avatar_file_name(style) ), @ns.avatar_file_name(style)
0
+ end
0
 
0
     resume_file_name = @ns.resume_file_name
0
     avatar_file_names = [:original, :bigger, :cropped].map{|style| @ns.avatar_file_name(style) }

Comments

    No one has commented yet.