dm-paperclip 2.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,80 @@
1
+ module Paperclip
2
+ # Handles thumbnailing images that are uploaded.
3
+ class Thumbnail
4
+
5
+ attr_accessor :file, :current_geometry, :target_geometry, :format, :whiny_thumbnails
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_thumbnails+ is true (which it is, by default.
12
+ def initialize file, target_geometry, format = nil, whiny_thumbnails = true
13
+ @file = file
14
+ @crop = target_geometry[-1,1] == '#'
15
+ @target_geometry = Geometry.parse target_geometry
16
+ @current_geometry = Geometry.from_file file
17
+ @whiny_thumbnails = whiny_thumbnails
18
+
19
+ @current_format = File.extname(@file.path)
20
+ @basename = File.basename(@file.path, @current_format)
21
+
22
+ @format = format
23
+ end
24
+
25
+ # Creates a thumbnail, as specified in +initialize+, +make+s it, and returns the
26
+ # resulting Tempfile.
27
+ def self.make file, dimensions, format = nil, whiny_thumbnails = true
28
+ new(file, dimensions, format, whiny_thumbnails).make
29
+ end
30
+
31
+ # Returns true if the +target_geometry+ is meant to crop.
32
+ def crop?
33
+ @crop
34
+ end
35
+
36
+ # Performs the conversion of the +file+ into a thumbnail. Returns the Tempfile
37
+ # that contains the new image.
38
+ def make
39
+ src = @file
40
+ dst = Tempfile.new([@basename, @format].compact.join("."))
41
+ dst.binmode
42
+
43
+ command = <<-end_command
44
+ #{ Paperclip.path_for_command('convert') }
45
+ "#{ File.expand_path(src.path) }"
46
+ #{ transformation_command }
47
+ "#{ File.expand_path(dst.path) }"
48
+ end_command
49
+ success = system(command.gsub(/\s+/, " "))
50
+
51
+ if success && $?.exitstatus != 0 && @whiny_thumbnails
52
+ raise PaperclipError, "There was an error processing this thumbnail"
53
+ end
54
+
55
+ dst
56
+ end
57
+
58
+ # Returns the command ImageMagick's +convert+ needs to transform the image
59
+ # into the thumbnail.
60
+ def transformation_command
61
+ scale, crop = @current_geometry.transformation_to(@target_geometry, crop?)
62
+ trans = "-scale \"#{scale}\""
63
+ trans << " -crop \"#{crop}\" +repage" if crop
64
+ trans
65
+ end
66
+ end
67
+
68
+ # Due to how ImageMagick handles its image format conversion and how Tempfile
69
+ # handles its naming scheme, it is necessary to override how Tempfile makes
70
+ # its names so as to allow for file extensions. Idea taken from the comments
71
+ # on this blog post:
72
+ # http://marsorange.com/archives/of-mogrify-ruby-tempfile-dynamic-class-definitions
73
+ class Tempfile < ::Tempfile
74
+ # Replaces Tempfile's +make_tmpname+ with one that honors file extensions.
75
+ def make_tmpname(basename, n)
76
+ extension = File.extname(basename)
77
+ sprintf("%s,%d,%d%s", File.basename(basename, extension), $$, n, extension)
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,33 @@
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"
10
+ case type
11
+ when "jpg", "png", "gif" then "image/#{type}"
12
+ when "txt" then "text/plain"
13
+ when "csv", "xml", "html", "htm", "css", "js" then "text/#{type}"
14
+ else "x-application/#{type}"
15
+ end
16
+ end
17
+
18
+ # Returns the file's normal name.
19
+ def original_filename
20
+ File.basename(self.path)
21
+ end
22
+
23
+ # Returns the size of the file.
24
+ def size
25
+ File.size(self)
26
+ end
27
+ end
28
+
29
+ end
30
+
31
+ class File #:nodoc:
32
+ include Paperclip::Upfile
33
+ end
@@ -0,0 +1,89 @@
1
+ module Paperclip
2
+ module Validate
3
+
4
+ class SizeValidator < DataMapper::Validate::GenericValidator #:nodoc:
5
+ def initialize(field_name, options={})
6
+ super
7
+ @field_name, @options = field_name, options
8
+ end
9
+
10
+ def call(target)
11
+ field_value = target.validation_property_value(:"#{@field_name}_file_size")
12
+ return true if field_value.nil?
13
+
14
+ @options[:in] = (@options[:greater_than]..(1/0)) unless @options[:greater_than].nil?
15
+ @options[:in] = (0..@options[:less_than]) unless @options[:less_than].nil?
16
+ return true if @options[:in].include? field_value.to_i
17
+
18
+ error_message ||= @options[:message] unless @options[:message].nil?
19
+ error_message ||= "%s must be less than %s bytes".t(DataMapper::Inflection.humanize(@field_name), @options[:less_than]) unless @options[:less_than].nil?
20
+ error_message ||= "%s must be greater than %s bytes".t(DataMapper::Inflection.humanize(@field_name), @options[:greater_than]) unless @options[:greater_than].nil?
21
+ error_message ||= "%s must be between %s and %s bytes".t(DataMapper::Inflection.humanize(@field_name), @options[:in].first, @options[:in].last)
22
+ add_error(target, error_message , @field_name)
23
+ return false
24
+ end
25
+ end
26
+
27
+ class RequiredFieldValidator < DataMapper::Validate::GenericValidator #:nodoc:
28
+ def initialize(field_name, options={})
29
+ super
30
+ @field_name, @options = field_name, options
31
+ end
32
+
33
+ def call(target)
34
+ field_value = target.validation_property_value(@field_name)
35
+ if field_value.nil? || field_value.original_filename.blank?
36
+ error_message = @options[:message] || "%s must be set".t(DataMapper::Inflection.humanize(@field_name))
37
+ add_error(target, error_message , @field_name)
38
+ return false
39
+ end
40
+ return true
41
+ end
42
+ end
43
+
44
+ class ContentTypeValidator < DataMapper::Validate::GenericValidator #:nodoc:
45
+ def initialize(field_name, options={})
46
+ super
47
+ @field_name, @options = field_name, options
48
+ end
49
+
50
+ def call(target)
51
+ valid_types = [@options[:content_type]].flatten
52
+ field_value = target.validation_property_value(@field_name)
53
+
54
+ unless field_value.nil? || field_value.original_filename.blank?
55
+ unless @options[:content_type].blank?
56
+ content_type = target.validation_property_value(:"#{@field_name}_content_type")
57
+ unless valid_types.any?{|t| t === content_type }
58
+ error_message ||= @options[:message] unless @options[:message].nil?
59
+ error_message ||= "%s's content type of '%s' is not a valid content type".t(DataMapper::Inflection.humanize(@field_name), content_type)
60
+ add_error(target, error_message , @field_name)
61
+ return false
62
+ end
63
+ end
64
+ end
65
+
66
+ return true
67
+ end
68
+ end
69
+
70
+ class CopyAttachmentErrors < DataMapper::Validate::GenericValidator #:nodoc:
71
+ def initialize(field_name, options={})
72
+ super
73
+ @field_name, @options = field_name, options
74
+ end
75
+
76
+ def call(target)
77
+ field_value = target.validation_property_value(@field_name)
78
+ unless field_value.nil? || field_value.original_filename.blank?
79
+ return true if field_value.errors.length == 0
80
+ field_value.errors.each { |message| add_error(target, message, @field_name) }
81
+ return false
82
+ end
83
+ return true
84
+ end
85
+ end
86
+
87
+ end
88
+ end
89
+
@@ -0,0 +1,38 @@
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
+ namespace :paperclip do
18
+ desc "Regenerates thumbnails for a given CLASS (and optional ATTACHMENT)"
19
+ task :refresh => :environment do
20
+ klass = obtain_class
21
+ names = obtain_attachments
22
+ instances = klass.all
23
+
24
+ puts "Regenerating thumbnails for #{instances.length} instances of #{klass.name}:"
25
+ instances.each do |instance|
26
+ names.each do |name|
27
+ result = if instance.send("#{ name }?")
28
+ instance.send(name).reprocess!
29
+ instance.send(name).save
30
+ else
31
+ true
32
+ end
33
+ print result ? "." : "x"; $stdout.flush
34
+ end
35
+ end
36
+ puts " Done."
37
+ end
38
+ end
Binary file
Binary file
Binary file
@@ -0,0 +1 @@
1
+ This is not an image.
File without changes
@@ -0,0 +1,67 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'mocha'
5
+ require 'tempfile'
6
+
7
+ require 'data_mapper'
8
+ require 'dm-validations'
9
+ require 'dm-migrations'
10
+ begin
11
+ require 'ruby-debug'
12
+ rescue LoadError
13
+ puts "ruby-debug not loaded"
14
+ end
15
+
16
+ ROOT = File.join(File.dirname(__FILE__), '..')
17
+ RAILS_ROOT = ROOT
18
+
19
+ Object.const_set("Merb", Class.new())
20
+ Merb.class_eval do
21
+ def self.root
22
+ "#{ROOT}"
23
+ end
24
+ def self.env
25
+ ENV['RAILS_ENV']
26
+ end
27
+ end
28
+
29
+ $LOAD_PATH << File.join(ROOT, 'lib')
30
+ $LOAD_PATH << File.join(ROOT, 'lib', 'dm-paperclip')
31
+
32
+ require File.join(ROOT, 'lib', 'dm-paperclip.rb')
33
+
34
+ ENV['RAILS_ENV'] ||= 'test'
35
+
36
+ FIXTURES_DIR = File.join(File.dirname(__FILE__), "fixtures")
37
+ DataMapper.setup(:default, 'sqlite3::memory:')
38
+
39
+ def rebuild_model options = {}
40
+ DataMapper::Migration.new( 1, :drop_dummies_table ) do
41
+ up do
42
+ create_table :dummies do
43
+ column :id, "integer", true
44
+ column :other, "varchar(255)"
45
+ column :avatar_file_name, "varchar(255)"
46
+ column :avatar_content_type, "varchar(255)"
47
+ column :avatar_file_size, "integer"
48
+ end
49
+ end
50
+ down do
51
+ drop_table :dummies
52
+ end
53
+ perform_down
54
+ perform_up
55
+ end
56
+
57
+ Object.send(:remove_const, "Dummy") rescue nil
58
+ Object.const_set("Dummy", Class.new())
59
+ Dummy.class_eval do
60
+ include DataMapper::Resource
61
+ include DataMapper::Validate
62
+ include Paperclip::Resource
63
+ property :id, Integer, :serial => true
64
+ property :other, String
65
+ has_attached_file :avatar, options
66
+ end
67
+ end
@@ -0,0 +1,225 @@
1
+ require 'test/helper'
2
+
3
+ class Dummy
4
+ # This is a dummy class
5
+ end
6
+
7
+ class AttachmentTest < Test::Unit::TestCase
8
+ context "Attachment default_options" do
9
+ setup do
10
+ rebuild_model
11
+ @old_default_options = Paperclip::Attachment.default_options.dup
12
+ @new_default_options = @old_default_options.merge({
13
+ :path => "argle/bargle",
14
+ :url => "fooferon",
15
+ :default_url => "not here.png"
16
+ })
17
+ end
18
+
19
+ teardown do
20
+ Paperclip::Attachment.default_options.merge! @old_default_options
21
+ end
22
+
23
+ should "be overrideable" do
24
+ Paperclip::Attachment.default_options.merge!(@new_default_options)
25
+ @new_default_options.keys.each do |key|
26
+ assert_equal @new_default_options[key],
27
+ Paperclip::Attachment.default_options[key]
28
+ end
29
+ end
30
+
31
+ context "without an Attachment" do
32
+ setup do
33
+ @dummy = Dummy.new
34
+ end
35
+
36
+ should "return false when asked exists?" do
37
+ assert !@dummy.avatar.exists?
38
+ end
39
+ end
40
+
41
+ context "on an Attachment" do
42
+ setup do
43
+ @dummy = Dummy.new
44
+ @attachment = @dummy.avatar
45
+ end
46
+
47
+ Paperclip::Attachment.default_options.keys.each do |key|
48
+ should "be the default_options for #{key}" do
49
+ assert_equal @old_default_options[key],
50
+ @attachment.instance_variable_get("@#{key}"),
51
+ key
52
+ end
53
+ end
54
+
55
+ context "when redefined" do
56
+ setup do
57
+ Paperclip::Attachment.default_options.merge!(@new_default_options)
58
+ @dummy = Dummy.new
59
+ @attachment = @dummy.avatar
60
+ end
61
+
62
+ Paperclip::Attachment.default_options.keys.each do |key|
63
+ should "be the new default_options for #{key}" do
64
+ assert_equal @new_default_options[key],
65
+ @attachment.instance_variable_get("@#{key}"),
66
+ key
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ context "An attachment with similarly named interpolations" do
74
+ setup do
75
+ rebuild_model :path => ":id.omg/:id-bbq/:idwhat/:id_partition.wtf"
76
+ @dummy = Dummy.new
77
+ @dummy.stubs(:id).returns(1024)
78
+ @file = File.new(File.join(File.dirname(__FILE__),
79
+ "fixtures",
80
+ "5k.png"))
81
+ @dummy.avatar = @file
82
+ end
83
+
84
+ should "make sure that they are interpolated correctly" do
85
+ assert_equal "1024.omg/1024-bbq/1024what/000/001/024.wtf", @dummy.avatar.path
86
+ end
87
+ end
88
+
89
+ context "An attachment" do
90
+ setup do
91
+ Paperclip::Attachment.default_options.merge!({
92
+ :path => ":merb_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
93
+ })
94
+ FileUtils.rm_rf("tmp")
95
+ @instance = stub
96
+ @instance.stubs(:id).returns(41)
97
+ @instance.stubs(:class).returns(Dummy)
98
+ @instance.stubs(:attribute_get).with(:test_file_name).returns(nil)
99
+ @attachment = Paperclip::Attachment.new(:test,
100
+ @instance)
101
+ @file = File.new(File.join(File.dirname(__FILE__),
102
+ "fixtures",
103
+ "5k.png"))
104
+ end
105
+
106
+ should "return its default_url when no file assigned" do
107
+ assert @attachment.to_file.nil?
108
+ assert_equal "/tests/original/missing.png", @attachment.url
109
+ assert_equal "/tests/blah/missing.png", @attachment.url(:blah)
110
+ end
111
+
112
+ context "with a file assigned in the database" do
113
+ setup do
114
+ @instance.stubs(:attribute_get).with(:test_file_name).returns('5k.png')
115
+ end
116
+
117
+ should "return a correct url even if the file does not exist" do
118
+ assert_nil @attachment.to_file
119
+ assert_equal "/tests/41/blah/5k.png", @attachment.url(:blah)
120
+ end
121
+
122
+ should "return the proper path when filename has a single .'s" do
123
+ assert_equal "./test/../tmp/tests/dummies/original/41/5k.png", @attachment.path
124
+ end
125
+
126
+ should "return the proper path when filename has multiple .'s" do
127
+ @instance.stubs(:attribute_get).with(:test_file_name).returns("5k.old.png")
128
+ assert_equal "./test/../tmp/tests/dummies/original/41/5k.old.png", @attachment.path
129
+ end
130
+
131
+ context "when expecting three styles" do
132
+ setup do
133
+ styles = {:styles => { :large => ["400x400", :png],
134
+ :medium => ["100x100", :gif],
135
+ :small => ["32x32#", :jpg]}}
136
+ @attachment = Paperclip::Attachment.new(:test,
137
+ @instance,
138
+ styles)
139
+ end
140
+
141
+ context "and assigned a file" do
142
+ setup do
143
+ @instance.expects(:update_attributes).with({ :test_file_name => nil,
144
+ :test_content_type => nil,
145
+ :test_file_size => nil })
146
+ @instance.expects(:update_attributes).with( { :test_file_name => File.basename(@file.path),
147
+ :test_content_type => "image/png",
148
+ :test_file_size => @file.size })
149
+ @attachment.assign(@file)
150
+ end
151
+
152
+ should "be dirty" do
153
+ assert @attachment.dirty?
154
+ end
155
+
156
+ context "and saved" do
157
+ setup do
158
+ @attachment.save
159
+ end
160
+
161
+ should "return the real url" do
162
+ assert @attachment.to_file
163
+ assert_equal "/tests/41/original/5k.png", @attachment.url
164
+ assert_equal "/tests/41/small/5k.jpg", @attachment.url(:small)
165
+ end
166
+
167
+ should "commit the files to disk" do
168
+ [:large, :medium, :small].each do |style|
169
+ io = @attachment.to_io(style)
170
+ assert File.exists?(io)
171
+ assert ! io.is_a?(::Tempfile)
172
+ end
173
+ end
174
+
175
+ should "save the files as the right formats and sizes" do
176
+ [[:large, 400, 61, "PNG"],
177
+ [:medium, 100, 15, "GIF"],
178
+ [:small, 32, 32, "JPEG"]].each do |style|
179
+ cmd = "identify -format '%w %h %b %m' " +
180
+ "#{@attachment.to_io(style.first).path}"
181
+ out = `#{cmd}`
182
+ width, height, size, format = out.split(" ")
183
+ assert_equal style[1].to_s, width.to_s
184
+ assert_equal style[2].to_s, height.to_s
185
+ assert_equal style[3].to_s, format.to_s
186
+ end
187
+ end
188
+
189
+ should "still have its #file attribute not be nil" do
190
+ assert ! @attachment.to_file.nil?
191
+ end
192
+
193
+ context "and deleted" do
194
+ setup do
195
+ @existing_names = @attachment.styles.keys.collect do |style|
196
+ @attachment.path(style)
197
+ end
198
+ @instance.expects(:update_attributes).with({ :test_file_name => nil,
199
+ :test_content_type => nil,
200
+ :test_file_size => nil })
201
+ @attachment.assign nil
202
+ @attachment.save
203
+ end
204
+
205
+ should "delete the files" do
206
+ @existing_names.each{|f| assert ! File.exists?(f) }
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ end
214
+
215
+ context "when trying a nonexistant storage type" do
216
+ setup do
217
+ rebuild_model :storage => :not_here
218
+ end
219
+
220
+ should "not be able to find the module" do
221
+ assert_raise(NameError){ Dummy.new.avatar }
222
+ end
223
+ end
224
+ end
225
+ end