cloudfuji_paperclip 2.4.6

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 (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