├── .gitignore
├── CHANGELOG
├── LICENSE
├── README
├── Rakefile
├── init.rb
├── lib
├── upload_column.rb
└── upload_column
│ ├── active_record_extension.rb
│ ├── configuration.rb
│ ├── magic_columns.rb
│ ├── manipulators
│ ├── image_science.rb
│ └── rmagick.rb
│ ├── rails
│ ├── action_controller_extension.rb
│ ├── asset_tag_extension.rb
│ └── upload_column_helper.rb
│ ├── sanitized_file.rb
│ └── uploaded_file.rb
└── spec
├── active_record_extension_spec.rb
├── custom_matchers.rb
├── fixtures
├── animated.gif
├── animated_solarized.gif
├── invalid-image.jpg
├── kerb.jpg
├── kerb_solarized.jpg
├── netscape.gif
└── skanthak.png
├── image_science_manipulator_spec.rb
├── integration_spec.rb
├── magic_columns_spec.rb
├── rmagick_manipulator_spec.rb
├── sanitized_file_spec.rb
├── spec_helper.rb
├── upload_column_spec.rb
└── uploaded_file_spec.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | doc
2 | spec/public
3 | spec/db
--------------------------------------------------------------------------------
/CHANGELOG:
--------------------------------------------------------------------------------
1 | 0.3
2 |
3 | Note: The 0.3 branch is a complete rewrite of everything, with a similar (but not identical) API, don't expect it to work drop-in.
4 |
5 | [TODO] You can now set a default image if the column is blank
6 |
7 | [TODO] Integrated mocking support, to make testing your UploadColumns easier.
8 |
9 | [NEW] Complete rewrite, maintaining most of the legacy API
10 |
11 | [NEW] Framework for custom manipulators, for clean and easy extension.
12 |
13 | [NEW] Manipulate images with ImageScience instead of RMagick if you want
14 |
15 | [NEW] Support for animated GIFs in the RMagick manipulator
16 |
17 | [NEW] Fully tested with RSpec
18 |
19 | [NEW] Following the law of Demeter better by adding _public_path, _thumb, _thumb_public_path etc... magic methods.
20 |
21 | [DEPRECATED] UploadedFile#url is deprecated in favour of UploadedFile#public_path and will be removed in the next major release
22 |
23 | [CHANGED] Store dir and temp dir procs now take two piped variables, the UploadedFile object and the ActiveRecord
24 |
25 | [CHANGED] the :root_path option is now called :root_dir
26 |
27 | [CHANGED] web_root must now be set with a leading slash if an absolute URL is desired and no trailing slash.
28 |
29 | [CHANGED] UploadedFile#filename_base is removed in favor of #basename
30 |
31 | [CHANGED] UploadedFile#filename_extension is removed in favor of #extension
32 |
33 | [CHANGED] UploadedFile#mime_type is removed in favor of #content_type
34 |
35 | [CHANGED] _store_dir and _tmp_dir callbacks now take the file object as an argument, which means that the methods always MUST take an argument, even if you don't need it.
36 |
37 | [CHANGED] The _after_assigns callback has changed name to _after_upload and take the UploadedFile as param.
38 |
39 | [REMOVED] Support for the exif columns, as in 0.2.X has been removed temporarily. It might be readded later.
40 |
41 | [REMOVED] The old_files option as in 0.2.X has been removed. All old files are now stored. This option will be reintroduced in later versions of 0.3.X
42 |
43 | [REMOVED] The :force_format option for image_columns as in 0.2.X has been removed and will be reintroduced later.
44 |
45 | [REMOVED] The remote_upload_form helper has been removed, for encouraging bad JS practice. It will not be readded. If you need remote uploads, uploading with Flash is much cooler, and can be done in an unobtrusive way. Check out Swiff.js for hints.
46 |
47 | [REMOVED] The 'image' helper has been removed, since its functionality is not really useful in any app (hopefully most apps) that uses named routes.
48 |
49 | ============================
50 |
51 | 0.2.1
52 |
53 | * Added :force_format option to image_column
54 |
55 | * Various Rails 1.2.1 compatibility fixes (mainly in test)
56 |
57 | * Added :permissions option to upload_column.
58 |
59 | * You can now assign normal Ruby File objects to upload and image columns
60 |
61 | * upload_form_tag and remote_upload_form_tag now accept a block, just like Rails' form_tag
62 |
63 | * You can now pass :none to image_column versions so nothing will be done to your image.
64 |
65 | * FIXED #8109 Parallell uploads no longer wipe each other out
66 |
67 | * WARNING: Compatibility with Rails < 1.2.1 dropped
68 |
69 | ============================
70 |
71 | 0.2
72 |
73 | * A freaking huge refactoring of the code, basically ALL of the methods for accessing paths have changed, except for path itself. This was overdue and I apologize if it breaks anything, but I felt that the gain in consistency was worth it. It now works like this:
74 |
75 | path --the current path of the file (including the filename)
76 | relative_path --the current path of the file relative to the root_path option
77 |
78 | dir --the directory where the file is currently stored
79 | relative_dir --like dir but relative to root_path
80 |
81 | store_dir --The directory where files are permanently stored
82 | relative_store_dir --the same but relative to root_dir
83 |
84 | tmp_dir --The directory where tempfiles are stored
85 | relative_tmp_dir --you can work this out yourself
86 |
87 | As you can see, this is now actually consistent, with all the relative paths relative to the same directory (err... wow?) and a consistent naming convention.
88 |
89 | * In related news: you can now pass a Proc to the :store_dir and :tmp_dir options. The default options are now also procs, instead of being some kind of arcane super-exception like before. The procs will be passed to arguments, first the current model instance and the name of the upload column as the second.
90 |
91 | * The :accumulate option was removed from :old_files. I really liked it, but it doesn't make sense with th new Proc-based system (it would wipe out data without thinking, thus potentially getting rid of files you want to keep). Use :keep instead or implement some kind of versioning. The new default is :delete. So beware, if you need to keep those files, make sure to change it!
92 |
93 | * You can now specify individual versions that should be cropped in image_columns, simply add a 'c' before the string that specifies the size, so you can do:
94 |
95 | image_column :picture, :versions => { :thumb => "100x100", :banner => "c400x200" }
96 |
97 | Where thumb will be no larger than 100x100 (but might be smaller) and banner will be cropped to 400x200 exactly!
98 |
99 | * Furthermore you can pass a Proc instead of a string to an image_column version:
100 |
101 | image_column :picture, :versions => { :thumb => "100x100", :solarized => proc{|img| img.solarize} }
102 |
103 | The Proc will be passed an RMagick object, just like process!
104 |
105 | * render_image now uses send_file if no block is given for faster performance.
106 |
107 | * FIXED #6955 store_dir callback called when the file is assigned
108 |
109 | * FIXED #7697 Editing with old-files :delete / :replace erases the original file
110 |
111 | * FIXED #7686 Problem uploading files with spaces in name
112 |
113 | ============================
114 |
115 | 1.1.2 (unreleased)
116 |
117 | * new :validates_integrity option replaces the old validates_integrity_of. The latter was more elegant, but posed a security risk, since files would be stored on the server in a remotely accessible location without having been validated. I tried to fix the bug, but couldn't make it work, so I opted for the less elegant, but safe solution instead.
118 |
119 | * readded the :file_exec option, it's now possible to set this manually again. I cut it originally, because I felt that it was unneccessary and that there were too many options already, I readded it mainly to make it possible to test the validation better.
120 |
121 | * assign, save, delete_temporary_files, delete, filename= and dir= are now all private, I see no reason why they should be public, and since they aren't really useful out of context I think it makes for a cleaner API to make them private, if you still need to use them, you can use .send(:save), etc.. instead.
122 |
123 | * Added magic columns, see the readme for detaills.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2006 Sebastian Kanthak, Jonas Nicklas
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | = UploadColumn
2 |
3 | UploadColumn is a plugin for the Ruby on Rails framework that enables easy uploading of files, especially images.
4 |
5 | Suppose you have a list of users, and you would like to associate a picture to each of them. You could upload the image to a database, or you could use upload_column for simple storage to the file system.
6 |
7 | Assuming you have a User model with a column called 'picture' that is of type String, you could simply add the upload_column instruction to your User model:
8 |
9 | class User < ActiveRecord::Base
10 | upload_column :picture
11 | end
12 |
13 | That's it! You can start uploading files. Of course, +upload_column+ has a lot of different options you can use to customize your uploads.
14 |
15 | Uploading files is no fun without a user interface, so get going and make one:
16 |
17 | add an upload_column_field to your form, maybe like this:
18 |
19 |
20 | <%= upload_column_field 'user', 'picture' %>
21 |
22 | You should use upload_column_field instead of Rails' file_field, since it will work even when the form is redisplayed, like when a validation fails. Unfortunately file_field doesn't work in that case.
23 |
24 | Now that's excellent, but most likely it will fail, because instead of sending the file, it just sends a string. No worries though, if we just set the form's encoding to multipart it will all work out, UploadColumn even comes with some nice helpers to avoid that nasty multipart syntax. This could look something like this:
25 |
26 | <%= upload_form_tag( :action => 'create' ) %>
27 |
28 | And that's it! Your uploads are up and running (hopefully) and you should now be able to add pictures to your users. The madness doesn't stop there of course!
29 |
30 | == Storage Path
31 |
32 | You won't always want to store the pictures in the directory that upload_column selects for you, but that's not a problem, because changing that directory is trivial. You can pass a :store_dir key to the upload_column declaration, this will override the default mechanism and always use that directory as the basis.
33 |
34 | upload_column :picture, :store_dir => "pictures"
35 |
36 | might be sensible in our case. Note that this way, all files will be stored in the same directory.
37 |
38 | If you need more refined control over the storage path (maybe you need to store it by the id of an association?) then you can use a proc instead. Our proc might look like this:
39 |
40 | upload_column :picture, :store_dir => proc{|record, file| "images/#{record.category.name}/#{record.id}/#{file.extension}"}
41 |
42 | The proc will be passed two parameters, the first is the current instance of your model class, the second is the name of the attribute that is being uploaded to (in our case +attr+ would be :picture).
43 |
44 | You can change the :tmp_dir in the same way.
45 |
46 | == Filename
47 |
48 | By default, UploadColumn will keep the name of the original file, however this might be inconvenient in some cases. You can pass a :filename directive to your upload_column declaration:
49 |
50 | upload_column :picture, :filename => "donkey.png"
51 |
52 | In which case all files will be named +donkey.png+. This is not desirable if the file in question is a jpeg file of course. Usually it is more sensible to pass a Proc to :filename.
53 |
54 | upload_column :picture, :filename => proc{|record, file| "avatar#{record.id}.#{file.extension}"}
55 |
56 | The Proc will be passed two parameters, the current instance, and the file itself.
57 |
58 | == Manipulators
59 |
60 | UploadColumn allows you to use manipulators on your file, that in some way transform your file, or perform any kind
61 | of operations on it. There are currently two manipulators bundled, the RMagick manipulator and the ImageScience
62 | manipulator, but writing your own is very easy. There are further instructions on the website.
63 |
64 | == Manipulating Images with RMagick
65 |
66 | Say you would want (for whatever reason) to have a funky solarize effect on your users' images. Manipulating images with upload_column can be done either at runtime or after the image is saved, let's look at some possibilities:
67 |
68 | class User < ActiveRecord::Base
69 | upload_column :picture, :manipulator => UploadColumn::Manipulators::RMagick
70 |
71 | def picture_after_assign
72 |
73 | picture.process! do |img|
74 | img.solarize
75 | end
76 |
77 | end
78 | end
79 |
80 | You can also use the :process instruction, which will automatically apply the manipulation when a new image is uploaded. If you wanted to resize your image to a maximum of 800 by 600 pixels for example, you could do:
81 |
82 | class User < ActiveRecord::Base
83 | upload_column :picture, :process => '800x600', :manipulator => UploadColumn::Manipulators::RMagick
84 | end
85 |
86 | the previous example with solarize could be written shorter as:
87 |
88 | class User < ActiveRecord::Base
89 | upload_column :picture, :process => proc{|img| img.solarize }, :manipulator => UploadColumn::Manipulators::RMagick
90 | end
91 |
92 | Or maybe we want different versions of our image, then we could simply specify:
93 |
94 | class User < ActiveRecord::Base
95 | upload_column :picture, :versions => [ :solarized, :sepiatoned ], :manipulator => UploadColumn::Manipulators::RMagick
96 |
97 | def picture_after_assign
98 | picture.solarized.process! do |img|
99 | img.solarize
100 | end
101 | picture.sepiatoned.process! do |img|
102 | img.sepiatone
103 | end
104 | end
105 | end
106 |
107 | you can also use a Hash for versions and pass a dimension or a proc to it, so you can do:
108 |
109 | class User < ActiveRecord::Base
110 | upload_column :picture, :versions => { :thumb => "c100x100", :large => "200x300", :sepiatoned => proc{ |img| img.sepiatone } }, :manipulator => UploadColumn::Manipulators::RMagick
111 | end
112 |
113 | Note the 'c' in front of the dimensions for the thumb image, this will crop the image to the exact dimensions. All of this is a bit wordy though, and it also doesn't take check, that the files really are images. Sepiatoning the latest GreenDay song somehow doesn't sound too good. For that reason UploadColumn comes with the image_column function:
114 |
115 | class User < ActiveRecord::Base
116 | image_column :picture, :versions => { :thumb => "c100x100", :large => "200x300", :sepiatoned => proc{ |img| img.sepiatone } }
117 | end
118 |
119 | This also puts your images in public/images instead of public, which is neat!
120 |
121 | == Runtime rendering
122 |
123 | You can manipulate images at runtime (it's a huge performance hit though!). In your controller add an action and use UploadColumnRenderHelper.render_image.
124 |
125 | def sepiatone
126 | @user = User.find(parms[:id])
127 | render_image @user.picture do |img|
128 | img.sepiatone
129 | end
130 | end
131 |
132 | And that's it!
133 |
134 | In your view, you can use UploadColumnHelper.image to easily create an image tag for your action:
135 |
136 | <%= image :action => "sepiatone", :id => 5 %>
137 |
138 | == Views
139 |
140 | If your uploaded file is an image you would most likely want to display it in your view, if it's another kind of file you'll want to link to it. Both of these are easy using UploadColumn::BaseUploadedFile.url.
141 |
142 | <%= link_to "Guitar Tablature", @song.tab.url %>
143 |
144 | <%= image_tag @user.picture.url %>
145 |
146 | == Magic Columns
147 |
148 | UploadColumn allows you to add 'magic' columns to your model, which will be automatically filled with the appropriate data. Just add the column, for example via migrations:
149 |
150 | add_column :users, :picture_content_type
151 |
152 | And if our model looks like this:
153 |
154 | class User < ActiveRecord::Base
155 | upload_column :picture
156 | end
157 |
158 | The column picture_content_type will now automatically be filled with the file's content-type (or at least with UploadColumn's best guess ;).
159 |
160 | You can use any method method on UploadColumn::UploadedFile that takes no argument, so you can use for example, size, url, store_dir and so on.
161 |
162 | You can also do picture_exif_date_time or picture_exif_model, etc. This works only, of course, if the uploaded file is a JPEG image, since that is the only filetype that has exif data. This requires the EXIFR library, which you can get by installing the gem via gem install exifr.
163 |
164 | == Validations
165 |
166 | UploadColumn comes with its own validation method, validates_integrity_of. This method will ensure that only files with an extension from a whitelist will be uploaded. This prevents a hacker from uploading executable files (such as .rb, .pl or .cgi for example) or it can be used to restrict what kind of file are allowed to be uploaded, for example only images. You can customize the whitelist with the :extensions parameter to upload column.
167 |
168 | If you want to only allow the upload of XHTML and XML files, so you can manipulate them with XSLT you could do:
169 |
170 | upload_column :xml, :extensions => %w(xml html htm), :manipulator => MyXSLTProcessor
171 |
172 | validate_integrity_of :xml
173 |
174 | You can also use some of Rails' validations with UploadColumn.
175 |
176 | validates_presence_of and validates_size_of have been verified to work.
177 |
178 | validates_size_of :image, :maximum => 200000, :message => "is too big, must be smaller than 200kB!"
179 |
180 | Remember to change the error message, the default one sounds a bit stupid with UploadColumn.
181 |
182 | validates_uniqueness_of does NOT work, this is because validates_uniqueness_of will send(:your_upload_column) instead of asking for the instance variable, thus it will get an UploadedFile object, which it can't really compare to other values in the database, this is rather difficult to work around without messing with Rails internals (if you manage, please let me know!). Meanwhile you could do
183 |
184 | validates_each :your_upload_column do |record, attr, value|
185 | record.errors.add attr, 'already exists!' if YourModel.find( :first, :conditions => ["#{attr.to_s} = ?", value ] )
186 | end
187 |
188 | It's not elegant I know, but it should work.
189 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rake'
2 | require 'rake/testtask'
3 | require 'rake/rdoctask'
4 | require 'spec/rake/spectask'
5 |
6 | file_list = FileList['spec/*_spec.rb']
7 |
8 | namespace :spec do
9 | desc "Run all examples with RCov"
10 | Spec::Rake::SpecTask.new('rcov') do |t|
11 | t.spec_files = file_list
12 | t.rcov = true
13 | t.rcov_dir = "doc/coverage"
14 | t.rcov_opts = ['--exclude', 'spec']
15 | end
16 |
17 | desc "Generate an html report"
18 | Spec::Rake::SpecTask.new('report') do |t|
19 | t.spec_files = file_list
20 | t.rcov = true
21 | t.rcov_dir = "doc/coverage"
22 | t.rcov_opts = ['--exclude', 'spec']
23 | t.spec_opts = ["--format", "html:doc/reports/specs.html"]
24 | t.fail_on_error = false
25 | end
26 |
27 | desc "heckle all"
28 | task :heckle => [ 'spec:heckle:uploaded_file', 'spec:heckle:sanitized_file' ]
29 |
30 | namespace :heckle do
31 | desc "Heckle UploadedFile"
32 | Spec::Rake::SpecTask.new('uploaded_file') do |t|
33 | t.spec_files = [ File.join(File.dirname(__FILE__), *%w[spec uploaded_file_spec.rb]) ]
34 | t.spec_opts = ["--heckle", "UploadColumn::UploadedFile"]
35 | end
36 |
37 | desc "Heckle SanitizedFile"
38 | Spec::Rake::SpecTask.new('sanitized_file') do |t|
39 | t.spec_files = [ File.join(File.dirname(__FILE__), *%w[spec uploaded_file_spec.rb]) ]
40 | t.spec_opts = ["--heckle", "UploadColumn::SanitizedFile"]
41 | end
42 | end
43 |
44 | end
45 |
46 |
47 | desc 'Default: run unit tests.'
48 | task :default => 'spec:rcov'
49 |
50 | namespace "doc" do
51 |
52 | desc 'Generate documentation for the UploadColumn plugin.'
53 | Rake::RDocTask.new(:normal) do |rdoc|
54 | rdoc.rdoc_dir = 'doc/rdoc'
55 | rdoc.title = 'UploadColumn'
56 | rdoc.options << '--line-numbers' << '--inline-source'
57 | rdoc.rdoc_files.include('README')
58 | rdoc.rdoc_files.include('lib/**/*.rb')
59 | end
60 |
61 | desc 'Generate documentation for the UploadColumn plugin using the allison template.'
62 | Rake::RDocTask.new(:allison) do |rdoc|
63 | rdoc.rdoc_dir = 'doc/rdoc'
64 | rdoc.title = 'UploadColumn'
65 | rdoc.options << '--line-numbers' << '--inline-source'
66 | rdoc.rdoc_files.include('README')
67 | rdoc.rdoc_files.include('lib/**/*.rb')
68 | rdoc.main = "README" # page to start on
69 | rdoc.template = "~/Projects/allison2/allison/allison.rb"
70 | end
71 | end
--------------------------------------------------------------------------------
/init.rb:
--------------------------------------------------------------------------------
1 | # plugin init file for rails
2 | # this file will be picked up by rails automatically and
3 | # add the upload_column extensions to rails
4 |
5 | require File.join(File.dirname(__FILE__), 'lib', 'upload_column')
6 | require File.join(File.dirname(__FILE__), 'lib', 'upload_column', 'rails', 'upload_column_helper')
7 | require File.join(File.dirname(__FILE__), 'lib', 'upload_column', 'rails', 'action_controller_extension')
8 | require File.join(File.dirname(__FILE__), 'lib', 'upload_column', 'rails', 'asset_tag_extension')
9 |
10 | Mime::Type.register "image/png", :png
11 | Mime::Type.register "image/jpeg", :jpg
12 | Mime::Type.register "image/gif", :gif
13 |
14 | UploadColumn::Root = RAILS_ROOT
--------------------------------------------------------------------------------
/lib/upload_column.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), 'upload_column', 'sanitized_file.rb')
2 | require File.join(File.dirname(__FILE__), 'upload_column', 'uploaded_file.rb')
3 | require File.join(File.dirname(__FILE__), 'upload_column', 'magic_columns.rb')
4 | require File.join(File.dirname(__FILE__), 'upload_column', 'active_record_extension.rb')
5 | require File.join(File.dirname(__FILE__), 'upload_column', 'manipulators', 'rmagick.rb')
6 | require File.join(File.dirname(__FILE__), 'upload_column', 'manipulators', 'image_science.rb')
7 | require File.join(File.dirname(__FILE__), 'upload_column', 'configuration.rb')
8 |
9 |
10 | ActiveRecord::Base.send(:include, UploadColumn::ActiveRecordExtension)
--------------------------------------------------------------------------------
/lib/upload_column/active_record_extension.rb:
--------------------------------------------------------------------------------
1 | require 'fileutils'
2 | require 'tempfile'
3 |
4 | module UploadColumn
5 |
6 | Column = Struct.new(:name, :options)
7 |
8 | module ActiveRecordExtension
9 |
10 | def self.append_features(base) #:nodoc:
11 | super
12 | base.extend(ClassMethods)
13 | base.after_save :save_uploaded_files
14 | end
15 |
16 | private
17 |
18 | def save_uploaded_files
19 | @files.each { |k, v| v.send(:save) if v and v.tempfile? } if @files
20 | end
21 |
22 | def get_upload_column(name)
23 | options = options_for_column(name) #TODO: Spec this!
24 | @files ||= {}
25 | return nil if @files[name].is_a?(UploadColumn::IntegrityError)
26 | @files[name] ||= if self[name] then UploadColumn::UploadedFile.retrieve(self[name], self, name, options) else nil end
27 | end
28 |
29 | def set_upload_column(name, file)
30 | options = options_for_column(name)
31 | @files ||= {}
32 | if file.nil?
33 | @files[name], self[name] = nil
34 | else
35 | begin
36 | if uploaded_file = UploadColumn::UploadedFile.upload(file, self, name, options)
37 | self[name] = uploaded_file.actual_filename
38 | @files[name] = uploaded_file
39 | end
40 | rescue IntegrityError => e
41 | @files[name] = e
42 | end
43 | end
44 | end
45 |
46 | def get_upload_column_temp(name)
47 | @files[name].temp_value if @files and @files[name].respond_to?(:temp_value)
48 | end
49 |
50 | def set_upload_column_temp(name, path)
51 | options = options_for_column(name)
52 | @files ||= {}
53 | return if path.nil? or path.empty?
54 | unless @files[name] and @files[name].new_file?
55 | @files[name] = UploadColumn::UploadedFile.retrieve_temp(path, self, name, options)
56 | self[name] = @files[name].actual_filename
57 | end
58 | end
59 |
60 | def options_for_column(name)
61 | return self.class.reflect_on_upload_columns[name].options.reverse_merge(UploadColumn.configuration)
62 | end
63 |
64 | # weave in the magic column methods
65 | include UploadColumn::MagicColumns
66 |
67 | module ClassMethods
68 |
69 | # handle the +attr+ attribute as an "upload-column" field, generating additional methods as explained
70 | # in the README. You should pass the attribute's name as a symbol, like this:
71 | #
72 | # upload_column :picture
73 | #
74 | # +upload_column+ can manipulate file with the following options:
75 | # [+versions+] Creates different versions of the file, can be an Array or a Hash, in the latter case the values of the Hash will be passed to the manipulator
76 | # [+manipulator+] Takes a module that must have a method called process! that takes a single argument. Use this in conjucntion with :versions and :process
77 | # [+process+] This instrucion is passed to the manipulators process! method.
78 | #
79 | # you can customize file storage with the following:
80 | # [+store_dir+] Determines where the file will be stored permanently, you can pass a String or a Proc that takes the current instance and the attribute name as parameters, see the +README+ for detaills.
81 | # [+tmp_dir+] Determines where the file will be stored temporarily before it is stored to its final location, you can pass a String or a Proc that takes the current instance and the attribute name as parameters, see the +README+ for detaills.
82 | # [+old_files+] Determines what happens when a file becomes outdated. It can be set to one of :keep, :delete and :replace. If set to :keep UploadColumn will always keep old files, and if set to :delete it will always delete them. If it's set to :replace, the file will be replaced when a new one is uploaded, but will be kept when the associated object is deleted. Default to :delete.
83 | # [+permissions+] Specify the Unix permissions to be used with UploadColumn. Defaults to 0644. Remember that permissions are usually counted in octal and that in Ruby octal numbers start with a zero, so 0644 != 644.
84 | # [+root_dir+] The root path where image will be stored, it will be prepended to store_dir and tmp_dir
85 | #
86 | # it also accepts the following, less common options:
87 | # [+web_root+] Prepended to all addresses returned by UploadColumn::UploadedFile.url
88 | # [+extensions+] A white list of files that can be used together with validates_integrity_of to secure your uploads against malicious files.
89 | # [+fix_file_extensions+] Try to fix the file's extension based on its mime-type, note that this does not give you any security, to make sure that no dangerous files are uploaded, use +validates_integrity_of+. This defaults to true.
90 | # [+get_content_type_from_file_exec+] If this is set to true, UploadColumn::SanitizedFile will use a *nix exec to try to figure out the content type of the uploaded file.
91 | def upload_column(name, options = {})
92 | @upload_columns ||= {}
93 | @upload_columns[name] = Column.new(name, options)
94 |
95 | define_method( name ) { get_upload_column(name) }
96 | define_method( "#{name}=" ) { |file| set_upload_column(name, file) }
97 |
98 | define_submethod( name, "temp" ) { get_upload_column_temp(name) }
99 | define_submethod( name, "temp=" ) { |path| set_upload_column_temp(name, path) }
100 |
101 | define_submethod( name, "public_path" ) { get_upload_column(name).public_path rescue nil }
102 | define_submethod( name, "path" ) { get_upload_column(name).path rescue nil }
103 |
104 | if options[:versions]
105 | options[:versions].each do |k, v|
106 | define_submethod( name, k ) { get_upload_column(name).send(k) rescue nil }
107 | define_submethod( name, k, "public_path" ) { get_upload_column(name).send(k).public_path rescue nil }
108 | define_submethod( name, k, "path" ) { get_upload_column(name).send(k).path rescue nil }
109 | end
110 | end
111 | end
112 |
113 | def image_column(name, options={})
114 | upload_column(name, options.reverse_merge(UploadColumn.image_column_configuration))
115 | end
116 |
117 | # Validates whether the images extension is in the array passed to :extensions.
118 | # By default this is the UploadColumn.extensions array
119 | #
120 | # Use this to prevent upload of files which could potentially damage your system,
121 | # such as executables or script files (.rb, .php, etc...).
122 | def validates_integrity_of(*attr_names)
123 | configuration = { :message => "is not of a valid file type." }
124 | configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
125 |
126 | attr_names.each { |name| self.reflect_on_upload_columns[name].options[:validate_integrity] = true }
127 |
128 | validates_each(attr_names, configuration) do |record, attr, value|
129 | value = record.instance_variable_get('@files')[attr]
130 | record.errors.add(attr, value.message) if value.is_a?(IntegrityError)
131 | end
132 | end
133 |
134 | # returns a hash of all UploadColumns defined on the model and their options.
135 | def reflect_on_upload_columns
136 | @upload_columns || {}
137 | end
138 |
139 | private
140 |
141 | def define_submethod(name, *subs, &b)
142 | define_method([name, subs].join('_'), &b)
143 | end
144 |
145 | # This is mostly for testing
146 | def reset_upload_columns
147 | @upload_columns = {}
148 | end
149 |
150 | end
151 |
152 | end
153 |
154 | end
--------------------------------------------------------------------------------
/lib/upload_column/configuration.rb:
--------------------------------------------------------------------------------
1 | module UploadColumn
2 |
3 | mattr_accessor :configuration, :image_column_configuration, :extensions, :image_extensions
4 |
5 | self.extensions = %w(asf ai avi doc dvi dwg eps gif gz jpg jpeg mov mp3 mpeg odf pac pdf png ppt psd swf swx tar tar.gz torrent txt wmv wav xls zip)
6 | self.image_extensions = %w(jpg jpeg gif png)
7 |
8 | DEFAULT_CONFIGURATION = {
9 | :tmp_dir => 'tmp',
10 | :store_dir => proc{ |r, f| f.attribute.to_s },
11 | :root_dir => File.join(RAILS_ROOT, 'public'),
12 | :get_content_type_from_file_exec => true,
13 | :fix_file_extensions => false,
14 | :process => nil,
15 | :permissions => 0644,
16 | :extensions => self.extensions,
17 | :web_root => '',
18 | :manipulator => nil,
19 | :versions => nil,
20 | :validate_integrity => false
21 | }
22 |
23 | self.configuration = UploadColumn::DEFAULT_CONFIGURATION.clone
24 | self.image_column_configuration = {
25 | :manipulator => UploadColumn::Manipulators::RMagick,
26 | :root_dir => File.join(RAILS_ROOT, 'public', 'images'),
27 | :web_root => '/images',
28 | :extensions => self.image_extensions
29 | }.freeze
30 |
31 | def self.configure
32 | yield ConfigurationProxy.new
33 | end
34 |
35 | def self.reset_configuration
36 | self.configuration = UploadColumn::DEFAULT_CONFIGURATION.clone
37 | end
38 |
39 | class ConfigurationProxy
40 | def method_missing(method, value)
41 | if name = (method.to_s.match(/^(.*?)=$/) || [])[1]
42 | UploadColumn.configuration[name.to_sym] = value
43 | else
44 | super
45 | end
46 | end
47 | end
48 |
49 | end
--------------------------------------------------------------------------------
/lib/upload_column/magic_columns.rb:
--------------------------------------------------------------------------------
1 | module UploadColumn
2 | module MagicColumns
3 |
4 | def self.included(base)
5 | super
6 | base.send :alias_method_chain, :set_upload_column, :magic_columns
7 | base.send :alias_method_chain, :set_upload_column_temp, :magic_columns
8 | base.send :alias_method_chain, :save_uploaded_files, :magic_columns
9 | end
10 |
11 | def set_upload_column_with_magic_columns(name, file)
12 | set_upload_column_without_magic_columns(name, file)
13 | evaluate_magic_columns_for_upload_column(name)
14 | end
15 |
16 | def set_upload_column_temp_with_magic_columns(name, path)
17 | set_upload_column_temp_without_magic_columns(name, path)
18 | evaluate_magic_columns_for_upload_column(name)
19 | end
20 |
21 | def save_uploaded_files_with_magic_columns
22 | save_uploaded_files_without_magic_columns
23 | self.class.reflect_on_upload_columns.each do |name, column|
24 | evaluate_magic_columns_for_upload_column(name)
25 | end
26 | end
27 |
28 | private
29 |
30 | def evaluate_magic_columns_for_upload_column(name)
31 |
32 | self.class.column_names.each do |column_name|
33 |
34 | statement, predicate = column_name.split('_', 2)
35 |
36 | if statement and predicate and name.to_s == statement and not self.read_attribute(column_name.to_sym)
37 | uploaded_file = self.send(:get_upload_column, name.to_sym)
38 |
39 | self.write_attribute(column_name.to_sym, handle_predicate(uploaded_file, predicate))
40 | end
41 |
42 | end
43 | end
44 |
45 | def handle_predicate(uploaded_file, predicate)
46 | return uploaded_file.send(predicate.to_sym) if uploaded_file.respond_to?(predicate.to_sym)
47 | end
48 |
49 | end
50 | end
--------------------------------------------------------------------------------
/lib/upload_column/manipulators/image_science.rb:
--------------------------------------------------------------------------------
1 | module UploadColumn
2 | module Manipulators
3 |
4 | module ImageScience
5 |
6 | attr_reader :width, :height
7 |
8 | def load_manipulator_dependencies #:nodoc:
9 | require 'image_science'
10 | end
11 |
12 | def process!(instruction)
13 | if instruction.to_s =~ /^c(\d+x\d+)/
14 | crop_resized!($1)
15 | elsif instruction.to_s =~ /\d+x\d+/
16 | resize!(instruction)
17 | end
18 | end
19 |
20 | # Resize the image so that it will not exceed the dimensions passed
21 | # via geometry, geometry should be a string, formatted like '200x100' where
22 | # the first number is the height and the second is the width
23 | def resize!( geometry )
24 | ::ImageScience.with_image(self.path) do |img|
25 | width, height = extract_dimensions(img.width, img.height, geometry)
26 | img.resize( width, height ) do |file|
27 | file.save( self.path )
28 | end
29 | end
30 | end
31 |
32 | # Resize and crop the image so that it will have the exact dimensions passed
33 | # via geometry, geometry should be a string, formatted like '200x100' where
34 | # the first number is the height and the second is the width
35 | def crop_resized!( geometry )
36 | ::ImageScience.with_image(self.path) do |img|
37 | new_width, new_height = geometry.split('x').map{|i| i.to_i }
38 |
39 | width, height = extract_dimensions_for_crop(img.width, img.height, geometry)
40 | x_offset, y_offset = extract_placement_for_crop(width, height, geometry)
41 |
42 | img.resize( width, height ) do |i2|
43 |
44 | i2.with_crop( x_offset, y_offset, new_width + x_offset, new_height + y_offset) do |file|
45 | file.save( self.path )
46 | end
47 | end
48 | end
49 | end
50 |
51 | private
52 |
53 | def extract_dimensions(width, height, new_geometry, type = :resize)
54 | new_width, new_height = convert_geometry(new_geometry)
55 |
56 | aspect_ratio = width.to_f / height.to_f
57 | new_aspect_ratio = new_width / new_height
58 |
59 | if (new_aspect_ratio > aspect_ratio) ^ ( type == :crop ) # Image is too wide, the caret is the XOR operator
60 | new_width, new_height = [ (new_height * aspect_ratio), new_height]
61 | else #Image is too narrow
62 | new_width, new_height = [ new_width, (new_width / aspect_ratio)]
63 | end
64 |
65 | [new_width, new_height].collect! { |v| v.round }
66 | end
67 |
68 | def extract_dimensions_for_crop(width, height, new_geometry)
69 | extract_dimensions(width, height, new_geometry, :crop)
70 | end
71 |
72 | def extract_placement_for_crop(width, height, new_geometry)
73 | new_width, new_height = convert_geometry(new_geometry)
74 | x_offset = (width / 2.0) - (new_width / 2.0)
75 | y_offset = (height / 2.0) - (new_height / 2.0)
76 | [x_offset, y_offset].collect! { |v| v.round }
77 | end
78 |
79 | def convert_geometry(geometry)
80 | geometry.split('x').map{|i| i.to_f }
81 | end
82 |
83 | end
84 |
85 | end
86 | end
--------------------------------------------------------------------------------
/lib/upload_column/manipulators/rmagick.rb:
--------------------------------------------------------------------------------
1 | module UploadColumn
2 |
3 | UploadError = Class.new(StandardError) unless defined?(UploadError)
4 | ManipulationError = Class.new(UploadError) unless defined?(ManipulationError)
5 |
6 | module Manipulators
7 |
8 | module RMagick
9 |
10 | def load_manipulator_dependencies #:nodoc:
11 | require 'RMagick'
12 | end
13 |
14 | def process!(instruction = nil, &block)
15 | if instruction.is_a?(Proc)
16 | manipulate!(&instruction)
17 | elsif instruction.to_s =~ /^c(\d+x\d+)$/
18 | crop_resized!($1)
19 | elsif instruction.to_s =~ /^(\d+x\d+)$/
20 | resize!($1)
21 | end
22 | manipulate!(&block) if block
23 | end
24 |
25 | # Convert the image to format
26 | def convert!(format)
27 | manipulate! do |img|
28 | img.format = format.to_s.upcase
29 | img
30 | end
31 | end
32 |
33 | # Resize the image so that it will not exceed the dimensions passed
34 | # via geometry, geometry should be a string, formatted like '200x100' where
35 | # the first number is the height and the second is the width
36 | def resize!( geometry )
37 | manipulate! do |img|
38 | img.change_geometry( geometry ) do |c, r, i|
39 | i.resize(c,r)
40 | end
41 | end
42 | end
43 |
44 | # Resize and crop the image so that it will have the exact dimensions passed
45 | # via geometry, geometry should be a string, formatted like '200x100' where
46 | # the first number is the height and the second is the width
47 | def crop_resized!( geometry )
48 | manipulate! do |img|
49 | h, w = geometry.split('x')
50 | img.crop_resized(h.to_i,w.to_i)
51 | end
52 | end
53 |
54 | def manipulate!
55 | image = ::Magick::Image.read(self.path)
56 |
57 | if image.size > 1
58 | list = ::Magick::ImageList.new
59 | image.each do |frame|
60 | list << yield( frame )
61 | end
62 | list.write(self.path)
63 | else
64 | yield( image.first ).write(self.path)
65 | end
66 | rescue ::Magick::ImageMagickError => e
67 | # this is a more meaningful error message, which we could catch later
68 | raise ManipulationError.new("Failed to manipulate with rmagick, maybe it is not an image? Original Error: #{e}")
69 | end
70 |
71 | end
72 |
73 | end
74 |
75 | end
--------------------------------------------------------------------------------
/lib/upload_column/rails/action_controller_extension.rb:
--------------------------------------------------------------------------------
1 | module UploadColumn::ActionControllerExtension
2 |
3 | def self.included(base)
4 | base.send :alias_method_chain, :url_for, :uploaded_file_check
5 | base.helper_method :url_for_path
6 | end
7 |
8 | protected
9 |
10 | def url_for_with_uploaded_file_check(options = {}, *parameters_for_method_reference)
11 | if(options.respond_to?(:public_path))
12 | options.public_path
13 | else
14 | url_for_without_uploaded_file_check(options || {}, *parameters_for_method_reference)
15 | end
16 | end
17 |
18 | def url_for_path(path)
19 | request.protocol + request.host_with_port + path
20 | end
21 |
22 | # You can use +render_image+ in your controllers to render an image
23 | # def picture
24 | # @user = User.find(params[:id])
25 | # render_image @user.picture
26 | # end
27 | # This of course, is not very useful at all (you could simply have linked to the image itself),
28 | # However it is even possible to pass a block to render_image that allows manipulation using
29 | # RMagick, here the fun begins:
30 | # def solarize_picture
31 | # @user = User.find(params[:id])
32 | # render_image @user.picture do |img|
33 | # img = img.segment
34 | # img.solarize
35 | # end
36 | # end
37 | # Note that like in UploadColumn::BaseUploadedFile.process you will need to 'carry' the image
38 | # since most Rmagick methods do not modify the image itself but rather return the result of the
39 | # transformation.
40 | #
41 | # Instead of passing an upload_column object to +render_image+ you can even pass a path String,
42 | # if you do you will have to pass a :mime-type option as well though.
43 | def render_image( file, options = {} )
44 | format = if options.is_a?(Hash) then options[:force_format] else nil end
45 | mime_type = if options.is_a?(String) then options else options[:mime_type] end
46 | mime_type ||= file.mime_type
47 | path = if file.is_a?( String ) then file else file.path end
48 | headers["Content-Type"] = mime_type unless format
49 |
50 | if block_given? or format
51 | img = ::Magick::Image::read(path).first
52 | img = yield( img ) if block_given?
53 | img.format = format.to_s.upcase if format
54 | render :text => img.to_blob, :layout => false
55 | else
56 | send_file( path )
57 | end
58 | end
59 | end
60 |
61 | ActionController::Base.send(:include, UploadColumn::ActionControllerExtension)
--------------------------------------------------------------------------------
/lib/upload_column/rails/asset_tag_extension.rb:
--------------------------------------------------------------------------------
1 | module UploadColumn::AssetTagExtension
2 |
3 | def self.included(base)
4 | base.send :alias_method_chain, :image_tag, :uploaded_file_check
5 | end
6 |
7 | def image_tag_with_uploaded_file_check(source, options = {})
8 | if(source.respond_to?(:public_path))
9 | image_tag_without_uploaded_file_check(source.public_path, options)
10 | else
11 | image_tag_without_uploaded_file_check(source, options)
12 | end
13 | end
14 |
15 | end
16 |
17 | ActionView::Helpers::AssetTagHelper.send(:include, UploadColumn::AssetTagExtension)
--------------------------------------------------------------------------------
/lib/upload_column/rails/upload_column_helper.rb:
--------------------------------------------------------------------------------
1 | module UploadColumn::UploadColumnHelper
2 |
3 | # Returns an input tag of the "file" type tailored for accessing an upload_column field
4 | # (identified by method) on an object assigned to the template (identified by object).
5 | # Additional options on the input tag can be passed as a hash with options.
6 | #
7 | # Example (call, result)
8 | # upload_column_field( :user, :picture )
9 | #
10 | #
11 | #
12 | # Note: if you use file_field instead of upload_column_field, the file will not be
13 | # stored across form redisplays.
14 | def upload_column_field(object, method, options={})
15 | file_field(object, method, options) + hidden_field(object, method.to_s + '_temp')
16 | end
17 |
18 | # A helper method for creating a form tag to use with uploadng files,
19 | # it works exactly like Rails' form_tag, except that :multipart is always true
20 | def upload_form_tag(url_for_options = {}, options = {}, *parameters_for_url, &proc)
21 | options[:multipart] = true
22 | form_tag( url_for_options, options, *parameters_for_url, &proc )
23 | end
24 |
25 | # A helper method for creating a form tag to use with uploadng files,
26 | # it works exactly like Rails' form_for, except that :multipart is always true
27 | def upload_form_for(*args, &block)
28 | options = args.extract_options!
29 | options[:html] ||= {}
30 | options[:html][:multipart] = true
31 | args.push(options)
32 |
33 | form_for(*args, &block)
34 | end
35 |
36 | end
37 |
38 | class ActionView::Helpers::FormBuilder #:nodoc:
39 | self.field_helpers += ['upload_column_field']
40 | def upload_column_field(method, options = {})
41 | @template.send(:upload_column_field, @object_name, method, options.merge(:object => @object))
42 | end
43 | end
44 |
45 | ActionView::Base.send(:include, UploadColumn::UploadColumnHelper)
--------------------------------------------------------------------------------
/lib/upload_column/sanitized_file.rb:
--------------------------------------------------------------------------------
1 | begin; require 'mime/types'; rescue Exception; end
2 |
3 | require 'fileutils'
4 |
5 | module UploadColumn
6 | # Sanitize is a base class that takes care of all the dirtywork when dealing with file uploads.
7 | # it is subclassed as UploadedFile in UploadColumn, which does most of the upload magic, but if
8 | # you want to roll you own uploading system, SanitizedFile might be for you since it takes care
9 | # of a lot of the unfun stuff.
10 | #
11 | # Usage is pretty simple, just do SanitizedFile.new(some_uploaded_file) and you're good to go
12 | # you can now use #copy_to and #move_to to place the file wherever you want, whether it is a StringIO
13 | # or a TempFile.
14 | #
15 | # SanitizedFile also deals with content type detection, which it does either through the 'file' *nix exec
16 | # or (if you are stuck on Windows) through the MIME::Types library (not to be confused with Rails' Mime class!).
17 | class SanitizedFile
18 |
19 | attr_reader :basename, :extension
20 |
21 | def initialize(file, options = {})
22 | @options = options
23 | if file && file.instance_of?(String) && !file.empty?
24 | @path = file
25 | self.filename = File.basename(file)
26 | else
27 | @file = file
28 | self.filename = self.original_filename unless self.empty?
29 | end
30 | end
31 |
32 | # Returns the filename before sanitation took place
33 | def original_filename
34 | @original_filename ||= if @file and @file.respond_to?(:original_filename)
35 | @file.original_filename
36 | elsif self.path
37 | File.basename(self.path)
38 | end
39 | end
40 |
41 | # Returns the files properly sanitized filename.
42 | def filename
43 | @filename ||= (self.extension && !self.extension.empty?) ? "#{self.basename}.#{self.extension}" : self.basename
44 | end
45 |
46 | # Returns the file's size
47 | def size
48 | return @file.size if @file.respond_to?(:size)
49 | File.size(self.path) rescue nil
50 | end
51 |
52 | # Returns the full path to the file
53 | def path
54 | @path ||= File.expand_path(@file.path) rescue nil
55 | end
56 |
57 | # Checks if the file is empty.
58 | def empty?
59 | (@file.nil? && @path.nil?) || self.size.nil? || self.size.zero?
60 | end
61 |
62 | # Checks if the file exists
63 | def exists?
64 | File.exists?(self.path) if self.path
65 | end
66 |
67 | # Moves the file to 'path'
68 | def move_to(path)
69 | if copy_file(path)
70 | # FIXME: This gets pretty broken in UploadedFile. E.g. moving avatar-thumb.jpg will change the filename
71 | # to avatar-thumb-thumb.jpg
72 | @basename, @extension = split_extension(File.basename(path))
73 | @file = nil
74 | @filename = nil
75 | @path = path
76 | end
77 | end
78 |
79 | # Copies the file to 'path' and returns a new SanitizedFile that points to the copy.
80 | def copy_to(path)
81 | copy = self.clone
82 | copy.move_to(path)
83 | return copy
84 | end
85 |
86 | # Returns the content_type of the file as determined through the MIME::Types library or through a *nix exec.
87 | def content_type
88 | unless content_type = get_content_type_from_exec || get_content_type_from_mime_types
89 | content_type ||= @file.content_type.chomp if @file.respond_to?(:content_type) and @file.content_type
90 | end
91 | return content_type
92 | end
93 |
94 | private
95 |
96 | def copy_file(path)
97 | unless self.empty?
98 | # create the directory if it doesn't exist
99 | FileUtils.mkdir_p(File.dirname(path)) unless File.exists?(File.dirname(path))
100 | # stringios don't have a path and can't be copied
101 | if not self.path and @file.respond_to?(:read)
102 | @file.rewind # Make sure we are at the beginning of the buffer
103 | File.open(path, "wb") { |f| f.write(@file.read) }
104 | else
105 | begin
106 | FileUtils.cp(self.path, path)
107 | rescue ArgumentError
108 | end
109 | end
110 | File.chmod(@options[:permissions], path) if @options[:permissions]
111 | return true
112 | end
113 | end
114 |
115 | def filename=(filename)
116 | basename, extension = split_extension(filename)
117 | @basename = sanitize(basename)
118 | @extension = correct_file_extension(extension)
119 | end
120 |
121 | # tries to identify the mime-type of file and correct self's extension
122 | # based on the found mime-type
123 | def correct_file_extension(ext)
124 | if @options[:fix_file_extensions] && defined?(MIME::Types)
125 | if mimes = MIME::Types[self.content_type]
126 | return mimes.first.extensions.first unless mimes.first.extensions.empty?
127 | end
128 | end
129 | return ext.downcase
130 | end
131 |
132 | # Try to use *nix exec to fetch content type
133 | def get_content_type_from_exec
134 | if @options[:get_content_type_from_file_exec] and not self.path.empty?
135 | return system_call(%(file -bi "#{self.path}")).chomp.scan(/^[a-z0-9\-_]+\/[a-z0-9\-_]+/).first
136 | end
137 | rescue
138 | nil
139 | end
140 |
141 | def system_call(command)
142 | `#{command}`
143 | end
144 |
145 | def get_content_type_from_mime_types
146 | if @extension and defined?(MIME::Types)
147 | mimes = MIME::Types.of(@extension)
148 | return mimes.first.content_type rescue nil
149 | end
150 | end
151 |
152 | def sanitize(name)
153 | # Sanitize the filename, to prevent hacking
154 | name = File.basename(name.gsub("\\", "/")) # work-around for IE
155 | name.gsub!(/[^a-zA-Z0-9\.\-\+_]/,"_")
156 | name = "_#{name}" if name =~ /^\.+$/
157 | name = "unnamed" if name.size == 0
158 | return name.downcase
159 | end
160 |
161 | def split_extension(fn)
162 | # regular expressions to try for identifying extensions
163 | ext_regexps = [
164 | /^(.+)\.([^\.]{1,3}\.[^\.]{1,4})$/, # matches "something.tar.gz"
165 | /^(.+)\.([^\.]+)$/ # matches "something.jpg"
166 | ]
167 | ext_regexps.each do |regexp|
168 | if fn =~ regexp
169 | return $1, $2
170 | end
171 | end
172 | return fn, "" # In case we weren't able to split the extension
173 | end
174 |
175 | end
176 | end
--------------------------------------------------------------------------------
/lib/upload_column/uploaded_file.rb:
--------------------------------------------------------------------------------
1 | module UploadColumn
2 |
3 | class UploadError < StandardError #:nodoc:
4 | end
5 | class IntegrityError < UploadError #:nodoc:
6 | end
7 | class TemporaryPathMalformedError < UploadError #:nodoc:
8 | end
9 | class UploadNotMultipartError < UploadError #:nodoc:
10 | end
11 |
12 | TempValueRegexp = %r{^((?:\d+\.)+\d+)/([^/;]+)(?:;([^/;]+))?$}
13 |
14 |
15 | # When you call an upload_column field, an instance of this class will be returned.
16 | #
17 | # Suppose a +User+ model has a +picture+ upload_column, like so:
18 | # class User < ActiveRecord::Base
19 | # upload_column :picture
20 | # end
21 | # Now in our controller we did:
22 | # @user = User.find(params[:id])
23 | # We could then access the file:
24 | # @user.picture.url
25 | # Which would output the url to the file (assuming it is stored in /public/)
26 | # = Versions
27 | # If we had instead added different versions in our model
28 | # upload_column :picture, :versions => [:thumb, :large]
29 | # Then we could access them like so:
30 | # @user.picture.thumb.url
31 | # See the +README+ for more detaills.
32 | class UploadedFile < SanitizedFile
33 |
34 | attr_reader :instance, :attribute, :options, :versions
35 | attr_accessor :suffix
36 |
37 | class << self
38 |
39 | # upload a file. In most cases you want to pass the ActiveRecord instance and the attribute
40 | # name as well as the file. For a more bare-bones approach, check out SanitizedFile.
41 | def upload(file, instance = nil, attribute = nil, options = {}) #:nodoc:
42 | uf = self.new(:upload, file, instance, attribute, options)
43 | return uf.empty? ? nil : uf
44 | end
45 |
46 | # Retrieve a file from the filesystem, based on the calculated store_dir and the filename
47 | # stored in the database.
48 | def retrieve(filename, instance = nil, attribute = nil, options = {}) #:nodoc:
49 | self.new(:retrieve, filename, instance, attribute, options)
50 | end
51 |
52 | # Retreieve a file that was stored as a temp file
53 | def retrieve_temp(path, instance = nil, attribute = nil, options = {}) #:nodoc:
54 | self.new(:retrieve_temp, path, instance, attribute, options)
55 | end
56 |
57 | end
58 |
59 | def initialize(mode, file, instance, attribute, options={})
60 | # TODO: the options are always reverse merged in here, in case UploadedFile has
61 | # been initialized outside UploadColumn proper, this is not a very elegant solution, imho.
62 | @options = options.reverse_merge(UploadColumn.configuration)
63 | @instance = instance
64 | @attribute = attribute
65 | @suffix = options[:suffix]
66 |
67 | load_manipulator
68 |
69 | case mode
70 | when :upload
71 | if file and file.is_a?(String) and not file.empty?
72 | raise UploadNotMultipartError.new("Do not know how to handle a string with value '#{file}' that was uploaded. Check if the form's encoding has been set to 'multipart/form-data'.")
73 | end
74 |
75 | super(file, @options)
76 |
77 | unless empty?
78 | if options[:validate_integrity]
79 | raise UploadError.new("No list of valid extensions supplied.") unless options[:extensions]
80 | raise IntegrityError.new("has an extension that is not allowed.") unless options[:extensions].include?(extension)
81 | end
82 |
83 | @temp_name = generate_tmpname
84 | @new_file = true
85 |
86 | move_to_directory(File.join(tmp_dir, @temp_name))
87 |
88 | # The original is processed before versions are initialized.
89 | self.process!(@options[:process]) if @options[:process] and self.respond_to?(:process!)
90 |
91 | initialize_versions do |version|
92 | copy_to_version(version)
93 | end
94 |
95 | apply_manipulations_to_versions
96 |
97 | # trigger the _after_upload callback
98 | self.instance.send("#{self.attribute}_after_upload", self) if self.instance.respond_to?("#{self.attribute}_after_upload")
99 | end
100 | when :retrieve
101 | @path = File.join(store_dir, file)
102 | @basename, @extension = split_extension(file)
103 | initialize_versions
104 | when :retrieve_temp
105 | if file and not file.empty?
106 | @temp_name, name, original_filename = file.scan( ::UploadColumn::TempValueRegexp ).first
107 |
108 | if @temp_name and name
109 | @path = File.join(tmp_dir, @temp_name, name)
110 | @basename, @extension = split_extension(name)
111 | @original_filename = original_filename
112 | initialize_versions
113 | else
114 | raise TemporaryPathMalformedError.new("#{file} is not a valid temporary path!")
115 | end
116 | end
117 | else
118 | super(file, @options)
119 | initialize_versions
120 | end
121 | end
122 |
123 | # Returns the directory where tmp files are stored for this UploadedFile, relative to :root_dir
124 | def relative_tmp_dir
125 | parse_dir_options(:tmp_dir)
126 | end
127 |
128 | # Returns the directory where tmp files are stored for this UploadedFile
129 | def tmp_dir
130 | File.expand_path(self.relative_tmp_dir, @options[:root_dir])
131 | end
132 |
133 | # Returns the directory where files are stored for this UploadedFile, relative to :root_dir
134 | def relative_store_dir
135 | parse_dir_options(:store_dir)
136 | end
137 |
138 | # Returns the directory where files are stored for this UploadedFile
139 | def store_dir
140 | File.expand_path(self.relative_store_dir, @options[:root_dir])
141 | end
142 |
143 | # Returns the path of the file relative to :root_dir
144 | def relative_path
145 | self.path.sub(File.expand_path(options[:root_dir]) + '/', '')
146 | end
147 |
148 | # returns the full path of the file.
149 | def path; super; end
150 |
151 | # returns the directory where the file is currently stored.
152 | def dir
153 | File.dirname(self.path)
154 | end
155 |
156 | # return true if the file has just been uploaded.
157 | def new_file?
158 | @new_file
159 | end
160 |
161 | # returns the url of the file, by merging the relative path with the web_root option.
162 | def public_path
163 | # TODO: this might present an attack vector if the file is outside the web_root
164 | options[:web_root].to_s + '/' + self.relative_path.gsub("\\", "/")
165 | end
166 |
167 | alias_method :to_s, :public_path
168 | alias_method :url, :public_path
169 |
170 | # this is the value returned when avatar_temp is called, where avatar is an upload_column
171 | def temp_value #:nodoc:
172 | if tempfile?
173 | if original_filename
174 | %(#{@temp_name}/#{filename};#{original_filename})
175 | else
176 | %(#{@temp_name}/#{filename})
177 | end
178 | end
179 | end
180 |
181 | def inspect #:nodoc:
182 | ""
183 | end
184 |
185 | def tempfile?
186 | @temp_name
187 | end
188 |
189 | alias_method :actual_filename, :filename
190 |
191 | def filename
192 | unless bn = parse_dir_options(:filename)
193 | bn = [self.basename, self.suffix].compact.join('-')
194 | bn += ".#{self.extension}" unless self.extension.blank?
195 | end
196 | return bn
197 | end
198 |
199 | # TODO: this is a public method, should be specced
200 | def move_to_directory(dir)
201 | p = File.join(dir, self.filename)
202 | if copy_file(p)
203 | @path = p
204 | end
205 | end
206 |
207 | private
208 |
209 | def copy_to_version(version)
210 | copy = self.clone
211 | copy.suffix = version
212 |
213 | if copy_file(File.join(self.dir, copy.filename))
214 | return copy
215 | end
216 | end
217 |
218 | def initialize_versions
219 | if self.options[:versions]
220 | @versions = {}
221 |
222 | version_keys = options[:versions].is_a?(Hash) ? options[:versions].keys : options[:versions]
223 |
224 | version_keys.each do |version|
225 |
226 | version = version.to_sym
227 |
228 | # Raise an error if the version name is a method on this class
229 | raise ArgumentError.new("#{version} is an illegal name for an UploadColumn version.") if self.respond_to?(version)
230 |
231 | if block_given?
232 | @versions[version] = yield(version)
233 | else
234 | # Copy the file and store it in the versions array
235 | # TODO: this might result in the manipulator not being loaded.
236 | @versions[version] = self.clone #class.new(:open, File.join(self.dir, "#{self.basename}-#{version}.#{self.extension}"), instance, attribute, options.merge(:versions => nil, :suffix => version))
237 | @versions[version].suffix = version
238 | end
239 |
240 | @versions[version].instance_eval { @path = File.join(self.dir, self.filename) } # ensure path is not cached
241 |
242 | # Add the version methods to the instance
243 | self.instance_eval <<-SRC
244 | def #{version}
245 | self.versions[:#{version}]
246 | end
247 | SRC
248 | end
249 | end
250 | end
251 |
252 | def load_manipulator
253 | if options[:manipulator]
254 | self.extend(options[:manipulator])
255 | self.load_manipulator_dependencies if self.respond_to?(:load_manipulator_dependencies)
256 | end
257 | end
258 |
259 | def apply_manipulations_to_versions
260 | @versions.each do |k, v|
261 | v.process! @options[:versions][k]
262 | end if @options[:versions].is_a?(Hash)
263 | end
264 |
265 | def save
266 | self.move_to_directory(self.store_dir)
267 | self.versions.each { |version, file| file.move_to_directory(self.store_dir) } if self.versions
268 | @new_file = false
269 | @temp_name = nil
270 | true
271 | end
272 |
273 | def parse_dir_options(option)
274 | if self.instance.respond_to?("#{self.attribute}_#{option}")
275 | self.instance.send("#{self.attribute}_#{option}", self)
276 | else
277 | option = @options[option]
278 | if option.is_a?(Proc)
279 | case option.arity
280 | when 2
281 | option.call(self.instance, self)
282 | when 1
283 | option.call(self.instance)
284 | else
285 | option.call
286 | end
287 | else
288 | option
289 | end
290 | end
291 | end
292 |
293 | def generate_tmpname
294 | now = Time.now
295 | "#{now.to_i}.#{now.usec}.#{Process.pid}"
296 | end
297 |
298 | end
299 | end
--------------------------------------------------------------------------------
/spec/active_record_extension_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), 'spec_helper')
2 |
3 | gem 'activerecord'
4 | require 'active_record'
5 |
6 | require File.join(File.dirname(__FILE__), '../lib/upload_column')
7 |
8 | class Entry < ActiveRecord::Base; end # setup a basic AR class for testing
9 |
10 | describe "an ActiveRecord class" do
11 |
12 | include UploadColumnSpecHelper
13 |
14 | it "should respond to upload_column" do
15 | Entry.should respond_to(:upload_column)
16 | end
17 |
18 | it "should reflect on upload_columns" do
19 | Entry.send(:reset_upload_columns)
20 |
21 | Entry.upload_column(:avatar)
22 |
23 | Entry.reflect_on_upload_columns[:avatar].should be_an_instance_of(UploadColumn::Column)
24 | Entry.reflect_on_upload_columns[:monkey].should == nil
25 |
26 | Entry.upload_column(:monkey)
27 |
28 | Entry.reflect_on_upload_columns[:avatar].should be_an_instance_of(UploadColumn::Column)
29 | Entry.reflect_on_upload_columns[:monkey].should be_an_instance_of(UploadColumn::Column)
30 | end
31 |
32 | it "should reset upload columns" do
33 | Entry.upload_column(:avatar)
34 |
35 | Entry.reflect_on_upload_columns[:avatar].should be_an_instance_of(UploadColumn::Column)
36 |
37 | Entry.send(:reset_upload_columns)
38 |
39 | Entry.reflect_on_upload_columns[:avatar].should == nil
40 | end
41 |
42 | end
43 |
44 | describe "an Active Record class with an upload_column" do
45 |
46 | include UploadColumnSpecHelper
47 |
48 | it "should add accessor methods" do
49 | # use a name that hasn't been used before!
50 | entry = disconnected_model(Entry)
51 | entry.should_not respond_to(:llama)
52 | entry.should_not respond_to(:llama_temp)
53 | entry.should_not respond_to(:llama=)
54 | entry.should_not respond_to(:llama_temp=)
55 |
56 | Entry.upload_column(:llama)
57 |
58 | entry = disconnected_model(Entry)
59 |
60 | entry.should respond_to(:llama)
61 | entry.should respond_to(:llama_temp)
62 | entry.should respond_to(:llama=)
63 | entry.should respond_to(:llama_temp=)
64 | end
65 |
66 | it "should save the name of the column to be reflected upon" do
67 | Entry.upload_column(:walruss)
68 | Entry.reflect_on_upload_columns[:walruss].name.should == :walruss
69 | end
70 |
71 | it "should save the options to be reflected upon" do
72 | options = { :donkey => true }
73 |
74 | Entry.upload_column(:walruss, options)
75 |
76 | Entry.reflect_on_upload_columns[:walruss].options.should == options
77 | end
78 | end
79 |
80 | describe "an Active Record with no upload_column" do
81 |
82 | before(:all) do
83 | class Monkey < ActiveRecord::Base; end
84 | end
85 |
86 | it "should have no uploads_column" do
87 | Monkey.reflect_on_upload_columns.should == {}
88 | end
89 |
90 | it "should be instantiable" do
91 | Monkey.stub!(:columns).and_return([])
92 | Monkey.new
93 | end
94 |
95 | end
96 |
97 | describe "uploading a file" do
98 |
99 | include UploadColumnSpecHelper
100 |
101 | before do
102 | setup_standard_mocking
103 | UploadColumn::UploadedFile.should_receive(:upload).with(@file, @entry, :avatar, @options).and_return(@uploaded_file)
104 | end
105 |
106 | it "should pass it to UploadedFile and remember it" do
107 | @entry.avatar.should == nil
108 | @entry.avatar = @file
109 | @entry.avatar.should == @uploaded_file
110 | end
111 |
112 | it "should set the attribute on the ActiveRecord" do
113 | @entry.should_receive(:[]=).with(:avatar, 'monkey.png')
114 | @entry.avatar = @file
115 | end
116 |
117 | end
118 |
119 | describe "uploading an empty String" do
120 |
121 | include UploadColumnSpecHelper
122 |
123 | before do
124 | setup_standard_mocking
125 | end
126 |
127 | it "should do nothing" do
128 | UploadColumn::UploadedFile.should_receive(:upload).with("", @entry, :avatar, @options).and_return(nil)
129 | @entry.avatar.should == nil
130 | @entry.avatar = ""
131 | @entry.avatar.should == nil
132 | end
133 |
134 | it "shouldn't affect an already uploaded file" do
135 | UploadColumn::UploadedFile.should_receive(:upload).with(@file, @entry, :avatar, @options).and_return(@uploaded_file)
136 | @entry.avatar = @file
137 | @entry.avatar.should == @uploaded_file
138 |
139 | UploadColumn::UploadedFile.should_receive(:upload).with("", @entry, :avatar, @options).and_return(nil)
140 | @entry.avatar = ""
141 | @entry.avatar.should == @uploaded_file
142 | end
143 |
144 | end
145 |
146 | describe "setting nil explicitly" do
147 |
148 | include UploadColumnSpecHelper
149 |
150 | before do
151 | setup_standard_mocking
152 | end
153 |
154 | it "should reset the column" do
155 | UploadColumn::UploadedFile.should_receive(:upload).with(@file, @entry, :avatar, @options).and_return(@uploaded_file)
156 | @entry.avatar = @file
157 | @entry.avatar.should == @uploaded_file
158 |
159 | @entry.avatar = nil
160 | @entry.avatar.should == nil
161 | end
162 | end
163 |
164 | describe "an upload_column with a value stored in the database and no uploaded_file" do
165 |
166 | include UploadColumnSpecHelper
167 |
168 | before do
169 | @options = mock('options', :null_object => true)
170 | Entry.upload_column(:avatar, @options)
171 |
172 | @entry = disconnected_model(Entry)
173 | @entry.stub!(:inspect).and_return('<#Entry>')
174 | @string = mock('some string')
175 | @entry.should_receive(:[]).with(:avatar).at_least(:once).and_return(@string)
176 | end
177 |
178 | it "should retrieve the file from the database" do
179 | uploaded_file = mock('uploaded file')
180 |
181 | UploadColumn::UploadedFile.should_receive(:retrieve).with(@string, @entry, :avatar, @options).and_return(uploaded_file)
182 |
183 | @entry.avatar.should == uploaded_file
184 | end
185 | end
186 |
187 | describe "saving uploaded files" do
188 |
189 | include UploadColumnSpecHelper
190 |
191 | before do
192 | setup_standard_mocking
193 | end
194 |
195 | it "should call save on the uploaded file if they are temporary files" do
196 | UploadColumn::UploadedFile.should_receive(:upload).with(@file, @entry, :avatar, @options).and_return(@uploaded_file)
197 |
198 | @uploaded_file.should_receive(:tempfile?).and_return(true)
199 | @uploaded_file.should_receive(:save)
200 | @entry.avatar = @file
201 |
202 | @entry.send(:save_uploaded_files)
203 | end
204 |
205 | it "should not call save on the uploaded file if they are not temporary files" do
206 | UploadColumn::UploadedFile.should_receive(:upload).with(@file, @entry, :avatar, @options).and_return(@uploaded_file)
207 |
208 | @uploaded_file.should_receive(:tempfile?).and_return(false)
209 | @uploaded_file.should_not_receive(:save)
210 | @entry.avatar = @file
211 |
212 | @entry.send(:save_uploaded_files)
213 | end
214 |
215 | it "should happen automatically" do
216 | # TODO: hmmm, how to test this? do we have to rely on an integration test?
217 | #@entry.should_receive(:save_uploaded_files)
218 | #@entry.save
219 | end
220 |
221 | end
222 |
223 | describe "fetching a temp value" do
224 |
225 | include UploadColumnSpecHelper
226 |
227 | setup do
228 | setup_standard_mocking
229 |
230 | UploadColumn::UploadedFile.should_receive(:upload).with(@file, @entry, :avatar, @options).and_return(@uploaded_file)
231 |
232 | @temp_value = '12345.1234.12345/somewhere.png'
233 |
234 | @uploaded_file.should_receive(:temp_value).and_return(@temp_value)
235 | @entry.avatar = @file
236 | end
237 |
238 | it "should fetch the value from the uploaded file" do
239 | @entry.avatar_temp.should == @temp_value
240 | end
241 |
242 | end
243 |
244 | describe "assigning a tempfile" do
245 |
246 | include UploadColumnSpecHelper
247 |
248 | setup do
249 | setup_standard_mocking
250 | end
251 |
252 | it "should not override a new file" do
253 | UploadColumn::UploadedFile.should_receive(:upload).with(@file, @entry, :avatar, @options).and_return(@uploaded_file)
254 | @uploaded_file.stub!(:new_file?).and_return(true)
255 | @entry.avatar = @file
256 |
257 | temp_value = '12345.1234.12345/somewhere.png'
258 |
259 | UploadColumn::UploadedFile.should_not_receive(:retrieve_temp)
260 | @entry.avatar_temp = temp_value
261 |
262 | @entry.avatar.should == @uploaded_file
263 | end
264 |
265 | it "should override a file that is not new" do
266 | UploadColumn::UploadedFile.should_receive(:upload).with(@file, @entry, :avatar, @options).and_return(@uploaded_file)
267 | @uploaded_file.stub!(:new_file?).and_return(false)
268 | @entry.avatar = @file
269 |
270 | temp_value = '12345.1234.12345/somewhere.png'
271 |
272 | retrieved_file = mock('a retrieved file')
273 | retrieved_file.should_receive(:actual_filename).and_return('walruss.png')
274 | UploadColumn::UploadedFile.should_receive(:retrieve_temp).with(temp_value, @entry, :avatar, @options).and_return(retrieved_file)
275 | @entry.should_receive(:[]=).with(:avatar, 'walruss.png')
276 |
277 | @entry.avatar_temp = temp_value
278 |
279 | @entry.avatar.should == retrieved_file
280 | end
281 |
282 | it "should set the file if there is none" do
283 |
284 | temp_value = '12345.1234.12345/somewhere.png'
285 |
286 | retrieved_file = mock('a retrieved file')
287 | retrieved_file.should_receive(:actual_filename).and_return('walruss.png')
288 | UploadColumn::UploadedFile.should_receive(:retrieve_temp).with(temp_value, @entry, :avatar, @options).and_return(retrieved_file)
289 | @entry.should_receive(:[]=).with(:avatar, 'walruss.png')
290 |
291 | @entry.avatar_temp = temp_value
292 |
293 | @entry.avatar.should == retrieved_file
294 | end
295 |
296 | end
297 |
298 | describe "assigning nil to temp" do
299 |
300 | include UploadColumnSpecHelper
301 |
302 | before(:each) do
303 | setup_standard_mocking
304 | end
305 |
306 | it "should do nothing" do
307 | UploadColumn::UploadedFile.stub!(:upload).and_return(@uploaded_file)
308 | @uploaded_file.stub!(:new_file?).and_return(false)
309 | @entry.avatar = @file
310 |
311 | UploadColumn::UploadedFile.should_not_receive(:retrieve_temp)
312 | @entry.should_not_receive(:[]=)
313 |
314 | lambda {
315 | @entry.avatar_temp = nil
316 | }.should_not change(@entry, :avatar)
317 | end
318 | end
319 |
320 | describe "assigning a blank string to temp" do
321 |
322 | include UploadColumnSpecHelper
323 |
324 | before(:each) do
325 | setup_standard_mocking
326 | end
327 |
328 | it "should do nothing" do
329 | UploadColumn::UploadedFile.should_receive(:upload).with(@file, @entry, :avatar, @options).and_return(@uploaded_file)
330 | @uploaded_file.stub!(:new_file?).and_return(false)
331 | @entry.avatar = @file
332 |
333 | UploadColumn::UploadedFile.should_not_receive(:retrieve_temp)
334 | @entry.should_not_receive(:[]=)
335 |
336 | lambda {
337 | @entry.avatar_temp = ""
338 | }.should_not change(@entry, :avatar)
339 | end
340 | end
341 |
342 | describe "an upload column with no file" do
343 |
344 | include UploadColumnSpecHelper
345 |
346 | before(:each) do
347 | setup_standard_mocking
348 | end
349 |
350 | it "should return no value" do
351 | @entry.avatar.should be_nil
352 | end
353 |
354 | it "should return no temp_value" do
355 | @entry.avatar_temp.should be_nil
356 | end
357 |
358 | it "should return nothing in the _public_path method" do
359 | @entry.avatar_public_path.should == nil
360 | end
361 |
362 | it "should return nothing in the _path method" do
363 | @entry.avatar_path.should == nil
364 | end
365 | end
366 |
367 | describe "an upload column with an uploaded file" do
368 |
369 | include UploadColumnSpecHelper
370 |
371 | before(:each) do
372 | setup_standard_mocking
373 | UploadColumn::UploadedFile.stub!(:upload).and_return(@uploaded_file)
374 | @entry.avatar = @file
375 | end
376 |
377 | it "should delegate the _public_path method to the column" do
378 | @uploaded_file.should_receive(:public_path).and_return('/url/to/file.exe')
379 | @entry.avatar_public_path.should == '/url/to/file.exe'
380 | end
381 |
382 | it "should delegate the _path method to the column" do
383 | @uploaded_file.should_receive(:path).and_return('/path/to/file.exe')
384 | @entry.avatar_path.should == '/path/to/file.exe'
385 | end
386 |
387 | end
388 |
389 | describe "an upload column with different versions and no uploaded file" do
390 |
391 | include UploadColumnSpecHelper
392 |
393 | before(:each) do
394 | setup_version_mocking # sets up a column with thumb and large versions
395 | end
396 |
397 | it "should return nil for the _thumb method" do
398 | @entry.avatar_thumb.should == nil
399 | end
400 |
401 | it "should return nil for the _large method" do
402 | @entry.avatar_large.should == nil
403 | end
404 |
405 | it "should return nil for the _thumb_url method" do
406 | @entry.avatar_thumb_public_path.should == nil
407 | end
408 |
409 | it "should return nil for the _large_path method" do
410 | @entry.avatar_large_path.should == nil
411 | end
412 |
413 | end
414 |
415 | describe "an upload column with different versions and an uploaded file" do
416 |
417 | include UploadColumnSpecHelper
418 |
419 | before(:each) do
420 | setup_version_mocking # sets up a column with thumb and large versions
421 | UploadColumn::UploadedFile.stub!(:upload).and_return(@uploaded_file)
422 | @entry.avatar = @file
423 | end
424 |
425 | it "should delegate the _thumb method to the column" do
426 | thumb = mock('thumb')
427 | @uploaded_file.should_receive(:thumb).and_return(thumb)
428 | @entry.avatar_thumb.should == thumb
429 | end
430 |
431 | it "should delegate the _large method to the column" do
432 | large = mock('large')
433 | @uploaded_file.should_receive(:large).and_return(large)
434 | @entry.avatar_large.should == large
435 | end
436 |
437 | it "should delegate the _thumb_url method to the column" do
438 | thumb = mock('thumb')
439 | thumb.should_receive(:public_path).and_return('/url/to/file.exe')
440 | @uploaded_file.should_receive(:thumb).and_return(thumb)
441 |
442 | @entry.avatar_thumb_public_path.should == '/url/to/file.exe'
443 | end
444 |
445 | it "should delegate the _large_path method to the column" do
446 | large = mock('large')
447 | large.should_receive(:path).and_return('/path/to/file.exe')
448 | @uploaded_file.should_receive(:large).and_return(large)
449 |
450 | @entry.avatar_large_path.should == '/path/to/file.exe'
451 | end
452 |
453 | end
454 |
455 | describe "uploading a file that fails an integrity check" do
456 |
457 | include UploadColumnSpecHelper
458 |
459 | before(:all) do
460 | Entry.validates_integrity_of :avatar
461 | end
462 |
463 | before(:each) do
464 | setup_standard_mocking
465 | end
466 |
467 | it "should set the column to nil" do
468 | UploadColumn::UploadedFile.should_receive(:upload).and_raise(UploadColumn::IntegrityError.new('something'))
469 | @entry.avatar = @file
470 |
471 | @entry.avatar.should be_nil
472 | end
473 |
474 | it "should fail an integrity validation" do
475 | UploadColumn::UploadedFile.should_receive(:upload).and_raise(UploadColumn::IntegrityError.new('something'))
476 | @entry.avatar = @file
477 |
478 | @entry.should_not be_valid
479 | @entry.errors.on(:avatar).should == 'something'
480 | end
481 | end
482 |
483 | describe UploadColumn::ActiveRecordExtension::ClassMethods, ".image_column" do
484 |
485 | include UploadColumnSpecHelper
486 |
487 | before(:each) do
488 | @class = Class.new(ActiveRecord::Base)
489 | @class.send(:include, UploadColumn)
490 | end
491 |
492 | it "should call an upload column with some specialized options" do
493 | @class.should_receive(:upload_column).with(:sicada,
494 | :manipulator => UploadColumn::Manipulators::RMagick,
495 | :root_dir => File.join(RAILS_ROOT, 'public', 'images'),
496 | :web_root => '/images',
497 | :monkey => 'blah',
498 | :extensions => UploadColumn.image_extensions
499 | )
500 | @class.image_column(:sicada, :monkey => 'blah')
501 | end
502 | end
503 |
504 | describe UploadColumn::ActiveRecordExtension::ClassMethods, ".validate_integrity" do
505 |
506 | include UploadColumnSpecHelper
507 |
508 | it "should change the options for this upload_column" do
509 | Entry.upload_column :avatar
510 | Entry.reflect_on_upload_columns[:avatar].options[:validate_integrity].should be_nil
511 | Entry.validates_integrity_of :avatar
512 | Entry.reflect_on_upload_columns[:avatar].options[:validate_integrity].should == true
513 | end
514 | end
--------------------------------------------------------------------------------
/spec/custom_matchers.rb:
--------------------------------------------------------------------------------
1 | class BeIdenticalWith
2 | def initialize(expected)
3 | @expected = expected
4 | end
5 | def matches?(actual)
6 | @actual = actual
7 | FileUtils.identical?(@actual, @expected)
8 | end
9 | def failure_message
10 | "expected #{@actual.inspect} to be identical with #{@expected.inspect}"
11 | end
12 | def negative_failure_message
13 | "expected #{@actual.inspect} to not be identical with #{@expected.inspect}"
14 | end
15 | end
16 |
17 | def be_identical_with(expected)
18 | BeIdenticalWith.new(expected)
19 | end
20 |
21 | class ExistsPredicate
22 |
23 | def matches?(actual)
24 | actual.exists?
25 | end
26 | def failure_message
27 | "expected #{@actual.inspect} to exist, it doesn't."
28 | end
29 | def negative_failure_message
30 | "expected #{@actual.inspect} to not exist, yet it does."
31 | end
32 | end
33 |
34 | def be_in_existence
35 | ExistsPredicate.new
36 | end
37 |
38 | class MatchPath
39 | def initialize(*expected)
40 | if(expected.size < 2)
41 | @expected = File.expand_path(expected.first)
42 | else
43 | @expected = expected.map {|e| e.is_a?(Regexp) ? e.to_s : Regexp.escape(e)}
44 | @expected = File.expand_path(File.join(*@expected), RAILS_ROOT)
45 | @expected = %r(^#{@expected}$)
46 | end
47 | end
48 | def matches?(actual)
49 | @actual = actual
50 | if @expected.is_a?(Regexp)
51 | File.expand_path(actual) =~ @expected
52 | else
53 | File.expand_path(actual) == @expected
54 | end
55 | end
56 | def failure_message
57 | "expected #{@actual.inspect} to match #{@expected}."
58 | end
59 | def negative_failure_message
60 | "expected #{@actual.inspect} to not match #{@expected}, yet it does."
61 | end
62 | end
63 |
64 | # Match a path without bothering whether they are formatted the same way.
65 | # can also take several parameters, any number of which may be regexes
66 | def match_path(*expected)
67 | MatchPath.new(*expected)
68 | end
69 |
70 | class HavePermissions
71 | def initialize(expected)
72 | @expected = expected
73 | end
74 |
75 | def matches?(actual)
76 | @actual = actual
77 | # Satisfy expectation here. Return false or raise an error if it's not met.
78 | (File.stat(@actual.path).mode & 0777) == @expected
79 | end
80 |
81 | def failure_message
82 | "expected #{@actual.inspect} to have permissions #{@expected.to_s(8)}, but they were #{(File.stat(@actual.path).mode & 0777).to_s(8)}"
83 | end
84 |
85 | def negative_failure_message
86 | "expected #{@actual.inspect} not to have permissions #{@expected.to_s(8)}, but it did"
87 | end
88 | end
89 |
90 | def have_permissions(expected)
91 | HavePermissions.new(expected)
92 | end
93 |
94 | class BeNoLargerThan
95 | def initialize(width, height)
96 | @width, @height = width, height
97 | end
98 |
99 | def matches?(actual)
100 | @actual = actual
101 | # Satisfy expectation here. Return false or raise an error if it's not met.
102 | require 'RMagick'
103 | img = ::Magick::Image.read(@actual.path).first
104 | @actual_width = img.columns
105 | @actual_height = img.rows
106 | @actual_width <= @width && @actual_height <= @height
107 | end
108 |
109 | def failure_message
110 | "expected #{@actual.inspect} to be no larger than #{@width} by #{@height}, but it was #{@actual_height} by #{@actual_width}."
111 | end
112 |
113 | def negative_failure_message
114 | "expected #{@actual.inspect} to be larger than #{@width} by #{@height}, but it wasn't."
115 | end
116 | end
117 |
118 | def be_no_larger_than(width, height)
119 | BeNoLargerThan.new(width, height)
120 | end
121 |
122 | class HaveTheExactDimensionsOf
123 | def initialize(width, height)
124 | @width, @height = width, height
125 | end
126 |
127 | def matches?(actual)
128 | @actual = actual
129 | # Satisfy expectation here. Return false or raise an error if it's not met.
130 | require 'RMagick'
131 | img = ::Magick::Image.read(@actual.path).first
132 | @actual_width = img.columns
133 | @actual_height = img.rows
134 | @actual_width == @width && @actual_height == @height
135 | end
136 |
137 | def failure_message
138 | "expected #{@actual.inspect} to have an exact size of #{@width} by #{@height}, but it was #{@actual_height} by #{@actual_width}."
139 | end
140 |
141 | def negative_failure_message
142 | "expected #{@actual.inspect} not to have an exact size of #{@width} by #{@height}, but it did."
143 | end
144 | end
145 |
146 | def have_the_exact_dimensions_of(width, height)
147 | HaveTheExactDimensionsOf.new(width, height)
148 | end
--------------------------------------------------------------------------------
/spec/fixtures/animated.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jnicklas/uploadcolumn/c2c14ad15467e97afd905f57d89e05b1b6c65d23/spec/fixtures/animated.gif
--------------------------------------------------------------------------------
/spec/fixtures/animated_solarized.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jnicklas/uploadcolumn/c2c14ad15467e97afd905f57d89e05b1b6c65d23/spec/fixtures/animated_solarized.gif
--------------------------------------------------------------------------------
/spec/fixtures/invalid-image.jpg:
--------------------------------------------------------------------------------
1 | this is certainly not a JPEG image
2 |
--------------------------------------------------------------------------------
/spec/fixtures/kerb.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jnicklas/uploadcolumn/c2c14ad15467e97afd905f57d89e05b1b6c65d23/spec/fixtures/kerb.jpg
--------------------------------------------------------------------------------
/spec/fixtures/kerb_solarized.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jnicklas/uploadcolumn/c2c14ad15467e97afd905f57d89e05b1b6c65d23/spec/fixtures/kerb_solarized.jpg
--------------------------------------------------------------------------------
/spec/fixtures/netscape.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jnicklas/uploadcolumn/c2c14ad15467e97afd905f57d89e05b1b6c65d23/spec/fixtures/netscape.gif
--------------------------------------------------------------------------------
/spec/fixtures/skanthak.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jnicklas/uploadcolumn/c2c14ad15467e97afd905f57d89e05b1b6c65d23/spec/fixtures/skanthak.png
--------------------------------------------------------------------------------
/spec/image_science_manipulator_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), 'spec_helper')
2 | require File.join(File.dirname(__FILE__), '../lib/upload_column/manipulators/image_science')
3 |
4 | describe UploadColumn::Manipulators::ImageScience, "#resize!" do
5 |
6 | before(:each) do
7 | @uploaded_file = class << self; self end # this is a singleton object
8 | @uploaded_file.extend( UploadColumn::Manipulators::ImageScience )
9 | @uploaded_file.load_manipulator_dependencies
10 | @uploaded_file.stub!(:path).and_return('/some_path.png')
11 | end
12 |
13 | it "should preserve the aspect ratio if the image is too wide" do
14 |
15 | image = mock('an image_science object')
16 |
17 | ::ImageScience.should_receive(:with_image).with('/some_path.png').and_yield(image)
18 |
19 | image.should_receive(:width).and_return(640)
20 | image.should_receive(:height).and_return(480)
21 |
22 | i2 = mock('another stupid mock')
23 | i2.should_receive(:save).with('/some_path.png')
24 |
25 | image.should_receive(:resize).with(160, 120).and_yield(i2)
26 |
27 | @uploaded_file.resize!('400x120')
28 | end
29 |
30 | it "should preserve the aspect ratio if the image is too narrow" do
31 |
32 | image = mock('an image_science object')
33 |
34 | ::ImageScience.should_receive(:with_image).with('/some_path.png').and_yield(image)
35 |
36 | image.should_receive(:width).and_return(640)
37 | image.should_receive(:height).and_return(480)
38 |
39 | i2 = mock('another stupid mock')
40 | i2.should_receive(:save).with('/some_path.png')
41 |
42 | image.should_receive(:resize).with(200, 150).and_yield(i2)
43 |
44 | @uploaded_file.resize!('200x400')
45 | end
46 |
47 | it "should rescale to the exact size if the aspect ratio is the same" do
48 |
49 | image = mock('an image_science object')
50 |
51 | ::ImageScience.should_receive(:with_image).with('/some_path.png').and_yield(image)
52 |
53 | image.should_receive(:width).and_return(640)
54 | image.should_receive(:height).and_return(480)
55 |
56 | i2 = mock('another stupid mock')
57 | i2.should_receive(:save).with('/some_path.png')
58 |
59 | image.should_receive(:resize).with(320, 240).and_yield(i2)
60 |
61 | @uploaded_file.resize!('320x240')
62 | end
63 |
64 | it "should not exceed the dimensions if the image is a rather weird size" do
65 |
66 | image = mock('an image_science object')
67 |
68 | ::ImageScience.should_receive(:with_image).with('/some_path.png').and_yield(image)
69 |
70 | image.should_receive(:width).and_return(737)
71 | image.should_receive(:height).and_return(237)
72 |
73 | i2 = mock('another stupid mock')
74 | i2.should_receive(:save).with('/some_path.png')
75 |
76 | image.should_receive(:resize).with(137, 44).and_yield(i2)
77 |
78 | @uploaded_file.resize!('137x137')
79 | end
80 |
81 | end
82 |
83 |
84 | describe UploadColumn::Manipulators::ImageScience, "#crop_resized!" do
85 |
86 | before(:each) do
87 | @uploaded_file = class << self; self end # this is a singleton object
88 | @uploaded_file.extend( UploadColumn::Manipulators::ImageScience )
89 | @uploaded_file.load_manipulator_dependencies
90 | @uploaded_file.stub!(:path).and_return('/some_path.png')
91 | end
92 |
93 | it "should crop and resize an image that is too tall" do
94 | image = mock('an image_science object')
95 |
96 | ::ImageScience.should_receive(:with_image).with('/some_path.png').and_yield(image)
97 |
98 | image.should_receive(:width).and_return(640)
99 | image.should_receive(:height).and_return(480)
100 |
101 | i2 = mock('another stupid mock')
102 | image.should_receive(:resize).with(400, 300).and_yield(i2)
103 |
104 | i3 = mock('image science is stupid')
105 | i2.should_receive(:with_crop).with(0, 90, 400, 210).and_yield(i3)
106 |
107 | i3.should_receive(:save).with('/some_path.png')
108 |
109 | @uploaded_file.crop_resized!('400x120')
110 | end
111 |
112 | it "should crop and resize an image that is too tall" do
113 | image = mock('an image_science object')
114 |
115 | ::ImageScience.should_receive(:with_image).with('/some_path.png').and_yield(image)
116 |
117 | image.should_receive(:width).and_return(640)
118 | image.should_receive(:height).and_return(480)
119 |
120 | i2 = mock('another stupid mock')
121 | image.should_receive(:resize).with(560, 420).and_yield(i2)
122 |
123 | i3 = mock('image science is stupid')
124 | i2.should_receive(:with_crop).with(180, 0, 380, 420).and_yield(i3)
125 |
126 | i3.should_receive(:save).with('/some_path.png')
127 |
128 | @uploaded_file.crop_resized!('200x420')
129 | end
130 |
131 | it "should crop and resize an image with the correct aspect ratio" do
132 | image = mock('an image_science object')
133 |
134 | ::ImageScience.should_receive(:with_image).with('/some_path.png').and_yield(image)
135 |
136 | image.should_receive(:width).and_return(640)
137 | image.should_receive(:height).and_return(480)
138 |
139 | i2 = mock('another stupid mock')
140 | image.should_receive(:resize).with(320, 240).and_yield(i2)
141 |
142 | i3 = mock('image science is stupid')
143 | i2.should_receive(:with_crop).with(0, 0, 320, 240).and_yield(i3)
144 |
145 | i3.should_receive(:save).with('/some_path.png')
146 |
147 | @uploaded_file.crop_resized!('320x240')
148 | end
149 |
150 | it "should crop and resize an image with weird dimensions" do
151 | image = mock('an image_science object')
152 |
153 | ::ImageScience.should_receive(:with_image).with('/some_path.png').and_yield(image)
154 |
155 | image.should_receive(:width).and_return(737)
156 | image.should_receive(:height).and_return(967)
157 |
158 | i2 = mock('another stupid mock')
159 | image.should_receive(:resize).with(333, 437).and_yield(i2)
160 |
161 | i3 = mock('image science is stupid')
162 | i2.should_receive(:with_crop).with(0, 150, 333, 287).and_yield(i3)
163 |
164 | i3.should_receive(:save).with('/some_path.png')
165 |
166 | @uploaded_file.crop_resized!('333x137')
167 | end
168 | end
169 |
170 | describe UploadColumn::Manipulators::ImageScience, "#process!" do
171 |
172 | before(:each) do
173 | @uploaded_file = class << self; self end
174 | @uploaded_file.extend( UploadColumn::Manipulators::ImageScience )
175 | @uploaded_file.load_manipulator_dependencies
176 | @uploaded_file.stub!(:path).and_return('/some_path.png')
177 | end
178 |
179 | it "should resize the image if a string like '333x444' is passed" do
180 | @uploaded_file.should_receive(:resize!).with('333x444')
181 | @uploaded_file.process!('333x444')
182 | end
183 |
184 | it "should crop and resize the image if a string like 'c333x444' is passed" do
185 | @uploaded_file.should_receive(:crop_resized!).with('333x444')
186 | @uploaded_file.process!('c333x444')
187 | end
188 |
189 | it "should do nothing if :none is passed" do
190 | @uploaded_file.should_not_receive(:manipulate!)
191 | @uploaded_file.process!(:none)
192 | end
193 |
194 | end
195 |
196 |
--------------------------------------------------------------------------------
/spec/integration_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), 'spec_helper')
2 |
3 | gem 'activerecord'
4 | require 'active_record'
5 |
6 | require File.join(File.dirname(__FILE__), '../lib/upload_column')
7 |
8 | # change this if sqlite is unavailable
9 | dbconfig = {
10 | :adapter => 'sqlite3',
11 | :database => 'db/test.sqlite3'
12 | }
13 |
14 | ActiveRecord::Base.establish_connection(dbconfig)
15 | ActiveRecord::Migration.verbose = false
16 |
17 | class TestMigration < ActiveRecord::Migration
18 | def self.up
19 | create_table :events, :force => true do |t|
20 | t.column :image, :string
21 | t.column :textfile, :string
22 | end
23 |
24 | create_table :movies, :force => true do |t|
25 | t.column :movie, :string
26 | t.column :name, :string
27 | t.column :description, :text
28 | end
29 |
30 | create_table :shrooms, :force => true do |t|
31 | t.column :image, :string
32 | t.column :image_size, :integer
33 | t.column :image_public_path, :string
34 | t.column :image_path, :string
35 | t.column :image_monkey, :string
36 | t.column :image_extension, :string
37 | end
38 | end
39 |
40 | def self.down
41 | drop_table :events
42 | drop_table :movies
43 | drop_table :shrooms
44 | end
45 | end
46 |
47 | class Event < ActiveRecord::Base; end # setup a basic AR class for testing
48 | class Movie < ActiveRecord::Base; end # setup a basic AR class for testing
49 | class Shroom < ActiveRecord::Base; end # setup a basic AR class for testing
50 |
51 | def migrate
52 | before(:all) do
53 | TestMigration.down rescue nil
54 | TestMigration.up
55 | end
56 |
57 | after(:all) { TestMigration.down }
58 | end
59 |
60 | # TODO: RSpec syntax and integration really don't mix. In the long run, it would
61 | # be nice to rewrite this stuff with the Story runner.
62 |
63 | describe "normally instantiating and saving a record" do
64 |
65 | migrate
66 |
67 | it "shouldn't fail" do
68 | Event.reflect_on_upload_columns.should == {}
69 | running { @event = Event.new }.should_not raise_error
70 | @event.image = "monkey"
71 | running { @event.save }.should_not raise_error
72 | end
73 |
74 | end
75 |
76 | describe "uploading a single file" do
77 |
78 | migrate
79 |
80 | before do
81 | Event.upload_column(:image)
82 | @event = Event.new
83 | @event.image = stub_tempfile('kerb.jpg')
84 | end
85 |
86 | it "should set the correct path" do
87 | @event.image.path.should match_path(PUBLIC, 'tmp', /(?:\d+\.)+\d+/, 'kerb.jpg' )
88 | end
89 |
90 | it "should copy the file to temp." do
91 | File.exists?(@event.image.path).should === true
92 | @event.image.path.should be_identical_with(file_path('kerb.jpg'))
93 | end
94 |
95 | it "should set the correct url" do
96 | @event.image.url.should =~ %r{/tmp/(?:\d+\.)+\d+/kerb.jpg}
97 | end
98 |
99 | after do
100 | FileUtils.rm_rf(PUBLIC)
101 | end
102 | end
103 |
104 | describe "uploading a file and then saving the record" do
105 |
106 | migrate
107 |
108 | before do
109 | Event.upload_column(:image)
110 | @event = Event.new
111 | @event.image = stub_tempfile('kerb.jpg')
112 | @event.save
113 | end
114 |
115 | it "should set the correct path" do
116 | @event.image.path.should match_path(PUBLIC, 'image', 'kerb.jpg')
117 | end
118 |
119 | it "should copy the file to the correct location" do
120 | File.exists?(@event.image.path)
121 | @event.image.path.should be_identical_with(file_path('kerb.jpg'))
122 | end
123 |
124 | it "should set the correct url" do
125 | @event.image.url.should == "/image/kerb.jpg"
126 | end
127 |
128 | it "should save the filename to the database" do
129 | Event.find(@event.id)['image'].should == 'kerb.jpg'
130 | end
131 |
132 | after do
133 | FileUtils.rm_rf(PUBLIC)
134 | end
135 |
136 | end
137 |
138 | describe "uploading a file with versions" do
139 |
140 | migrate
141 |
142 | before do
143 | Event.upload_column(:image, :versions => [ :thumb, :large ] )
144 | @event = Event.new
145 | @event.image = stub_tempfile('kerb.jpg')
146 | end
147 |
148 | it "should set the correct path" do
149 | @event.image.path.should match_path(PUBLIC, 'tmp', /(?:\d+\.)+\d+/, 'kerb.jpg' )
150 | @event.image.thumb.path.should match_path(PUBLIC, 'tmp', /(?:\d+\.)+\d+/, 'kerb-thumb.jpg' )
151 | @event.image.large.path.should match_path(PUBLIC, 'tmp', /(?:\d+\.)+\d+/, 'kerb-large.jpg' )
152 | end
153 |
154 | it "should copy the file to temp." do
155 | File.exists?(@event.image.path).should === true
156 | File.exists?(@event.image.thumb.path).should === true
157 | File.exists?(@event.image.large.path).should === true
158 | @event.image.path.should be_identical_with(file_path('kerb.jpg'))
159 | @event.image.thumb.path.should be_identical_with(file_path('kerb.jpg'))
160 | @event.image.large.path.should be_identical_with(file_path('kerb.jpg'))
161 | end
162 |
163 | it "should set the correct url" do
164 | @event.image.url.should =~ %r{/tmp/(?:\d+\.)+\d+/kerb.jpg}
165 | @event.image.thumb.url.should =~ %r{/tmp/(?:\d+\.)+\d+/kerb-thumb.jpg}
166 | @event.image.large.url.should =~ %r{/tmp/(?:\d+\.)+\d+/kerb-large.jpg}
167 | end
168 |
169 | after do
170 | FileUtils.rm_rf(PUBLIC)
171 | end
172 |
173 | end
174 |
175 | describe "uploading a file with versions and then saving the record" do
176 |
177 | migrate
178 |
179 | before do
180 | Event.upload_column(:image, :versions => [ :thumb, :large ] )
181 | @event = Event.new
182 | @event.image = stub_tempfile('kerb.jpg')
183 | @event.save
184 | end
185 |
186 | it "should set the correct path" do
187 | @event.image.path.should match_path(PUBLIC, 'image', 'kerb.jpg' )
188 | @event.image.thumb.path.should match_path(PUBLIC, 'image', 'kerb-thumb.jpg' )
189 | @event.image.large.path.should match_path(PUBLIC, 'image', 'kerb-large.jpg' )
190 | end
191 |
192 | it "should copy the file to the correct location." do
193 | File.exists?(@event.image.path).should === true
194 | File.exists?(@event.image.thumb.path).should === true
195 | File.exists?(@event.image.large.path).should === true
196 | @event.image.path.should be_identical_with(file_path('kerb.jpg'))
197 | @event.image.thumb.path.should be_identical_with(file_path('kerb.jpg'))
198 | @event.image.large.path.should be_identical_with(file_path('kerb.jpg'))
199 | end
200 |
201 | it "should set the correct url" do
202 | @event.image.url.should == "/image/kerb.jpg"
203 | @event.image.thumb.url.should == "/image/kerb-thumb.jpg"
204 | @event.image.large.url.should == "/image/kerb-large.jpg"
205 | end
206 |
207 | it "should save the filename to the database" do
208 | Event.find(@event.id)['image'].should == 'kerb.jpg'
209 | end
210 |
211 | after do
212 | FileUtils.rm_rf(PUBLIC)
213 | end
214 |
215 | end
216 |
217 |
218 | describe "assigning a file from temp with versions" do
219 |
220 | migrate
221 |
222 | before do
223 | Event.upload_column(:image, :versions => [ :thumb, :large ] )
224 | @blah = Event.new
225 |
226 | @event = Event.new
227 |
228 | @blah.image = stub_tempfile('kerb.jpg') # we've alredy tested this...
229 |
230 | @event.image_temp = @blah.image_temp
231 | end
232 |
233 | it "should set the correct path" do
234 | @event.image.path.should == @blah.image.path
235 | @event.image.thumb.path.should == @blah.image.thumb.path
236 | @event.image.large.path.should == @blah.image.large.path
237 | end
238 |
239 | it "should set the correct url" do
240 | @event.image.url.should == @blah.image.url
241 | @event.image.thumb.url.should == @blah.image.thumb.url
242 | @event.image.large.url.should == @blah.image.large.url
243 | end
244 |
245 | after do
246 | FileUtils.rm_rf(PUBLIC)
247 | end
248 |
249 | end
250 |
251 |
252 | describe "assigning a file from temp with versions and then saving the record" do
253 |
254 | migrate
255 |
256 | before do
257 | Event.upload_column(:image, :versions => [ :thumb, :large ] )
258 | @blah = Event.new
259 |
260 | @event = Event.new
261 |
262 | @blah.image = stub_tempfile('kerb.jpg') # we've alredy tested this...
263 |
264 | @event.image_temp = @blah.image_temp
265 |
266 | @event.save
267 | end
268 |
269 | it "should set the correct path" do
270 | @event.image.path.should match_path(PUBLIC, 'image', 'kerb.jpg' )
271 | @event.image.thumb.path.should match_path(PUBLIC, 'image', 'kerb-thumb.jpg' )
272 | @event.image.large.path.should match_path(PUBLIC, 'image', 'kerb-large.jpg' )
273 | end
274 |
275 | it "should copy the file to the correct location." do
276 | File.exists?(@event.image.path).should === true
277 | File.exists?(@event.image.thumb.path).should === true
278 | File.exists?(@event.image.large.path).should === true
279 | @event.image.path.should be_identical_with(file_path('kerb.jpg'))
280 | @event.image.thumb.path.should be_identical_with(file_path('kerb.jpg'))
281 | @event.image.large.path.should be_identical_with(file_path('kerb.jpg'))
282 | end
283 |
284 | it "should set the correct url" do
285 | @event.image.url.should == "/image/kerb.jpg"
286 | @event.image.thumb.url.should == "/image/kerb-thumb.jpg"
287 | @event.image.large.url.should == "/image/kerb-large.jpg"
288 | end
289 |
290 | it "should save the filename to the database" do
291 | Event.find(@event.id)['image'].should == 'kerb.jpg'
292 | end
293 |
294 | after do
295 | FileUtils.rm_rf(PUBLIC)
296 | end
297 |
298 | end
299 |
300 | describe "an upload_column with an uploaded file" do
301 |
302 | migrate
303 |
304 | before do
305 | Event.upload_column(:image)
306 | @event = Event.new
307 | @event.image = stub_tempfile('kerb.jpg')
308 | @event.save
309 | end
310 |
311 | it "should not be overwritten by an empty String" do
312 | @e2 = Event.find(@event.id)
313 | lambda {
314 | @e2.image = ""
315 | @e2.save
316 | }.should_not change(@e2.image, :path)
317 | @e2[:image].should == "kerb.jpg"
318 | end
319 |
320 | it "should not be overwritten by an empty StringIO" do
321 | @e2 = Event.find(@event.id)
322 | lambda {
323 | @e2.image = StringIO.new('')
324 | @e2.save
325 | }.should_not change(@e2.image, :path)
326 | @e2[:image].should == "kerb.jpg"
327 | end
328 |
329 | it "should not be overwritten by an empty file" do
330 | @e2 = Event.find(@event.id)
331 | lambda {
332 | file = stub_file('kerb.jpg')
333 | file.stub!(:size).and_return(0)
334 | @e2.image = file
335 | @e2.save
336 | }.should_not change(@e2.image, :path)
337 | @e2[:image].should == "kerb.jpg"
338 | end
339 |
340 | it "should be overwritten by another file" do
341 | @e2 = Event.find(@event.id)
342 | lambda {
343 | file = stub_file('skanthak.png')
344 | @e2.image = file
345 | @e2.save
346 | }.should_not change(@e2.image, :path)
347 | @e2[:image].should == "skanthak.png"
348 | end
349 |
350 | it "should be marshallable" do
351 | running { Marshal.dump(@entry) }.should_not raise_error
352 | end
353 |
354 | after do
355 | FileUtils.rm_rf(PUBLIC)
356 | end
357 | end
358 |
359 | describe "uploading an image with several versions, the rmagick manipulator and instructions to rescale" do
360 |
361 | migrate
362 |
363 | # buuhuu so sue me. This spec runs a whole second faster if we do this before all instead of
364 | # before each.
365 | before(:all) do
366 | Event.upload_column(:image,
367 | :versions => { :thumb => 'c100x100', :large => '200x200' },
368 | :manipulator => UploadColumn::Manipulators::RMagick
369 | )
370 | @event = Event.new
371 | @event.image = stub_tempfile('kerb.jpg')
372 | end
373 |
374 | it "should set the correct path" do
375 | @event.image.path.should match_path(PUBLIC, 'tmp', /(?:\d+\.)+\d+/, 'kerb.jpg' )
376 | @event.image.thumb.path.should match_path(PUBLIC, 'tmp', /(?:\d+\.)+\d+/, 'kerb-thumb.jpg' )
377 | @event.image.large.path.should match_path(PUBLIC, 'tmp', /(?:\d+\.)+\d+/, 'kerb-large.jpg' )
378 | end
379 |
380 | it "should copy the files to temp." do
381 | File.exists?(@event.image.path).should === true
382 | File.exists?(@event.image.thumb.path).should === true
383 | File.exists?(@event.image.large.path).should === true
384 | end
385 |
386 | it "should set the correct url" do
387 | @event.image.url.should =~ %r{/tmp/(?:\d+\.)+\d+/kerb.jpg}
388 | @event.image.thumb.url.should =~ %r{/tmp/(?:\d+\.)+\d+/kerb-thumb.jpg}
389 | @event.image.large.url.should =~ %r{/tmp/(?:\d+\.)+\d+/kerb-large.jpg}
390 | end
391 |
392 | it "should preserve the main file" do
393 | @event.image.path.should be_identical_with(file_path('kerb.jpg'))
394 | end
395 |
396 | it "should change the versions" do
397 | @event.image.thumb.path.should_not be_identical_with(file_path('kerb.jpg'))
398 | @event.image.large.path.should_not be_identical_with(file_path('kerb.jpg'))
399 | end
400 |
401 | it "should rescale the images to the correct sizes" do
402 | @event.image.large.should be_no_larger_than(200, 200)
403 | @event.image.thumb.should have_the_exact_dimensions_of(100, 100)
404 | end
405 |
406 | after(:all) do
407 | FileUtils.rm_rf(PUBLIC)
408 | end
409 | end
410 |
411 |
412 | # TODO: make image_science not crap out on my macbook
413 | #describe "uploading an image with several versions, the image_science manipulator and instructions to rescale" do
414 | #
415 | # migrate
416 | #
417 | # # buuhuu so sue me. This spec runs a whole second faster if we do this before all instead of
418 | # # before each.
419 | # before(:all) do
420 | # Event.upload_column(:image,
421 | # :versions => { :thumb => 'c100x100', :large => '200x200' },
422 | # :manipulator => UploadColumn::Manipulators::ImageScience
423 | # )
424 | # @event = Event.new
425 | # @event.image = stub_tempfile('kerb.jpg')
426 | # end
427 | #
428 | # it "should set the correct path" do
429 | # @event.image.path.should match_path(PUBLIC, 'tmp', /(?:\d+\.)+\d+/, 'kerb.jpg' )
430 | # @event.image.thumb.path.should match_path(PUBLIC, 'tmp', /(?:\d+\.)+\d+/, 'kerb-thumb.jpg' )
431 | # @event.image.large.path.should match_path(PUBLIC, 'tmp', /(?:\d+\.)+\d+/, 'kerb-large.jpg' )
432 | # end
433 | #
434 | # it "should copy the files to temp." do
435 | # File.exists?(@event.image.path).should === true
436 | # File.exists?(@event.image.thumb.path).should === true
437 | # File.exists?(@event.image.large.path).should === true
438 | # end
439 | #
440 | # it "should set the correct url" do
441 | # @event.image.url.should =~ %r{/tmp/(?:\d+\.)+\d+/kerb.jpg}
442 | # @event.image.thumb.url.should =~ %r{/tmp/(?:\d+\.)+\d+/kerb-thumb.jpg}
443 | # @event.image.large.url.should =~ %r{/tmp/(?:\d+\.)+\d+/kerb-large.jpg}
444 | # end
445 | #
446 | # it "should preserve the main file" do
447 | # @event.image.path.should be_identical_with(file_path('kerb.jpg'))
448 | # end
449 | #
450 | # it "should change the versions" do
451 | # @event.image.thumb.path.should_not be_identical_with(file_path('kerb.jpg'))
452 | # @event.image.large.path.should_not be_identical_with(file_path('kerb.jpg'))
453 | # end
454 | #
455 | # it "should rescale the images to the correct sizes" do
456 | # @event.image.large.should be_no_larger_than(200, 200)
457 | # @event.image.thumb.should have_the_exact_dimensions_of(100, 100)
458 | # end
459 | #
460 | # after(:all) do
461 | # FileUtils.rm_rf(PUBLIC)
462 | # end
463 | #end
464 |
465 | describe "uploading a file with an extension that is not in the whitelist" do
466 |
467 | migrate
468 |
469 | before(:each) do
470 | Event.upload_column(:image, :fix_file_extensions => false)
471 | Event.validates_integrity_of :image
472 |
473 | @event = Event.new
474 | end
475 |
476 | it "should add an error to the record" do
477 | @event.image = stub_tempfile('kerb.jpg', nil, 'monkey.exe')
478 | @event.should_not be_valid
479 | @event.errors.on(:image).should == "has an extension that is not allowed."
480 | @event.image.should be_nil
481 | end
482 |
483 | it "should be reversible by uploading a valid file" do
484 |
485 | @event.image = stub_tempfile('kerb.jpg', nil, 'monkey.exe')
486 |
487 | @event.should_not be_valid
488 | @event.errors.on(:image).should include('has an extension that is not allowed.')
489 |
490 | @event.image = stub_tempfile('kerb.jpg')
491 |
492 | @event.should be_valid
493 | @event.errors.on(:image).should be_nil
494 | end
495 | end
496 |
497 | describe "uploading a file with magic columns" do
498 |
499 | migrate
500 |
501 | before(:each) do
502 | Shroom.upload_column :image
503 | @shroom = Shroom.new
504 | @shroom.image = stub_tempfile('kerb.jpg')
505 | end
506 |
507 | it "should automatically set the image path" do
508 | @shroom.image_path.should == @shroom.image.path
509 | end
510 |
511 | it "should automatically set the image size" do
512 | @shroom.image_size.should == @shroom.image.size
513 | end
514 |
515 | it "should automatically set the image public path" do
516 | @shroom.image_public_path.should == @shroom.image.public_path
517 | end
518 |
519 | it "should ignore columns whose names aren't methods on the column" do
520 | @shroom.image_monkey.should == nil
521 | end
522 | end
523 |
524 | describe "assigning a file from tmp with magic columns" do
525 |
526 | migrate
527 |
528 | before(:each) do
529 | Shroom.upload_column :image
530 | e1 = Shroom.new
531 | e1.image = stub_tempfile('kerb.jpg')
532 | @shroom = Shroom.new
533 | @shroom.image_temp = e1.image_temp
534 | end
535 |
536 | it "should automatically set the image size" do
537 | @shroom.image_size.should == @shroom.image.size
538 | end
539 |
540 | it "should automatically set the image path" do
541 | @shroom.image_path.should == @shroom.image.path
542 | end
543 |
544 | it "should automatically set the image public path" do
545 | @shroom.image_public_path.should == @shroom.image.public_path
546 | end
547 |
548 | it "should ignore columns whose names aren't methods on the column" do
549 | @shroom.image_monkey.should == nil
550 | end
551 | end
552 |
553 | describe "uploading and saving a file with magic columns" do
554 |
555 | migrate
556 |
557 | before(:each) do
558 | Shroom.upload_column :image
559 | @shroom = Shroom.new
560 | @shroom.image_extension = "Some other Extension"
561 | @shroom.image = stub_tempfile('kerb.jpg')
562 | @shroom.save
563 | end
564 |
565 | it "should automatically set the image size" do
566 | @shroom.image_size.should == @shroom.image.size
567 | end
568 |
569 | it "should automatically set the image path" do
570 | @shroom.image_path.should == @shroom.image.path
571 | end
572 |
573 | it "should automatically set the image public path" do
574 | @shroom.image_public_path.should == @shroom.image.public_path
575 | end
576 |
577 | it "should ignore columns whose names aren't methods on the column" do
578 | @shroom.image_monkey.should == nil
579 | end
580 |
581 | it "should ignore columns who already have a value set" do
582 | @shroom.image_extension.should == "Some other Extension"
583 | end
584 | end
585 |
586 | describe "assigning a file from tmp and saving it with magic columns" do
587 |
588 | migrate
589 |
590 | before(:each) do
591 | Shroom.upload_column :image
592 | e1 = Shroom.new
593 | e1.image = stub_tempfile('kerb.jpg')
594 | @shroom = Shroom.new
595 | @shroom.image_temp = e1.image_temp
596 | @shroom.save
597 | end
598 |
599 | it "should automatically set the image size" do
600 | @shroom.image_size.should == @shroom.image.size
601 | end
602 |
603 | it "should automatically set the image path" do
604 | @shroom.image_path.should == @shroom.image.path
605 | end
606 |
607 | it "should automatically set the image public path" do
608 | @shroom.image_public_path.should == @shroom.image.public_path
609 | end
610 |
611 | it "should ignore columns whose names aren't methods on the column" do
612 | @shroom.image_monkey.should == nil
613 | end
614 | end
615 |
616 | describe "uploading a file with a filename instruction" do
617 |
618 | migrate
619 |
620 | before(:each) do
621 | Event.upload_column :image, :filename => 'arg.png'
622 | @event = Event.new
623 | @event.image = stub_tempfile('kerb.jpg')
624 | @event.save
625 | end
626 |
627 | it "should give it the correct filename" do
628 | @event.image.filename.should == 'arg.png'
629 | end
630 |
631 | it "should give it the correct path" do
632 | @event.image.path.should match_path(PUBLIC, 'image', 'arg.png')
633 | end
634 | end
635 |
636 | describe "uploading a file with a complex filename instruction" do
637 |
638 | migrate
639 |
640 | before(:each) do
641 | Movie.upload_column :image, :filename => proc{ |r, f| "#{r.name}-#{f.basename}-#{f.suffix}quox.#{f.extension}"}, :versions => [:thumb, :large]
642 | @movie = Movie.new
643 | @movie.name = "indiana_jones"
644 | @movie.image = stub_tempfile('kerb.jpg')
645 | @movie.save
646 | end
647 |
648 | it "should give it the correct filename" do
649 | @movie.image.filename.should == 'indiana_jones-kerb-quox.jpg'
650 | @movie.image.thumb.filename.should == 'indiana_jones-kerb-thumbquox.jpg'
651 | @movie.image.large.filename.should == 'indiana_jones-kerb-largequox.jpg'
652 | end
653 |
654 | it "should have correct paths" do
655 | @movie.image.path.should match_path(PUBLIC, 'image', 'indiana_jones-kerb-quox.jpg' )
656 | @movie.image.thumb.path.should match_path(PUBLIC, 'image', 'indiana_jones-kerb-thumbquox.jpg' )
657 | @movie.image.large.path.should match_path(PUBLIC, 'image', 'indiana_jones-kerb-largequox.jpg' )
658 | end
659 |
660 | it "should remember the original filename" do
661 | @movie.image.actual_filename.should == "kerb.jpg"
662 | end
663 |
664 | it "should store the _original_ filename in the database" do
665 | @movie[:image].should == "kerb.jpg"
666 | end
667 |
668 | end
--------------------------------------------------------------------------------
/spec/magic_columns_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), 'spec_helper')
2 |
3 | gem 'activerecord'
4 | require 'active_record'
5 |
6 | require File.join(File.dirname(__FILE__), '../lib/upload_column')
7 |
8 | ActiveRecord::Base.send(:include, UploadColumn)
9 |
10 | class Entry < ActiveRecord::Base; end # setup a basic AR class for testing
11 |
12 | describe "UploadColumn::MagicColumns.set_upload_column_with_magic_columns" do
13 |
14 | include UploadColumnSpecHelper
15 |
16 | before(:each) do
17 | setup_standard_mocking
18 | UploadColumn::UploadedFile.should_receive(:upload).with(@file, @entry, :avatar, @options).and_return(@uploaded_file)
19 | end
20 |
21 | it "should assign methods from the uploaded file to database columns" do
22 | Entry.should_receive(:column_names).and_return([ 'monkey', 'llama', 'avatar_path', 'avatar_size'])
23 |
24 | @uploaded_file.stub!(:path).and_return('/path/to/my/file')
25 | @uploaded_file.stub!(:size).and_return(9999)
26 |
27 | @entry.avatar = @file
28 |
29 | @entry.avatar_path.should == '/path/to/my/file'
30 | @entry.avatar_size.should == 9999
31 | end
32 |
33 | it "should do nothing when the column names do not exist on the object" do
34 | Entry.should_receive(:column_names).and_return([ 'monkey', 'llama', 'avatar_monkey', 'avatar_size'])
35 |
36 | @uploaded_file.stub!(:size).and_return(9999)
37 |
38 | @entry.avatar = @file
39 |
40 | @entry.avatar_monkey.should be_nil
41 | @entry.avatar_size.should == 9999
42 | end
43 | end
44 |
45 | describe "UploadColumn::MagicColumns.set_upload_column_temp_with_magic_columns" do
46 |
47 | include UploadColumnSpecHelper
48 |
49 | before(:each) do
50 | setup_standard_mocking
51 |
52 | @temp_value = '12345.1234.12345/somewhere.png'
53 |
54 | @retrieved_file = mock('a retrieved file')
55 | @retrieved_file.should_receive(:actual_filename).and_return('walruss.png')
56 |
57 | UploadColumn::UploadedFile.should_receive(:retrieve_temp).with(@temp_value, @entry, :avatar, @options).and_return(@retrieved_file)
58 | @entry.should_receive(:[]=).with(:avatar, 'walruss.png')
59 | end
60 |
61 | it "should assign methods from the uploaded file to database columns" do
62 | Entry.stub!(:column_names).and_return([ 'monkey', 'llama', 'avatar_path', 'avatar_size'])
63 |
64 | @retrieved_file.stub!(:path).and_return('/path/to/my/file')
65 | @retrieved_file.stub!(:size).and_return(9999)
66 |
67 | @entry.avatar_temp = @temp_value
68 |
69 | @entry.avatar_path.should == '/path/to/my/file'
70 | @entry.avatar_size.should == 9999
71 | end
72 |
73 | it "should do nothing when the column names do not exist on the object" do
74 | Entry.stub!(:column_names).and_return([ 'monkey', 'llama', 'avatar_monkey', 'avatar_size'])
75 |
76 | @retrieved_file.stub!(:size).and_return(9999)
77 |
78 | @entry.avatar_temp = @temp_value
79 |
80 | @entry.avatar_monkey.should be_nil
81 | @entry.avatar_size.should == 9999
82 | end
83 | end
84 |
85 | describe "UploadColumn::MagicColumns.save_uploaded_files_with_magic_columns" do
86 |
87 | include UploadColumnSpecHelper
88 |
89 | before(:each) do
90 | setup_standard_mocking
91 | UploadColumn::UploadedFile.should_receive(:upload).with(@file, @entry, :avatar, @options).and_return(@uploaded_file)
92 | @entry.avatar = @file
93 | @uploaded_file.stub!(:tempfile?).and_return(false)
94 | end
95 |
96 | it "should reevaluate magic columns" do
97 | Entry.stub!(:column_names).and_return([ 'monkey', 'llama', 'avatar_path', 'avatar_size'])
98 |
99 | @uploaded_file.stub!(:path).and_return('/path/to/my/file')
100 | @uploaded_file.stub!(:size).and_return(9999)
101 |
102 | @entry.send(:save_uploaded_files)
103 |
104 | @entry.avatar_path.should == '/path/to/my/file'
105 | @entry.avatar_size.should == 9999
106 | end
107 |
108 | it "should do nothing when the column names do not exist on the object" do
109 | Entry.stub!(:column_names).and_return([ 'monkey', 'llama', 'avatar_monkey', 'avatar_size'])
110 |
111 | @uploaded_file.stub!(:size).and_return(9999)
112 |
113 | @entry.send(:save_uploaded_files)
114 |
115 | @entry.avatar_monkey.should be_nil
116 | @entry.avatar_size.should == 9999
117 | end
118 |
119 | end
120 |
121 |
--------------------------------------------------------------------------------
/spec/rmagick_manipulator_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), 'spec_helper')
2 | require File.join(File.dirname(__FILE__), '../lib/upload_column/manipulators/rmagick')
3 |
4 | describe UploadColumn::Manipulators::RMagick, "#manipulate!" do
5 |
6 | before(:each) do
7 | @uploaded_file = class << self; self end # this is a singleton object
8 | @uploaded_file.extend( UploadColumn::Manipulators::RMagick )
9 | @uploaded_file.load_manipulator_dependencies
10 | @uploaded_file.stub!(:path).and_return('/some_path.png')
11 | end
12 |
13 | it "should yield the first frame of the image and then save the result, for a single-framed image" do
14 | a_frame = mock('a frame')
15 | Magick::Image.should_receive(:read).with('/some_path.png').and_return( [a_frame] )
16 |
17 | @uploaded_file.manipulate! do |img|
18 | img.should == a_frame
19 | img.should_receive(:write).with('/some_path.png')
20 | img
21 | end
22 | end
23 |
24 | it "should yield all frames and save the result, for a multi-framed image" do
25 | image = Magick::Image.read(file_path('netscape.gif'))
26 | Magick::Image.should_receive(:read).with('/some_path.png').and_return( image )
27 |
28 | imagelist = Magick::ImageList.new
29 | Magick::ImageList.should_receive(:new).and_return(imagelist)
30 |
31 | imagelist.should_receive(:<<).with(image[0]).exactly(:once).ordered
32 | imagelist.should_receive(:<<).with(image[1]).exactly(:once).ordered
33 |
34 | image[0].should_receive(:solarize)
35 | image[1].should_receive(:solarize)
36 |
37 | imagelist.should_receive(:write).with('/some_path.png')
38 |
39 | @uploaded_file.manipulate! do |img|
40 | img.solarize
41 | img
42 | end
43 |
44 | end
45 |
46 | it "should raise an more meaningful error if something goes wrong" do
47 | Magick::Image.should_receive(:read).and_raise(Magick::ImageMagickError.new('arrggh'))
48 |
49 | lambda do
50 | @uploaded_file.manipulate! do |img|
51 | img
52 | end
53 | end.should raise_error( UploadColumn::ManipulationError, "Failed to manipulate with rmagick, maybe it is not an image? Original Error: arrggh" )
54 |
55 | end
56 |
57 | end
58 |
59 | describe UploadColumn::Manipulators::RMagick, "#resize!" do
60 |
61 | before(:each) do
62 | @uploaded_file = class << self; self end
63 | @uploaded_file.extend( UploadColumn::Manipulators::RMagick )
64 | @uploaded_file.load_manipulator_dependencies
65 | @uploaded_file.stub!(:path).and_return('/some_path.png')
66 | end
67 |
68 | it "should use rmagick to resize the image to the appropriate size" do
69 |
70 | img = mock('an image frame')
71 | @uploaded_file.should_receive(:manipulate!).and_yield(img)
72 |
73 | geometry_img = mock('image returned by change_geometry')
74 |
75 | img.should_receive(:change_geometry).with("200x200").and_yield(20, 40, geometry_img)
76 |
77 | geometry_img.should_receive(:resize).with(20, 40)
78 |
79 | @uploaded_file.resize!("200x200")
80 | end
81 |
82 | end
83 |
84 |
85 | describe UploadColumn::Manipulators::RMagick, "#crop_resized!" do
86 |
87 | before(:each) do
88 | @uploaded_file = class << self; self end
89 | @uploaded_file.extend( UploadColumn::Manipulators::RMagick )
90 | @uploaded_file.load_manipulator_dependencies
91 | @uploaded_file.stub!(:path).and_return('/some_path.png')
92 | end
93 |
94 | it "should use rmagick to resize and crop the image to the appropriate size" do
95 |
96 | img = mock('an image frame')
97 | @uploaded_file.should_receive(:manipulate!).and_yield(img)
98 |
99 | img.should_receive(:crop_resized).with(200, 200)
100 |
101 | @uploaded_file.crop_resized!("200x200")
102 | end
103 |
104 | end
105 |
106 | describe UploadColumn::Manipulators::RMagick, "#convert!" do
107 |
108 | before(:each) do
109 | @uploaded_file = class << self; self end
110 | @uploaded_file.extend( UploadColumn::Manipulators::RMagick )
111 | @uploaded_file.load_manipulator_dependencies
112 | @uploaded_file.stub!(:path).and_return('/some_path.png')
113 | end
114 |
115 | it "should use rmagick to change the image format" do
116 |
117 | img = mock('an image frame')
118 | @uploaded_file.should_receive(:manipulate!).and_yield(img)
119 |
120 | img.should_receive(:format=).with("PNG")
121 |
122 | @uploaded_file.convert!(:png)
123 | end
124 |
125 | end
126 |
127 | describe UploadColumn::Manipulators::RMagick, "#process!" do
128 |
129 | before(:each) do
130 | @uploaded_file = class << self; self end
131 | @uploaded_file.extend( UploadColumn::Manipulators::RMagick )
132 | @uploaded_file.load_manipulator_dependencies
133 | @uploaded_file.stub!(:path).and_return('/some_path.png')
134 | end
135 |
136 | it "should resize the image if a string like '333x444' is passed" do
137 | @uploaded_file.should_receive(:resize!).with('333x444')
138 | @uploaded_file.process!('333x444')
139 | end
140 |
141 | it "should crop and resize the image if a string like 'c333x444' is passed" do
142 | @uploaded_file.should_receive(:crop_resized!).with('333x444')
143 | @uploaded_file.process!('c333x444')
144 | end
145 |
146 | it "should pass on a proc to manipulate!" do
147 | img_frame = mock('an image frame')
148 | proc = proc { |img| img.solarize }
149 | img_frame.should_receive(:solarize)
150 |
151 | @uploaded_file.should_receive(:manipulate!).and_yield(img_frame)
152 |
153 | @uploaded_file.process!(proc)
154 | end
155 |
156 | it "should yield to manipulate! if a block is given" do
157 | img_frame = mock('an image frame')
158 | img_frame.should_receive(:solarize)
159 |
160 | @uploaded_file.should_receive(:manipulate!).and_yield(img_frame)
161 |
162 | @uploaded_file.process! do |img|
163 | img.solarize
164 | end
165 | end
166 |
167 | it "should resize first and then yield to manipulate! if both a block and a size string are given" do
168 | img_frame = mock('an image frame')
169 | img_frame.should_receive(:solarize)
170 |
171 | @uploaded_file.should_receive(:resize!).with('200x200').ordered
172 | @uploaded_file.should_receive(:manipulate!).ordered.and_yield(img_frame)
173 |
174 | @uploaded_file.process!('200x200') do |img|
175 | img.solarize
176 | end
177 | end
178 |
179 | it "should do nothing if :none is passed" do
180 | @uploaded_file.should_not_receive(:manipulate!)
181 | @uploaded_file.process!(:none)
182 | end
183 |
184 | end
185 |
186 |
187 |
--------------------------------------------------------------------------------
/spec/sanitized_file_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), 'spec_helper')
2 | require File.join(File.dirname(__FILE__), '../lib/upload_column/sanitized_file')
3 | begin
4 | require 'mime/types'
5 | rescue LoadError
6 | end
7 |
8 | describe "creating a new SanitizedFile" do
9 | it "should yield an empty file on empty String, nil, empty StringIO" do
10 | UploadColumn::SanitizedFile.new("").should be_empty
11 | UploadColumn::SanitizedFile.new(StringIO.new("")).should be_empty
12 | UploadColumn::SanitizedFile.new(nil).should be_empty
13 | file = mock('emptyfile')
14 | file.should_receive(:size).at_least(:once).and_return(0)
15 | UploadColumn::SanitizedFile.new(file).should be_empty
16 | end
17 |
18 | it "should yield a non empty file" do
19 | UploadColumn::SanitizedFile.new(stub_stringio('kerb.jpg', 'image/jpeg')).should_not be_empty
20 | UploadColumn::SanitizedFile.new(stub_file('kerb.jpg', 'image/jpeg')).should_not be_empty
21 | end
22 |
23 | it "should not change a valid filename" do
24 | t = UploadColumn::SanitizedFile.new(stub_file('kerb.jpg', nil, "test.jpg"))
25 | t.filename.should == "test.jpg"
26 | end
27 |
28 | it "should remove illegal characters from a filename" do
29 | t = UploadColumn::SanitizedFile.new(stub_file('kerb.jpg', nil, "test-s,%&m#st?.jpg"))
30 | t.filename.should == "test-s___m_st_.jpg"
31 | end
32 |
33 | it "should remove slashes from the filename" do
34 | t = UploadColumn::SanitizedFile.new(stub_file('kerb.jpg', nil, "../../very_tricky/foo.bar"))
35 | t.filename.should_not =~ /[\\\/]/
36 | end
37 |
38 | it "should remove illegal characters if there is no extension" do
39 | t = UploadColumn::SanitizedFile.new(stub_file('kerb.jpg', nil, '`*foo'))
40 | t.filename.should == "__foo"
41 | end
42 |
43 | it "should remove the path prefix on Windows" do
44 | t = UploadColumn::SanitizedFile.new(stub_file('kerb.jpg', nil, 'c:\temp\foo.txt'))
45 | t.filename.should == "foo.txt"
46 | end
47 |
48 | it "should make sure the *nix directory thingies can't be used as filenames" do
49 | t = UploadColumn::SanitizedFile.new(stub_file('kerb.jpg', nil, "."))
50 | t.filename.should == "_."
51 | end
52 |
53 | it "should downcase uppercase filenames" do
54 | t = UploadColumn::SanitizedFile.new(stub_file('kerb.jpg', nil, "DSC4056.JPG"))
55 | t.filename.should == "dsc4056.jpg"
56 | end
57 |
58 | end
59 |
60 | # Note that SanitizedFile#path and #exists? need to be checked seperately as the return values will vary
61 | describe "all sanitized files", :shared => true do
62 |
63 | it "should not be empty" do
64 | @file.should_not be_empty
65 | end
66 |
67 | it "should return the original filename" do
68 | @file.original_filename.should == "kerb.jpg"
69 | end
70 |
71 | it "should return the filename" do
72 | @file.filename.should == "kerb.jpg"
73 | end
74 |
75 | it "should return the basename" do
76 | @file.basename.should == "kerb"
77 | end
78 |
79 | it "should return the extension" do
80 | @file.extension.should == "jpg"
81 | end
82 |
83 | it "should be moved to the correct location" do
84 | @file.move_to(public_path('gurr.jpg'))
85 | File.exists?( public_path('gurr.jpg') ).should === true
86 | file_path('kerb.jpg').should be_identical_with(public_path('gurr.jpg'))
87 | end
88 |
89 | it "should have changed its path when moved" do
90 | @file.move_to(public_path('gurr.jpg'))
91 | @file.path.should match_path(public_path('gurr.jpg'))
92 | end
93 |
94 | it "should have changed its filename when moved" do
95 | @file.filename # Make sure the filename has been cached
96 | @file.move_to(public_path('gurr.jpg'))
97 | @file.filename.should == 'gurr.jpg'
98 | end
99 |
100 | it "should have split the filename when moved" do
101 | @file.move_to(public_path('gurr.monk'))
102 | @file.basename.should == 'gurr'
103 | @file.extension.should == 'monk'
104 | end
105 |
106 | it "should be copied to the correct location" do
107 | @file.copy_to(public_path('gurr.jpg'))
108 | File.exists?( public_path('gurr.jpg') ).should === true
109 | file_path('kerb.jpg').should be_identical_with(public_path('gurr.jpg'))
110 | end
111 |
112 | it "should not have changed its path when copied" do
113 | running { @file.copy_to(public_path('gurr.jpg')) }.should_not change(@file, :path)
114 | end
115 |
116 | it "should not have changed its filename when copied" do
117 | running { @file.copy_to(public_path('gurr.jpg')) }.should_not change(@file, :filename)
118 | end
119 |
120 | it "should return an object of the same class when copied" do
121 | new_file = @file.copy_to(public_path('gurr.jpg'))
122 | new_file.should be_an_instance_of(@file.class)
123 | end
124 |
125 | it "should adjust the path of the object that is returned when copied" do
126 | new_file = @file.copy_to(public_path('gurr.jpg'))
127 | new_file.path.should match_path(public_path('gurr.jpg'))
128 | end
129 |
130 | it "should adjust the filename of the object that is returned when copied" do
131 | @file.filename # Make sure the filename has been cached
132 | @file = @file.copy_to(public_path('gurr.monk'))
133 | @file.filename.should == 'gurr.monk'
134 | end
135 |
136 | it "should split the filename of the object that is returned when copied" do
137 | @file = @file.copy_to(public_path('gurr.monk'))
138 | @file.basename.should == 'gurr'
139 | @file.extension.should == 'monk'
140 | end
141 |
142 | after do
143 | FileUtils.rm_rf(PUBLIC)
144 | end
145 | end
146 |
147 | describe "a sanitized Tempfile" do
148 | before do
149 | @tempfile = stub_tempfile('kerb.jpg', 'image/jpeg')
150 | @file = UploadColumn::SanitizedFile.new(@tempfile)
151 | end
152 |
153 | it_should_behave_like "all sanitized files"
154 |
155 | it "should not raise an error when moved to its own location" do
156 | running { @file.move_to(@file.path) }.should_not raise_error
157 | end
158 |
159 | it "should return a new instance when copied to its own location" do
160 | running {
161 | new_file = @file.copy_to(@file.path)
162 | new_file.should be_an_instance_of(@file.class)
163 | }.should_not raise_error
164 | end
165 |
166 | it "should exist" do
167 | @file.should be_in_existence
168 | end
169 |
170 | it "should return the correct path" do
171 | @file.path.should_not == nil
172 | @file.path.should == @tempfile.path
173 | end
174 | end
175 |
176 | describe "a sanitized StringIO" do
177 | before do
178 | @file = UploadColumn::SanitizedFile.new(stub_stringio('kerb.jpg', 'image/jpeg'))
179 | end
180 |
181 | it_should_behave_like "all sanitized files"
182 |
183 | it "should not exist" do
184 | @file.should_not be_in_existence
185 | end
186 |
187 | it "should return no path" do
188 | @file.path.should == nil
189 | end
190 |
191 | end
192 |
193 | describe "a sanitized File object" do
194 | before do
195 | @file = UploadColumn::SanitizedFile.new(stub_file('kerb.jpg', 'image/jpeg'))
196 | @file.should_not be_empty
197 | end
198 |
199 | it_should_behave_like "all sanitized files"
200 |
201 | it "should not raise an error when moved to its own location" do
202 | running { @file.move_to(@file.path) }.should_not raise_error
203 | end
204 |
205 | it "should return a new instance when copied to its own location" do
206 | running {
207 | new_file = @file.copy_to(@file.path)
208 | new_file.should be_an_instance_of(@file.class)
209 | }.should_not raise_error
210 | end
211 |
212 | it "should exits" do
213 | @file.should be_in_existence
214 | end
215 |
216 | it "should return correct path" do
217 | @file.path.should match_path(file_path('kerb.jpg'))
218 | end
219 | end
220 |
221 | describe "a SanitizedFile opened from a path" do
222 | before do
223 | @file = UploadColumn::SanitizedFile.new(file_path('kerb.jpg'))
224 | @file.should_not be_empty
225 | end
226 |
227 | it_should_behave_like "all sanitized files"
228 |
229 | it "should not raise an error when moved to its own location" do
230 | running { @file.move_to(@file.path) }.should_not raise_error
231 | end
232 |
233 | it "should return a new instance when copied to its own location" do
234 | running {
235 | new_file = @file.copy_to(@file.path)
236 | new_file.should be_an_instance_of(@file.class)
237 | }.should_not raise_error
238 | end
239 |
240 | it "should exits" do
241 | @file.should be_in_existence
242 | end
243 |
244 | it "should return correct path" do
245 | @file.path.should == file_path('kerb.jpg')
246 | end
247 | end
248 |
249 | describe "an empty SanitizedFile" do
250 | before do
251 | @empty = UploadColumn::SanitizedFile.new(nil)
252 | end
253 |
254 | it "should be empty" do
255 | @empty.should be_empty
256 | end
257 |
258 | it "should not exist" do
259 | @empty.should_not be_in_existence
260 | end
261 |
262 | it "should have no size" do
263 | @empty.size.should == nil
264 | end
265 |
266 | it "should have no path" do
267 | @empty.path.should == nil
268 | end
269 |
270 | it "should have no original filename" do
271 | @empty.original_filename.should == nil
272 | end
273 |
274 | it "should have no filename" do
275 | @empty.filename.should == nil
276 | end
277 |
278 | it "should have no basename" do
279 | @empty.basename.should == nil
280 | end
281 |
282 | it "should have no extension" do
283 | @empty.extension.should == nil
284 | end
285 | end
286 |
287 | describe "a SanitizedFile" do
288 |
289 | before do
290 | @file = UploadColumn::SanitizedFile.new(stub_tempfile('kerb.jpg', 'image/jpeg'))
291 | end
292 |
293 | it "should properly split into basename and extension" do
294 | @file.basename.should == "kerb"
295 | @file.extension.should == "jpg"
296 | end
297 |
298 | it "should do a system call" do
299 | @file.send(:system_call, 'echo "monkey"').chomp.should == "monkey"
300 | end
301 |
302 | end
303 |
304 | describe "a SanizedFile with a complex filename" do
305 | it "properly split into basename and extension" do
306 | t = UploadColumn::SanitizedFile.new(stub_tempfile('kerb.jpg', nil, 'complex.filename.tar.gz'))
307 | t.basename.should == "complex.filename"
308 | t.extension.should == "tar.gz"
309 | end
310 | end
311 |
312 | # FIXME: figure out why this doesn't run
313 | #describe "determinating the mime-type with a *nix exec" do
314 | #
315 | # before do
316 | # @file = stub_file('kerb.jpg', nil, 'harg.css')
317 | # @sanitized = UploadColumn::SanitizedFile.new(@file)
318 | # end
319 | #
320 | # it "should chomp and return if it has no encoding" do
321 | # @sanitized.should_receive(:system_call).with(%(file -bi "#{@file.path}")).and_return("image/jpeg\n")
322 | #
323 | # @sanitized.send(:get_content_type_from_exec) #.should == "image/jpeg"
324 | # end
325 | #
326 | # it "should chomp and return and chop off the encoding if it has one" do
327 | # @sanitized.should_receive(:system_call).with(%(file -bi "#{@file.path}")).and_return("text/plain; charset=utf-8;\n")
328 | #
329 | # @sanitized.send(:get_content_type_from_exec) #.should == "text/plain"
330 | # end
331 | #
332 | # it "should not crap out when something weird happens" do
333 | # @sanitized.should_receive(:system_call).with(%(file -bi "#{@file.path}")).and_return("^blah//(?)wtf???")
334 | #
335 | # @sanitized.send(:get_content_type_from_exec).should == nil
336 | # end
337 | #
338 | #end
339 |
340 | describe "The mime-type of a Sanitized File" do
341 |
342 | before do
343 | @file = stub_file('kerb.jpg', nil, 'harg.css')
344 | end
345 |
346 | # TODO: refactor this test so it mocks out system_call
347 | it "should be determined via *nix exec" do
348 |
349 | @sanitized = UploadColumn::SanitizedFile.new(@file, :get_content_type_from_file_exec => true)
350 |
351 | @sanitized.stub!(:path).and_return('/path/to/file.jpg')
352 | @sanitized.should_receive(:system_call).with(%(file -bi "/path/to/file.jpg")).and_return('text/monkey')
353 |
354 | @sanitized.content_type.should == "text/monkey"
355 | end
356 |
357 | it "shouldn't choke up when the *nix exec errors out" do
358 | @sanitized = UploadColumn::SanitizedFile.new(@file, :get_content_type_from_file_exec => true)
359 |
360 | lambda {
361 | @sanitized.should_receive(:system_call).and_raise('monkey')
362 | @sanitized.content_type
363 | }.should_not raise_error
364 | end
365 |
366 | it "should otherwise be loaded from MIME::Types" do
367 | if defined?(MIME::Types)
368 | @sanitized = UploadColumn::SanitizedFile.new(@file)
369 |
370 | @sanitized.should_receive(:get_content_type_from_exec).and_return(nil) # Make sure the *nix exec isn't interfering
371 | @sanitized.content_type.should == "text/css"
372 | else
373 | puts "WARNING: Could not run all examples because MIME::Types is not defined, try installing the mime-types gem!"
374 | end
375 | end
376 |
377 | it "should be taken from the browser if all else fails" do
378 | @sanitized = UploadColumn::SanitizedFile.new(@file)
379 |
380 | @file.should_receive(:content_type).at_least(:once).and_return('application/xhtml+xml') # Set up browser behavior
381 | # FIXME: this is brittle. There really should be another way of changing this behaviour.
382 | @sanitized.should_receive(:get_content_type_from_mime_types).and_return(nil) # Make sure MIME::Types isn't interfering
383 | @sanitized.content_type.should == "application/xhtml+xml"
384 | end
385 | end
386 |
387 | describe "a SanitizedFile with a wrong extension" do
388 |
389 | # This test currently always fails if MIME::Types is unavailable,
390 | # TODO: come up with a clever way to stub out the content_type-y behaviour.
391 | it "should fix extention if fix_file_extensions is true" do
392 | t = UploadColumn::SanitizedFile.new(stub_file('kerb.jpg', 'image/jpeg', 'kerb.css'), :fix_file_extensions => true)
393 |
394 | t.content_type.should == "image/jpeg"
395 | t.extension.should == "jpeg"
396 | t.filename.should == "kerb.jpeg"
397 | end
398 |
399 | it "should not fix extention if fix_file_extensions is false" do
400 | t = UploadColumn::SanitizedFile.new(stub_file('kerb.jpg', 'image/jpeg', 'kerb.css'), :fix_file_extensions => false)
401 |
402 | #t.content_type.should == "image/css" FIXME: the result of this is upredictable and
403 | # differs, depending on whether or not the user has MIME::Types installed
404 | t.extension.should == "css"
405 | t.filename.should == "kerb.css"
406 | end
407 | end
408 |
409 | describe "copying a sanitized Tempfile with permissions set" do
410 | before do
411 | @file = UploadColumn::SanitizedFile.new(stub_tempfile('kerb.jpg', 'image/jpeg'), :permissions => 0755)
412 | @file = @file.copy_to(public_path('gurr.jpg'))
413 | end
414 |
415 | it "should set the right permissions" do
416 | @file.should have_permissions(0755)
417 | end
418 | end
419 |
420 | describe "copying a sanitized StringIO with permissions set" do
421 | before do
422 | @file = UploadColumn::SanitizedFile.new(stub_stringio('kerb.jpg', 'image/jpeg'), :permissions => 0755)
423 | @file = @file.copy_to(public_path('gurr.jpg'))
424 | end
425 |
426 | it "should set the right permissions" do
427 | @file.should have_permissions(0755)
428 | end
429 | end
430 |
431 | describe "copying a sanitized File object with permissions set" do
432 | before do
433 | @file = UploadColumn::SanitizedFile.new(stub_file('kerb.jpg', 'image/jpeg'), :permissions => 0755)
434 | @file = @file.copy_to(public_path('gurr.jpg'))
435 | end
436 |
437 | it "should set the right permissions" do
438 | @file.should have_permissions(0755)
439 | end
440 | end
441 |
442 | describe "copying a sanitized file by path with permissions set" do
443 | before do
444 | @file = UploadColumn::SanitizedFile.new(file_path('kerb.jpg'), :permissions => 0755)
445 | @file = @file.copy_to(public_path('gurr.jpg'))
446 | end
447 |
448 | it "should set the right permissions" do
449 | @file.should have_permissions(0755)
450 | end
451 | end
452 |
453 |
454 | describe "moving a sanitized Tempfile with permissions set" do
455 | before do
456 | @file = UploadColumn::SanitizedFile.new(stub_tempfile('kerb.jpg', 'image/jpeg'), :permissions => 0755)
457 | @file.move_to(public_path('gurr.jpg'))
458 | end
459 |
460 | it "should set the right permissions" do
461 | @file.should have_permissions(0755)
462 | end
463 | end
464 |
465 | describe "moving a sanitized StringIO with permissions set" do
466 | before do
467 | @file = UploadColumn::SanitizedFile.new(stub_stringio('kerb.jpg', 'image/jpeg'), :permissions => 0755)
468 | @file.move_to(public_path('gurr.jpg'))
469 | end
470 |
471 | it "should set the right permissions" do
472 | @file.should have_permissions(0755)
473 | end
474 | end
475 |
476 | describe "moving a sanitized File object with permissions set" do
477 | before do
478 | @file = UploadColumn::SanitizedFile.new(stub_file('kerb.jpg', 'image/jpeg'), :permissions => 0755)
479 | @file.move_to(public_path('gurr.jpg'))
480 | end
481 |
482 | it "should set the right permissions" do
483 | @file.should have_permissions(0755)
484 | end
485 | end
486 |
487 | describe "moving a sanitized file by path with permissions set" do
488 | before do
489 | @file = UploadColumn::SanitizedFile.new(file_path('kerb.jpg'), :permissions => 0755)
490 | @file.move_to(public_path('gurr.jpg'))
491 | end
492 |
493 | it "should set the right permissions" do
494 | @file.should have_permissions(0755)
495 | end
496 | end
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'tempfile'
2 | require 'rubygems'
3 | require 'spec'
4 | require 'ruby-debug'
5 |
6 | require File.join(File.dirname(__FILE__), 'custom_matchers')
7 |
8 | RAILS_ROOT = File.expand_path(File.dirname(__FILE__)) unless defined?(RAILS_ROOT)
9 | PUBLIC = File.expand_path(File.join(RAILS_ROOT, 'public')) unless defined?(PUBLIC)
10 |
11 | def file_path( filename )
12 | File.join(File.dirname(__FILE__), 'fixtures', filename)
13 | end
14 |
15 | def public_path( filename )
16 | File.join(File.dirname(__FILE__), 'public', filename)
17 | end
18 |
19 | def stub_tempfile(filename, mime_type=nil, fake_name=nil)
20 | raise "#{path} file does not exist" unless File.exist?(file_path(filename))
21 |
22 | t = Tempfile.new(filename)
23 | FileUtils.copy_file(file_path(filename), t.path)
24 |
25 | t.stub!(:original_filename).and_return(fake_name || filename)
26 | t.stub!(:content_type).and_return(mime_type)
27 | t.stub!(:local_path).and_return(t.path)
28 | return t
29 | end
30 |
31 | def stub_stringio(filename, mime_type=nil, fake_name=nil)
32 | if filename
33 | t = StringIO.new( IO.read( file_path( filename ) ) )
34 | else
35 | t = StringIO.new
36 | end
37 | t.stub!(:local_path).and_return("")
38 | t.stub!(:original_filename).and_return(filename || fake_name)
39 | t.stub!(:content_type).and_return(mime_type)
40 | return t
41 | end
42 |
43 | def stub_file(filename, mime_type=nil, fake_name=nil)
44 | f = File.open(file_path(filename))
45 | f.stub!(:content_type).and_return(mime_type)
46 | f.stub!(:original_filename).and_return(fake_name) if fake_name
47 | return f
48 | end
49 |
50 | module UploadColumnSpecHelper
51 |
52 | def disconnected_model(model_class)
53 | model_class.stub!(:columns).and_return([])
54 | return model_class.new
55 | end
56 |
57 | def setup_standard_mocking
58 | @options = mock('options', :null_object => true)
59 | Entry.upload_column :avatar, @options
60 | @entry = disconnected_model(Entry)
61 | mock_file
62 | end
63 |
64 | def setup_version_mocking
65 | Entry.upload_column :avatar, :versions => [ :thumb, :large ]
66 | @entry = disconnected_model(Entry)
67 | mock_file
68 | end
69 |
70 | private
71 |
72 | def mock_file
73 | @file = mock('file')
74 |
75 | @uploaded_file = mock('uploaded_file')
76 | @uploaded_file.stub!(:actual_filename).and_return('monkey.png')
77 | end
78 | end
79 |
80 | module UniversalSpecHelper
81 |
82 | def running(&block)
83 | lambda(&block)
84 | end
85 |
86 | end
87 |
88 | Spec::Runner.configure do |config|
89 | config.include UniversalSpecHelper
90 | end
--------------------------------------------------------------------------------
/spec/upload_column_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), 'spec_helper')
2 |
3 | gem 'activerecord'
4 | require 'active_record'
5 |
6 | require File.join(File.dirname(__FILE__), '../lib/upload_column')
7 |
8 | describe "UploadColumn" do
9 |
10 | it "should have a default configuration" do
11 | UploadColumn.configuration.should be_an_instance_of(Hash)
12 | config = UploadColumn.configuration
13 |
14 | config[:tmp_dir].should == 'tmp'
15 | config[:store_dir].should be_an_instance_of(Proc)
16 | config[:root_dir].should == File.join(RAILS_ROOT, 'public')
17 | config[:get_content_type_from_file_exec].should == true
18 | config[:fix_file_extensions].should == false
19 | config[:process].should == nil
20 | config[:permissions].should == 0644
21 | config[:extensions].should == UploadColumn.extensions
22 | config[:web_root].should == ''
23 | config[:manipulator].should == nil
24 | config[:versions].should == nil
25 | config[:validate_integrity].should == false
26 | end
27 |
28 | it "should have a list of allowed extensions" do
29 | UploadColumn.extensions.should == %w(asf ai avi doc dvi dwg eps gif gz jpg jpeg mov mp3 mpeg odf pac pdf png ppt psd swf swx tar tar.gz torrent txt wmv wav xls zip)
30 | end
31 |
32 | it "should have a list of allowed image extensions" do
33 | UploadColumn.image_extensions.should == %w(jpg jpeg gif png)
34 | end
35 |
36 | end
37 |
38 | describe "UploadColumn.configure" do
39 |
40 | after do
41 | UploadColumn.reset_configuration
42 | end
43 |
44 | it "should yield a configuration proxy" do
45 | UploadColumn.configure do |config|
46 | config.should be_an_instance_of(UploadColumn::ConfigurationProxy)
47 | end
48 | end
49 |
50 | it "should change the configuration of a known option" do
51 | UploadColumn.configure do |config|
52 | config.web_root = "/monkey"
53 | end
54 |
55 | UploadColumn.configuration[:web_root].should == "/monkey"
56 | end
57 |
58 | it "should change the configuration of an unknown option" do
59 | UploadColumn.configure do |config|
60 | config.monkey = ":)"
61 | end
62 |
63 | UploadColumn.configuration[:monkey].should == ":)"
64 | end
65 | end
--------------------------------------------------------------------------------
/spec/uploaded_file_spec.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), 'spec_helper')
2 |
3 | require 'active_record'
4 |
5 | require File.join(File.dirname(__FILE__), '../lib/upload_column')
6 |
7 | ActiveRecord::Base.send(:include, UploadColumn)
8 |
9 | describe "uploading a file" do
10 | it "should trigger an _after_upload callback" do
11 | record = mock('a record')
12 | record.should_receive(:avatar_after_upload)
13 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), record, :avatar)
14 | end
15 | end
16 |
17 | describe "all uploaded files", :shared => true do
18 | it "should not be empty" do
19 | @file.should_not be_empty
20 | end
21 |
22 | it "should return the correct filesize" do
23 | @file.size.should == 87582
24 | end
25 |
26 | it "should return the original filename" do
27 | @file.original_filename.should == "kerb.jpg"
28 | end
29 |
30 | it "should return the filename" do
31 | @file.filename.should == "kerb.jpg"
32 | end
33 |
34 | it "should return the basename" do
35 | @file.basename.should == "kerb"
36 | end
37 |
38 | it "should return the extension" do
39 | @file.extension.should == "jpg"
40 | end
41 |
42 | after do
43 | FileUtils.rm_rf(public_path('*'))
44 | end
45 | end
46 |
47 | describe "an uploaded tempfile" do
48 |
49 | before do
50 | @file = UploadColumn::UploadedFile.upload(stub_tempfile('kerb.jpg'))
51 | end
52 |
53 | it_should_behave_like "all uploaded files"
54 |
55 | it "should return the correct path" do
56 | @file.path.should match_path('public', 'tmp', %r{((\d+\.)+\d+)}, 'kerb.jpg')
57 | end
58 |
59 | it "should return the correct relative_path" do
60 | @file.relative_path.should =~ %r{^tmp/((\d+\.)+\d+)/kerb.jpg}
61 | end
62 |
63 | it "should return correct dir" do
64 | @file.dir.should match_path('public', 'tmp', %r{((\d+\.)+\d+)})
65 | end
66 | end
67 |
68 | describe "an uploaded StringIO" do
69 |
70 | before do
71 | @file = UploadColumn::UploadedFile.upload(stub_stringio('kerb.jpg'))
72 | end
73 |
74 | it_should_behave_like "all uploaded files"
75 |
76 | it "should return the correct path" do
77 | @file.path.should match_path('public', 'tmp', %r{((\d+\.)+\d+)}, 'kerb.jpg')
78 | end
79 |
80 | it "should return the correct relative_path" do
81 | @file.relative_path.should =~ %r{^tmp/((\d+\.)+\d+)/kerb.jpg}
82 | end
83 |
84 | it "should return correct dir" do
85 | @file.dir.should match_path('public', 'tmp', %r{((\d+\.)+\d+)})
86 | end
87 | end
88 |
89 | describe "an uploaded File object" do
90 |
91 | before do
92 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'))
93 | end
94 |
95 | it_should_behave_like "all uploaded files"
96 |
97 | it "should return the correct path" do
98 | @file.path.should match_path('public', 'tmp', %r{((\d+\.)+\d+)}, 'kerb.jpg')
99 | end
100 |
101 | it "should return the correct relative_path" do
102 | @file.relative_path.should =~ %r{^tmp/((\d+\.)+\d+)/kerb.jpg}
103 | end
104 |
105 | it "should return correct dir" do
106 | @file.dir.should match_path('public', 'tmp', %r{((\d+\.)+\d+)})
107 | end
108 | end
109 |
110 | describe "an uploaded non-empty String" do
111 | it "should raise an error" do
112 | lambda do
113 | UploadColumn::UploadedFile.upload("../README")
114 | end.should raise_error(UploadColumn::UploadNotMultipartError)
115 | end
116 | end
117 |
118 | describe "an uploded empty file" do
119 | it "should return nil" do
120 | file = mock('uploaded empty file')
121 | file.should_receive(:empty?).and_return(true)
122 | upload = mock('upload')
123 | UploadColumn::UploadedFile.should_receive(:new).and_return(file)
124 |
125 | UploadColumn::UploadedFile.upload(upload).should == nil
126 | end
127 | end
128 |
129 | describe "an UploadedFile" do
130 | before do
131 | @file = UploadColumn::UploadedFile.new(:open, stub_file('kerb.jpg'), nil, :donkey)
132 | end
133 |
134 | it "should have the correct relative store dir" do
135 | @file.relative_store_dir.should == 'donkey'
136 | end
137 |
138 | it "should have the correct store dir" do
139 | @file.store_dir.should == File.expand_path('donkey', PUBLIC)
140 | end
141 |
142 | it "should have the correct relative tmp dir" do
143 | @file.relative_tmp_dir.should == 'tmp'
144 | end
145 |
146 | it "should have the correct tmp dir" do
147 | @file.tmp_dir.should == File.expand_path('tmp', PUBLIC)
148 | end
149 |
150 | it "should return something sensible on inspect" do
151 | @file.inspect.should == ""
152 | end
153 | end
154 |
155 | describe "an UploadedFile where store_dir is a String" do
156 | before do
157 | @file = UploadColumn::UploadedFile.new(:open, stub_file('kerb.jpg'), nil, nil, :store_dir => 'monkey')
158 | end
159 |
160 | it "should have the correct relative store dir" do
161 | @file.relative_store_dir.should == 'monkey'
162 | end
163 |
164 | it "should have the correct store dir" do
165 | @file.store_dir.should == File.expand_path('monkey', PUBLIC)
166 | end
167 | end
168 |
169 | describe "an UploadedFile where tmp_dir is a String" do
170 | before do
171 | @file = UploadColumn::UploadedFile.new(:open, stub_file('kerb.jpg'), nil, nil, :tmp_dir => 'monkey')
172 | end
173 |
174 | it "should have the correct relative tmp dir" do
175 | @file.relative_tmp_dir.should == 'monkey'
176 | end
177 |
178 | it "should have the correct tmp dir" do
179 | @file.tmp_dir.should == File.expand_path('monkey', PUBLIC)
180 | end
181 | end
182 |
183 | describe "an UploadedFile where filename is a String" do
184 | before do
185 | @file = UploadColumn::UploadedFile.new(:open, stub_file('kerb.jpg'), nil, nil, :filename => 'monkey.png', :versions => [:thumb, :large])
186 | end
187 |
188 | it "should have the correct filename" do
189 | @file.filename.should == 'monkey.png'
190 | end
191 |
192 | it "should remember the actual filename" do
193 | @file.actual_filename.should == "kerb.jpg"
194 | end
195 |
196 | it "should have versions with the correct filename" do
197 | @file.thumb.filename.should == 'monkey.png'
198 | @file.large.filename.should == 'monkey.png'
199 | end
200 | end
201 |
202 | describe "an UploadedFile where filename is a Proc with the record piped in" do
203 | before do
204 | record = mock('a record')
205 | record.stub!(:name).and_return('quack')
206 | @file = UploadColumn::UploadedFile.new(:open, stub_file('kerb.jpg'), record, nil, :versions => [:thumb, :large], :filename => proc{ |r| r.name })
207 | end
208 |
209 | it "should have the correct filename" do
210 | @file.filename.should == 'quack'
211 | end
212 |
213 | it "should remember the actual filename" do
214 | @file.actual_filename.should == "kerb.jpg"
215 | end
216 |
217 | it "should have versions with the correct filename" do
218 | @file.thumb.filename.should == 'quack'
219 | @file.large.filename.should == 'quack'
220 | end
221 | end
222 |
223 | describe "an UploadedFile where filename is a Proc with the record and file piped in" do
224 | before do
225 | record = mock('a record')
226 | record.stub!(:name).and_return('quack')
227 | @file = UploadColumn::UploadedFile.new(:open, stub_file('kerb.jpg'), record, nil, :versions => [:thumb, :large], :filename => proc{ |r, f| "#{r.name}-#{f.basename}-#{f.suffix}quox.#{f.extension}"})
228 | end
229 |
230 | it "should have the correct filename" do
231 | @file.filename.should == 'quack-kerb-quox.jpg'
232 | end
233 |
234 | it "should remember the actual filename" do
235 | @file.actual_filename.should == "kerb.jpg"
236 | end
237 |
238 | it "should have versions with the correct filename" do
239 | @file.thumb.filename.should == 'quack-kerb-thumbquox.jpg'
240 | @file.large.filename.should == 'quack-kerb-largequox.jpg'
241 | end
242 | end
243 |
244 | describe "an UploadedFile with a filename callback" do
245 | before do
246 | @instance = mock('instance with filename callback')
247 | @file = UploadColumn::UploadedFile.new(:open, stub_file('kerb.jpg'), @instance, :monkey, :versions => [:thumb, :large])
248 | end
249 |
250 | it "should have the correct filename" do
251 | @instance.should_receive(:monkey_filename).with(@file).and_return("llama")
252 | @file.filename.should == 'llama'
253 | end
254 |
255 | it "should remember the actual filename" do
256 | @file.actual_filename.should == "kerb.jpg"
257 | end
258 |
259 | it "should have versions with the correct filename" do
260 | @instance.should_receive(:monkey_filename).with(@file.thumb).and_return("barr")
261 | @instance.should_receive(:monkey_filename).with(@file.large).and_return("quox")
262 | @file.thumb.filename.should == 'barr'
263 | @file.large.filename.should == 'quox'
264 | end
265 | end
266 |
267 | describe "uploading an UploadedFile where filename is a Proc" do
268 | before do
269 | record = mock('a record')
270 | record.stub!(:name).and_return('quack')
271 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), record, nil, :versions => [:thumb, :large], :filename => proc{ |r, f| "#{r.name}-#{f.basename}-#{f.suffix}quox.#{f.extension}"})
272 | end
273 |
274 | it "should have the correct filename" do
275 | @file.filename.should == 'quack-kerb-quox.jpg'
276 | end
277 |
278 | it "should remember the actual filename" do
279 | @file.actual_filename.should == "kerb.jpg"
280 | end
281 |
282 | it "should have versions with the correct filename" do
283 | @file.thumb.filename.should == 'quack-kerb-thumbquox.jpg'
284 | @file.large.filename.should == 'quack-kerb-largequox.jpg'
285 | end
286 |
287 | it "should have a correct path" do
288 | @file.path.should match_path(PUBLIC, 'tmp', /(?:\d+\.)+\d+/, 'quack-kerb-quox.jpg' )
289 | end
290 |
291 | it "should have versions with correct paths" do
292 | @file.thumb.path.should match_path(PUBLIC, 'tmp', /(?:\d+\.)+\d+/, 'quack-kerb-thumbquox.jpg' )
293 | @file.large.path.should match_path(PUBLIC, 'tmp', /(?:\d+\.)+\d+/, 'quack-kerb-largequox.jpg' )
294 | end
295 | end
296 |
297 | describe "an UploadedFile where store_dir is a simple Proc" do
298 | before do
299 | @file = UploadColumn::UploadedFile.new(:open, stub_file('kerb.jpg'), nil, nil, :store_dir => proc{'monkey'})
300 | end
301 |
302 | it "should have the correct relative store dir" do
303 | @file.relative_store_dir.should == 'monkey'
304 | end
305 |
306 | it "should have the correct store dir" do
307 | @file.store_dir.should == File.expand_path('monkey', PUBLIC)
308 | end
309 | end
310 |
311 | describe "an UploadedFile where tmp_dir is a simple Proc" do
312 | before do
313 | @file = UploadColumn::UploadedFile.new(:open, stub_file('kerb.jpg'), nil, nil, :tmp_dir => proc{'monkey'})
314 | end
315 |
316 | it "should have the correct relative tmp dir" do
317 | @file.relative_tmp_dir.should == 'monkey'
318 | end
319 |
320 | it "should have the correct tmp dir" do
321 | @file.tmp_dir.should == File.expand_path('monkey', PUBLIC)
322 | end
323 | end
324 |
325 | describe "an UploadedFile where store_dir is a Proc and has the record piped in" do
326 | before do
327 | record = mock('a record')
328 | record.stub!(:name).and_return('quack')
329 | @file = UploadColumn::UploadedFile.new(:open, stub_file('kerb.jpg'), record, nil, :store_dir => proc{ |record| File.join(record.name, 'monkey')})
330 | end
331 |
332 | it "should have the correct relative store dir" do
333 | @file.relative_store_dir.should == 'quack/monkey'
334 | end
335 |
336 | it "should have the correct store dir" do
337 | @file.store_dir.should == File.expand_path('quack/monkey', PUBLIC)
338 | end
339 | end
340 |
341 | describe "an UploadedFile where tmp_dir is a Proc and has the record piped in" do
342 | before do
343 | record = mock('a record')
344 | record.stub!(:name).and_return('quack')
345 | @file = UploadColumn::UploadedFile.new(:open, stub_file('kerb.jpg'), record, nil, :tmp_dir => proc{ |record| File.join(record.name, 'monkey')})
346 | end
347 |
348 | it "should have the correct relative tmp dir" do
349 | @file.relative_tmp_dir.should == 'quack/monkey'
350 | end
351 |
352 | it "should have the correct tmp dir" do
353 | @file.tmp_dir.should == File.expand_path('quack/monkey', PUBLIC)
354 | end
355 | end
356 |
357 |
358 | describe "an UploadedFile where store_dir is a Proc and has the record and file piped in" do
359 | before do
360 | record = mock('a record')
361 | record.stub!(:name).and_return('quack')
362 | @file = UploadColumn::UploadedFile.new(:open, stub_file('kerb.jpg'), record, nil, :store_dir => proc{ |r, f| File.join(record.name, f.basename, 'monkey')})
363 | end
364 |
365 | it "should have the correct relative store dir" do
366 | @file.relative_store_dir.should == 'quack/kerb/monkey'
367 | end
368 |
369 | it "should have the correct store dir" do
370 | @file.store_dir.should == File.expand_path('quack/kerb/monkey', PUBLIC)
371 | end
372 | end
373 |
374 | describe "an UploadedFile where tmp_dir is a Proc and has the record and file piped in" do
375 | before do
376 | record = mock('a record')
377 | record.stub!(:name).and_return('quack')
378 | @file = UploadColumn::UploadedFile.new(:open, stub_file('kerb.jpg'), record, nil, :tmp_dir => proc{ |r, f| File.join(record.name, f.basename, 'monkey')})
379 | end
380 |
381 | it "should have the correct relative tmp dir" do
382 | @file.relative_tmp_dir.should == 'quack/kerb/monkey'
383 | end
384 |
385 | it "should have the correct tmp dir" do
386 | @file.tmp_dir.should == File.expand_path('quack/kerb/monkey', PUBLIC)
387 | end
388 | end
389 |
390 |
391 | describe "an UploadedFile with a store_dir callback" do
392 | before do
393 | i = mock('instance with store_dir callback')
394 | @file = UploadColumn::UploadedFile.new(:open, stub_file('kerb.jpg'), i, :monkey)
395 | i.should_receive(:monkey_store_dir).with(@file).and_return('llama')
396 | end
397 |
398 | it "should have the correct relative store dir" do
399 | @file.relative_store_dir.should == 'llama'
400 | end
401 |
402 | it "should have the correct store dir" do
403 | @file.store_dir.should == File.expand_path('llama', PUBLIC)
404 | end
405 | end
406 |
407 | describe "an UploadedFile with a tmp_dir callback" do
408 | before do
409 | i = mock('instance with a tmp_dir callback')
410 | @file = UploadColumn::UploadedFile.new(:open, stub_file('kerb.jpg'), i, :monkey)
411 | i.should_receive(:monkey_tmp_dir).with(@file).and_return('gorilla')
412 | end
413 |
414 | it "should have the correct relative tmp dir" do
415 | @file.relative_tmp_dir.should == 'gorilla'
416 | end
417 |
418 | it "should have the correct tmp dir" do
419 | @file.tmp_dir.should == File.expand_path('gorilla', PUBLIC)
420 | end
421 | end
422 |
423 | describe "an UploadedFile that has just been uploaded" do
424 |
425 | before do
426 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, :monkey)
427 | end
428 |
429 | it_should_behave_like "all uploaded files"
430 |
431 | it "should be new" do
432 | @file.should be_new_file
433 | end
434 |
435 | it "should be a tempfile" do
436 | @file.should be_a_tempfile
437 | end
438 |
439 | it "should exist" do
440 | @file.should be_in_existence
441 | end
442 |
443 | it "should be stored in tmp" do
444 | @file.path.should match_path('public', 'tmp', %r{((\d+\.)+\d+)}, 'kerb.jpg')
445 | end
446 |
447 | end
448 |
449 | describe "saving an UploadedFile" do
450 | before do
451 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, :monkey)
452 | end
453 |
454 | it "should return true" do
455 | @file.send(:save).should === true
456 | end
457 |
458 | it "should copy the file to the correct location" do
459 | @file.send(:save)
460 | @file.path.should match_path('public', 'monkey', 'kerb.jpg')
461 | @file.should be_in_existence
462 | end
463 |
464 | after do
465 | FileUtils.rm_rf(PUBLIC)
466 | end
467 |
468 | end
469 |
470 | describe "a saved UploadedFile" do
471 | before do
472 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, :monkey)
473 | @file.send(:save)
474 | end
475 |
476 | it_should_behave_like "all uploaded files"
477 |
478 | it "should not be new" do
479 | @file.should_not be_new_file
480 | end
481 |
482 | it "should not be a tempfile" do
483 | @file.should_not be_a_tempfile
484 | end
485 |
486 | it "should return the correct path" do
487 | @file.path.should match_path('public', 'monkey', 'kerb.jpg')
488 | end
489 |
490 | it "should return the correct relative_path" do
491 | @file.relative_path.should == "monkey/kerb.jpg"
492 | end
493 |
494 | it "should return the correct dir" do
495 | @file.dir.should match_path('public', 'monkey')
496 | end
497 |
498 | after do
499 | FileUtils.rm_rf(PUBLIC)
500 | end
501 |
502 | end
503 |
504 | describe "an UploadedFile with a manipulator" do
505 | before do
506 | a_manipulator = Module.new
507 | a_manipulator.send(:define_method, :monkey! ) { |stuff| stuff }
508 | @file = UploadColumn::UploadedFile.new(:open, stub_file('kerb.jpg'), nil, :donkey, :manipulator => a_manipulator)
509 | end
510 |
511 | it "should extend the object with the manipulator methods." do
512 | @file.should respond_to(:monkey!)
513 | end
514 |
515 | end
516 |
517 | describe "an UploadedFile with a manipulator and versions" do
518 | before do
519 | a_manipulator = Module.new
520 | a_manipulator.send(:define_method, :monkey! ) { |stuff| stuff }
521 | @file = UploadColumn::UploadedFile.new(:open, stub_file('kerb.jpg'), nil, :donkey, :versions => [ :thumb, :large ], :manipulator => a_manipulator)
522 | end
523 |
524 | it "should extend the object with the manipulator methods." do
525 | @file.should respond_to(:monkey!)
526 | end
527 |
528 | it "should extend the versions with the manipulator methods." do
529 | @file.thumb.should respond_to(:monkey!)
530 | @file.large.should respond_to(:monkey!)
531 | end
532 |
533 | end
534 |
535 | describe "an UploadedFile with a manipulator with dependencies" do
536 |
537 | it "should extend the object with the manipulator methods and load dependencies." do
538 | process_proxy = mock('nothing in particular')
539 | a_manipulator = Module.new
540 | a_manipulator.send(:define_method, :monkey! ) { |stuff| stuff }
541 | a_manipulator.send(:define_method, :load_manipulator_dependencies) do
542 | # horrible abuse of Ruby's closures. This allows us to set expectations on the process_proxy
543 | # and if process! is called, the process_proxy will be adressed instead.
544 | process_proxy.load_manipulator_dependencies
545 | end
546 |
547 | process_proxy.should_receive(:load_manipulator_dependencies)
548 |
549 | @file = UploadColumn::UploadedFile.new(:open, stub_file('kerb.jpg'), nil, :donkey, :manipulator => a_manipulator)
550 |
551 | @file.should respond_to(:monkey!)
552 | end
553 |
554 | end
555 |
556 | describe "an UploadedFile with a manipulator and process instruction" do
557 |
558 | it "should process before iterating versions" do
559 | process_proxy = mock('nothing in particular')
560 | a_manipulator = Module.new
561 | a_manipulator.send(:define_method, :process!) do |*args|
562 | process_proxy.process!(*args)
563 | end
564 | # this will override the base classes initialize_versions option, so we can catch it.
565 | a_manipulator.send(:define_method, :initialize_versions) do |*args|
566 | process_proxy.initialize_versions *args
567 | end
568 |
569 | process_proxy.should_receive(:process!).with('100x100').ordered
570 | process_proxy.should_receive(:initialize_versions).ordered
571 |
572 |
573 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, :donkey, :process => '100x100', :manipulator => a_manipulator)
574 | end
575 |
576 | end
577 |
578 | describe "an UploadedFile with no versions" do
579 | it "should not respond to version methods" do
580 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, :monkey)
581 | @file.should_not respond_to(:thumb)
582 | @file.should_not respond_to(:large)
583 | end
584 | end
585 |
586 | describe "an UploadedFile with versions with illegal names" do
587 | it "should raise an ArgumentError" do
588 | lambda do
589 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, :monkey, :versions => [ :thumb, :path ])
590 | end.should raise_error(ArgumentError, 'path is an illegal name for an UploadColumn version.')
591 | end
592 | end
593 |
594 | describe "an UploadedFile with versions" do
595 | before do
596 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, :monkey, :versions => [ :thumb, :large ])
597 | end
598 |
599 | it "should respond to version methods" do
600 | @file.should respond_to(:thumb)
601 | @file.should respond_to(:large)
602 | end
603 |
604 | it "should return an UploadedFile instance when a version method is called" do
605 | @file.thumb.should be_instance_of(UploadColumn::UploadedFile)
606 | @file.large.should be_instance_of(UploadColumn::UploadedFile)
607 | end
608 | end
609 |
610 | describe "all versions of uploaded files", :shared => true do
611 | it "should return the filename including the version" do
612 | @thumb.filename.should == "kerb-thumb.jpg"
613 | @large.filename.should == "kerb-large.jpg"
614 | end
615 |
616 | it "should return the basename without the version" do
617 | @thumb.basename.should == "kerb"
618 | @large.basename.should == "kerb"
619 | end
620 |
621 | it "should return the extension" do
622 | @thumb.extension.should == "jpg"
623 | @large.extension.should == "jpg"
624 | end
625 |
626 | it "should return the correct suffix" do
627 | @thumb.suffix.should == :thumb
628 | @large.suffix.should == :large
629 | end
630 | end
631 |
632 | describe "a version of an uploaded UploadedFile" do
633 | before do
634 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, :monkey, :versions => [ :thumb, :large ])
635 | @thumb = @file.thumb
636 | @large = @file.large
637 | end
638 |
639 | it_should_behave_like "all versions of uploaded files"
640 |
641 | it "should not be empty" do
642 | @thumb.should_not be_empty
643 | @large.should_not be_empty
644 | end
645 |
646 | it "should return the correct filesize" do
647 | @thumb.size.should == 87582
648 | @large.size.should == 87582
649 | end
650 |
651 | it "should return the original filename" do
652 | @thumb.original_filename.should == "kerb.jpg"
653 | @large.original_filename.should == "kerb.jpg"
654 | end
655 | end
656 |
657 | describe "uploading a file with versions as a Hash" do
658 |
659 | it "should process the files with the manipulator" do
660 |
661 | process_proxy = mock('nothing in particular')
662 | a_manipulator = Module.new
663 | a_manipulator.send(:define_method, :process! ) do |stuff|
664 | # horrible abuse of Ruby's closures. This allows us to set expectations on the process_proxy
665 | # and if process! is called, the process_proxy will be adressed instead.
666 | process_proxy.process!(self.filename, stuff)
667 | end
668 |
669 | process_proxy.should_receive(:process!).with('kerb-thumb.jpg', '200x200')
670 | process_proxy.should_receive(:process!).with('kerb-large.jpg', '300x300')
671 |
672 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, :donkey, :manipulator => a_manipulator, :versions => { :thumb => '200x200', :large => '300x300' })
673 | @thumb = @file.thumb
674 | @large = @file.large
675 | end
676 |
677 | end
678 |
679 |
680 | describe "an version of an UploadedFile with versions as a hash" do
681 |
682 | before(:each) do
683 | process_proxy = mock('nothing in particular')
684 | a_manipulator = Module.new
685 | a_manipulator.send(:define_method, :process! ) { |stuff| true }
686 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, :donkey, :manipulator => a_manipulator, :versions => { :thumb => '200x200', :large => '300x300' })
687 | @thumb = @file.thumb
688 | @large = @file.large
689 | end
690 |
691 | it_should_behave_like "all versions of uploaded files"
692 |
693 | it "should not be empty" do
694 | @thumb.should_not be_empty
695 | @large.should_not be_empty
696 | end
697 |
698 | it "should return the original filename" do
699 | @thumb.original_filename.should == "kerb.jpg"
700 | @large.original_filename.should == "kerb.jpg"
701 | end
702 |
703 | end
704 |
705 | describe "a retrieved UploadedFile" do
706 |
707 | before do
708 | @file = UploadColumn::UploadedFile.retrieve('kerb.jpg', nil, :monkey)
709 | @file.stub!(:size).and_return(87582)
710 | end
711 |
712 | it_should_behave_like "all uploaded files"
713 |
714 | it "should not be new" do
715 | @file.should_not be_new_file
716 | end
717 |
718 | it "should not be a tempfile" do
719 | @file.should_not be_a_tempfile
720 | end
721 |
722 | it "should return the correct path" do
723 | @file.path.should match_path(public_path('monkey/kerb.jpg'))
724 | end
725 | end
726 |
727 | describe "a version of a retrieved UploadedFile" do
728 |
729 | before do
730 | @file = UploadColumn::UploadedFile.retrieve('kerb.jpg', nil, :monkey, :versions => [:thumb, :large])
731 | @thumb = @file.thumb
732 | @large = @file.large
733 | end
734 |
735 | it_should_behave_like "all versions of uploaded files"
736 |
737 | it "should not be new" do
738 | @file.should_not be_new_file
739 | end
740 |
741 | it "should not be a tempfile" do
742 | @file.should_not be_a_tempfile
743 | end
744 |
745 | it "should return the correct path" do
746 | @thumb.path.should match_path(public_path('monkey/kerb-thumb.jpg'))
747 | @large.path.should match_path(public_path('monkey/kerb-large.jpg'))
748 | end
749 |
750 | # Since the files don't exist in fixtures/ it wouldn't make sense to test their size
751 | end
752 |
753 | describe "a version of a saved UploadedFile" do
754 | before do
755 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, :monkey, :versions => [:thumb, :large])
756 | @file.send(:save)
757 | @thumb = @file.thumb
758 | @large = @file.large
759 | end
760 |
761 | it_should_behave_like "all versions of uploaded files"
762 |
763 | it "should not be new" do
764 | @file.should_not be_new_file
765 | end
766 |
767 | it "should not be a tempfile" do
768 | @file.should_not be_a_tempfile
769 | end
770 |
771 | it "should return the correct path" do
772 | @thumb.path.should match_path('public', 'monkey', 'kerb-thumb.jpg')
773 | @large.path.should match_path('public', 'monkey', 'kerb-large.jpg')
774 | end
775 | end
776 |
777 | describe "opening a temporary UploadedFile" do
778 |
779 | it "should raise an error if the path is incorrectly formed" do
780 | lambda do
781 | @file = UploadColumn::UploadedFile.retrieve_temp(file_path('kerb.jpg'))
782 | end.should raise_error(UploadColumn::TemporaryPathMalformedError, "#{file_path('kerb.jpg')} is not a valid temporary path!")
783 | end
784 |
785 | it "should raise an error if its in a subdirectory" do
786 | lambda do
787 | @file = UploadColumn::UploadedFile.retrieve_temp('somefolder/1234.56789.1234/donkey.jpg;llama.png')
788 | end.should raise_error(UploadColumn::TemporaryPathMalformedError, "somefolder/1234.56789.1234/donkey.jpg;llama.png is not a valid temporary path!")
789 | end
790 |
791 | it "should raise an error if its relative" do
792 | lambda do
793 | @file = UploadColumn::UploadedFile.retrieve_temp('../1234.56789.1234/donkey.jpg;llama.png')
794 | end.should raise_error(UploadColumn::TemporaryPathMalformedError, "../1234.56789.1234/donkey.jpg;llama.png is not a valid temporary path!")
795 | end
796 |
797 | it "should raise an error if the filename is omitted" do
798 | lambda do
799 | @file = UploadColumn::UploadedFile.retrieve_temp('1234.56789.1234;llama.png')
800 | end.should raise_error(UploadColumn::TemporaryPathMalformedError, "1234.56789.1234;llama.png is not a valid temporary path!")
801 | end
802 |
803 | it "should not raise an error on nil" do
804 | lambda do
805 | @file = UploadColumn::UploadedFile.retrieve_temp(nil)
806 | end.should_not raise_error
807 | end
808 |
809 | it "should not raise an error on empty String" do
810 | lambda do
811 | @file = UploadColumn::UploadedFile.retrieve_temp('')
812 | end.should_not raise_error
813 | end
814 | end
815 |
816 | describe "a retrieved temporary UploadedFile" do
817 |
818 | before(:all) do
819 | FileUtils.mkdir_p(public_path('tmp/123455.1233.1233'))
820 | FileUtils.cp(file_path('kerb.jpg'), public_path('tmp/123455.1233.1233/kerb.jpg'))
821 | end
822 |
823 | before do
824 | @file = UploadColumn::UploadedFile.retrieve_temp('123455.1233.1233/kerb.jpg')
825 | end
826 |
827 | it_should_behave_like "all uploaded files"
828 |
829 | it "should not be new" do
830 | @file.should_not be_new_file
831 | end
832 |
833 | it "should be a tempfile" do
834 | @file.should be_a_tempfile
835 | end
836 |
837 | it "should return the correct path" do
838 | @file.path.should match_path('public', 'tmp', '123455.1233.1233', 'kerb.jpg')
839 | end
840 |
841 | after(:all) do
842 | FileUtils.rm_rf(PUBLIC)
843 | end
844 | end
845 |
846 | describe "a retrieved temporary UploadedFile with an appended original filename" do
847 | before(:all) do
848 | FileUtils.mkdir_p(public_path('tmp/123455.1233.1233'))
849 | FileUtils.cp(file_path('kerb.jpg'), public_path('tmp/123455.1233.1233/kerb.jpg'))
850 | end
851 |
852 | before do
853 | @file = UploadColumn::UploadedFile.retrieve_temp('123455.1233.1233/kerb.jpg;monkey.png')
854 | end
855 |
856 | it "should not be new" do
857 | @file.should_not be_new_file
858 | end
859 |
860 | it "should be a tempfile" do
861 | @file.should be_a_tempfile
862 | end
863 |
864 | it "should return the correct original filename" do
865 | @file.original_filename.should == "monkey.png"
866 | end
867 |
868 | it "should return the correct path" do
869 | @file.path.should match_path('public', 'tmp', '123455.1233.1233', 'kerb.jpg')
870 | end
871 |
872 | after(:all) do
873 | FileUtils.rm_rf(PUBLIC)
874 | end
875 | end
876 |
877 | describe "a version of a retrieved temporary UploadedFile" do
878 |
879 | before(:all) do
880 | FileUtils.mkdir_p(public_path('tmp/123455.1233.1233'))
881 | FileUtils.cp(file_path('kerb.jpg'), public_path('tmp/123455.1233.1233/kerb.jpg'))
882 | end
883 |
884 | before do
885 | @file = UploadColumn::UploadedFile.retrieve_temp('123455.1233.1233/kerb.jpg', nil, :monkey, :versions => [:thumb, :large])
886 | @thumb = @file.thumb
887 | @large = @file.large
888 | end
889 |
890 | it_should_behave_like "all versions of uploaded files"
891 |
892 | it "should not be new" do
893 | @file.should_not be_new_file
894 | end
895 |
896 | it "should be a tempfile" do
897 | @file.should be_a_tempfile
898 | end
899 |
900 | it "should return the correct path" do
901 | @thumb.path.should match_path(public_path('tmp/123455.1233.1233/kerb-thumb.jpg'))
902 | @large.path.should match_path(public_path('tmp/123455.1233.1233/kerb-large.jpg'))
903 | end
904 |
905 | after(:all) do
906 | FileUtils.rm_rf(PUBLIC)
907 | end
908 | end
909 |
910 | describe "uploading a file with validate_integrity set to true" do
911 |
912 | it "should raise an error if no extensions are set" do
913 | lambda do
914 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, nil, :validate_integrity => true)
915 | end.should raise_error(UploadColumn::UploadError)
916 | end
917 |
918 | it "should not raise an error if the extension is in extensions" do
919 | lambda do
920 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, nil, :validate_integrity => true, :extensions => %w(jpg gif png))
921 | end.should_not raise_error
922 | end
923 |
924 | it "should raise an error if the extension is not in extensions" do
925 | lambda do
926 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, nil, :validate_integrity => true, :extensions => %w(doc gif png))
927 | end.should raise_error(UploadColumn::IntegrityError)
928 | end
929 | end
930 |
931 | describe "An UploadedFile with no web_root set" do
932 | it "should return the correct URL and to_s" do
933 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, :donkey, :versions => [:thumb, :large])
934 | @file.send(:save)
935 |
936 | @file.url.should == "/donkey/kerb.jpg"
937 | @file.to_s.should == "/donkey/kerb.jpg"
938 | @file.thumb.url.should == "/donkey/kerb-thumb.jpg"
939 | @file.large.url.should == "/donkey/kerb-large.jpg"
940 | end
941 | end
942 |
943 | describe "An UploadedFile with no web_root set and MS style slashes in its relative path" do
944 | it "should return the correct URL and to_s" do
945 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, :donkey, :versions => [:thumb, :large])
946 |
947 | @file.should_receive(:relative_path).at_least(:once).and_return('stylesheets\something\monkey\kerb.jpg')
948 | @file.thumb.should_receive(:relative_path).at_least(:once).and_return('stylesheets\something\monkey\kerb-thumb.jpg')
949 | @file.large.should_receive(:relative_path).at_least(:once).and_return('stylesheets\something\monkey\kerb-large.jpg')
950 |
951 | @file.send(:save)
952 |
953 | @file.url.should == "/stylesheets/something/monkey/kerb.jpg"
954 | @file.to_s.should == "/stylesheets/something/monkey/kerb.jpg"
955 | @file.thumb.url.should == "/stylesheets/something/monkey/kerb-thumb.jpg"
956 | @file.large.url.should == "/stylesheets/something/monkey/kerb-large.jpg"
957 | end
958 | end
959 |
960 | describe "An UploadedFile with an absolute web_root set" do
961 | it "should return the correct URL and to_s" do
962 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, :donkey, :web_root => 'http://ape.com', :versions => [:thumb, :large])
963 | @file.send(:save)
964 |
965 | @file.url.should == "http://ape.com/donkey/kerb.jpg"
966 | @file.to_s.should == "http://ape.com/donkey/kerb.jpg"
967 | @file.thumb.url.should == "http://ape.com/donkey/kerb-thumb.jpg"
968 | @file.large.url.should == "http://ape.com/donkey/kerb-large.jpg"
969 | end
970 | end
971 |
972 | describe "An UploadedFile with an absolute web_root set and MS style slashes in its relative path" do
973 | it "should return the correct URL and to_s" do
974 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, :donkey, :web_root => 'http://ape.com', :versions => [:thumb, :large])
975 | @file.should_receive(:relative_path).at_least(:once).and_return('stylesheets\something\monkey\kerb.jpg')
976 | @file.thumb.should_receive(:relative_path).at_least(:once).and_return('stylesheets\something\monkey\kerb-thumb.jpg')
977 | @file.large.should_receive(:relative_path).at_least(:once).and_return('stylesheets\something\monkey\kerb-large.jpg')
978 |
979 | @file.send(:save)
980 |
981 | @file.url.should == "http://ape.com/stylesheets/something/monkey/kerb.jpg"
982 | @file.to_s.should == "http://ape.com/stylesheets/something/monkey/kerb.jpg"
983 | @file.thumb.url.should == "http://ape.com/stylesheets/something/monkey/kerb-thumb.jpg"
984 | @file.large.url.should == "http://ape.com/stylesheets/something/monkey/kerb-large.jpg"
985 | end
986 | end
987 |
988 | describe "An UploadedFile with a web_root set" do
989 | it "should return the correct URL" do
990 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, :donkey, :web_root => '/ape', :versions => [:thumb, :large])
991 | @file.send(:save)
992 |
993 | @file.url.should == "/ape/donkey/kerb.jpg"
994 | @file.to_s.should == "/ape/donkey/kerb.jpg"
995 | @file.thumb.url.should == "/ape/donkey/kerb-thumb.jpg"
996 | @file.large.url.should == "/ape/donkey/kerb-large.jpg"
997 | end
998 | end
999 |
1000 | describe "the temp_value of an UploadedFile without an original filename" do
1001 |
1002 | setup do
1003 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, :donkey)
1004 | @file.should_receive(:original_filename).and_return(nil)
1005 | end
1006 |
1007 | it "should match the TempValueRegexp" do
1008 | @file.temp_value.should match(::UploadColumn::TempValueRegexp)
1009 | end
1010 |
1011 | it "should end in the filename" do
1012 | @file.temp_value.should match(/\/kerb\.jpg$/)
1013 | end
1014 | end
1015 |
1016 | describe "the temp_value of an UploadedFile with a different orignal filename" do
1017 |
1018 | setup do
1019 | @file = UploadColumn::UploadedFile.upload(stub_file('kerb.jpg'), nil, :donkey)
1020 | @file.should_receive(:original_filename).at_least(:once).and_return('monkey.png')
1021 | end
1022 |
1023 | it "should match the TempValueRegexp" do
1024 | @file.temp_value.should match(::UploadColumn::TempValueRegexp)
1025 | end
1026 |
1027 | it "should append the original_filename" do
1028 | @file.temp_value.should match(/kerb\.jpg;monkey\.png$/)
1029 | end
1030 | end
1031 |
1032 | describe "the temp_value of a retrieved temporary UploadedFile" do
1033 |
1034 | setup do
1035 | @file = UploadColumn::UploadedFile.retrieve_temp('12345.1234.12345/kerb.jpg', nil, :donkey)
1036 | @file.should_receive(:original_filename).at_least(:once).and_return(nil)
1037 | end
1038 |
1039 | it "should be mainatained" do
1040 | @file.temp_value.should == '12345.1234.12345/kerb.jpg'
1041 | end
1042 | end
1043 |
1044 | describe "the temp_value of an UploadedFile that is not temporary" do
1045 |
1046 | setup do
1047 | @file = UploadColumn::UploadedFile.retrieve('kerb.jpg', nil, :donkey)
1048 | end
1049 |
1050 | it "should be mainatained" do
1051 | @file.temp_value.should be_nil
1052 | end
1053 | end
--------------------------------------------------------------------------------