paperclip 2.2.2 → 2.2.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of paperclip might be problematic. Click here for more details.

@@ -109,7 +109,7 @@ a set of styles for an attachment, by default it is expected that those
109
109
  "styles" are actually "thumbnails". However, you can do more than just
110
110
  thumbnail images. By defining a subclass of Paperclip::Processor, you can
111
111
  perform any processing you want on the files that are attached. Any file in
112
- your Rails app's lib/paperclip_processor directory is automatically loaded by
112
+ your Rails app's lib/paperclip_processors directory is automatically loaded by
113
113
  paperclip, allowing you to easily define custom processors. You can specify a
114
114
  processor with the :processors option to has_attached_file:
115
115
 
@@ -152,7 +152,7 @@ are called before and after the processing of each attachment), and the
152
152
  attachment-specific "before_<attachment>_post_process" and
153
153
  "after_<attachment>_post_process". The callbacks are intended to be as close to
154
154
  normal ActiveRecord callbacks as possible, so if you return false (specifically
155
- -- returning nil is not the same) in a before_ filter, the post processing step
155
+ - returning nil is not the same) in a before_ filter, the post processing step
156
156
  will halt. Returning false in an after_ filter will not halt anything, but you
157
157
  can access the model and the attachment if necessary.
158
158
 
data/Rakefile CHANGED
@@ -1,7 +1,6 @@
1
1
  require 'rake'
2
2
  require 'rake/testtask'
3
3
  require 'rake/rdoctask'
4
- require 'rake/gempackagetask'
5
4
 
6
5
  $LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
7
6
  require 'paperclip'
@@ -70,18 +69,6 @@ spec = Gem::Specification.new do |s|
70
69
  s.add_development_dependency 'mocha'
71
70
  end
72
71
 
73
- desc "Release new version"
74
- task :release => [:test, :sync_docs, :gem] do
75
- require 'rubygems'
76
- require 'rubyforge'
77
- r = RubyForge.new
78
- r.login
79
- r.add_release spec.rubyforge_project,
80
- spec.name,
81
- spec.version,
82
- File.join("pkg", "#{spec.name}-#{spec.version}.gem")
83
- end
84
-
85
72
  desc "Generate a gemspec file for GitHub"
86
73
  task :gemspec do
87
74
  File.open("#{spec.name}.gemspec", 'w') do |f|
@@ -43,7 +43,7 @@ end
43
43
  # documentation for Paperclip::ClassMethods for more useful information.
44
44
  module Paperclip
45
45
 
46
- VERSION = "2.2.2"
46
+ VERSION = "2.2.3"
47
47
 
48
48
  class << self
49
49
  # Provides configurability to Paperclip. There are a number of options available, such as:
@@ -60,20 +60,23 @@ module Paperclip
60
60
  :whiny_thumbnails => true,
61
61
  :image_magick_path => nil,
62
62
  :command_path => nil,
63
- :log => true
63
+ :log => true,
64
+ :swallow_stderr => true
64
65
  }
65
66
  end
66
67
 
67
68
  def path_for_command command #:nodoc:
68
69
  if options[:image_magick_path]
69
- ActiveSupport::Deprecation.warn(":image_magick_path is deprecated and "+
70
- "will be removed. Use :command_path "+
71
- "instead")
70
+ warn("[DEPRECATION] :image_magick_path is deprecated and will be removed. Use :command_path instead")
72
71
  end
73
- path = [options[:image_magick_path] || options[:command_path], command].compact
72
+ path = [options[:command_path] || options[:image_magick_path], command].compact
74
73
  File.join(*path)
75
74
  end
76
75
 
76
+ def interpolates key, &block
77
+ Paperclip::Attachment.interpolations[key] = block
78
+ end
79
+
77
80
  # The run method takes a command to execute and a string of parameters
78
81
  # that get passed to it. The command is prefixed with the :command_path
79
82
  # option from Paperclip.options. If you have many commands to run and
@@ -84,7 +87,9 @@ module Paperclip
84
87
  # expected_outcodes, a PaperclipCommandLineError will be raised. Generally
85
88
  # a code of 0 is expected, but a list of codes may be passed if necessary.
86
89
  def run cmd, params = "", expected_outcodes = 0
87
- output = `#{%Q[#{path_for_command(cmd)} #{params} 2>#{bit_bucket}].gsub(/\s+/, " ")}`
90
+ command = %Q<#{%Q[#{path_for_command(cmd)} #{params}].gsub(/\s+/, " ")}>
91
+ command = "#{command} 2>#{bit_bucket}" if Paperclip.options[:swallow_stderr]
92
+ output = `#{command}`
88
93
  unless [expected_outcodes].flatten.include?($?.exitstatus)
89
94
  raise PaperclipCommandLineError, "Error while running #{cmd}"
90
95
  end
@@ -204,7 +209,8 @@ module Paperclip
204
209
  end
205
210
 
206
211
  validates_each(name) do |record, attr, value|
207
- value.send(:flush_errors) unless value.valid?
212
+ attachment = record.attachment_for(name)
213
+ attachment.send(:flush_errors) unless attachment.valid?
208
214
  end
209
215
  end
210
216
 
@@ -250,6 +256,9 @@ module Paperclip
250
256
  # match. Allows all by default.
251
257
  # * +message+: The message to display when the uploaded file has an invalid
252
258
  # content type.
259
+ # NOTE: If you do not specify an [attachment]_content_type field on your
260
+ # model, content_type validation will work _ONLY upon assignment_ and
261
+ # re-validation after the instance has been reloaded will always succeed.
253
262
  def validates_attachment_content_type name, options = {}
254
263
  attachment_definitions[name][:validations][:content_type] = lambda do |attachment, instance|
255
264
  valid_types = [options[:content_type]].flatten
@@ -257,7 +266,7 @@ module Paperclip
257
266
  unless attachment.original_filename.blank?
258
267
  unless valid_types.blank?
259
268
  content_type = attachment.instance_read(:content_type)
260
- unless valid_types.any?{|t| t === content_type }
269
+ unless valid_types.any?{|t| content_type.nil? || t === content_type }
261
270
  options[:message] || "is not one of the allowed file types."
262
271
  end
263
272
  end
@@ -89,6 +89,7 @@ module Paperclip
89
89
 
90
90
  @dirty = true
91
91
 
92
+ solidify_style_definitions
92
93
  post_process if valid?
93
94
 
94
95
  # Reset the file size if the original file was reprocessed.
@@ -241,6 +242,7 @@ module Paperclip
241
242
  def instance_write(attr, value)
242
243
  setter = :"#{name}_#{attr}="
243
244
  responds = instance.respond_to?(setter)
245
+ self.instance_variable_set("@_#{setter.to_s.chop}", value)
244
246
  instance.send(setter, value) if responds || attr.to_s == "file_name"
245
247
  end
246
248
 
@@ -249,20 +251,22 @@ module Paperclip
249
251
  def instance_read(attr)
250
252
  getter = :"#{name}_#{attr}"
251
253
  responds = instance.respond_to?(getter)
254
+ cached = self.instance_variable_get("@_#{getter}")
255
+ return cached if cached
252
256
  instance.send(getter) if responds || attr.to_s == "file_name"
253
257
  end
254
258
 
255
259
  private
256
260
 
257
- def logger
261
+ def logger #:nodoc:
258
262
  instance.logger
259
263
  end
260
264
 
261
- def log message
265
+ def log message #:nodoc:
262
266
  logger.info("[paperclip] #{message}") if logging?
263
267
  end
264
268
 
265
- def logging?
269
+ def logging? #:nodoc:
266
270
  Paperclip.options[:log]
267
271
  end
268
272
 
@@ -283,7 +287,7 @@ module Paperclip
283
287
  @validation_errors
284
288
  end
285
289
 
286
- def normalize_style_definition
290
+ def normalize_style_definition #:nodoc:
287
291
  @styles.each do |name, args|
288
292
  unless args.is_a? Hash
289
293
  dimensions, format = [args, nil].flatten[0..1]
@@ -305,7 +309,15 @@ module Paperclip
305
309
  end
306
310
  end
307
311
 
308
- def initialize_storage
312
+ def solidify_style_definitions #:nodoc:
313
+ @styles.each do |name, args|
314
+ if @styles[name][:geometry].respond_to?(:call)
315
+ @styles[name][:geometry] = @styles[name][:geometry].call(instance)
316
+ end
317
+ end
318
+ end
319
+
320
+ def initialize_storage #:nodoc:
309
321
  @storage_module = Paperclip::Storage.const_get(@storage.to_s.capitalize)
310
322
  self.extend(@storage_module)
311
323
  end
@@ -321,8 +333,17 @@ module Paperclip
321
333
 
322
334
  def post_process #:nodoc:
323
335
  return if @queued_for_write[:original].nil?
324
- return if callback(:before_post_process) == false
325
- return if callback(:"before_#{name}_post_process") == false
336
+ return if fire_events(:before)
337
+ post_process_styles
338
+ return if fire_events(:after)
339
+ end
340
+
341
+ def fire_events(which)
342
+ return true if callback(:"#{which}_post_process") == false
343
+ return true if callback(:"#{which}_#{name}_post_process") == false
344
+ end
345
+
346
+ def post_process_styles
326
347
  log("Post-processing #{name}")
327
348
  @styles.each do |name, args|
328
349
  begin
@@ -336,11 +357,9 @@ module Paperclip
336
357
  (@errors[:processing] ||= []) << e.message if @whiny
337
358
  end
338
359
  end
339
- callback(:"after_#{name}_post_process")
340
- callback(:after_post_process)
341
360
  end
342
361
 
343
- def callback which
362
+ def callback which #:nodoc:
344
363
  instance.run_callbacks(which, @queued_for_write){|result, obj| result == false }
345
364
  end
346
365
 
@@ -16,7 +16,7 @@ module Paperclip
16
16
  def self.from_file file
17
17
  file = file.path if file.respond_to? "path"
18
18
  geometry = begin
19
- Paperclip.run("identify", %Q[-format "%wx%h" "#{file}"])
19
+ Paperclip.run("identify", %Q[-format "%wx%h" "#{file}"[0]])
20
20
  rescue PaperclipCommandLineError
21
21
  ""
22
22
  end
@@ -61,8 +61,11 @@ module Paperclip
61
61
  path = File.dirname(path)
62
62
  FileUtils.rmdir(path)
63
63
  end
64
- rescue Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL
64
+ rescue Errno::EEXIST, Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL, Errno::ENOTDIR
65
65
  # Stop trying to remove parent directories
66
+ rescue SystemCallError => e
67
+ logger.info("[paperclip] There was an unexpected error while deleting directories: #{e.class}")
68
+ # Ignore it
66
69
  end
67
70
  end
68
71
  @queued_for_delete = []
@@ -118,11 +121,11 @@ module Paperclip
118
121
  require 'right_aws'
119
122
  base.instance_eval do
120
123
  @s3_credentials = parse_credentials(@options[:s3_credentials])
121
- @bucket = @options[:bucket] || @s3_credentials[:bucket]
122
- @s3_options = @options[:s3_options] || {}
124
+ @bucket = @options[:bucket] || @s3_credentials[:bucket]
125
+ @s3_options = @options[:s3_options] || {}
123
126
  @s3_permissions = @options[:s3_permissions] || 'public-read'
124
- @s3_protocol = @options[:s3_protocol] || (@s3_permissions == 'public-read' ? 'http' : 'https')
125
- @s3_headers = @options[:s3_headers] || {}
127
+ @s3_protocol = @options[:s3_protocol] || (@s3_permissions == 'public-read' ? 'http' : 'https')
128
+ @s3_headers = @options[:s3_headers] || {}
126
129
  @url = ":s3_path_url" unless @url.to_s.match(/^:s3.*url$/)
127
130
  end
128
131
  base.class.interpolations[:s3_path_url] = lambda do |attachment, style|
@@ -0,0 +1,4 @@
1
+ require 'shoulda_macros/matchers/have_attached_file_matcher'
2
+ require 'shoulda_macros/matchers/validate_attachment_presence_matcher'
3
+ require 'shoulda_macros/matchers/validate_attachment_content_type_matcher'
4
+ require 'shoulda_macros/matchers/validate_attachment_size_matcher'
@@ -0,0 +1,49 @@
1
+ module Paperclip
2
+ module Shoulda
3
+ module Matchers
4
+ def have_attached_file name
5
+ HaveAttachedFileMatcher.new(name)
6
+ end
7
+
8
+ class HaveAttachedFileMatcher
9
+ def initialize attachment_name
10
+ @attachment_name = attachment_name
11
+ end
12
+
13
+ def matches? subject
14
+ @subject = subject
15
+ responds? && has_column? && included?
16
+ end
17
+
18
+ def failure_message
19
+ "Should have an attachment named #{@attachment_name}"
20
+ end
21
+
22
+ def negative_failure_message
23
+ "Should not have an attachment named #{@attachment_name}"
24
+ end
25
+
26
+ def description
27
+ "have an attachment named #{@attachment_name}"
28
+ end
29
+
30
+ protected
31
+
32
+ def responds?
33
+ methods = @subject.instance_methods
34
+ methods.include?("#{@attachment_name}") &&
35
+ methods.include?("#{@attachment_name}=") &&
36
+ methods.include?("#{@attachment_name}?")
37
+ end
38
+
39
+ def has_column?
40
+ @subject.column_names.include?("#{@attachment_name}_file_name")
41
+ end
42
+
43
+ def included?
44
+ @subject.ancestors.include?(Paperclip::InstanceMethods)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,66 @@
1
+ module Paperclip
2
+ module Shoulda
3
+ module Matchers
4
+ def validate_attachment_content_type name
5
+ ValidateAttachmentContentTypeMatcher.new(name)
6
+ end
7
+
8
+ class ValidateAttachmentContentTypeMatcher
9
+ def initialize attachment_name
10
+ @attachment_name = attachment_name
11
+ end
12
+
13
+ def allowing *types
14
+ @allowed_types = types.flatten
15
+ self
16
+ end
17
+
18
+ def rejecting *types
19
+ @rejected_types = types.flatten
20
+ self
21
+ end
22
+
23
+ def matches? subject
24
+ @subject = subject
25
+ @allowed_types && @rejected_types &&
26
+ allowed_types_allowed? && rejected_types_rejected?
27
+ end
28
+
29
+ def failure_message
30
+ "Content types #{@allowed_types.join(", ")} should be accepted" +
31
+ " and #{@rejected_types.join(", ")} rejected by #{@attachment_name}"
32
+ end
33
+
34
+ def negative_failure_message
35
+ "Content types #{@allowed_types.join(", ")} should be rejected" +
36
+ " and #{@rejected_types.join(", ")} accepted by #{@attachment_name}"
37
+ end
38
+
39
+ def description
40
+ "validate the content types allowed on attachment #{@attachment_name}"
41
+ end
42
+
43
+ protected
44
+
45
+ def allow_types?(types)
46
+ types.all? do |type|
47
+ file = StringIO.new(".")
48
+ file.content_type = type
49
+ attachment = @subject.new.attachment_for(@attachment_name)
50
+ attachment.assign(file)
51
+ attachment.errors[:content_type].nil?
52
+ end
53
+ end
54
+
55
+ def allowed_types_allowed?
56
+ allow_types?(@allowed_types)
57
+ end
58
+
59
+ def rejected_types_rejected?
60
+ not allow_types?(@rejected_types)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
@@ -0,0 +1,48 @@
1
+ module Paperclip
2
+ module Shoulda
3
+ module Matchers
4
+ def validate_attachment_presence name
5
+ ValidateAttachmentPresenceMatcher.new(name)
6
+ end
7
+
8
+ class ValidateAttachmentPresenceMatcher
9
+ def initialize attachment_name
10
+ @attachment_name = attachment_name
11
+ end
12
+
13
+ def matches? subject
14
+ @subject = subject
15
+ error_when_not_valid? && no_error_when_valid?
16
+ end
17
+
18
+ def failure_message
19
+ "Attachment #{@attachment_name} should be required"
20
+ end
21
+
22
+ def negative_failure_message
23
+ "Attachment #{@attachment_name} should not be required"
24
+ end
25
+
26
+ def description
27
+ "require presence of attachment #{@attachment_name}"
28
+ end
29
+
30
+ protected
31
+
32
+ def error_when_not_valid?
33
+ @attachment = @subject.new.send(@attachment_name)
34
+ @attachment.assign(nil)
35
+ not @attachment.errors[:presence].nil?
36
+ end
37
+
38
+ def no_error_when_valid?
39
+ @file = StringIO.new(".")
40
+ @attachment = @subject.new.send(@attachment_name)
41
+ @attachment.assign(@file)
42
+ @attachment.errors[:presence].nil?
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
@@ -0,0 +1,83 @@
1
+ module Paperclip
2
+ module Shoulda
3
+ module Matchers
4
+ def validate_attachment_size name
5
+ ValidateAttachmentSizeMatcher.new(name)
6
+ end
7
+
8
+ class ValidateAttachmentSizeMatcher
9
+ def initialize attachment_name
10
+ @attachment_name = attachment_name
11
+ @low, @high = 0, (1.0/0)
12
+ end
13
+
14
+ def less_than size
15
+ @high = size
16
+ self
17
+ end
18
+
19
+ def greater_than size
20
+ @low = size
21
+ self
22
+ end
23
+
24
+ def in range
25
+ @low, @high = range.first, range.last
26
+ self
27
+ end
28
+
29
+ def matches? subject
30
+ @subject = subject
31
+ lower_than_low? && higher_than_low? && lower_than_high? && higher_than_high?
32
+ end
33
+
34
+ def failure_message
35
+ "Attachment #{@attachment_name} must be between #{@low} and #{@high} bytes"
36
+ end
37
+
38
+ def negative_failure_message
39
+ "Attachment #{@attachment_name} cannot be between #{@low} and #{@high} bytes"
40
+ end
41
+
42
+ def description
43
+ "validate the size of attachment #{@attachment_name}"
44
+ end
45
+
46
+ protected
47
+
48
+ def override_method object, method, &replacement
49
+ (class << object; self; end).class_eval do
50
+ define_method(method, &replacement)
51
+ end
52
+ end
53
+
54
+ def passes_validation_with_size(new_size)
55
+ file = StringIO.new(".")
56
+ override_method(file, :size){ new_size }
57
+ attachment = @subject.new.attachment_for(@attachment_name)
58
+ attachment.assign(file)
59
+ attachment.errors[:size].nil?
60
+ end
61
+
62
+ def lower_than_low?
63
+ not passes_validation_with_size(@low - 1)
64
+ end
65
+
66
+ def higher_than_low?
67
+ passes_validation_with_size(@low + 1)
68
+ end
69
+
70
+ def lower_than_high?
71
+ return true if @high == (1.0/0)
72
+ passes_validation_with_size(@high - 1)
73
+ end
74
+
75
+ def higher_than_high?
76
+ return true if @high == (1.0/0)
77
+ not passes_validation_with_size(@high + 1)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
@@ -1,3 +1,5 @@
1
+ require 'shoulda_macros/matchers'
2
+
1
3
  module Paperclip
2
4
  # =Paperclip Shoulda Macros
3
5
  #
@@ -10,39 +12,20 @@ module Paperclip
10
12
  # This will test whether you have defined your attachment correctly by
11
13
  # checking for all the required fields exist after the definition of the
12
14
  # attachment.
13
- def should_have_attached_file name, options = {}
14
- klass = self.name.gsub(/Test$/, '').constantize
15
- context "Class #{klass.name} with attachment #{name}" do
16
- should "respond to all the right methods" do
17
- ["#{name}", "#{name}=", "#{name}?"].each do |meth|
18
- assert klass.instance_methods.include?(meth), "#{klass.name} does not respond to #{name}."
19
- end
20
- end
15
+ def should_have_attached_file name
16
+ klass = self.name.gsub(/Test$/, '').constantize
17
+ matcher = have_attached_file name
18
+ should matcher.description do
19
+ assert_accepts(matcher, klass)
21
20
  end
22
21
  end
23
22
 
24
23
  # Tests for validations on the presence of the attachment.
25
24
  def should_validate_attachment_presence name
26
25
  klass = self.name.gsub(/Test$/, '').constantize
27
- context "Class #{klass.name} validating presence on #{name}" do
28
- context "when the assignment is nil" do
29
- setup do
30
- @attachment = klass.new.send(name)
31
- @attachment.assign(nil)
32
- end
33
- should "have a :presence validation error" do
34
- assert @assignment.errors[:presence]
35
- end
36
- end
37
- context "when the assignment is valid" do
38
- setup do
39
- @attachment = klass.new.send(name)
40
- @attachment.assign(nil)
41
- end
42
- should "have a :presence validation error" do
43
- assert ! @assignment.errors[:presence]
44
- end
45
- end
26
+ matcher = validate_attachment_presence name
27
+ should matcher.description do
28
+ assert_accepts(matcher, klass)
46
29
  end
47
30
  end
48
31
 
@@ -54,35 +37,9 @@ module Paperclip
54
37
  klass = self.name.gsub(/Test$/, '').constantize
55
38
  valid = [options[:valid]].flatten
56
39
  invalid = [options[:invalid]].flatten
57
- context "Class #{klass.name} validating content_types on #{name}" do
58
- valid.each do |type|
59
- context "being assigned a file with a content_type of #{type}" do
60
- setup do
61
- @file = StringIO.new(".")
62
- class << @file; attr_accessor :content_type; end
63
- @file.content_type = type
64
- @attachment = klass.new.send(name)
65
- @attachment.assign(@file)
66
- end
67
- should "not have a :content_type validation error" do
68
- assert ! @assignment.errors[:content_type]
69
- end
70
- end
71
- end
72
- invalid.each do |type|
73
- context "being assigned a file with a content_type of #{type}" do
74
- setup do
75
- @file = StringIO.new(".")
76
- class << @file; attr_accessor :content_type; end
77
- @file.content_type = type
78
- @attachment = klass.new.send(name)
79
- @attachment.assign(@file)
80
- end
81
- should "have a :content_type validation error" do
82
- assert @assignment.errors[:content_type]
83
- end
84
- end
85
- end
40
+ matcher = validate_attachment_presence(name).allows(valid).rejects(invalid)
41
+ should matcher.description do
42
+ assert_accepts(matcher, klass)
86
43
  end
87
44
  end
88
45
 
@@ -97,57 +54,15 @@ module Paperclip
97
54
  min = options[:greater_than] || (options[:in] && options[:in].first) || 0
98
55
  max = options[:less_than] || (options[:in] && options[:in].last) || (1.0/0)
99
56
  range = (min..max)
100
- context "Class #{klass.name} validating file size on #{name}" do
101
- context "with an attachment that is #{max+1} bytes" do
102
- setup do
103
- @file = StringIO.new("." * (max+1))
104
- @attachment = klass.new.send(name)
105
- @attachment.assign(@file)
106
- end
107
-
108
- should "have a :size validation error" do
109
- assert @attachment.errors[:size]
110
- end
111
- end
112
- context "with an attachment that us #{max-1} bytes" do
113
- setup do
114
- @file = StringIO.new("." * (max-1))
115
- @attachment = klass.new.send(name)
116
- @attachment.assign(@file)
117
- end
118
-
119
- should "not have a :size validation error" do
120
- assert ! @attachment.errors[:size]
121
- end
122
- end
123
-
124
- if min > 0
125
- context "with an attachment that is #{min-1} bytes" do
126
- setup do
127
- @file = StringIO.new("." * (min-1))
128
- @attachment = klass.new.send(name)
129
- @attachment.assign(@file)
130
- end
131
-
132
- should "have a :size validation error" do
133
- assert @attachment.errors[:size]
134
- end
135
- end
136
- context "with an attachment that us #{min+1} bytes" do
137
- setup do
138
- @file = StringIO.new("." * (min+1))
139
- @attachment = klass.new.send(name)
140
- @attachment.assign(@file)
141
- end
142
-
143
- should "not have a :size validation error" do
144
- assert ! @attachment.errors[:size]
145
- end
146
- end
147
- end
57
+ matcher = validate_attachment_size(name).in(range)
58
+ should matcher.description do
59
+ assert_accepts(matcher, klass)
148
60
  end
149
61
  end
150
62
  end
151
63
  end
152
64
 
153
- Test::Unit::TestCase.extend(Paperclip::Shoulda)
65
+ class Test::Unit::TestCase #:nodoc:
66
+ extend Paperclip::Shoulda
67
+ include Paperclip::Shoulda::Matchers
68
+ end
@@ -17,7 +17,7 @@ end
17
17
  def for_all_attachments
18
18
  klass = obtain_class
19
19
  names = obtain_attachments
20
- ids = klass.connection.select_values("SELECT #{klass.primary_key} FROM #{klass.table_name}")
20
+ ids = klass.connection.select_values(klass.send(:construct_finder_sql, :select => 'id'))
21
21
 
22
22
  ids.each do |id|
23
23
  instance = klass.find(id)
@@ -95,14 +95,10 @@ class AttachmentTest < Test::Unit::TestCase
95
95
  rebuild_model :path => ":rails_env/:id.png"
96
96
  @dummy = Dummy.new
97
97
  @dummy.stubs(:id).returns(@id)
98
- @file = File.new(File.join(File.dirname(__FILE__),
99
- "fixtures",
100
- "5k.png"), 'rb')
98
+ @file = StringIO.new(".")
101
99
  @dummy.avatar = @file
102
100
  end
103
101
 
104
- teardown { @file.close }
105
-
106
102
  should "return the proper path" do
107
103
  temporary_rails_env(@rails_env) {
108
104
  assert_equal "#{@rails_env}/#{@id}.png", @dummy.avatar.path
@@ -170,6 +166,35 @@ class AttachmentTest < Test::Unit::TestCase
170
166
  end
171
167
  end
172
168
 
169
+ geometry_specs = [
170
+ [ lambda{|z| "50x50#" }, :png ],
171
+ lambda{|z| "50x50#" },
172
+ { :geometry => lambda{|z| "50x50#" } }
173
+ ]
174
+ geometry_specs.each do |geometry_spec|
175
+ context "An attachment geometry like #{geometry_spec}" do
176
+ setup do
177
+ rebuild_model :styles => { :normal => geometry_spec }
178
+ @attachment = Dummy.new.avatar
179
+ end
180
+
181
+ should "not run the procs immediately" do
182
+ assert_kind_of Proc, @attachment.styles[:normal][:geometry]
183
+ end
184
+
185
+ context "when assigned" do
186
+ setup do
187
+ @file = StringIO.new(".")
188
+ @attachment.assign(@file)
189
+ end
190
+
191
+ should "have the correct geometry" do
192
+ assert_equal "50x50#", @attachment.styles[:normal][:geometry]
193
+ end
194
+ end
195
+ end
196
+ end
197
+
173
198
  context "An attachment with both 'normal' and hash-style styles" do
174
199
  setup do
175
200
  rebuild_model :styles => {
@@ -234,7 +259,7 @@ class AttachmentTest < Test::Unit::TestCase
234
259
  def do_before_all; end
235
260
  def do_after_all; end
236
261
  end
237
- @file = File.new(File.join(FIXTURES_DIR, "5k.png"), 'rb')
262
+ @file = StringIO.new(".")
238
263
  @file.stubs(:to_tempfile).returns(@file)
239
264
  @dummy = Dummy.new
240
265
  Paperclip::Thumbnail.stubs(:make).returns(@file)
@@ -281,7 +306,7 @@ class AttachmentTest < Test::Unit::TestCase
281
306
  context "Assigning an attachment" do
282
307
  setup do
283
308
  rebuild_model :styles => { :something => "100x100#" }
284
- @file = File.new(File.join(FIXTURES_DIR, "5k.png"), 'rb')
309
+ @file = StringIO.new(".")
285
310
  @file.expects(:original_filename).returns("5k.png\n\n")
286
311
  @file.expects(:content_type).returns("image/png\n\n")
287
312
  @file.stubs(:to_tempfile).returns(@file)
@@ -521,8 +546,16 @@ class AttachmentTest < Test::Unit::TestCase
521
546
  assert_nothing_raised { @dummy.avatar = @file }
522
547
  end
523
548
 
524
- should "return nil when sent #avatar_updated_at" do
549
+ should "return the time when sent #avatar_updated_at" do
550
+ now = Time.now
551
+ Time.stubs(:now).returns(now)
525
552
  @dummy.avatar = @file
553
+ assert now, @dummy.avatar.updated_at
554
+ end
555
+
556
+ should "return nil when reloaded and sent #avatar_updated_at" do
557
+ @dummy.save
558
+ @dummy.reload
526
559
  assert_nil @dummy.avatar.updated_at
527
560
  end
528
561
 
Binary file
@@ -1,5 +1,6 @@
1
1
  require 'rubygems'
2
2
  require 'test/unit'
3
+ gem 'thoughtbot-shoulda', ">= 2.9.0"
3
4
  require 'shoulda'
4
5
  require 'mocha'
5
6
  require 'tempfile'
@@ -22,12 +23,31 @@ $LOAD_PATH << File.join(ROOT, 'lib', 'paperclip')
22
23
 
23
24
  require File.join(ROOT, 'lib', 'paperclip.rb')
24
25
 
26
+ require 'shoulda_macros/paperclip'
27
+
25
28
  ENV['RAILS_ENV'] ||= 'test'
26
29
 
27
30
  FIXTURES_DIR = File.join(File.dirname(__FILE__), "fixtures")
28
31
  config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
29
32
  ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
30
- ActiveRecord::Base.establish_connection(config[ENV['RAILS_ENV'] || 'test'])
33
+ ActiveRecord::Base.establish_connection(config['test'])
34
+
35
+ def reset_class class_name
36
+ ActiveRecord::Base.send(:include, Paperclip)
37
+ Object.send(:remove_const, class_name) rescue nil
38
+ klass = Object.const_set(class_name, Class.new(ActiveRecord::Base))
39
+ klass.class_eval{ include Paperclip }
40
+ klass
41
+ end
42
+
43
+ def reset_table table_name, &block
44
+ block ||= lambda{ true }
45
+ ActiveRecord::Base.connection.create_table :dummies, {:force => true}, &block
46
+ end
47
+
48
+ def modify_table table_name, &block
49
+ ActiveRecord::Base.connection.change_table :dummies, &block
50
+ end
31
51
 
32
52
  def rebuild_model options = {}
33
53
  ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
@@ -106,6 +106,10 @@ class IntegrationTest < Test::Unit::TestCase
106
106
  assert ! File.exists?(File.dirname(@saved_path))
107
107
  assert ! File.exists?(File.dirname(File.dirname(@saved_path)))
108
108
  end
109
+
110
+ before_should "not die if an unexpected SystemCallError happens" do
111
+ FileUtils.stubs(:rmdir).raises(Errno::EPIPE)
112
+ end
109
113
  end
110
114
  end
111
115
  end
@@ -0,0 +1,21 @@
1
+ require 'test/helper'
2
+
3
+ class HaveAttachedFileMatcherTest < Test::Unit::TestCase
4
+ context "have_attached_file" do
5
+ setup do
6
+ @dummy_class = reset_class "Dummy"
7
+ reset_table "dummies"
8
+ @matcher = have_attached_file(:avatar)
9
+ end
10
+
11
+ should "reject a class with no attachment" do
12
+ assert_rejects @matcher, @dummy_class
13
+ end
14
+
15
+ should "accept a class with an attachment" do
16
+ modify_table("dummies"){|d| d.string :avatar_file_name }
17
+ @dummy_class.has_attached_file :avatar
18
+ assert_accepts @matcher, @dummy_class
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,30 @@
1
+ require 'test/helper'
2
+
3
+ class ValidateAttachmentContentTypeMatcherTest < Test::Unit::TestCase
4
+ context "validate_attachment_content_type" do
5
+ setup do
6
+ reset_table("dummies") do |d|
7
+ d.string :avatar_file_name
8
+ end
9
+ @dummy_class = reset_class "Dummy"
10
+ @dummy_class.has_attached_file :avatar
11
+ @matcher = validate_attachment_content_type(:avatar).
12
+ allowing(%w(image/png image/jpeg)).
13
+ rejecting(%w(audio/mp3 application/octet-stream))
14
+ end
15
+
16
+ should "reject a class with no validation" do
17
+ assert_rejects @matcher, @dummy_class
18
+ end
19
+
20
+ should "reject a class with a validation that doesn't match" do
21
+ @dummy_class.validates_attachment_content_type :avatar, :content_type => %r{audio/.*}
22
+ assert_rejects @matcher, @dummy_class
23
+ end
24
+
25
+ should "accept a class with a validation" do
26
+ @dummy_class.validates_attachment_content_type :avatar, :content_type => %r{image/.*}
27
+ assert_accepts @matcher, @dummy_class
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,21 @@
1
+ require 'test/helper'
2
+
3
+ class ValidateAttachmentPresenceMatcherTest < Test::Unit::TestCase
4
+ context "validate_attachment_presence" do
5
+ setup do
6
+ reset_table("dummies"){|d| d.string :avatar_file_name }
7
+ @dummy_class = reset_class "Dummy"
8
+ @dummy_class.has_attached_file :avatar
9
+ @matcher = validate_attachment_presence(:avatar)
10
+ end
11
+
12
+ should "reject a class with no validation" do
13
+ assert_rejects @matcher, @dummy_class
14
+ end
15
+
16
+ should "accept a class with a validation" do
17
+ @dummy_class.validates_attachment_presence :avatar
18
+ assert_accepts @matcher, @dummy_class
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,50 @@
1
+ require 'test/helper'
2
+
3
+ class ValidateAttachmentSizeMatcherTest < Test::Unit::TestCase
4
+ context "validate_attachment_size" do
5
+ setup do
6
+ reset_table("dummies") do |d|
7
+ d.string :avatar_file_name
8
+ end
9
+ @dummy_class = reset_class "Dummy"
10
+ @dummy_class.has_attached_file :avatar
11
+ end
12
+
13
+ context "of limited size" do
14
+ setup{ @matcher = validate_attachment_size(:avatar).in(256..1024) }
15
+
16
+ should "reject a class with no validation" do
17
+ assert_rejects @matcher, @dummy_class
18
+ end
19
+
20
+ should "reject a class with a validation that's too high" do
21
+ @dummy_class.validates_attachment_size :avatar, :in => 256..2048
22
+ assert_rejects @matcher, @dummy_class
23
+ end
24
+
25
+ should "reject a class with a validation that's too low" do
26
+ @dummy_class.validates_attachment_size :avatar, :in => 0..1024
27
+ assert_rejects @matcher, @dummy_class
28
+ end
29
+
30
+ should "accept a class with a validation that matches" do
31
+ @dummy_class.validates_attachment_size :avatar, :in => 256..1024
32
+ assert_accepts @matcher, @dummy_class
33
+ end
34
+ end
35
+
36
+ context "validates_attachment_size with infinite range" do
37
+ setup{ @matcher = validate_attachment_size(:avatar) }
38
+
39
+ should "accept a class with an upper limit" do
40
+ @dummy_class.validates_attachment_size :avatar, :less_than => 1
41
+ assert_accepts @matcher, @dummy_class
42
+ end
43
+
44
+ should "accept a class with no upper limit" do
45
+ @dummy_class.validates_attachment_size :avatar, :greater_than => 1
46
+ assert_accepts @matcher, @dummy_class
47
+ end
48
+ end
49
+ end
50
+ end
@@ -141,4 +141,37 @@ class ThumbnailTest < Test::Unit::TestCase
141
141
  end
142
142
  end
143
143
  end
144
+
145
+ context "A multipage PDF" do
146
+ setup do
147
+ @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "twopage.pdf"), 'rb')
148
+ end
149
+
150
+ teardown { @file.close }
151
+
152
+ should "start with two pages with dimensions 612x792" do
153
+ cmd = %Q[identify -format "%wx%h" "#{@file.path}"]
154
+ assert_equal "612x792"*2, `#{cmd}`.chomp
155
+ end
156
+
157
+ context "being thumbnailed at 100x100 with cropping" do
158
+ setup do
159
+ @thumb = Paperclip::Thumbnail.new(@file, :geometry => "100x100#", :format => :png)
160
+ end
161
+
162
+ should "report its correct current and target geometries" do
163
+ assert_equal "100x100#", @thumb.target_geometry.to_s
164
+ assert_equal "612x792", @thumb.current_geometry.to_s
165
+ end
166
+
167
+ should "report its correct format" do
168
+ assert_equal :png, @thumb.format
169
+ end
170
+
171
+ should "create the thumbnail when sent #make" do
172
+ dst = @thumb.make
173
+ assert_match /100x100/, `identify "#{dst.path}"`
174
+ end
175
+ end
176
+ end
144
177
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paperclip
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.2
4
+ version: 2.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Yurek
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-12-30 00:00:00 -05:00
12
+ date: 2009-02-07 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -76,15 +76,25 @@ files:
76
76
  - test/fixtures/5k.png
77
77
  - test/fixtures/bad.png
78
78
  - test/fixtures/text.txt
79
+ - test/fixtures/twopage.pdf
79
80
  - test/geometry_test.rb
80
81
  - test/helper.rb
81
82
  - test/integration_test.rb
82
83
  - test/iostream_test.rb
84
+ - test/matchers/have_attached_file_matcher_test.rb
85
+ - test/matchers/validate_attachment_content_type_matcher_test.rb
86
+ - test/matchers/validate_attachment_presence_matcher_test.rb
87
+ - test/matchers/validate_attachment_size_matcher_test.rb
83
88
  - test/paperclip_test.rb
84
89
  - test/processor_test.rb
85
90
  - test/s3.yml
86
91
  - test/storage_test.rb
87
92
  - test/thumbnail_test.rb
93
+ - shoulda_macros/matchers/have_attached_file_matcher.rb
94
+ - shoulda_macros/matchers/validate_attachment_content_type_matcher.rb
95
+ - shoulda_macros/matchers/validate_attachment_presence_matcher.rb
96
+ - shoulda_macros/matchers/validate_attachment_size_matcher.rb
97
+ - shoulda_macros/matchers.rb
88
98
  - shoulda_macros/paperclip.rb
89
99
  has_rdoc: true
90
100
  homepage: http://www.thoughtbot.com/projects/paperclip