paperclip-cloudfiles 2.3.8.1 → 2.3.8.3

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.
data/README.md CHANGED
@@ -25,6 +25,21 @@ on Gemcutter's gem server.
25
25
 
26
26
  The complete [RDoc](http://rdoc.info/github/minter/paperclip) is online.
27
27
 
28
+ Requirements
29
+ ------------
30
+
31
+ ImageMagick must be installed and Paperclip must have access to it. To ensure
32
+ that it does, on your command line, run `which convert` (one of the ImageMagick
33
+ utilities). This will give you the path where that utility is installed. For
34
+ example, it might return `/usr/local/bin/convert`.
35
+
36
+ Then, in your environment config file, let Paperclip know to look there by adding that
37
+ directory to its path.
38
+
39
+ In development mode, you might add this line to `config/environments/development.rb)`:
40
+
41
+ Paperclip.options[:command_path] = "/usr/local/bin/"
42
+
28
43
  Installation
29
44
  ------------
30
45
 
@@ -8,21 +8,24 @@ module Paperclip
8
8
 
9
9
  def self.default_options
10
10
  @default_options ||= {
11
- :url => "/system/:attachment/:id/:style/:filename",
12
- :path => ":rails_root/public:url",
13
- :styles => {},
14
- :processors => [:thumbnail],
15
- :convert_options => {},
16
- :default_url => "/:attachment/:style/missing.png",
17
- :default_style => :original,
18
- :validations => [],
19
- :storage => :filesystem,
20
- :use_timestamp => true,
21
- :whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails]
11
+ :url => "/system/:attachment/:id/:style/:filename",
12
+ :path => ":rails_root/public:url",
13
+ :styles => {},
14
+ :processors => [:thumbnail],
15
+ :convert_options => {},
16
+ :default_url => "/:attachment/:style/missing.png",
17
+ :default_style => :original,
18
+ :storage => :filesystem,
19
+ :use_timestamp => true,
20
+ :whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails],
21
+ :use_default_time_zone => true,
22
+ :hash_digest => "SHA1",
23
+ :hash_data => ":class/:attachment/:id/:style/:updated_at"
22
24
  }
23
25
  end
24
26
 
25
27
  attr_reader :name, :instance, :default_style, :convert_options, :queued_for_write, :whiny, :options
28
+ attr_accessor :post_processing
26
29
 
27
30
  # Creates an Attachment object. +name+ is the name of the attachment,
28
31
  # +instance+ is the ActiveRecord object instance it's attached to, and
@@ -33,26 +36,29 @@ module Paperclip
33
36
 
34
37
  options = self.class.default_options.merge(options)
35
38
 
36
- @url = options[:url]
37
- @url = @url.call(self) if @url.is_a?(Proc)
38
- @path = options[:path]
39
- @path = @path.call(self) if @path.is_a?(Proc)
40
- @styles = options[:styles]
41
- @normalized_styles = nil
42
- @default_url = options[:default_url]
43
- @validations = options[:validations]
44
- @default_style = options[:default_style]
45
- @storage = options[:storage]
46
- @use_timestamp = options[:use_timestamp]
47
- @whiny = options[:whiny_thumbnails] || options[:whiny]
48
- @convert_options = options[:convert_options]
49
- @processors = options[:processors]
50
- @options = options
51
- @queued_for_delete = []
52
- @queued_for_write = {}
53
- @errors = {}
54
- @validation_errors = nil
55
- @dirty = false
39
+ @url = options[:url]
40
+ @url = @url.call(self) if @url.is_a?(Proc)
41
+ @path = options[:path]
42
+ @path = @path.call(self) if @path.is_a?(Proc)
43
+ @styles = options[:styles]
44
+ @normalized_styles = nil
45
+ @default_url = options[:default_url]
46
+ @default_style = options[:default_style]
47
+ @storage = options[:storage]
48
+ @use_timestamp = options[:use_timestamp]
49
+ @whiny = options[:whiny_thumbnails] || options[:whiny]
50
+ @use_default_time_zone = options[:use_default_time_zone]
51
+ @hash_digest = options[:hash_digest]
52
+ @hash_data = options[:hash_data]
53
+ @hash_secret = options[:hash_secret]
54
+ @convert_options = options[:convert_options]
55
+ @processors = options[:processors]
56
+ @options = options
57
+ @post_processing = true
58
+ @queued_for_delete = []
59
+ @queued_for_write = {}
60
+ @errors = {}
61
+ @dirty = false
56
62
 
57
63
  initialize_storage
58
64
  end
@@ -103,14 +109,13 @@ module Paperclip
103
109
 
104
110
  @dirty = true
105
111
 
106
- post_process if valid?
107
-
112
+ post_process if @post_processing
113
+
108
114
  # Reset the file size if the original file was reprocessed.
109
115
  instance_write(:file_size, @queued_for_write[:original].size.to_i)
110
116
  instance_write(:fingerprint, generate_fingerprint(@queued_for_write[:original]))
111
117
  ensure
112
118
  uploaded_file.close if close_uploaded_file
113
- validate
114
119
  end
115
120
 
116
121
  # Returns the public URL of the attachment, with a given style. Note that
@@ -133,16 +138,10 @@ module Paperclip
133
138
  end
134
139
 
135
140
  # Alias to +url+
136
- def to_s style_name = nil
141
+ def to_s style_name = default_style
137
142
  url(style_name)
138
143
  end
139
144
 
140
- # Returns true if there are no errors on this attachment.
141
- def valid?
142
- validate
143
- errors.empty?
144
- end
145
-
146
145
  # Returns an array containing the errors on this attachment.
147
146
  def errors
148
147
  @errors
@@ -156,15 +155,10 @@ module Paperclip
156
155
  # Saves the file, if there are no errors. If there are, it flushes them to
157
156
  # the instance's errors and returns false, cancelling the save.
158
157
  def save
159
- if valid?
160
- flush_deletes
161
- flush_writes
162
- @dirty = false
163
- true
164
- else
165
- flush_errors
166
- false
167
- end
158
+ flush_deletes
159
+ flush_writes
160
+ @dirty = false
161
+ true
168
162
  end
169
163
 
170
164
  # Clears out the attachment. Has the same effect as previously assigning
@@ -173,7 +167,6 @@ module Paperclip
173
167
  def clear
174
168
  queue_existing_for_delete
175
169
  @errors = {}
176
- @validation_errors = nil
177
170
  end
178
171
 
179
172
  # Destroys the attachment. Has the same effect as previously assigning
@@ -215,6 +208,21 @@ module Paperclip
215
208
  time && time.to_f.to_i
216
209
  end
217
210
 
211
+ # The time zone to use for timestamp interpolation. Using the default
212
+ # time zone ensures that results are consistent across all threads.
213
+ def time_zone
214
+ @use_default_time_zone ? Time.zone_default : Time.zone
215
+ end
216
+
217
+ # Returns a unique hash suitable for obfuscating the URL of an otherwise
218
+ # publicly viewable attachment.
219
+ def hash(style_name = default_style)
220
+ raise ArgumentError, "Unable to generate hash without :hash_secret" unless @hash_secret
221
+ require 'openssl' unless defined?(OpenSSL)
222
+ data = interpolate(@hash_data, style_name)
223
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@hash_digest).new, @hash_secret, data)
224
+ end
225
+
218
226
  def generate_fingerprint(source)
219
227
  data = source.read
220
228
  source.rewind if source.respond_to?(:rewind)
@@ -237,7 +245,7 @@ module Paperclip
237
245
  # in the paperclip:refresh rake task and that's it. It will regenerate all
238
246
  # thumbnails forcefully, by reobtaining the original file and going through
239
247
  # the post-process again.
240
- def reprocess!
248
+ def reprocess!(*style_args)
241
249
  new_original = Tempfile.new("paperclip-reprocess")
242
250
  new_original.binmode
243
251
  if old_original = to_file(:original)
@@ -245,7 +253,7 @@ module Paperclip
245
253
  new_original.rewind
246
254
 
247
255
  @queued_for_write = { :original => new_original }
248
- post_process
256
+ post_process(*style_args)
249
257
 
250
258
  old_original.close if old_original.respond_to?(:close)
251
259
 
@@ -301,52 +309,6 @@ module Paperclip
301
309
  file.nil? || (file.respond_to?(:original_filename) && file.respond_to?(:content_type))
302
310
  end
303
311
 
304
- def validate #:nodoc:
305
- unless @validation_errors
306
- @validation_errors = @validations.inject({}) do |errors, validation|
307
- name, options = validation
308
- errors[name] = send(:"validate_#{name}", options) if allow_validation?(options)
309
- errors
310
- end
311
- @validation_errors.reject!{|k,v| v == nil }
312
- @errors.merge!(@validation_errors)
313
- end
314
- @validation_errors
315
- end
316
-
317
- def allow_validation? options #:nodoc:
318
- (options[:if].nil? || check_guard(options[:if])) && (options[:unless].nil? || !check_guard(options[:unless]))
319
- end
320
-
321
- def check_guard guard #:nodoc:
322
- if guard.respond_to? :call
323
- guard.call(instance)
324
- elsif ! guard.blank?
325
- instance.send(guard.to_s)
326
- end
327
- end
328
-
329
- def validate_size options #:nodoc:
330
- if file? && !options[:range].include?(size.to_i)
331
- options[:message].gsub(/:min/, options[:min].to_s).gsub(/:max/, options[:max].to_s)
332
- end
333
- end
334
-
335
- def validate_presence options #:nodoc:
336
- options[:message] unless file?
337
- end
338
-
339
- def validate_content_type options #:nodoc:
340
- valid_types = [options[:content_type]].flatten
341
- unless original_filename.blank?
342
- unless valid_types.blank?
343
- content_type = instance_read(:content_type)
344
- unless valid_types.any?{|t| content_type.nil? || t === content_type }
345
- options[:message] || "is not one of the allowed file types."
346
- end
347
- end
348
- end
349
- end
350
312
 
351
313
  def initialize_storage #:nodoc:
352
314
  storage_class_name = @storage.to_s.capitalize
@@ -367,21 +329,23 @@ module Paperclip
367
329
  [ style_options, all_options ].compact.join(" ")
368
330
  end
369
331
 
370
- def post_process #:nodoc:
332
+ def post_process(*style_args) #:nodoc:
371
333
  return if @queued_for_write[:original].nil?
372
334
  instance.run_paperclip_callbacks(:post_process) do
373
335
  instance.run_paperclip_callbacks(:"#{name}_post_process") do
374
- post_process_styles
336
+ post_process_styles(*style_args)
375
337
  end
376
338
  end
377
339
  end
378
340
 
379
- def post_process_styles #:nodoc:
341
+ def post_process_styles(*style_args) #:nodoc:
380
342
  styles.each do |name, style|
381
343
  begin
382
- raise RuntimeError.new("Style #{name} has no processors defined.") if style.processors.blank?
383
- @queued_for_write[name] = style.processors.inject(@queued_for_write[:original]) do |file, processor|
384
- Paperclip.processor(processor).make(file, style.processor_options, self)
344
+ if style_args.empty? || style_args.include?(name)
345
+ raise RuntimeError.new("Style #{name} has no processors defined.") if style.processors.blank?
346
+ @queued_for_write[name] = style.processors.inject(@queued_for_write[:original]) do |file, processor|
347
+ Paperclip.processor(processor).make(file, style.processor_options, self)
348
+ end
385
349
  end
386
350
  rescue PaperclipError => e
387
351
  log("An error was received while processing: #{e.inspect}")
@@ -52,7 +52,7 @@ module Paperclip
52
52
  raise PaperclipCommandLineError,
53
53
  "Interpolation of #{key} isn't allowed."
54
54
  end
55
- shell_quote(vars[key.to_sym])
55
+ interpolation(vars, key) || match
56
56
  end
57
57
  end
58
58
 
@@ -60,6 +60,12 @@ module Paperclip
60
60
  %w(expected_outcodes swallow_stderr)
61
61
  end
62
62
 
63
+ def interpolation(vars, key)
64
+ if vars.key?(key.to_sym)
65
+ shell_quote(vars[key.to_sym])
66
+ end
67
+ end
68
+
63
69
  def shell_quote(string)
64
70
  return "" if string.nil? or string.blank?
65
71
  if self.class.unix?
@@ -48,8 +48,18 @@ module Paperclip
48
48
  end
49
49
 
50
50
  # Returns the timestamp as defined by the <attachment>_updated_at field
51
+ # in the server default time zone unless :use_global_time_zone is set
52
+ # to false. Note that a Rails.config.time_zone change will still
53
+ # invalidate any path or URL that uses :timestamp. For a
54
+ # time_zone-agnostic timestamp, use #updated_at.
51
55
  def timestamp attachment, style_name
52
- attachment.instance_read(:updated_at).to_s
56
+ attachment.instance_read(:updated_at).in_time_zone(attachment.time_zone).to_s
57
+ end
58
+
59
+ # Returns an integer timestamp that is time zone-neutral, so that paths
60
+ # remain valid even if a server's time zone changes.
61
+ def updated_at attachment, style_name
62
+ attachment.updated_at
53
63
  end
54
64
 
55
65
  # Returns the Rails.root constant.
@@ -94,6 +104,12 @@ module Paperclip
94
104
  attachment.fingerprint
95
105
  end
96
106
 
107
+ # Returns a the attachment hash. See Paperclip::Attachment#hash for
108
+ # more details.
109
+ def hash attachment, style_name
110
+ attachment.hash(style_name)
111
+ end
112
+
97
113
  # Returns the id of the instance in a split path form. e.g. returns
98
114
  # 000/001/234 for an id of 1234.
99
115
  def id_partition attachment, style_name
@@ -43,9 +43,17 @@ module Paperclip
43
43
  # * +auth_url+: The URL to the authentication endpoint. If blank, defaults to the Rackspace Cloud Files
44
44
  # USA endpoint. You can use this to specify things like the Rackspace Cloud Files UK infrastructure, or
45
45
  # a non-Rackspace OpenStack Swift installation. Requires 1.4.11 or higher of the Cloud Files gem.
46
+ # * +ssl+: Whether or not to serve this content over SSL. If set to true, serves content as https, otherwise
47
+ # not. Can also take a lambda that returns true or false (for example, if the attachment object has a user object
48
+ # and that user has ssl enabled)
46
49
  module Cloud_files
47
50
  def self.extended base
48
- require 'cloudfiles'
51
+ begin
52
+ require 'cloudfiles'
53
+ rescue LoadError => e
54
+ e.message << " (You may need to install the cloudfiles gem)"
55
+ raise e
56
+ end unless defined?(CloudFiles)
49
57
  @@container ||= {}
50
58
  base.instance_eval do
51
59
  @cloudfiles_credentials = parse_credentials(@options[:cloudfiles_credentials])
@@ -53,8 +61,10 @@ module Paperclip
53
61
  @container_name = @container_name.call(self) if @container_name.is_a?(Proc)
54
62
  @cloudfiles_options = @options[:cloudfiles_options] || {}
55
63
  @@cdn_url = cloudfiles_container.cdn_url
64
+ @@ssl_url = cloudfiles_container.cdn_ssl_url
65
+ @use_ssl = @options[:ssl] || false
56
66
  @path_filename = ":cf_path_filename" unless @url.to_s.match(/^:cf.*filename$/)
57
- @url = @@cdn_url + "/#{URI.encode(@path_filename).gsub(/&/,'%26')}"
67
+ @url = (@use_ssl == true ? @@ssl_url : @@cdn_url) + "/#{URI.encode(@path_filename).gsub(/&/,'%26')}"
58
68
  @path = (Paperclip::Attachment.default_options[:path] == @options[:path]) ? ":attachment/:id/:style/:basename.:extension" : @options[:path]
59
69
  end
60
70
  Paperclip.interpolates(:cf_path_filename) do |attachment, style|
@@ -63,7 +63,7 @@ module Paperclip
63
63
  rescue LoadError => e
64
64
  e.message << " (You may need to install the aws-s3 gem)"
65
65
  raise e
66
- end
66
+ end unless defined?(AWS::S3)
67
67
 
68
68
  base.instance_eval do
69
69
  @s3_credentials = parse_credentials(@options[:s3_credentials])
@@ -85,13 +85,13 @@ module Paperclip
85
85
  end
86
86
  Paperclip.interpolates(:s3_alias_url) do |attachment, style|
87
87
  "#{attachment.s3_protocol}://#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
88
- end
88
+ end unless Paperclip::Interpolations.respond_to? :s3_alias_url
89
89
  Paperclip.interpolates(:s3_path_url) do |attachment, style|
90
90
  "#{attachment.s3_protocol}://s3.amazonaws.com/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
91
- end
91
+ end unless Paperclip::Interpolations.respond_to? :s3_path_url
92
92
  Paperclip.interpolates(:s3_domain_url) do |attachment, style|
93
93
  "#{attachment.s3_protocol}://#{attachment.bucket_name}.s3.amazonaws.com/#{attachment.path(style).gsub(%r{^/}, "")}"
94
- end
94
+ end unless Paperclip::Interpolations.respond_to? :s3_domain_url
95
95
  end
96
96
 
97
97
  def expiring_url(time = 3600)
@@ -1,3 +1,3 @@
1
1
  module Paperclip
2
- VERSION = "2.3.8.1" unless defined? Paperclip::VERSION
2
+ VERSION = "2.3.8.3" unless defined? Paperclip::VERSION
3
3
  end
data/lib/paperclip.rb CHANGED
@@ -258,7 +258,7 @@ module Paperclip
258
258
 
259
259
  validates_each(name) do |record, attr, value|
260
260
  attachment = record.attachment_for(name)
261
- attachment.send(:flush_errors) unless attachment.valid?
261
+ attachment.send(:flush_errors)
262
262
  end
263
263
  end
264
264
 
@@ -276,6 +276,7 @@ module Paperclip
276
276
  max = options[:less_than] || (options[:in] && options[:in].last) || (1.0/0)
277
277
  range = (min..max)
278
278
  message = options[:message] || "file size must be between :min and :max bytes."
279
+ message = message.call if message.respond_to?(:call)
279
280
  message = message.gsub(/:min/, min.to_s).gsub(/:max/, max.to_s)
280
281
 
281
282
  validates_inclusion_of :"#{name}_file_size",
@@ -330,6 +331,7 @@ module Paperclip
330
331
  if !allowed_types.any?{|t| t === value } && !(value.nil? || value.blank?)
331
332
  if record.errors.method(:add).arity == -2
332
333
  message = options[:message] || "is not one of #{allowed_types.join(", ")}"
334
+ message = message.call if message.respond_to?(:call)
333
335
  record.errors.add(:"#{name}_content_type", message)
334
336
  else
335
337
  record.errors.add(:"#{name}_content_type", :inclusion, :default => options[:message], :value => value)
@@ -14,18 +14,6 @@ class AttachmentTest < Test::Unit::TestCase
14
14
  assert_equal "#{Rails.root}/public/fake_models/1234/fake", @attachment.path
15
15
  end
16
16
 
17
- should "call a proc sent to check_guard" do
18
- @dummy = Dummy.new
19
- @dummy.expects(:one).returns(:one)
20
- assert_equal :one, @dummy.avatar.send(:check_guard, lambda{|x| x.one })
21
- end
22
-
23
- should "call a method name sent to check_guard" do
24
- @dummy = Dummy.new
25
- @dummy.expects(:one).returns(:one)
26
- assert_equal :one, @dummy.avatar.send(:check_guard, :one)
27
- end
28
-
29
17
  context "Attachment default_options" do
30
18
  setup do
31
19
  rebuild_model
@@ -109,6 +97,83 @@ class AttachmentTest < Test::Unit::TestCase
109
97
  end
110
98
  end
111
99
 
100
+ context "An attachment with :timestamp interpolations" do
101
+ setup do
102
+ @file = StringIO.new("...")
103
+ @zone = 'UTC'
104
+ Time.stubs(:zone).returns(@zone)
105
+ @zone_default = 'Eastern Time (US & Canada)'
106
+ Time.stubs(:zone_default).returns(@zone_default)
107
+ end
108
+
109
+ context "using default time zone" do
110
+ setup do
111
+ rebuild_model :path => ":timestamp", :use_default_time_zone => true
112
+ @dummy = Dummy.new
113
+ @dummy.avatar = @file
114
+ end
115
+
116
+ should "return a time in the default zone" do
117
+ assert_equal @dummy.avatar_updated_at.in_time_zone(@zone_default).to_s, @dummy.avatar.path
118
+ end
119
+ end
120
+
121
+ context "using per-thread time zone" do
122
+ setup do
123
+ rebuild_model :path => ":timestamp", :use_default_time_zone => false
124
+ @dummy = Dummy.new
125
+ @dummy.avatar = @file
126
+ end
127
+
128
+ should "return a time in the per-thread zone" do
129
+ assert_equal @dummy.avatar_updated_at.in_time_zone(@zone).to_s, @dummy.avatar.path
130
+ end
131
+ end
132
+ end
133
+
134
+ context "An attachment with :hash interpolations" do
135
+ setup do
136
+ @file = StringIO.new("...")
137
+ end
138
+
139
+ should "raise if no secret is provided" do
140
+ @attachment = attachment :path => ":hash"
141
+ @attachment.assign @file
142
+
143
+ assert_raise ArgumentError do
144
+ @attachment.path
145
+ end
146
+ end
147
+
148
+ context "when secret is set" do
149
+ setup do
150
+ @attachment = attachment :path => ":hash", :hash_secret => "w00t"
151
+ @attachment.stubs(:instance_read).with(:updated_at).returns(Time.at(1234567890))
152
+ @attachment.stubs(:instance_read).with(:file_name).returns("bla.txt")
153
+ @attachment.instance.id = 1234
154
+ @attachment.assign @file
155
+ end
156
+
157
+ should "interpolate the hash data" do
158
+ @attachment.expects(:interpolate).with(@attachment.options[:hash_data],anything).returns("interpolated_stuff")
159
+ @attachment.hash
160
+ end
161
+
162
+ should "result in the correct interpolation" do
163
+ assert_equal "fake_models/avatars/1234/original/1234567890", @attachment.send(:interpolate,@attachment.options[:hash_data])
164
+ end
165
+
166
+ should "result in a correct hash" do
167
+ assert_equal "d22b617d1bf10016aa7d046d16427ae203f39fce", @attachment.path
168
+ end
169
+
170
+ should "generate a hash digest with the correct style" do
171
+ OpenSSL::HMAC.expects(:hexdigest).with(anything, anything, "fake_models/avatars/1234/medium/1234567890")
172
+ @attachment.path("medium")
173
+ end
174
+ end
175
+ end
176
+
112
177
  context "An attachment with a :rails_env interpolation" do
113
178
  setup do
114
179
  @rails_env = "blah"
@@ -488,8 +553,6 @@ class AttachmentTest < Test::Unit::TestCase
488
553
  @attachment.expects(:valid_assignment?).with(@not_file).returns(true)
489
554
  @attachment.expects(:queue_existing_for_delete)
490
555
  @attachment.expects(:post_process)
491
- @attachment.expects(:valid?).returns(true)
492
- @attachment.expects(:validate)
493
556
  @attachment.expects(:to_tempfile).returns(@tempfile)
494
557
  @attachment.expects(:generate_fingerprint).with(@tempfile).returns("12345")
495
558
  @attachment.expects(:generate_fingerprint).with(@not_file).returns("12345")
@@ -501,6 +564,46 @@ class AttachmentTest < Test::Unit::TestCase
501
564
  end
502
565
  end
503
566
 
567
+ context "Attachment with uppercase extension and a default style" do
568
+ setup do
569
+ @old_defaults = Paperclip::Attachment.default_options.dup
570
+ Paperclip::Attachment.default_options.merge!({
571
+ :path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
572
+ })
573
+ FileUtils.rm_rf("tmp")
574
+ rebuild_model
575
+ @instance = Dummy.new
576
+ @instance.stubs(:id).returns 123
577
+
578
+ @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "uppercase.PNG"), 'rb')
579
+
580
+ styles = {:styles => { :large => ["400x400", :jpg],
581
+ :medium => ["100x100", :jpg],
582
+ :small => ["32x32#", :jpg]},
583
+ :default_style => :small}
584
+ @attachment = Paperclip::Attachment.new(:avatar,
585
+ @instance,
586
+ styles)
587
+ now = Time.now
588
+ Time.stubs(:now).returns(now)
589
+ @attachment.assign(@file)
590
+ @attachment.save
591
+ end
592
+
593
+ teardown do
594
+ @file.close
595
+ Paperclip::Attachment.default_options.merge!(@old_defaults)
596
+ end
597
+
598
+ should "should have matching to_s and url methods" do
599
+ file = @attachment.to_file
600
+ assert file
601
+ assert_match @attachment.to_s, @attachment.url
602
+ assert_match @attachment.to_s(:small), @attachment.url(:small)
603
+ file.close
604
+ end
605
+ end
606
+
504
607
  context "An attachment" do
505
608
  setup do
506
609
  @old_defaults = Paperclip::Attachment.default_options.dup
@@ -6,6 +6,11 @@ class CommandLineTest < Test::Unit::TestCase
6
6
  File.stubs(:exist?).with("/dev/null").returns(true)
7
7
  end
8
8
 
9
+ should "allow colons in parameters" do
10
+ cmd = Paperclip::CommandLine.new("convert", "'a.jpg' -resize 175x220> -size 175x220 xc:black +swap -gravity center -composite 'b.jpg'", :swallow_stderr => false)
11
+ assert_equal "convert 'a.jpg' -resize 175x220> -size 175x220 xc:black +swap -gravity center -composite 'b.jpg'", cmd.command
12
+ end
13
+
9
14
  should "take a command and parameters and produce a shell command for bash" do
10
15
  cmd = Paperclip::CommandLine.new("convert", "a.jpg b.png", :swallow_stderr => false)
11
16
  assert_equal "convert a.jpg b.png", cmd.command
Binary file
data/test/helper.rb CHANGED
@@ -93,7 +93,7 @@ end
93
93
  class FakeModel
94
94
  attr_accessor :avatar_file_name,
95
95
  :avatar_file_size,
96
- :avatar_last_updated,
96
+ :avatar_updated_at,
97
97
  :avatar_content_type,
98
98
  :avatar_fingerprint,
99
99
  :id
@@ -69,6 +69,78 @@ class IntegrationTest < Test::Unit::TestCase
69
69
  end
70
70
  end
71
71
 
72
+ context "Attachment" do
73
+ setup do
74
+ @thumb_path = "./test/../public/system/avatars/1/thumb/5k.png"
75
+ File.delete(@thumb_path) if File.exists?(@thumb_path)
76
+ rebuild_model :styles => { :thumb => "50x50#" }
77
+ @dummy = Dummy.new
78
+ @file = File.new(File.join(File.dirname(__FILE__),
79
+ "fixtures",
80
+ "5k.png"), 'rb')
81
+
82
+ end
83
+
84
+ teardown { @file.close }
85
+
86
+ should "not create the thumbnails upon saving when post-processing is disabled" do
87
+ @dummy.avatar.post_processing = false
88
+ @dummy.avatar = @file
89
+ assert @dummy.save
90
+ assert !File.exists?(@thumb_path)
91
+ end
92
+
93
+ should "create the thumbnails upon saving when post_processing is enabled" do
94
+ @dummy.avatar.post_processing = true
95
+ @dummy.avatar = @file
96
+ assert @dummy.save
97
+ assert File.exists?(@thumb_path)
98
+ end
99
+ end
100
+
101
+ context "Attachment with no generated thumbnails" do
102
+ setup do
103
+ @thumb_small_path = "./test/../public/system/avatars/1/thumb_small/5k.png"
104
+ @thumb_large_path = "./test/../public/system/avatars/1/thumb_large/5k.png"
105
+ File.delete(@thumb_small_path) if File.exists?(@thumb_small_path)
106
+ File.delete(@thumb_large_path) if File.exists?(@thumb_large_path)
107
+ rebuild_model :styles => { :thumb_small => "50x50#", :thumb_large => "60x60#" }
108
+ @dummy = Dummy.new
109
+ @file = File.new(File.join(File.dirname(__FILE__),
110
+ "fixtures",
111
+ "5k.png"), 'rb')
112
+
113
+ @dummy.avatar.post_processing = false
114
+ @dummy.avatar = @file
115
+ assert @dummy.save
116
+ @dummy.avatar.post_processing = true
117
+ end
118
+
119
+ teardown { @file.close }
120
+
121
+ should "allow us to create all thumbnails in one go" do
122
+ assert !File.exists?(@thumb_small_path)
123
+ assert !File.exists?(@thumb_large_path)
124
+
125
+ @dummy.avatar.reprocess!
126
+
127
+ assert File.exists?(@thumb_small_path)
128
+ assert File.exists?(@thumb_large_path)
129
+ end
130
+
131
+ should "allow us to selectively create each thumbnail" do
132
+ assert !File.exists?(@thumb_small_path)
133
+ assert !File.exists?(@thumb_large_path)
134
+
135
+ @dummy.avatar.reprocess! :thumb_small
136
+ assert File.exists?(@thumb_small_path)
137
+ assert !File.exists?(@thumb_large_path)
138
+
139
+ @dummy.avatar.reprocess! :thumb_large
140
+ assert File.exists?(@thumb_large_path)
141
+ end
142
+ end
143
+
72
144
  context "A model that modifies its original" do
73
145
  setup do
74
146
  rebuild_model :styles => { :original => "2x2#" }
@@ -112,9 +112,25 @@ class InterpolationsTest < Test::Unit::TestCase
112
112
 
113
113
  should "return the timestamp" do
114
114
  now = Time.now
115
+ zone = 'UTC'
115
116
  attachment = mock
116
117
  attachment.expects(:instance_read).with(:updated_at).returns(now)
117
- assert_equal now.to_s, Paperclip::Interpolations.timestamp(attachment, :style)
118
+ attachment.expects(:time_zone).returns(zone)
119
+ assert_equal now.in_time_zone(zone).to_s, Paperclip::Interpolations.timestamp(attachment, :style)
120
+ end
121
+
122
+ should "return updated_at" do
123
+ attachment = mock
124
+ seconds_since_epoch = 1234567890
125
+ attachment.expects(:updated_at).returns(seconds_since_epoch)
126
+ assert_equal seconds_since_epoch, Paperclip::Interpolations.updated_at(attachment, :style)
127
+ end
128
+
129
+ should "return hash" do
130
+ attachment = mock
131
+ fake_hash = "a_wicked_secure_hash"
132
+ attachment.expects(:hash).returns(fake_hash)
133
+ assert_equal fake_hash, Paperclip::Interpolations.hash(attachment, :style)
118
134
  end
119
135
 
120
136
  should "call all expected interpolations with the given arguments" do
@@ -151,27 +151,6 @@ class PaperclipTest < Test::Unit::TestCase
151
151
  should "be valid" do
152
152
  assert @dummy.valid?
153
153
  end
154
-
155
- context "then has a validation added that makes it invalid" do
156
- setup do
157
- assert @dummy.save
158
- Dummy.class_eval do
159
- validates_attachment_content_type :avatar, :content_type => ["text/plain"]
160
- end
161
- @dummy2 = Dummy.find(@dummy.id)
162
- end
163
-
164
- should "be invalid when reloaded" do
165
- assert ! @dummy2.valid?, @dummy2.errors.inspect
166
- end
167
-
168
- should "be able to call #valid? twice without having duplicate errors" do
169
- @dummy2.avatar.valid?
170
- first_errors = @dummy2.avatar.errors
171
- @dummy2.avatar.valid?
172
- assert_equal first_errors, @dummy2.avatar.errors
173
- end
174
- end
175
154
  end
176
155
 
177
156
  context "a validation with an if guard clause" do
@@ -273,6 +252,21 @@ class PaperclipTest < Test::Unit::TestCase
273
252
  should_validate validation, options, valid_file, invalid_file
274
253
  end
275
254
 
255
+ context "with content_type validation and lambda message" do
256
+ context "and assigned an invalid file" do
257
+ setup do
258
+ Dummy.send(:"validates_attachment_content_type", :avatar, :content_type => %r{image/.*}, :message => lambda {'lambda content type message'})
259
+ @dummy = Dummy.new
260
+ @dummy.avatar &&= File.open(File.join(FIXTURES_DIR, "text.txt"), "rb")
261
+ @dummy.valid?
262
+ end
263
+
264
+ should "have a content type error message" do
265
+ assert [@dummy.errors[:avatar_content_type]].flatten.any?{|error| error =~ %r/lambda content type message/ }
266
+ end
267
+ end
268
+ end
269
+
276
270
  context "with size validation and less_than 10240 option" do
277
271
  context "and assigned an invalid file" do
278
272
  setup do
@@ -288,5 +282,20 @@ class PaperclipTest < Test::Unit::TestCase
288
282
  end
289
283
  end
290
284
 
285
+ context "with size validation and less_than 10240 option with lambda message" do
286
+ context "and assigned an invalid file" do
287
+ setup do
288
+ Dummy.send(:"validates_attachment_size", :avatar, :less_than => 10240, :message => lambda {'lambda between 0 and 10240 bytes'})
289
+ @dummy = Dummy.new
290
+ @dummy.avatar &&= File.open(File.join(FIXTURES_DIR, "12k.png"), "rb")
291
+ @dummy.valid?
292
+ end
293
+
294
+ should "have a file size min/max error message" do
295
+ assert [@dummy.errors[:avatar_file_size]].flatten.any?{|error| error =~ %r/lambda between 0 and 10240 bytes/ }
296
+ end
297
+ end
298
+ end
299
+
291
300
  end
292
301
  end
data/test/storage_test.rb CHANGED
@@ -122,8 +122,9 @@ class StorageTest < Test::Unit::TestCase
122
122
  setup do
123
123
  container = mock
124
124
  container.stubs(:make_public).returns(true)
125
- container.stubs(:public_url).returns('http://c0010181.cdn.cloudfiles.rackspacecloud.com/avatars/stringio.txt')
126
- container.stubs(:cdn_url).returns('http://c0010181.cdn.cloudfiles.rackspacecloud.com')
125
+ container.stubs(:public_url).returns('http://c186397.r97.cf1.rackcdn.com/c0010181.cdn.cloudfiles.rackspacecloud.com/avatars/stringio.txt')
126
+ container.stubs(:cdn_url).returns('http://c186397.r97.cf1.rackcdn.com')
127
+ container.stubs(:cdn_ssl_url).returns('https://c186397.ssl.cf1.rackcdn.com')
127
128
  connection = mock
128
129
  connection.stubs(:create_container).returns(container)
129
130
  CloudFiles::Connection.expects(:new).returns(connection)
@@ -137,7 +138,7 @@ class StorageTest < Test::Unit::TestCase
137
138
  end
138
139
 
139
140
  should "return a url based on an Cloud Files path" do
140
- assert_match %r{^http://c0010181.cdn.cloudfiles.rackspacecloud.com/avatars/stringio.txt}, @dummy.avatar.url
141
+ assert_match %r{^http://c186397.r97.cf1.rackcdn.com/avatars/stringio.txt}, @dummy.avatar.url
141
142
  end
142
143
  end
143
144
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paperclip-cloudfiles
3
3
  version: !ruby/object:Gem::Version
4
- hash: 85
4
+ hash: 81
5
5
  prerelease:
6
6
  segments:
7
7
  - 2
8
8
  - 3
9
9
  - 8
10
- - 1
11
- version: 2.3.8.1
10
+ - 3
11
+ version: 2.3.8.3
12
12
  platform: ruby
13
13
  authors:
14
14
  - Jon Yurek
@@ -17,7 +17,7 @@ autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
19
 
20
- date: 2011-02-05 00:00:00 -05:00
20
+ date: 2011-03-10 00:00:00 -05:00
21
21
  default_executable:
22
22
  dependencies:
23
23
  - !ruby/object:Gem::Dependency
@@ -112,12 +112,12 @@ dependencies:
112
112
  requirements:
113
113
  - - ">="
114
114
  - !ruby/object:Gem::Version
115
- hash: 21
115
+ hash: 25
116
116
  segments:
117
117
  - 1
118
118
  - 4
119
- - 9
120
- version: 1.4.9
119
+ - 15
120
+ version: 1.4.15
121
121
  name: cloudfiles
122
122
  version_requirements: *id007
123
123
  - !ruby/object:Gem::Dependency
@@ -186,6 +186,7 @@ files:
186
186
  - test/fixtures/s3.yml
187
187
  - test/fixtures/text.txt
188
188
  - test/fixtures/twopage.pdf
189
+ - test/fixtures/uppercase.PNG
189
190
  - test/geometry_test.rb
190
191
  - test/helper.rb
191
192
  - test/integration_test.rb