paperclip-cloudfiles 2.3.8.1 → 2.3.8.3

Sign up to get free protection for your applications and to get access to all the features.
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