arielvalentin-paperclip 2.3.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/LICENSE +26 -0
  2. data/README.rdoc +182 -0
  3. data/Rakefile +80 -0
  4. data/generators/paperclip/USAGE +5 -0
  5. data/generators/paperclip/paperclip_generator.rb +27 -0
  6. data/generators/paperclip/templates/paperclip_migration.rb.erb +19 -0
  7. data/init.rb +1 -0
  8. data/lib/generators/paperclip/USAGE +8 -0
  9. data/lib/generators/paperclip/paperclip_generator.rb +31 -0
  10. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +19 -0
  11. data/lib/paperclip.rb +370 -0
  12. data/lib/paperclip/attachment.rb +347 -0
  13. data/lib/paperclip/callback_compatability.rb +61 -0
  14. data/lib/paperclip/command_line.rb +80 -0
  15. data/lib/paperclip/geometry.rb +115 -0
  16. data/lib/paperclip/interpolations.rb +114 -0
  17. data/lib/paperclip/iostream.rb +45 -0
  18. data/lib/paperclip/matchers.rb +33 -0
  19. data/lib/paperclip/matchers/have_attached_file_matcher.rb +57 -0
  20. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +75 -0
  21. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +54 -0
  22. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +95 -0
  23. data/lib/paperclip/processor.rb +58 -0
  24. data/lib/paperclip/railtie.rb +24 -0
  25. data/lib/paperclip/storage.rb +2 -0
  26. data/lib/paperclip/storage/filesystem.rb +73 -0
  27. data/lib/paperclip/storage/s3.rb +192 -0
  28. data/lib/paperclip/style.rb +90 -0
  29. data/lib/paperclip/thumbnail.rb +79 -0
  30. data/lib/paperclip/upfile.rb +55 -0
  31. data/lib/paperclip/version.rb +3 -0
  32. data/lib/tasks/paperclip.rake +79 -0
  33. data/rails/init.rb +2 -0
  34. data/shoulda_macros/paperclip.rb +118 -0
  35. data/test/attachment_test.rb +804 -0
  36. data/test/command_line_test.rb +133 -0
  37. data/test/database.yml +4 -0
  38. data/test/fixtures/12k.png +0 -0
  39. data/test/fixtures/50x50.png +0 -0
  40. data/test/fixtures/5k.png +0 -0
  41. data/test/fixtures/bad.png +1 -0
  42. data/test/fixtures/s3.yml +8 -0
  43. data/test/fixtures/text.txt +0 -0
  44. data/test/fixtures/twopage.pdf +0 -0
  45. data/test/geometry_test.rb +177 -0
  46. data/test/helper.rb +146 -0
  47. data/test/integration_test.rb +482 -0
  48. data/test/interpolations_test.rb +127 -0
  49. data/test/iostream_test.rb +71 -0
  50. data/test/matchers/have_attached_file_matcher_test.rb +24 -0
  51. data/test/matchers/validate_attachment_content_type_matcher_test.rb +47 -0
  52. data/test/matchers/validate_attachment_presence_matcher_test.rb +26 -0
  53. data/test/matchers/validate_attachment_size_matcher_test.rb +51 -0
  54. data/test/paperclip_test.rb +254 -0
  55. data/test/processor_test.rb +10 -0
  56. data/test/storage_test.rb +363 -0
  57. data/test/style_test.rb +141 -0
  58. data/test/thumbnail_test.rb +227 -0
  59. data/test/upfile_test.rb +36 -0
  60. metadata +227 -0
@@ -0,0 +1,90 @@
1
+ # encoding: utf-8
2
+ module Paperclip
3
+ # The Style class holds the definition of a thumbnail style, applying
4
+ # whatever processing is required to normalize the definition and delaying
5
+ # the evaluation of block parameters until useful context is available.
6
+
7
+ class Style
8
+
9
+ attr_reader :name, :attachment, :format
10
+
11
+ # Creates a Style object. +name+ is the name of the attachment,
12
+ # +definition+ is the style definition from has_attached_file, which
13
+ # can be string, array or hash
14
+ def initialize name, definition, attachment
15
+ @name = name
16
+ @attachment = attachment
17
+ if definition.is_a? Hash
18
+ @geometry = definition.delete(:geometry)
19
+ @format = definition.delete(:format)
20
+ @processors = definition.delete(:processors)
21
+ @other_args = definition
22
+ else
23
+ @geometry, @format = [definition, nil].flatten[0..1]
24
+ @other_args = {}
25
+ end
26
+ @format = nil if @format.blank?
27
+ end
28
+
29
+ # retrieves from the attachment the processors defined in the has_attached_file call
30
+ # (which method (in the attachment) will call any supplied procs)
31
+ # There is an important change of interface here: a style rule can set its own processors
32
+ # by default we behave as before, though.
33
+ def processors
34
+ @processors || attachment.processors
35
+ end
36
+
37
+ # retrieves from the attachment the whiny setting
38
+ def whiny
39
+ attachment.whiny
40
+ end
41
+
42
+ # returns true if we're inclined to grumble
43
+ def whiny?
44
+ !!whiny
45
+ end
46
+
47
+ def convert_options
48
+ attachment.send(:extra_options_for, name)
49
+ end
50
+
51
+ # returns the geometry string for this style
52
+ # if a proc has been supplied, we call it here
53
+ def geometry
54
+ @geometry.respond_to?(:call) ? @geometry.call(attachment.instance) : @geometry
55
+ end
56
+
57
+ # Supplies the hash of options that processors expect to receive as their second argument
58
+ # Arguments other than the standard geometry, format etc are just passed through from
59
+ # initialization and any procs are called here, just before post-processing.
60
+ def processor_options
61
+ args = {}
62
+ @other_args.each do |k,v|
63
+ args[k] = v.respond_to?(:call) ? v.call(attachment) : v
64
+ end
65
+ [:processors, :geometry, :format, :whiny, :convert_options].each do |k|
66
+ (arg = send(k)) && args[k] = arg
67
+ end
68
+ args
69
+ end
70
+
71
+ # Supports getting and setting style properties with hash notation to ensure backwards-compatibility
72
+ # eg. @attachment.styles[:large][:geometry]@ will still work
73
+ def [](key)
74
+ if [:name, :convert_options, :whiny, :processors, :geometry, :format].include?(key)
75
+ send(key)
76
+ elsif defined? @other_args[key]
77
+ @other_args[key]
78
+ end
79
+ end
80
+
81
+ def []=(key, value)
82
+ if [:name, :convert_options, :whiny, :processors, :geometry, :format].include?(key)
83
+ send("#{key}=".intern, value)
84
+ else
85
+ @other_args[key] = value
86
+ end
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,79 @@
1
+ module Paperclip
2
+ # Handles thumbnailing images that are uploaded.
3
+ class Thumbnail < Processor
4
+
5
+ attr_accessor :current_geometry, :target_geometry, :format, :whiny, :convert_options, :source_file_options
6
+
7
+ # Creates a Thumbnail object set to work on the +file+ given. It
8
+ # will attempt to transform the image into one defined by +target_geometry+
9
+ # which is a "WxH"-style string. +format+ will be inferred from the +file+
10
+ # unless specified. Thumbnail creation will raise no errors unless
11
+ # +whiny+ is true (which it is, by default. If +convert_options+ is
12
+ # set, the options will be appended to the convert command upon image conversion
13
+ def initialize file, options = {}, attachment = nil
14
+ super
15
+
16
+ geometry = options[:geometry]
17
+ @file = file
18
+ @crop = geometry[-1,1] == '#'
19
+ @target_geometry = Geometry.parse geometry
20
+ @current_geometry = Geometry.from_file @file
21
+ @source_file_options = options[:source_file_options]
22
+ @convert_options = options[:convert_options]
23
+ @whiny = options[:whiny].nil? ? true : options[:whiny]
24
+ @format = options[:format]
25
+
26
+ @source_file_options = @source_file_options.split(/\s+/) if @source_file_options.respond_to?(:split)
27
+ @convert_options = @convert_options.split(/\s+/) if @convert_options.respond_to?(:split)
28
+
29
+ @current_format = File.extname(@file.path)
30
+ @basename = File.basename(@file.path, @current_format)
31
+
32
+ end
33
+
34
+ # Returns true if the +target_geometry+ is meant to crop.
35
+ def crop?
36
+ @crop
37
+ end
38
+
39
+ # Returns true if the image is meant to make use of additional convert options.
40
+ def convert_options?
41
+ !@convert_options.nil? && !@convert_options.empty?
42
+ end
43
+
44
+ # Performs the conversion of the +file+ into a thumbnail. Returns the Tempfile
45
+ # that contains the new image.
46
+ def make
47
+ src = @file
48
+ dst = Tempfile.new([@basename, @format ? ".#{@format}" : ''])
49
+ dst.binmode
50
+
51
+ begin
52
+ parameters = []
53
+ parameters << source_file_options
54
+ parameters << ":source"
55
+ parameters << transformation_command
56
+ parameters << convert_options
57
+ parameters << ":dest"
58
+
59
+ parameters = parameters.flatten.compact.join(" ").strip.squeeze(" ")
60
+
61
+ success = Paperclip.run("convert", parameters, :source => "#{File.expand_path(src.path)}[0]", :dest => File.expand_path(dst.path))
62
+ rescue PaperclipCommandLineError => e
63
+ raise PaperclipError, "There was an error processing the thumbnail for #{@basename}" if @whiny
64
+ end
65
+
66
+ dst
67
+ end
68
+
69
+ # Returns the command ImageMagick's +convert+ needs to transform the image
70
+ # into the thumbnail.
71
+ def transformation_command
72
+ scale, crop = @current_geometry.transformation_to(@target_geometry, crop?)
73
+ trans = []
74
+ trans << "-resize" << %["#{scale}"] unless scale.nil? || scale.empty?
75
+ trans << "-crop" << %["#{crop}"] << "+repage" if crop
76
+ trans
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,55 @@
1
+ module Paperclip
2
+ # The Upfile module is a convenience module for adding uploaded-file-type methods
3
+ # to the +File+ class. Useful for testing.
4
+ # user.avatar = File.new("test/test_avatar.jpg")
5
+ module Upfile
6
+
7
+ # Infer the MIME-type of the file from the extension.
8
+ def content_type
9
+ type = (self.path.match(/\.(\w+)$/)[1] rescue "octet-stream").downcase
10
+ case type
11
+ when %r"jp(e|g|eg)" then "image/jpeg"
12
+ when %r"tiff?" then "image/tiff"
13
+ when %r"png", "gif", "bmp" then "image/#{type}"
14
+ when "txt" then "text/plain"
15
+ when %r"html?" then "text/html"
16
+ when "js" then "application/js"
17
+ when "csv", "xml", "css" then "text/#{type}"
18
+ else
19
+ # On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
20
+ content_type = (Paperclip.run("file", "-b --mime-type :file", :file => self.path).split(':').last.strip rescue "application/x-#{type}")
21
+ content_type = "application/x-#{type}" if content_type.match(/\(.*?\)/)
22
+ content_type
23
+ end
24
+ end
25
+
26
+ # Returns the file's normal name.
27
+ def original_filename
28
+ File.basename(self.path)
29
+ end
30
+
31
+ # Returns the size of the file.
32
+ def size
33
+ File.size(self)
34
+ end
35
+ end
36
+ end
37
+
38
+ if defined? StringIO
39
+ class StringIO
40
+ attr_accessor :original_filename, :content_type, :fingerprint
41
+ def original_filename
42
+ @original_filename ||= "stringio.txt"
43
+ end
44
+ def content_type
45
+ @content_type ||= "text/plain"
46
+ end
47
+ def fingerprint
48
+ @fingerprint ||= Digest::MD5.hexdigest(self.string)
49
+ end
50
+ end
51
+ end
52
+
53
+ class File #:nodoc:
54
+ include Paperclip::Upfile
55
+ end
@@ -0,0 +1,3 @@
1
+ module Paperclip
2
+ VERSION = "2.3.6" unless defined? Paperclip::VERSION
3
+ end
@@ -0,0 +1,79 @@
1
+ def obtain_class
2
+ class_name = ENV['CLASS'] || ENV['class']
3
+ raise "Must specify CLASS" unless class_name
4
+ @klass = Object.const_get(class_name)
5
+ end
6
+
7
+ def obtain_attachments
8
+ name = ENV['ATTACHMENT'] || ENV['attachment']
9
+ raise "Class #{@klass.name} has no attachments specified" unless @klass.respond_to?(:attachment_definitions)
10
+ if !name.blank? && @klass.attachment_definitions.keys.include?(name)
11
+ [ name ]
12
+ else
13
+ @klass.attachment_definitions.keys
14
+ end
15
+ end
16
+
17
+ def for_all_attachments
18
+ klass = obtain_class
19
+ names = obtain_attachments
20
+ ids = klass.connection.select_values(klass.send(:construct_finder_sql, :select => 'id'))
21
+
22
+ ids.each do |id|
23
+ instance = klass.find(id)
24
+ names.each do |name|
25
+ result = if instance.send("#{ name }?")
26
+ yield(instance, name)
27
+ else
28
+ true
29
+ end
30
+ print result ? "." : "x"; $stdout.flush
31
+ end
32
+ end
33
+ puts " Done."
34
+ end
35
+
36
+ namespace :paperclip do
37
+ desc "Refreshes both metadata and thumbnails."
38
+ task :refresh => ["paperclip:refresh:metadata", "paperclip:refresh:thumbnails"]
39
+
40
+ namespace :refresh do
41
+ desc "Regenerates thumbnails for a given CLASS (and optional ATTACHMENT)."
42
+ task :thumbnails => :environment do
43
+ errors = []
44
+ for_all_attachments do |instance, name|
45
+ result = instance.send(name).reprocess!
46
+ errors << [instance.id, instance.errors] unless instance.errors.blank?
47
+ result
48
+ end
49
+ errors.each{|e| puts "#{e.first}: #{e.last.full_messages.inspect}" }
50
+ end
51
+
52
+ desc "Regenerates content_type/size metadata for a given CLASS (and optional ATTACHMENT)."
53
+ task :metadata => :environment do
54
+ for_all_attachments do |instance, name|
55
+ if file = instance.send(name).to_file
56
+ instance.send("#{name}_file_name=", instance.send("#{name}_file_name").strip)
57
+ instance.send("#{name}_content_type=", file.content_type.strip)
58
+ instance.send("#{name}_file_size=", file.size) if instance.respond_to?("#{name}_file_size")
59
+ instance.save(false)
60
+ else
61
+ true
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ desc "Cleans out invalid attachments. Useful after you've added new validations."
68
+ task :clean => :environment do
69
+ for_all_attachments do |instance, name|
70
+ instance.send(name).send(:validate)
71
+ if instance.send(name).valid?
72
+ true
73
+ else
74
+ instance.send("#{name}=", nil)
75
+ instance.save
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,2 @@
1
+ require 'paperclip/railtie'
2
+ Paperclip::Railtie.insert
@@ -0,0 +1,118 @@
1
+ require 'paperclip/matchers'
2
+
3
+ module Paperclip
4
+ # =Paperclip Shoulda Macros
5
+ #
6
+ # These macros are intended for use with shoulda, and will be included into
7
+ # your tests automatically. All of the macros use the standard shoulda
8
+ # assumption that the name of the test is based on the name of the model
9
+ # you're testing (that is, UserTest is the test for the User model), and
10
+ # will load that class for testing purposes.
11
+ module Shoulda
12
+ include Matchers
13
+ # This will test whether you have defined your attachment correctly by
14
+ # checking for all the required fields exist after the definition of the
15
+ # attachment.
16
+ def should_have_attached_file name
17
+ klass = self.name.gsub(/Test$/, '').constantize
18
+ matcher = have_attached_file name
19
+ should matcher.description do
20
+ assert_accepts(matcher, klass)
21
+ end
22
+ end
23
+
24
+ # Tests for validations on the presence of the attachment.
25
+ def should_validate_attachment_presence name
26
+ klass = self.name.gsub(/Test$/, '').constantize
27
+ matcher = validate_attachment_presence name
28
+ should matcher.description do
29
+ assert_accepts(matcher, klass)
30
+ end
31
+ end
32
+
33
+ # Tests that you have content_type validations specified. There are two
34
+ # options, :valid and :invalid. Both accept an array of strings. The
35
+ # strings should be a list of content types which will pass and fail
36
+ # validation, respectively.
37
+ def should_validate_attachment_content_type name, options = {}
38
+ klass = self.name.gsub(/Test$/, '').constantize
39
+ valid = [options[:valid]].flatten
40
+ invalid = [options[:invalid]].flatten
41
+ matcher = validate_attachment_content_type(name).allowing(valid).rejecting(invalid)
42
+ should matcher.description do
43
+ assert_accepts(matcher, klass)
44
+ end
45
+ end
46
+
47
+ # Tests to ensure that you have file size validations turned on. You
48
+ # can pass the same options to this that you can to
49
+ # validate_attachment_file_size - :less_than, :greater_than, and :in.
50
+ # :less_than checks that a file is less than a certain size, :greater_than
51
+ # checks that a file is more than a certain size, and :in takes a Range or
52
+ # Array which specifies the lower and upper limits of the file size.
53
+ def should_validate_attachment_size name, options = {}
54
+ klass = self.name.gsub(/Test$/, '').constantize
55
+ min = options[:greater_than] || (options[:in] && options[:in].first) || 0
56
+ max = options[:less_than] || (options[:in] && options[:in].last) || (1.0/0)
57
+ range = (min..max)
58
+ matcher = validate_attachment_size(name).in(range)
59
+ should matcher.description do
60
+ assert_accepts(matcher, klass)
61
+ end
62
+ end
63
+
64
+ # Stubs the HTTP PUT for an attachment using S3 storage.
65
+ #
66
+ # @example
67
+ # stub_paperclip_s3('user', 'avatar', 'png')
68
+ def stub_paperclip_s3(model, attachment, extension)
69
+ definition = model.gsub(" ", "_").classify.constantize.
70
+ attachment_definitions[attachment.to_sym]
71
+
72
+ path = "http://s3.amazonaws.com/:id/#{definition[:path]}"
73
+ path.gsub!(/:([^\/\.]+)/) do |match|
74
+ "([^\/\.]+)"
75
+ end
76
+
77
+ begin
78
+ FakeWeb.register_uri(:put, Regexp.new(path), :body => "OK")
79
+ rescue NameError
80
+ raise NameError, "the stub_paperclip_s3 shoulda macro requires the fakeweb gem."
81
+ end
82
+ end
83
+
84
+ # Stub S3 and return a file for attachment. Best with Factory Girl.
85
+ # Uses a strict directory convention:
86
+ #
87
+ # features/support/paperclip
88
+ #
89
+ # This method is used by the Paperclip-provided Cucumber step:
90
+ #
91
+ # When I attach a "demo_tape" "mp3" file to a "band" on S3
92
+ #
93
+ # @example
94
+ # Factory.define :band_with_demo_tape, :parent => :band do |band|
95
+ # band.demo_tape { band.paperclip_fixture("band", "demo_tape", "png") }
96
+ # end
97
+ def paperclip_fixture(model, attachment, extension)
98
+ stub_paperclip_s3(model, attachment, extension)
99
+ base_path = File.join(File.dirname(__FILE__), "..", "..",
100
+ "features", "support", "paperclip")
101
+ File.new(File.join(base_path, model, "#{attachment}.#{extension}"))
102
+ end
103
+ end
104
+ end
105
+
106
+ if defined?(ActionController::Integration::Session)
107
+ class ActionController::Integration::Session #:nodoc:
108
+ include Paperclip::Shoulda
109
+ end
110
+ end
111
+
112
+ class Factory
113
+ include Paperclip::Shoulda #:nodoc:
114
+ end
115
+
116
+ class Test::Unit::TestCase #:nodoc:
117
+ extend Paperclip::Shoulda
118
+ end
@@ -0,0 +1,804 @@
1
+ # encoding: utf-8
2
+ require './test/helper'
3
+
4
+ class Dummy
5
+ # This is a dummy class
6
+ end
7
+
8
+ class AttachmentTest < Test::Unit::TestCase
9
+ should "return the path based on the url by default" do
10
+ @attachment = attachment :url => "/:class/:id/:basename"
11
+ @model = @attachment.instance
12
+ @model.id = 1234
13
+ @model.avatar_file_name = "fake.jpg"
14
+ assert_equal "#{Rails.root}/public/fake_models/1234/fake", @attachment.path
15
+ end
16
+
17
+ context "Attachment default_options" do
18
+ setup do
19
+ rebuild_model
20
+ @old_default_options = Paperclip::Attachment.default_options.dup
21
+ @new_default_options = @old_default_options.merge({
22
+ :path => "argle/bargle",
23
+ :url => "fooferon",
24
+ :default_url => "not here.png"
25
+ })
26
+ end
27
+
28
+ teardown do
29
+ Paperclip::Attachment.default_options.merge! @old_default_options
30
+ end
31
+
32
+ should "be overrideable" do
33
+ Paperclip::Attachment.default_options.merge!(@new_default_options)
34
+ @new_default_options.keys.each do |key|
35
+ assert_equal @new_default_options[key],
36
+ Paperclip::Attachment.default_options[key]
37
+ end
38
+ end
39
+
40
+ context "without an Attachment" do
41
+ setup do
42
+ @dummy = Dummy.new
43
+ end
44
+
45
+ should "return false when asked exists?" do
46
+ assert !@dummy.avatar.exists?
47
+ end
48
+ end
49
+
50
+ context "on an Attachment" do
51
+ setup do
52
+ @dummy = Dummy.new
53
+ @attachment = @dummy.avatar
54
+ end
55
+
56
+ Paperclip::Attachment.default_options.keys.each do |key|
57
+ should "be the default_options for #{key}" do
58
+ assert_equal @old_default_options[key],
59
+ @attachment.instance_variable_get("@#{key}"),
60
+ key
61
+ end
62
+ end
63
+
64
+ context "when redefined" do
65
+ setup do
66
+ Paperclip::Attachment.default_options.merge!(@new_default_options)
67
+ @dummy = Dummy.new
68
+ @attachment = @dummy.avatar
69
+ end
70
+
71
+ Paperclip::Attachment.default_options.keys.each do |key|
72
+ should "be the new default_options for #{key}" do
73
+ assert_equal @new_default_options[key],
74
+ @attachment.instance_variable_get("@#{key}"),
75
+ key
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ context "An attachment with similarly named interpolations" do
83
+ setup do
84
+ rebuild_model :path => ":id.omg/:id-bbq/:idwhat/:id_partition.wtf"
85
+ @dummy = Dummy.new
86
+ @dummy.stubs(:id).returns(1024)
87
+ @file = File.new(File.join(File.dirname(__FILE__),
88
+ "fixtures",
89
+ "5k.png"), 'rb')
90
+ @dummy.avatar = @file
91
+ end
92
+
93
+ teardown { @file.close }
94
+
95
+ should "make sure that they are interpolated correctly" do
96
+ assert_equal "1024.omg/1024-bbq/1024what/000/001/024.wtf", @dummy.avatar.path
97
+ end
98
+ end
99
+
100
+ context "An attachment with a :rails_env interpolation" do
101
+ setup do
102
+ @rails_env = "blah"
103
+ @id = 1024
104
+ rebuild_model :path => ":rails_env/:id.png"
105
+ @dummy = Dummy.new
106
+ @dummy.stubs(:id).returns(@id)
107
+ @file = StringIO.new(".")
108
+ @dummy.avatar = @file
109
+ Rails.stubs(:env).returns(@rails_env)
110
+ end
111
+
112
+ should "return the proper path" do
113
+ assert_equal "#{@rails_env}/#{@id}.png", @dummy.avatar.path
114
+ end
115
+ end
116
+
117
+ context "An attachment with a default style and an extension interpolation" do
118
+ setup do
119
+ @attachment = attachment :path => ":basename.:extension",
120
+ :styles => { :default => ["100x100", :png] },
121
+ :default_style => :default
122
+ @file = StringIO.new("...")
123
+ @file.stubs(:original_filename).returns("file.jpg")
124
+ end
125
+ should "return the right extension for the path" do
126
+ @attachment.assign(@file)
127
+ assert_equal "file.png", @attachment.path
128
+ end
129
+ end
130
+
131
+ context "An attachment with :convert_options" do
132
+ setup do
133
+ rebuild_model :styles => {
134
+ :thumb => "100x100",
135
+ :large => "400x400"
136
+ },
137
+ :convert_options => {
138
+ :all => "-do_stuff",
139
+ :thumb => "-thumbnailize"
140
+ }
141
+ @dummy = Dummy.new
142
+ @dummy.avatar
143
+ end
144
+
145
+ should "report the correct options when sent #extra_options_for(:thumb)" do
146
+ assert_equal "-thumbnailize -do_stuff", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect
147
+ end
148
+
149
+ should "report the correct options when sent #extra_options_for(:large)" do
150
+ assert_equal "-do_stuff", @dummy.avatar.send(:extra_options_for, :large)
151
+ end
152
+ end
153
+
154
+ context "An attachment with :convert_options that is a proc" do
155
+ setup do
156
+ rebuild_model :styles => {
157
+ :thumb => "100x100",
158
+ :large => "400x400"
159
+ },
160
+ :convert_options => {
161
+ :all => lambda{|i| i.all },
162
+ :thumb => lambda{|i| i.thumb }
163
+ }
164
+ Dummy.class_eval do
165
+ def all; "-all"; end
166
+ def thumb; "-thumb"; end
167
+ end
168
+ @dummy = Dummy.new
169
+ @dummy.avatar
170
+ end
171
+
172
+ should "report the correct options when sent #extra_options_for(:thumb)" do
173
+ assert_equal "-thumb -all", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect
174
+ end
175
+
176
+ should "report the correct options when sent #extra_options_for(:large)" do
177
+ assert_equal "-all", @dummy.avatar.send(:extra_options_for, :large)
178
+ end
179
+ end
180
+
181
+ context "An attachment with :path that is a proc" do
182
+ setup do
183
+ rebuild_model :path => lambda{ |attachment| "path/#{attachment.instance.other}.:extension" }
184
+
185
+ @file = File.new(File.join(File.dirname(__FILE__),
186
+ "fixtures",
187
+ "5k.png"), 'rb')
188
+ @dummyA = Dummy.new(:other => 'a')
189
+ @dummyA.avatar = @file
190
+ @dummyB = Dummy.new(:other => 'b')
191
+ @dummyB.avatar = @file
192
+ end
193
+
194
+ teardown { @file.close }
195
+
196
+ should "return correct path" do
197
+ assert_equal "path/a.png", @dummyA.avatar.path
198
+ assert_equal "path/b.png", @dummyB.avatar.path
199
+ end
200
+ end
201
+
202
+ context "An attachment with :styles that is a proc" do
203
+ setup do
204
+ rebuild_model :styles => lambda{ |attachment| {:thumb => "50x50#", :large => "400x400"} }
205
+
206
+ @attachment = Dummy.new.avatar
207
+ end
208
+
209
+ should "have the correct geometry" do
210
+ assert_equal "50x50#", @attachment.styles[:thumb][:geometry]
211
+ end
212
+ end
213
+
214
+ context "An attachment with :url that is a proc" do
215
+ setup do
216
+ rebuild_model :url => lambda{ |attachment| "path/#{attachment.instance.other}.:extension" }
217
+
218
+ @file = File.new(File.join(File.dirname(__FILE__),
219
+ "fixtures",
220
+ "5k.png"), 'rb')
221
+ @dummyA = Dummy.new(:other => 'a')
222
+ @dummyA.avatar = @file
223
+ @dummyB = Dummy.new(:other => 'b')
224
+ @dummyB.avatar = @file
225
+ end
226
+
227
+ teardown { @file.close }
228
+
229
+ should "return correct url" do
230
+ assert_equal "path/a.png", @dummyA.avatar.url(:original, false)
231
+ assert_equal "path/b.png", @dummyB.avatar.url(:original, false)
232
+ end
233
+ end
234
+
235
+ geometry_specs = [
236
+ [ lambda{|z| "50x50#" }, :png ],
237
+ lambda{|z| "50x50#" },
238
+ { :geometry => lambda{|z| "50x50#" } }
239
+ ]
240
+ geometry_specs.each do |geometry_spec|
241
+ context "An attachment geometry like #{geometry_spec}" do
242
+ setup do
243
+ rebuild_model :styles => { :normal => geometry_spec }
244
+ @attachment = Dummy.new.avatar
245
+ end
246
+
247
+ context "when assigned" do
248
+ setup do
249
+ @file = StringIO.new(".")
250
+ @attachment.assign(@file)
251
+ end
252
+
253
+ should "have the correct geometry" do
254
+ assert_equal "50x50#", @attachment.styles[:normal][:geometry]
255
+ end
256
+ end
257
+ end
258
+ end
259
+
260
+ context "An attachment with both 'normal' and hash-style styles" do
261
+ setup do
262
+ rebuild_model :styles => {
263
+ :normal => ["50x50#", :png],
264
+ :hash => { :geometry => "50x50#", :format => :png }
265
+ }
266
+ @dummy = Dummy.new
267
+ @attachment = @dummy.avatar
268
+ end
269
+
270
+ [:processors, :whiny, :convert_options, :geometry, :format].each do |field|
271
+ should "have the same #{field} field" do
272
+ assert_equal @attachment.styles[:normal][field], @attachment.styles[:hash][field]
273
+ end
274
+ end
275
+ end
276
+
277
+ context "An attachment with :processors that is a proc" do
278
+ setup do
279
+ rebuild_model :styles => { :normal => '' }, :processors => lambda { |a| [ :test ] }
280
+ @attachment = Dummy.new.avatar
281
+ end
282
+
283
+ context "when assigned" do
284
+ setup do
285
+ @attachment.assign(StringIO.new("."))
286
+ end
287
+
288
+ should "have the correct processors" do
289
+ assert_equal [ :test ], @attachment.styles[:normal][:processors]
290
+ end
291
+ end
292
+ end
293
+
294
+ context "An attachment with erroring processor" do
295
+ setup do
296
+ rebuild_model :processor => [:thumbnail], :styles => { :small => '' }, :whiny_thumbnails => true
297
+ @dummy = Dummy.new
298
+ Paperclip::Thumbnail.expects(:make).raises(Paperclip::PaperclipError, "cannot be processed.")
299
+ @file = StringIO.new("...")
300
+ @file.stubs(:to_tempfile).returns(@file)
301
+ @dummy.avatar = @file
302
+ end
303
+
304
+ should "correctly forward processing error message to the instance" do
305
+ @dummy.valid?
306
+ assert_contains @dummy.errors.full_messages, "Avatar cannot be processed."
307
+ end
308
+ end
309
+
310
+ context "An attachment with multiple processors" do
311
+ setup do
312
+ class Paperclip::Test < Paperclip::Processor; end
313
+ @style_params = { :once => {:one => 1, :two => 2} }
314
+ rebuild_model :processors => [:thumbnail, :test], :styles => @style_params
315
+ @dummy = Dummy.new
316
+ @file = StringIO.new("...")
317
+ @file.stubs(:to_tempfile).returns(@file)
318
+ Paperclip::Test.stubs(:make).returns(@file)
319
+ Paperclip::Thumbnail.stubs(:make).returns(@file)
320
+ end
321
+
322
+ context "when assigned" do
323
+ setup { @dummy.avatar = @file }
324
+
325
+ before_should "call #make on all specified processors" do
326
+ Paperclip::Thumbnail.expects(:make).with(any_parameters).returns(@file)
327
+ Paperclip::Test.expects(:make).with(any_parameters).returns(@file)
328
+ end
329
+
330
+ before_should "call #make with the right parameters passed as second argument" do
331
+ expected_params = @style_params[:once].merge({:processors => [:thumbnail, :test], :whiny => true, :convert_options => ""})
332
+ Paperclip::Thumbnail.expects(:make).with(anything, expected_params, anything).returns(@file)
333
+ end
334
+
335
+ before_should "call #make with attachment passed as third argument" do
336
+ Paperclip::Test.expects(:make).with(anything, anything, @dummy.avatar).returns(@file)
337
+ end
338
+ end
339
+ end
340
+
341
+ should "include the filesystem module when loading the filesystem storage" do
342
+ rebuild_model :storage => :filesystem
343
+ @dummy = Dummy.new
344
+ assert @dummy.avatar.is_a?(Paperclip::Storage::Filesystem)
345
+ end
346
+
347
+ should "include the filesystem module even if capitalization is wrong" do
348
+ rebuild_model :storage => :FileSystem
349
+ @dummy = Dummy.new
350
+ assert @dummy.avatar.is_a?(Paperclip::Storage::Filesystem)
351
+ end
352
+
353
+ should "raise an error if you try to include a storage module that doesn't exist" do
354
+ rebuild_model :storage => :not_here
355
+ @dummy = Dummy.new
356
+ assert_raises(Paperclip::StorageMethodNotFound) do
357
+ @dummy.avatar
358
+ end
359
+ end
360
+
361
+ context "An attachment with styles but no processors defined" do
362
+ setup do
363
+ rebuild_model :processors => [], :styles => {:something => '1'}
364
+ @dummy = Dummy.new
365
+ @file = StringIO.new("...")
366
+ end
367
+ should "raise when assigned to" do
368
+ assert_raises(RuntimeError){ @dummy.avatar = @file }
369
+ end
370
+ end
371
+
372
+ context "An attachment without styles and with no processors defined" do
373
+ setup do
374
+ rebuild_model :processors => [], :styles => {}
375
+ @dummy = Dummy.new
376
+ @file = StringIO.new("...")
377
+ end
378
+ should "not raise when assigned to" do
379
+ @dummy.avatar = @file
380
+ end
381
+ end
382
+
383
+ context "Assigning an attachment with post_process hooks" do
384
+ setup do
385
+ rebuild_class :styles => { :something => "100x100#" }
386
+ Dummy.class_eval do
387
+ before_avatar_post_process :do_before_avatar
388
+ after_avatar_post_process :do_after_avatar
389
+ before_post_process :do_before_all
390
+ after_post_process :do_after_all
391
+ def do_before_avatar; end
392
+ def do_after_avatar; end
393
+ def do_before_all; end
394
+ def do_after_all; end
395
+ end
396
+ @file = StringIO.new(".")
397
+ @file.stubs(:to_tempfile).returns(@file)
398
+ @dummy = Dummy.new
399
+ Paperclip::Thumbnail.stubs(:make).returns(@file)
400
+ @attachment = @dummy.avatar
401
+ end
402
+
403
+ should "call the defined callbacks when assigned" do
404
+ @dummy.expects(:do_before_avatar).with()
405
+ @dummy.expects(:do_after_avatar).with()
406
+ @dummy.expects(:do_before_all).with()
407
+ @dummy.expects(:do_after_all).with()
408
+ Paperclip::Thumbnail.expects(:make).returns(@file)
409
+ @dummy.avatar = @file
410
+ end
411
+
412
+ should "not cancel the processing if a before_post_process returns nil" do
413
+ @dummy.expects(:do_before_avatar).with().returns(nil)
414
+ @dummy.expects(:do_after_avatar).with()
415
+ @dummy.expects(:do_before_all).with().returns(nil)
416
+ @dummy.expects(:do_after_all).with()
417
+ Paperclip::Thumbnail.expects(:make).returns(@file)
418
+ @dummy.avatar = @file
419
+ end
420
+
421
+ should "cancel the processing if a before_post_process returns false" do
422
+ @dummy.expects(:do_before_avatar).never
423
+ @dummy.expects(:do_after_avatar).never
424
+ @dummy.expects(:do_before_all).with().returns(false)
425
+ @dummy.expects(:do_after_all)
426
+ Paperclip::Thumbnail.expects(:make).never
427
+ @dummy.avatar = @file
428
+ end
429
+
430
+ should "cancel the processing if a before_avatar_post_process returns false" do
431
+ @dummy.expects(:do_before_avatar).with().returns(false)
432
+ @dummy.expects(:do_after_avatar)
433
+ @dummy.expects(:do_before_all).with().returns(true)
434
+ @dummy.expects(:do_after_all)
435
+ Paperclip::Thumbnail.expects(:make).never
436
+ @dummy.avatar = @file
437
+ end
438
+ end
439
+
440
+ context "Assigning an attachment" do
441
+ setup do
442
+ rebuild_model :styles => { :something => "100x100#" }
443
+ @file = StringIO.new(".")
444
+ @file.stubs(:original_filename).returns("5k.png\n\n")
445
+ @file.stubs(:content_type).returns("image/png\n\n")
446
+ @file.stubs(:to_tempfile).returns(@file)
447
+ @dummy = Dummy.new
448
+ Paperclip::Thumbnail.expects(:make).returns(@file)
449
+ @attachment = @dummy.avatar
450
+ @dummy.avatar = @file
451
+ end
452
+
453
+ should "strip whitespace from original_filename field" do
454
+ assert_equal "5k.png", @dummy.avatar.original_filename
455
+ end
456
+
457
+ should "strip whitespace from content_type field" do
458
+ assert_equal "image/png", @dummy.avatar.instance.avatar_content_type
459
+ end
460
+ end
461
+
462
+ context "Attachment with strange letters" do
463
+ setup do
464
+ rebuild_model
465
+
466
+ @not_file = mock("not_file")
467
+ @tempfile = mock("tempfile")
468
+ @not_file.stubs(:nil?).returns(false)
469
+ @not_file.expects(:size).returns(10)
470
+ @tempfile.expects(:size).returns(10)
471
+ @not_file.expects(:original_filename).returns("sheep_say_bæ.png\r\n")
472
+ @not_file.expects(:content_type).returns("image/png\r\n")
473
+
474
+ @dummy = Dummy.new
475
+ @attachment = @dummy.avatar
476
+ @attachment.expects(:valid_assignment?).with(@not_file).returns(true)
477
+ @attachment.expects(:queue_existing_for_delete)
478
+ @attachment.expects(:post_process)
479
+ @attachment.expects(:to_tempfile).returns(@tempfile)
480
+ @attachment.expects(:generate_fingerprint).with(@tempfile).returns("12345")
481
+ @attachment.expects(:generate_fingerprint).with(@not_file).returns("12345")
482
+ @dummy.avatar = @not_file
483
+ end
484
+
485
+ should "not remove strange letters" do
486
+ assert_equal "sheep_say_bæ.png", @dummy.avatar.original_filename
487
+ end
488
+ end
489
+
490
+ context "An attachment" do
491
+ setup do
492
+ @old_defaults = Paperclip::Attachment.default_options.dup
493
+ Paperclip::Attachment.default_options.merge!({
494
+ :path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
495
+ })
496
+ FileUtils.rm_rf("tmp")
497
+ rebuild_model
498
+ @instance = Dummy.new
499
+ @instance.stubs(:id).returns 123
500
+ @attachment = Paperclip::Attachment.new(:avatar, @instance)
501
+ @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
502
+ end
503
+
504
+ teardown do
505
+ @file.close
506
+ Paperclip::Attachment.default_options.merge!(@old_defaults)
507
+ end
508
+
509
+ should "raise if there are not the correct columns when you try to assign" do
510
+ @other_attachment = Paperclip::Attachment.new(:not_here, @instance)
511
+ assert_raises(Paperclip::PaperclipError) do
512
+ @other_attachment.assign(@file)
513
+ end
514
+ end
515
+
516
+ should "return its default_url when no file assigned" do
517
+ assert @attachment.to_file.nil?
518
+ assert_equal "/avatars/original/missing.png", @attachment.url
519
+ assert_equal "/avatars/blah/missing.png", @attachment.url(:blah)
520
+ end
521
+
522
+ should "return nil as path when no file assigned" do
523
+ assert @attachment.to_file.nil?
524
+ assert_equal nil, @attachment.path
525
+ assert_equal nil, @attachment.path(:blah)
526
+ end
527
+
528
+ context "with a file assigned in the database" do
529
+ setup do
530
+ @attachment.stubs(:instance_read).with(:file_name).returns("5k.png")
531
+ @attachment.stubs(:instance_read).with(:content_type).returns("image/png")
532
+ @attachment.stubs(:instance_read).with(:file_size).returns(12345)
533
+ dtnow = DateTime.now
534
+ @now = Time.now
535
+ Time.stubs(:now).returns(@now)
536
+ @attachment.stubs(:instance_read).with(:updated_at).returns(dtnow)
537
+ end
538
+
539
+ should "return a correct url even if the file does not exist" do
540
+ assert_nil @attachment.to_file
541
+ assert_match %r{^/system/avatars/#{@instance.id}/blah/5k\.png}, @attachment.url(:blah)
542
+ end
543
+
544
+ should "make sure the updated_at mtime is in the url if it is defined" do
545
+ assert_match %r{#{@now.to_i}$}, @attachment.url(:blah)
546
+ end
547
+
548
+ should "make sure the updated_at mtime is NOT in the url if false is passed to the url method" do
549
+ assert_no_match %r{#{@now.to_i}$}, @attachment.url(:blah, false)
550
+ end
551
+
552
+ context "with the updated_at field removed" do
553
+ setup do
554
+ @attachment.stubs(:instance_read).with(:updated_at).returns(nil)
555
+ end
556
+
557
+ should "only return the url without the updated_at when sent #url" do
558
+ assert_match "/avatars/#{@instance.id}/blah/5k.png", @attachment.url(:blah)
559
+ end
560
+ end
561
+
562
+ should "return the proper path when filename has a single .'s" do
563
+ assert_equal File.expand_path("./test/../tmp/avatars/dummies/original/#{@instance.id}/5k.png"), File.expand_path(@attachment.path)
564
+ end
565
+
566
+ should "return the proper path when filename has multiple .'s" do
567
+ @attachment.stubs(:instance_read).with(:file_name).returns("5k.old.png")
568
+ assert_equal File.expand_path("./test/../tmp/avatars/dummies/original/#{@instance.id}/5k.old.png"), File.expand_path(@attachment.path)
569
+ end
570
+
571
+ context "when expecting three styles" do
572
+ setup do
573
+ styles = {:styles => { :large => ["400x400", :png],
574
+ :medium => ["100x100", :gif],
575
+ :small => ["32x32#", :jpg]}}
576
+ @attachment = Paperclip::Attachment.new(:avatar,
577
+ @instance,
578
+ styles)
579
+ end
580
+
581
+ context "and assigned a file" do
582
+ setup do
583
+ now = Time.now
584
+ Time.stubs(:now).returns(now)
585
+ @attachment.assign(@file)
586
+ end
587
+
588
+ should "be dirty" do
589
+ assert @attachment.dirty?
590
+ end
591
+
592
+ context "and saved" do
593
+ setup do
594
+ @attachment.save
595
+ end
596
+
597
+ should "return the real url" do
598
+ file = @attachment.to_file
599
+ assert file
600
+ assert_match %r{^/system/avatars/#{@instance.id}/original/5k\.png}, @attachment.url
601
+ assert_match %r{^/system/avatars/#{@instance.id}/small/5k\.jpg}, @attachment.url(:small)
602
+ file.close
603
+ end
604
+
605
+ should "commit the files to disk" do
606
+ [:large, :medium, :small].each do |style|
607
+ io = @attachment.to_file(style)
608
+ # p "in commit to disk test, io is #{io.inspect} and @instance.id is #{@instance.id}"
609
+ assert File.exists?(io.path)
610
+ assert ! io.is_a?(::Tempfile)
611
+ io.close
612
+ end
613
+ end
614
+
615
+ should "save the files as the right formats and sizes" do
616
+ [[:large, 400, 61, "PNG"],
617
+ [:medium, 100, 15, "GIF"],
618
+ [:small, 32, 32, "JPEG"]].each do |style|
619
+ cmd = %Q[identify -format "%w %h %b %m" "#{@attachment.path(style.first)}"]
620
+ out = `#{cmd}`
621
+ width, height, size, format = out.split(" ")
622
+ assert_equal style[1].to_s, width.to_s
623
+ assert_equal style[2].to_s, height.to_s
624
+ assert_equal style[3].to_s, format.to_s
625
+ end
626
+ end
627
+
628
+ should "still have its #file attribute not be nil" do
629
+ assert ! (file = @attachment.to_file).nil?
630
+ file.close
631
+ end
632
+
633
+ context "and trying to delete" do
634
+ setup do
635
+ @existing_names = @attachment.styles.keys.collect do |style|
636
+ @attachment.path(style)
637
+ end
638
+ end
639
+
640
+ should "delete the files after assigning nil" do
641
+ @attachment.expects(:instance_write).with(:file_name, nil)
642
+ @attachment.expects(:instance_write).with(:content_type, nil)
643
+ @attachment.expects(:instance_write).with(:file_size, nil)
644
+ @attachment.expects(:instance_write).with(:updated_at, nil)
645
+ @attachment.assign nil
646
+ @attachment.save
647
+ @existing_names.each{|f| assert ! File.exists?(f) }
648
+ end
649
+
650
+ should "delete the files when you call #clear and #save" do
651
+ @attachment.expects(:instance_write).with(:file_name, nil)
652
+ @attachment.expects(:instance_write).with(:content_type, nil)
653
+ @attachment.expects(:instance_write).with(:file_size, nil)
654
+ @attachment.expects(:instance_write).with(:updated_at, nil)
655
+ @attachment.clear
656
+ @attachment.save
657
+ @existing_names.each{|f| assert ! File.exists?(f) }
658
+ end
659
+
660
+ should "delete the files when you call #delete" do
661
+ @attachment.expects(:instance_write).with(:file_name, nil)
662
+ @attachment.expects(:instance_write).with(:content_type, nil)
663
+ @attachment.expects(:instance_write).with(:file_size, nil)
664
+ @attachment.expects(:instance_write).with(:updated_at, nil)
665
+ @attachment.destroy
666
+ @existing_names.each{|f| assert ! File.exists?(f) }
667
+ end
668
+ end
669
+ end
670
+ end
671
+ end
672
+
673
+ end
674
+
675
+ context "when trying a nonexistant storage type" do
676
+ setup do
677
+ rebuild_model :storage => :not_here
678
+ end
679
+
680
+ should "not be able to find the module" do
681
+ assert_raise(Paperclip::StorageMethodNotFound){ Dummy.new.avatar }
682
+ end
683
+ end
684
+ end
685
+
686
+ context "An attachment with only a avatar_file_name column" do
687
+ setup do
688
+ ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
689
+ table.column :avatar_file_name, :string
690
+ end
691
+ rebuild_class
692
+ @dummy = Dummy.new
693
+ @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
694
+ end
695
+
696
+ teardown { @file.close }
697
+
698
+ should "not error when assigned an attachment" do
699
+ assert_nothing_raised { @dummy.avatar = @file }
700
+ end
701
+
702
+ should "return the time when sent #avatar_updated_at" do
703
+ now = Time.now
704
+ Time.stubs(:now).returns(now)
705
+ @dummy.avatar = @file
706
+ assert_equal now.to_i, @dummy.avatar.updated_at.to_i
707
+ end
708
+
709
+ should "return nil when reloaded and sent #avatar_updated_at" do
710
+ @dummy.save
711
+ @dummy.reload
712
+ assert_nil @dummy.avatar.updated_at
713
+ end
714
+
715
+ should "return the right value when sent #avatar_file_size" do
716
+ @dummy.avatar = @file
717
+ assert_equal @file.size, @dummy.avatar.size
718
+ end
719
+
720
+ context "and avatar_updated_at column" do
721
+ setup do
722
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_updated_at, :timestamp
723
+ rebuild_class
724
+ @dummy = Dummy.new
725
+ end
726
+
727
+ should "not error when assigned an attachment" do
728
+ assert_nothing_raised { @dummy.avatar = @file }
729
+ end
730
+
731
+ should "return the right value when sent #avatar_updated_at" do
732
+ now = Time.now
733
+ Time.stubs(:now).returns(now)
734
+ @dummy.avatar = @file
735
+ assert_equal now.to_i, @dummy.avatar.updated_at
736
+ end
737
+ end
738
+
739
+ context "and avatar_content_type column" do
740
+ setup do
741
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_content_type, :string
742
+ rebuild_class
743
+ @dummy = Dummy.new
744
+ end
745
+
746
+ should "not error when assigned an attachment" do
747
+ assert_nothing_raised { @dummy.avatar = @file }
748
+ end
749
+
750
+ should "return the right value when sent #avatar_content_type" do
751
+ @dummy.avatar = @file
752
+ assert_equal "image/png", @dummy.avatar.content_type
753
+ end
754
+ end
755
+
756
+ context "and avatar_file_size column" do
757
+ setup do
758
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_file_size, :integer
759
+ rebuild_class
760
+ @dummy = Dummy.new
761
+ end
762
+
763
+ should "not error when assigned an attachment" do
764
+ assert_nothing_raised { @dummy.avatar = @file }
765
+ end
766
+
767
+ should "return the right value when sent #avatar_file_size" do
768
+ @dummy.avatar = @file
769
+ assert_equal @file.size, @dummy.avatar.size
770
+ end
771
+
772
+ should "return the right value when saved, reloaded, and sent #avatar_file_size" do
773
+ @dummy.avatar = @file
774
+ @dummy.save
775
+ @dummy = Dummy.find(@dummy.id)
776
+ assert_equal @file.size, @dummy.avatar.size
777
+ end
778
+ end
779
+
780
+ context "and avatar_fingerprint column" do
781
+ setup do
782
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_fingerprint, :string
783
+ rebuild_class
784
+ @dummy = Dummy.new
785
+ end
786
+
787
+ should "not error when assigned an attachment" do
788
+ assert_nothing_raised { @dummy.avatar = @file }
789
+ end
790
+
791
+ should "return the right value when sent #avatar_fingerprint" do
792
+ @dummy.avatar = @file
793
+ assert_equal 'aec488126c3b33c08a10c3fa303acf27', @dummy.avatar_fingerprint
794
+ end
795
+
796
+ should "return the right value when saved, reloaded, and sent #avatar_fingerprint" do
797
+ @dummy.avatar = @file
798
+ @dummy.save
799
+ @dummy = Dummy.find(@dummy.id)
800
+ assert_equal 'aec488126c3b33c08a10c3fa303acf27', @dummy.avatar_fingerprint
801
+ end
802
+ end
803
+ end
804
+ end