cloudfuji_paperclip 2.4.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. data/.gitignore +22 -0
  2. data/.travis.yml +13 -0
  3. data/Appraisals +14 -0
  4. data/CONTRIBUTING.md +38 -0
  5. data/Gemfile +5 -0
  6. data/Gemfile.lock +137 -0
  7. data/LICENSE +26 -0
  8. data/README.md +444 -0
  9. data/Rakefile +41 -0
  10. data/cucumber/paperclip_steps.rb +6 -0
  11. data/features/basic_integration.feature +46 -0
  12. data/features/rake_tasks.feature +63 -0
  13. data/features/step_definitions/attachment_steps.rb +65 -0
  14. data/features/step_definitions/html_steps.rb +15 -0
  15. data/features/step_definitions/rails_steps.rb +182 -0
  16. data/features/step_definitions/s3_steps.rb +14 -0
  17. data/features/step_definitions/web_steps.rb +209 -0
  18. data/features/support/env.rb +8 -0
  19. data/features/support/fakeweb.rb +3 -0
  20. data/features/support/fixtures/.boot_config.rb.swo +0 -0
  21. data/features/support/fixtures/boot_config.txt +15 -0
  22. data/features/support/fixtures/gemfile.txt +5 -0
  23. data/features/support/fixtures/preinitializer.txt +20 -0
  24. data/features/support/paths.rb +28 -0
  25. data/features/support/rails.rb +46 -0
  26. data/features/support/selectors.rb +19 -0
  27. data/gemfiles/rails2.gemfile +9 -0
  28. data/gemfiles/rails3.gemfile +9 -0
  29. data/gemfiles/rails3_1.gemfile +9 -0
  30. data/generators/paperclip/USAGE +5 -0
  31. data/generators/paperclip/paperclip_generator.rb +27 -0
  32. data/generators/paperclip/templates/paperclip_migration.rb.erb +19 -0
  33. data/init.rb +4 -0
  34. data/lib/generators/paperclip/USAGE +8 -0
  35. data/lib/generators/paperclip/paperclip_generator.rb +33 -0
  36. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +19 -0
  37. data/lib/paperclip/attachment.rb +468 -0
  38. data/lib/paperclip/callback_compatibility.rb +61 -0
  39. data/lib/paperclip/geometry.rb +120 -0
  40. data/lib/paperclip/interpolations.rb +174 -0
  41. data/lib/paperclip/iostream.rb +45 -0
  42. data/lib/paperclip/matchers/have_attached_file_matcher.rb +57 -0
  43. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +81 -0
  44. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +54 -0
  45. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +95 -0
  46. data/lib/paperclip/matchers.rb +64 -0
  47. data/lib/paperclip/missing_attachment_styles.rb +87 -0
  48. data/lib/paperclip/processor.rb +58 -0
  49. data/lib/paperclip/railtie.rb +31 -0
  50. data/lib/paperclip/schema.rb +39 -0
  51. data/lib/paperclip/storage/filesystem.rb +81 -0
  52. data/lib/paperclip/storage/fog.rb +174 -0
  53. data/lib/paperclip/storage/s3.rb +333 -0
  54. data/lib/paperclip/storage.rb +3 -0
  55. data/lib/paperclip/style.rb +103 -0
  56. data/lib/paperclip/thumbnail.rb +105 -0
  57. data/lib/paperclip/upfile.rb +62 -0
  58. data/lib/paperclip/url_generator.rb +64 -0
  59. data/lib/paperclip/version.rb +3 -0
  60. data/lib/paperclip.rb +487 -0
  61. data/lib/tasks/paperclip.rake +101 -0
  62. data/paperclip.gemspec +41 -0
  63. data/rails/init.rb +2 -0
  64. data/shoulda_macros/paperclip.rb +124 -0
  65. data/test/.gitignore +1 -0
  66. data/test/attachment_test.rb +1116 -0
  67. data/test/database.yml +4 -0
  68. data/test/fixtures/12k.png +0 -0
  69. data/test/fixtures/50x50.png +0 -0
  70. data/test/fixtures/5k.png +0 -0
  71. data/test/fixtures/animated.gif +0 -0
  72. data/test/fixtures/bad.png +1 -0
  73. data/test/fixtures/fog.yml +8 -0
  74. data/test/fixtures/question?mark.png +0 -0
  75. data/test/fixtures/s3.yml +8 -0
  76. data/test/fixtures/spaced file.png +0 -0
  77. data/test/fixtures/text.txt +1 -0
  78. data/test/fixtures/twopage.pdf +0 -0
  79. data/test/fixtures/uppercase.PNG +0 -0
  80. data/test/geometry_test.rb +206 -0
  81. data/test/helper.rb +177 -0
  82. data/test/integration_test.rb +654 -0
  83. data/test/interpolations_test.rb +216 -0
  84. data/test/iostream_test.rb +71 -0
  85. data/test/matchers/have_attached_file_matcher_test.rb +24 -0
  86. data/test/matchers/validate_attachment_content_type_matcher_test.rb +87 -0
  87. data/test/matchers/validate_attachment_presence_matcher_test.rb +26 -0
  88. data/test/matchers/validate_attachment_size_matcher_test.rb +51 -0
  89. data/test/paperclip_missing_attachment_styles_test.rb +80 -0
  90. data/test/paperclip_test.rb +390 -0
  91. data/test/processor_test.rb +10 -0
  92. data/test/schema_test.rb +98 -0
  93. data/test/storage/filesystem_test.rb +56 -0
  94. data/test/storage/fog_test.rb +219 -0
  95. data/test/storage/s3_live_test.rb +138 -0
  96. data/test/storage/s3_test.rb +943 -0
  97. data/test/style_test.rb +209 -0
  98. data/test/support/mock_attachment.rb +22 -0
  99. data/test/support/mock_interpolator.rb +24 -0
  100. data/test/support/mock_model.rb +2 -0
  101. data/test/support/mock_url_generator_builder.rb +27 -0
  102. data/test/thumbnail_test.rb +383 -0
  103. data/test/upfile_test.rb +53 -0
  104. data/test/url_generator_test.rb +187 -0
  105. metadata +404 -0
@@ -0,0 +1,33 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ class PaperclipGenerator < ActiveRecord::Generators::Base
4
+ desc "Create a migration to add paperclip-specific fields to your model. " +
5
+ "The NAME argument is the name of your model, and the following " +
6
+ "arguments are the name of the attachments"
7
+
8
+ argument :attachment_names, :required => true, :type => :array, :desc => "The names of the attachment(s) to add.",
9
+ :banner => "attachment_one attachment_two attachment_three ..."
10
+
11
+ def self.source_root
12
+ @source_root ||= File.expand_path('../templates', __FILE__)
13
+ end
14
+
15
+ def generate_migration
16
+ migration_template "paperclip_migration.rb.erb", "db/migrate/#{migration_file_name}"
17
+ end
18
+
19
+ protected
20
+
21
+ def migration_name
22
+ "add_attachment_#{attachment_names.join("_")}_to_#{name.underscore}"
23
+ end
24
+
25
+ def migration_file_name
26
+ "#{migration_name}.rb"
27
+ end
28
+
29
+ def migration_class_name
30
+ migration_name.camelize
31
+ end
32
+
33
+ end
@@ -0,0 +1,19 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ <% attachment_names.each do |attachment| -%>
4
+ add_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_file_name, :string
5
+ add_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_content_type, :string
6
+ add_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_file_size, :integer
7
+ add_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_updated_at, :datetime
8
+ <% end -%>
9
+ end
10
+
11
+ def self.down
12
+ <% attachment_names.each do |attachment| -%>
13
+ remove_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_file_name
14
+ remove_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_content_type
15
+ remove_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_file_size
16
+ remove_column :<%= name.underscore.camelize.tableize %>, :<%= attachment %>_updated_at
17
+ <% end -%>
18
+ end
19
+ end
@@ -0,0 +1,468 @@
1
+ # encoding: utf-8
2
+ require 'uri'
3
+ require 'paperclip/url_generator'
4
+
5
+ module Paperclip
6
+ # The Attachment class manages the files for a given attachment. It saves
7
+ # when the model saves, deletes when the model is destroyed, and processes
8
+ # the file upon assignment.
9
+ class Attachment
10
+ include IOStream
11
+
12
+ def self.default_options
13
+ @default_options ||= {
14
+ :url => "/system/:attachment/:id/:style/:filename",
15
+ :path => ":rails_root/public:url",
16
+ :styles => {},
17
+ :only_process => [],
18
+ :processors => [:thumbnail],
19
+ :convert_options => {},
20
+ :source_file_options => {},
21
+ :default_url => "/:attachment/:style/missing.png",
22
+ :default_style => :original,
23
+ :storage => :filesystem,
24
+ :use_timestamp => true,
25
+ :whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails],
26
+ :use_default_time_zone => true,
27
+ :hash_digest => "SHA1",
28
+ :hash_data => ":class/:attachment/:id/:style/:updated_at",
29
+ :preserve_files => false,
30
+ :interpolator => Paperclip::Interpolations,
31
+ :url_generator => Paperclip::UrlGenerator
32
+ }
33
+ end
34
+
35
+ attr_reader :name, :instance, :default_style, :convert_options, :queued_for_write, :whiny, :options, :interpolator
36
+ attr_reader :source_file_options, :whiny
37
+ attr_accessor :post_processing
38
+
39
+ # Creates an Attachment object. +name+ is the name of the attachment,
40
+ # +instance+ is the ActiveRecord object instance it's attached to, and
41
+ # +options+ is the same as the hash passed to +has_attached_file+.
42
+ #
43
+ # Options include:
44
+ #
45
+ # +url+ - a relative URL of the attachment. This is interpolated using +interpolator+
46
+ # +path+ - where on the filesystem to store the attachment. This is interpolated using +interpolator+
47
+ # +styles+ - a hash of options for processing the attachment. See +has_attached_file+ for the details
48
+ # +only_process+ - style args to be run through the post-processor. This defaults to the empty list
49
+ # +default_url+ - a URL for the missing image
50
+ # +default_style+ - the style to use when don't specify an argument to e.g. #url, #path
51
+ # +storage+ - the storage mechanism. Defaults to :filesystem
52
+ # +use_timestamp+ - whether to append an anti-caching timestamp to image URLs. Defaults to true
53
+ # +whiny+, +whiny_thumbnails+ - whether to raise when thumbnailing fails
54
+ # +use_default_time_zone+ - related to +use_timestamp+. Defaults to true
55
+ # +hash_digest+ - a string representing a class that will be used to hash URLs for obfuscation
56
+ # +hash_data+ - the relative URL for the hash data. This is interpolated using +interpolator+
57
+ # +hash_secret+ - a secret passed to the +hash_digest+
58
+ # +convert_options+ - flags passed to the +convert+ command for processing
59
+ # +source_file_options+ - flags passed to the +convert+ command that controls how the file is read
60
+ # +processors+ - classes that transform the attachment. Defaults to [:thumbnail]
61
+ # +preserve_files+ - whether to keep files on the filesystem when deleting to clearing the attachment. Defaults to false
62
+ # +interpolator+ - the object used to interpolate filenames and URLs. Defaults to Paperclip::Interpolations
63
+ # +url_generator+ - the object used to generate URLs, using the interpolator. Defaults to Paperclip::UrlGenerator
64
+ def initialize(name, instance, options = {})
65
+ @name = name
66
+ @instance = instance
67
+
68
+ options = self.class.default_options.merge(options)
69
+
70
+ @options = options
71
+ @post_processing = true
72
+ @queued_for_delete = []
73
+ @queued_for_write = {}
74
+ @errors = {}
75
+ @dirty = false
76
+ @interpolator = options[:interpolator]
77
+ @url_generator = options[:url_generator].new(self, @options)
78
+ @source_file_options = options[:source_file_options]
79
+ @whiny = options[:whiny]
80
+
81
+ initialize_storage
82
+ end
83
+
84
+ # What gets called when you call instance.attachment = File. It clears
85
+ # errors, assigns attributes, and processes the file. It
86
+ # also queues up the previous file for deletion, to be flushed away on
87
+ # #save of its host. In addition to form uploads, you can also assign
88
+ # another Paperclip attachment:
89
+ # new_user.avatar = old_user.avatar
90
+ def assign uploaded_file
91
+ ensure_required_accessors!
92
+
93
+ if uploaded_file.is_a?(Paperclip::Attachment)
94
+ uploaded_filename = uploaded_file.original_filename
95
+ uploaded_file = uploaded_file.to_file(:original)
96
+ close_uploaded_file = uploaded_file.respond_to?(:close)
97
+ else
98
+ instance_write(:uploaded_file, uploaded_file) if uploaded_file
99
+ end
100
+
101
+ return nil unless valid_assignment?(uploaded_file)
102
+
103
+ uploaded_file.binmode if uploaded_file.respond_to? :binmode
104
+ self.clear
105
+
106
+ return nil if uploaded_file.nil?
107
+
108
+ uploaded_filename ||= uploaded_file.original_filename
109
+ @queued_for_write[:original] = to_tempfile(uploaded_file)
110
+ instance_write(:file_name, uploaded_filename.strip)
111
+ instance_write(:content_type, uploaded_file.content_type.to_s.strip)
112
+ instance_write(:file_size, uploaded_file.size.to_i)
113
+ instance_write(:fingerprint, generate_fingerprint(uploaded_file))
114
+ instance_write(:updated_at, Time.now)
115
+
116
+ @dirty = true
117
+
118
+ post_process(*@options[:only_process]) if post_processing
119
+
120
+ # Reset the file size if the original file was reprocessed.
121
+ instance_write(:file_size, @queued_for_write[:original].size.to_i)
122
+ instance_write(:fingerprint, generate_fingerprint(@queued_for_write[:original]))
123
+ ensure
124
+ uploaded_file.close if close_uploaded_file
125
+ end
126
+
127
+ # Returns the public URL of the attachment with a given style. This does
128
+ # not necessarily need to point to a file that your Web server can access
129
+ # and can instead point to an action in your app, for example for fine grained
130
+ # security; this has a serious performance tradeoff.
131
+ #
132
+ # Options:
133
+ #
134
+ # +timestamp+ - Add a timestamp to the end of the URL. Default: true.
135
+ # +escape+ - Perform URI escaping to the URL. Default: true.
136
+ #
137
+ # Global controls (set on has_attached_file):
138
+ #
139
+ # +interpolator+ - The object that fills in a URL pattern's variables.
140
+ # +default_url+ - The image to show when the attachment has no image.
141
+ # +url+ - The URL for a saved image.
142
+ # +url_generator+ - The object that generates a URL. Default: Paperclip::UrlGenerator.
143
+ #
144
+ # As mentioned just above, the object that generates this URL can be passed
145
+ # in, for finer control. This object must respond to two methods:
146
+ #
147
+ # +#new(Paperclip::Attachment, options_hash)+
148
+ # +#for(style_name, options_hash)+
149
+ def url(style_name = default_style, options = {})
150
+ default_options = {:timestamp => @options[:use_timestamp], :escape => true}
151
+
152
+ if options == true || options == false # Backwards compatibility.
153
+ @url_generator.for(style_name, default_options.merge(:timestamp => options))
154
+ else
155
+ @url_generator.for(style_name, default_options.merge(options))
156
+ end
157
+ end
158
+
159
+ # Returns the path of the attachment as defined by the :path option. If the
160
+ # file is stored in the filesystem the path refers to the path of the file
161
+ # on disk. If the file is stored in S3, the path is the "key" part of the
162
+ # URL, and the :bucket option refers to the S3 bucket.
163
+ def path(style_name = default_style)
164
+ path = original_filename.nil? ? nil : interpolate(path_option, style_name)
165
+ path.respond_to?(:unescape) ? path.unescape : path
166
+ end
167
+
168
+ # Alias to +url+
169
+ def to_s style_name = default_style
170
+ url(style_name)
171
+ end
172
+
173
+ def default_style
174
+ @options[:default_style]
175
+ end
176
+
177
+ def styles
178
+ styling_option = @options[:styles]
179
+ if styling_option.respond_to?(:call) || !@normalized_styles
180
+ @normalized_styles = ActiveSupport::OrderedHash.new
181
+ (styling_option.respond_to?(:call) ? styling_option.call(self) : styling_option).each do |name, args|
182
+ @normalized_styles[name] = Paperclip::Style.new(name, args.dup, self)
183
+ end
184
+ end
185
+ @normalized_styles
186
+ end
187
+
188
+ def processors
189
+ processing_option = @options[:processors]
190
+
191
+ if processing_option.respond_to?(:call)
192
+ processing_option.call(instance)
193
+ else
194
+ processing_option
195
+ end
196
+ end
197
+
198
+ # Returns an array containing the errors on this attachment.
199
+ def errors
200
+ @errors
201
+ end
202
+
203
+ # Returns true if there are changes that need to be saved.
204
+ def dirty?
205
+ @dirty
206
+ end
207
+
208
+ # Saves the file, if there are no errors. If there are, it flushes them to
209
+ # the instance's errors and returns false, cancelling the save.
210
+ def save
211
+ flush_deletes unless @options[:keep_old_files]
212
+ flush_writes
213
+ @dirty = false
214
+ true
215
+ end
216
+
217
+ # Clears out the attachment. Has the same effect as previously assigning
218
+ # nil to the attachment. Does NOT save. If you wish to clear AND save,
219
+ # use #destroy.
220
+ def clear
221
+ queue_existing_for_delete
222
+ @queued_for_write = {}
223
+ @errors = {}
224
+ end
225
+
226
+ # Destroys the attachment. Has the same effect as previously assigning
227
+ # nil to the attachment *and saving*. This is permanent. If you wish to
228
+ # wipe out the existing attachment but not save, use #clear.
229
+ def destroy
230
+ unless @options[:preserve_files]
231
+ clear
232
+ save
233
+ end
234
+ end
235
+
236
+ # Returns the uploaded file if present.
237
+ def uploaded_file
238
+ instance_read(:uploaded_file)
239
+ end
240
+
241
+ # Returns the name of the file as originally assigned, and lives in the
242
+ # <attachment>_file_name attribute of the model.
243
+ def original_filename
244
+ instance_read(:file_name)
245
+ end
246
+
247
+ # Returns the size of the file as originally assigned, and lives in the
248
+ # <attachment>_file_size attribute of the model.
249
+ def size
250
+ instance_read(:file_size) || (@queued_for_write[:original] && @queued_for_write[:original].size)
251
+ end
252
+
253
+ # Returns the hash of the file as originally assigned, and lives in the
254
+ # <attachment>_fingerprint attribute of the model.
255
+ def fingerprint
256
+ instance_read(:fingerprint) || (@queued_for_write[:original] && generate_fingerprint(@queued_for_write[:original]))
257
+ end
258
+
259
+ # Returns the content_type of the file as originally assigned, and lives
260
+ # in the <attachment>_content_type attribute of the model.
261
+ def content_type
262
+ instance_read(:content_type)
263
+ end
264
+
265
+ # Returns the last modified time of the file as originally assigned, and
266
+ # lives in the <attachment>_updated_at attribute of the model.
267
+ def updated_at
268
+ time = instance_read(:updated_at)
269
+ time && time.to_f.to_i
270
+ end
271
+
272
+ # The time zone to use for timestamp interpolation. Using the default
273
+ # time zone ensures that results are consistent across all threads.
274
+ def time_zone
275
+ @options[:use_default_time_zone] ? Time.zone_default : Time.zone
276
+ end
277
+
278
+ # Returns a unique hash suitable for obfuscating the URL of an otherwise
279
+ # publicly viewable attachment.
280
+ def hash(style_name = default_style)
281
+ raise ArgumentError, "Unable to generate hash without :hash_secret" unless @options[:hash_secret]
282
+ require 'openssl' unless defined?(OpenSSL)
283
+ data = interpolate(@options[:hash_data], style_name)
284
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@options[:hash_digest]).new, @options[:hash_secret], data)
285
+ end
286
+
287
+ def generate_fingerprint(source)
288
+ if source.respond_to?(:path) && source.path && !source.path.blank?
289
+ Digest::MD5.file(source.path).to_s
290
+ else
291
+ data = source.read
292
+ source.rewind if source.respond_to?(:rewind)
293
+ Digest::MD5.hexdigest(data)
294
+ end
295
+ end
296
+
297
+ # Paths and URLs can have a number of variables interpolated into them
298
+ # to vary the storage location based on name, id, style, class, etc.
299
+ # This method is a deprecated access into supplying and retrieving these
300
+ # interpolations. Future access should use either Paperclip.interpolates
301
+ # or extend the Paperclip::Interpolations module directly.
302
+ def self.interpolations
303
+ warn('[DEPRECATION] Paperclip::Attachment.interpolations is deprecated ' +
304
+ 'and will be removed from future versions. ' +
305
+ 'Use Paperclip.interpolates instead')
306
+ Paperclip::Interpolations
307
+ end
308
+
309
+ # This method really shouldn't be called that often. It's expected use is
310
+ # in the paperclip:refresh rake task and that's it. It will regenerate all
311
+ # thumbnails forcefully, by reobtaining the original file and going through
312
+ # the post-process again.
313
+ def reprocess!(*style_args)
314
+ new_original = Tempfile.new("paperclip-reprocess")
315
+ new_original.binmode
316
+ if old_original = to_file(:original)
317
+ new_original.write( old_original.respond_to?(:get) ? old_original.get : old_original.read )
318
+ new_original.rewind
319
+
320
+ @queued_for_write = { :original => new_original }
321
+ instance_write(:updated_at, Time.now)
322
+ post_process(*style_args)
323
+
324
+ old_original.close if old_original.respond_to?(:close)
325
+ old_original.unlink if old_original.respond_to?(:unlink)
326
+
327
+ save
328
+ else
329
+ true
330
+ end
331
+ rescue Errno::EACCES => e
332
+ warn "#{e} - skipping file"
333
+ false
334
+ end
335
+
336
+ # Returns true if a file has been assigned.
337
+ def file?
338
+ !original_filename.blank?
339
+ end
340
+
341
+ alias :present? :file?
342
+
343
+ # Writes the attachment-specific attribute on the instance. For example,
344
+ # instance_write(:file_name, "me.jpg") will write "me.jpg" to the instance's
345
+ # "avatar_file_name" field (assuming the attachment is called avatar).
346
+ def instance_write(attr, value)
347
+ setter = :"#{name}_#{attr}="
348
+ responds = instance.respond_to?(setter)
349
+ self.instance_variable_set("@_#{setter.to_s.chop}", value)
350
+ instance.send(setter, value) if responds || attr.to_s == "file_name"
351
+ end
352
+
353
+ # Reads the attachment-specific attribute on the instance. See instance_write
354
+ # for more details.
355
+ def instance_read(attr)
356
+ getter = :"#{name}_#{attr}"
357
+ responds = instance.respond_to?(getter)
358
+ cached = self.instance_variable_get("@_#{getter}")
359
+ return cached if cached
360
+ instance.send(getter) if responds || attr.to_s == "file_name"
361
+ end
362
+
363
+ private
364
+
365
+ def path_option
366
+ @options[:path].respond_to?(:call) ? @options[:path].call(self) : @options[:path]
367
+ end
368
+
369
+ def ensure_required_accessors! #:nodoc:
370
+ %w(file_name).each do |field|
371
+ unless @instance.respond_to?("#{name}_#{field}") && @instance.respond_to?("#{name}_#{field}=")
372
+ raise PaperclipError.new("#{@instance.class} model missing required attr_accessor for '#{name}_#{field}'")
373
+ end
374
+ end
375
+ end
376
+
377
+ def log message #:nodoc:
378
+ Paperclip.log(message)
379
+ end
380
+
381
+ def valid_assignment? file #:nodoc:
382
+ file.nil? || (file.respond_to?(:original_filename) && file.respond_to?(:content_type))
383
+ end
384
+
385
+ def initialize_storage #:nodoc:
386
+ storage_class_name = @options[:storage].to_s.downcase.camelize
387
+ begin
388
+ storage_module = Paperclip::Storage.const_get(storage_class_name)
389
+ rescue NameError
390
+ raise StorageMethodNotFound, "Cannot load storage module '#{storage_class_name}'"
391
+ end
392
+ self.extend(storage_module)
393
+ end
394
+
395
+ def extra_options_for(style) #:nodoc:
396
+ all_options = @options[:convert_options][:all]
397
+ all_options = all_options.call(instance) if all_options.respond_to?(:call)
398
+ style_options = @options[:convert_options][style]
399
+ style_options = style_options.call(instance) if style_options.respond_to?(:call)
400
+
401
+ [ style_options, all_options ].compact.join(" ")
402
+ end
403
+
404
+ def extra_source_file_options_for(style) #:nodoc:
405
+ all_options = @options[:source_file_options][:all]
406
+ all_options = all_options.call(instance) if all_options.respond_to?(:call)
407
+ style_options = @options[:source_file_options][style]
408
+ style_options = style_options.call(instance) if style_options.respond_to?(:call)
409
+
410
+ [ style_options, all_options ].compact.join(" ")
411
+ end
412
+
413
+ def post_process(*style_args) #:nodoc:
414
+ return if @queued_for_write[:original].nil?
415
+ instance.run_paperclip_callbacks(:post_process) do
416
+ instance.run_paperclip_callbacks(:"#{name}_post_process") do
417
+ post_process_styles(*style_args)
418
+ end
419
+ end
420
+ end
421
+
422
+ def post_process_styles(*style_args) #:nodoc:
423
+ styles.each do |name, style|
424
+ begin
425
+ if style_args.empty? || style_args.include?(name)
426
+ raise RuntimeError.new("Style #{name} has no processors defined.") if style.processors.blank?
427
+ @queued_for_write[name] = style.processors.inject(@queued_for_write[:original]) do |file, processor|
428
+ Paperclip.processor(processor).make(file, style.processor_options, self)
429
+ end
430
+ end
431
+ rescue PaperclipError => e
432
+ log("An error was received while processing: #{e.inspect}")
433
+ (@errors[:processing] ||= []) << e.message if @options[:whiny]
434
+ end
435
+ end
436
+ end
437
+
438
+ def interpolate(pattern, style_name = default_style) #:nodoc:
439
+ interpolator.interpolate(pattern, self, style_name)
440
+ end
441
+
442
+ def queue_existing_for_delete #:nodoc:
443
+ return if @options[:preserve_files] || !file?
444
+ @queued_for_delete += [:original, *styles.keys].uniq.map do |style|
445
+ path(style) if exists?(style)
446
+ end.compact
447
+ instance_write(:file_name, nil)
448
+ instance_write(:content_type, nil)
449
+ instance_write(:file_size, nil)
450
+ instance_write(:updated_at, nil)
451
+ end
452
+
453
+ def flush_errors #:nodoc:
454
+ @errors.each do |error, message|
455
+ [message].flatten.each {|m| instance.errors.add(name, m) }
456
+ end
457
+ end
458
+
459
+ # called by storage after the writes are flushed and before @queued_for_writes is cleared
460
+ def after_flush_writes
461
+ @queued_for_write.each do |style, file|
462
+ file.close unless file.closed?
463
+ file.unlink if file.respond_to?(:unlink) && file.path.present? && File.exist?(file.path)
464
+ end
465
+ end
466
+
467
+ end
468
+ end
@@ -0,0 +1,61 @@
1
+ module Paperclip
2
+ module CallbackCompatability
3
+ module Rails21
4
+ def self.included(base)
5
+ base.extend(Defining)
6
+ base.send(:include, Running)
7
+ end
8
+
9
+ module Defining
10
+ def define_paperclip_callbacks(*args)
11
+ args.each do |callback|
12
+ define_callbacks("before_#{callback}")
13
+ define_callbacks("after_#{callback}")
14
+ end
15
+ end
16
+ end
17
+
18
+ module Running
19
+ def run_paperclip_callbacks(callback, opts = nil, &blk)
20
+ # The overall structure of this isn't ideal since after callbacks run even if
21
+ # befores return false. But this is how rails 3's callbacks work, unfortunately.
22
+ if run_callbacks(:"before_#{callback}"){ |result, object| result == false } != false
23
+ blk.call
24
+ end
25
+ run_callbacks(:"after_#{callback}"){ |result, object| result == false }
26
+ end
27
+ end
28
+ end
29
+
30
+ module Rails3
31
+ def self.included(base)
32
+ base.extend(Defining)
33
+ base.send(:include, Running)
34
+ end
35
+
36
+ module Defining
37
+ def define_paperclip_callbacks(*callbacks)
38
+ define_callbacks *[callbacks, {:terminator => "result == false"}].flatten
39
+ callbacks.each do |callback|
40
+ eval <<-end_callbacks
41
+ def before_#{callback}(*args, &blk)
42
+ set_callback(:#{callback}, :before, *args, &blk)
43
+ end
44
+ def after_#{callback}(*args, &blk)
45
+ set_callback(:#{callback}, :after, *args, &blk)
46
+ end
47
+ end_callbacks
48
+ end
49
+ end
50
+ end
51
+
52
+ module Running
53
+ def run_paperclip_callbacks(callback, opts = nil, &block)
54
+ run_callbacks(callback, opts, &block)
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+ end