jcnetdev-paperclip 1.0.20080704

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
data/paperclip.gemspec ADDED
@@ -0,0 +1,55 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'paperclip'
3
+ s.version = '1.0.20080704'
4
+ s.date = '2008-07-04'
5
+
6
+ s.summary = "Allows easy file uploading for Rails"
7
+ s.description = "Paperclip is intended as an easy file attachment library for ActiveRecord. The intent behind it was to keep setup as easy as possible and to treat files as much like other attributes as possible. This means they aren't saved to their final locations on disk, nor are they deleted if set to nil, until ActiveRecord::Base#save is called. It manages validations based on size and presence, if required. It can transform its assigned image into thumbnails if needed, and the prerequisites are as simple as installing ImageMagick (which, for most modern Unix-based systems, is as easy as installing the right packages). Attached files are saved to the filesystem and referenced in the browser by an easily understandable specification, which has sensible and useful defaults."
8
+
9
+ s.authors = ['Jon Yurek']
10
+ s.email = 'info@thoughtbot.com'
11
+ s.homepage = 'http://github.com/thoughtbot/paperclip'
12
+
13
+ s.has_rdoc = true
14
+ s.rdoc_options = ["--main", "README"]
15
+ s.extra_rdoc_files = ["README"]
16
+
17
+ s.add_dependency 'rails', ['>= 2.1']
18
+
19
+ s.files = ["LICENSE",
20
+ "README",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "generators/paperclip/paperclip_generator.rb",
24
+ "generators/paperclip/templates/paperclip_migration.rb",
25
+ "generators/paperclip/USAGE",
26
+ "init.rb",
27
+ "lib/paperclip/attachment.rb",
28
+ "lib/paperclip/geometry.rb",
29
+ "lib/paperclip/iostream.rb",
30
+ "lib/paperclip/storage.rb",
31
+ "lib/paperclip/thumbnail.rb",
32
+ "lib/paperclip/upfile.rb",
33
+ "lib/paperclip.rb",
34
+ "paperclip.gemspec",
35
+ "rails/init.rb",
36
+ "tasks/paperclip_tasks.rake"]
37
+
38
+ s.test_files = ["test/.gitignore",
39
+ "test/database.yml",
40
+ "test/fixtures/12k.png",
41
+ "test/fixtures/50x50.png",
42
+ "test/fixtures/5k.png",
43
+ "test/fixtures/bad.png",
44
+ "test/fixtures/text.txt",
45
+ "test/helper.rb",
46
+ "test/test_attachment.rb",
47
+ "test/test_geometry.rb",
48
+ "test/test_integration.rb",
49
+ "test/test_iostream.rb",
50
+ "test/test_paperclip.rb",
51
+ "test/test_storage.rb",
52
+ "test/test_thumbnail.rb"]
53
+
54
+ end
55
+
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'paperclip'
@@ -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.find(: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
data/test/.gitignore ADDED
@@ -0,0 +1 @@
1
+ debug.log
data/test/database.yml ADDED
@@ -0,0 +1,5 @@
1
+ test:
2
+ adapter: sqlite3
3
+ #dbfile: paperclip.db
4
+ database: ":memory:"
5
+
Binary file
Binary file
Binary file
@@ -0,0 +1 @@
1
+ This is not an image.
File without changes
data/test/helper.rb ADDED
@@ -0,0 +1,44 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'mocha'
5
+ require 'tempfile'
6
+
7
+ require 'active_record'
8
+ begin
9
+ require 'ruby-debug'
10
+ rescue LoadError
11
+ puts "ruby-debug not loaded"
12
+ end
13
+
14
+ ROOT = File.join(File.dirname(__FILE__), '..')
15
+ RAILS_ROOT = ROOT
16
+
17
+ $LOAD_PATH << File.join(ROOT, 'lib')
18
+ $LOAD_PATH << File.join(ROOT, 'lib', 'paperclip')
19
+
20
+ require File.join(ROOT, 'lib', 'paperclip.rb')
21
+
22
+ ENV['RAILS_ENV'] ||= 'test'
23
+
24
+ FIXTURES_DIR = File.join(File.dirname(__FILE__), "fixtures")
25
+ config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
26
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
27
+ ActiveRecord::Base.establish_connection(config[ENV['RAILS_ENV'] || 'test'])
28
+
29
+ def rebuild_model options = {}
30
+ ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
31
+ table.column :other, :string
32
+ table.column :avatar_file_name, :string
33
+ table.column :avatar_content_type, :string
34
+ table.column :avatar_file_size, :integer
35
+ end
36
+
37
+ ActiveRecord::Base.send(:include, Paperclip)
38
+ Object.send(:remove_const, "Dummy") rescue nil
39
+ Object.const_set("Dummy", Class.new(ActiveRecord::Base))
40
+ Dummy.class_eval do
41
+ include Paperclip
42
+ has_attached_file :avatar, options
43
+ end
44
+ end
@@ -0,0 +1,286 @@
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 "Assigning an attachment" do
90
+ setup do
91
+ rebuild_model
92
+
93
+ @not_file = mock
94
+ @not_file.stubs(:nil?).returns(false)
95
+ @not_file.expects(:to_tempfile).returns(self)
96
+ @not_file.expects(:original_filename).returns("filename.png\r\n")
97
+ @not_file.expects(:content_type).returns("image/png\r\n")
98
+ @not_file.expects(:size).returns(10)
99
+
100
+ @dummy = Dummy.new
101
+ @attachment = @dummy.avatar
102
+ @attachment.expects(:valid_assignment?).with(@not_file).returns(true)
103
+ @attachment.expects(:queue_existing_for_delete)
104
+ @attachment.expects(:post_process)
105
+ @attachment.expects(:validate)
106
+ @dummy.avatar = @not_file
107
+ end
108
+
109
+ should "strip whitespace from original_filename field" do
110
+ assert_equal "filename.png", @dummy.avatar.original_filename
111
+ end
112
+
113
+ should "strip whitespace from content_type field" do
114
+ assert_equal "image/png", @dummy.avatar.instance.avatar_content_type
115
+ end
116
+
117
+ end
118
+
119
+ context "Attachment with strange letters" do
120
+ setup do
121
+ rebuild_model
122
+
123
+ @not_file = mock
124
+ @not_file.stubs(:nil?).returns(false)
125
+ @not_file.expects(:to_tempfile).returns(self)
126
+ @not_file.expects(:original_filename).returns("sheep_say_bæ.png\r\n")
127
+ @not_file.expects(:content_type).returns("image/png\r\n")
128
+ @not_file.expects(:size).returns(10)
129
+
130
+ @dummy = Dummy.new
131
+ @attachment = @dummy.avatar
132
+ @attachment.expects(:valid_assignment?).with(@not_file).returns(true)
133
+ @attachment.expects(:queue_existing_for_delete)
134
+ @attachment.expects(:post_process)
135
+ @attachment.expects(:validate)
136
+ @dummy.avatar = @not_file
137
+ end
138
+
139
+ should "remove strange letters and replace with underscore (_)" do
140
+ assert_equal "sheep_say_b_.png", @dummy.avatar.original_filename
141
+ end
142
+
143
+ end
144
+
145
+ context "An attachment" do
146
+ setup do
147
+ Paperclip::Attachment.default_options.merge!({
148
+ :path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
149
+ })
150
+ FileUtils.rm_rf("tmp")
151
+ @instance = stub
152
+ @instance.stubs(:id).returns(41)
153
+ @instance.stubs(:class).returns(Dummy)
154
+ @instance.stubs(:[]).with(:test_file_name).returns(nil)
155
+ @instance.stubs(:[]).with(:test_content_type).returns(nil)
156
+ @instance.stubs(:[]).with(:test_file_size).returns(nil)
157
+ @attachment = Paperclip::Attachment.new(:test,
158
+ @instance)
159
+ @file = File.new(File.join(File.dirname(__FILE__),
160
+ "fixtures",
161
+ "5k.png"))
162
+ end
163
+
164
+ should "return its default_url when no file assigned" do
165
+ assert @attachment.to_file.nil?
166
+ assert_equal "/tests/original/missing.png", @attachment.url
167
+ assert_equal "/tests/blah/missing.png", @attachment.url(:blah)
168
+ end
169
+
170
+ context "with a file assigned in the database" do
171
+ setup do
172
+ @instance.stubs(:[]).with(:test_file_name).returns("5k.png")
173
+ @instance.stubs(:[]).with(:test_content_type).returns("image/png")
174
+ @instance.stubs(:[]).with(:test_file_size).returns(12345)
175
+ end
176
+
177
+ should "return a correct url even if the file does not exist" do
178
+ assert_nil @attachment.to_file
179
+ assert_equal "/tests/41/blah/5k.png", @attachment.url(:blah)
180
+ end
181
+
182
+ should "return the proper path when filename has a single .'s" do
183
+ assert_equal "./test/../tmp/tests/dummies/original/41/5k.png", @attachment.path
184
+ end
185
+
186
+ should "return the proper path when filename has multiple .'s" do
187
+ @instance.stubs(:[]).with(:test_file_name).returns("5k.old.png")
188
+ assert_equal "./test/../tmp/tests/dummies/original/41/5k.old.png", @attachment.path
189
+ end
190
+
191
+ context "when expecting three styles" do
192
+ setup do
193
+ styles = {:styles => { :large => ["400x400", :png],
194
+ :medium => ["100x100", :gif],
195
+ :small => ["32x32#", :jpg]}}
196
+ @attachment = Paperclip::Attachment.new(:test,
197
+ @instance,
198
+ styles)
199
+ end
200
+
201
+ context "and assigned a file" do
202
+ setup do
203
+ @instance.expects(:[]=).with(:test_file_name,
204
+ File.basename(@file.path))
205
+ @instance.expects(:[]=).with(:test_content_type, "image/png")
206
+ @instance.expects(:[]=).with(:test_file_size, @file.size)
207
+ @instance.expects(:[]=).with(:test_file_name, nil)
208
+ @instance.expects(:[]=).with(:test_content_type, nil)
209
+ @instance.expects(:[]=).with(:test_file_size, nil)
210
+ @attachment.assign(@file)
211
+ end
212
+
213
+ should "be dirty" do
214
+ assert @attachment.dirty?
215
+ end
216
+
217
+ context "and saved" do
218
+ setup do
219
+ @attachment.save
220
+ end
221
+
222
+ should "return the real url" do
223
+ assert @attachment.to_file
224
+ assert_equal "/tests/41/original/5k.png", @attachment.url
225
+ assert_equal "/tests/41/small/5k.jpg", @attachment.url(:small)
226
+ end
227
+
228
+ should "commit the files to disk" do
229
+ [:large, :medium, :small].each do |style|
230
+ io = @attachment.to_io(style)
231
+ assert File.exists?(io)
232
+ assert ! io.is_a?(::Tempfile)
233
+ end
234
+ end
235
+
236
+ should "save the files as the right formats and sizes" do
237
+ [[:large, 400, 61, "PNG"],
238
+ [:medium, 100, 15, "GIF"],
239
+ [:small, 32, 32, "JPEG"]].each do |style|
240
+ cmd = "identify -format '%w %h %b %m' " +
241
+ "#{@attachment.to_io(style.first).path}"
242
+ out = `#{cmd}`
243
+ width, height, size, format = out.split(" ")
244
+ assert_equal style[1].to_s, width.to_s
245
+ assert_equal style[2].to_s, height.to_s
246
+ assert_equal style[3].to_s, format.to_s
247
+ end
248
+ end
249
+
250
+ should "still have its #file attribute not be nil" do
251
+ assert ! @attachment.to_file.nil?
252
+ end
253
+
254
+ context "and deleted" do
255
+ setup do
256
+ @existing_names = @attachment.styles.keys.collect do |style|
257
+ @attachment.path(style)
258
+ end
259
+ @instance.expects(:[]=).with(:test_file_name, nil)
260
+ @instance.expects(:[]=).with(:test_content_type, nil)
261
+ @instance.expects(:[]=).with(:test_file_size, nil)
262
+ @attachment.assign nil
263
+ @attachment.save
264
+ end
265
+
266
+ should "delete the files" do
267
+ @existing_names.each{|f| assert ! File.exists?(f) }
268
+ end
269
+ end
270
+ end
271
+ end
272
+ end
273
+
274
+ end
275
+
276
+ context "when trying a nonexistant storage type" do
277
+ setup do
278
+ rebuild_model :storage => :not_here
279
+ end
280
+
281
+ should "not be able to find the module" do
282
+ assert_raise(NameError){ Dummy.new.avatar }
283
+ end
284
+ end
285
+ end
286
+ end