uploadcolumn 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/CHANGELOG +123 -0
- data/LICENSE +22 -0
- data/README.rdoc +206 -0
- data/Rakefile +90 -0
- data/VERSION +1 -0
- data/init.rb +15 -0
- data/lib/upload_column/active_record_extension.rb +154 -0
- data/lib/upload_column/configuration.rb +49 -0
- data/lib/upload_column/magic_columns.rb +50 -0
- data/lib/upload_column/manipulators/image_science.rb +86 -0
- data/lib/upload_column/manipulators/rmagick.rb +75 -0
- data/lib/upload_column/rails/action_controller_extension.rb +61 -0
- data/lib/upload_column/rails/asset_tag_extension.rb +17 -0
- data/lib/upload_column/rails/upload_column_helper.rb +45 -0
- data/lib/upload_column/sanitized_file.rb +176 -0
- data/lib/upload_column/uploaded_file.rb +299 -0
- data/lib/upload_column.rb +12 -0
- data/spec/active_record_extension_spec.rb +514 -0
- data/spec/custom_matchers.rb +148 -0
- data/spec/fixtures/animated.gif +0 -0
- data/spec/fixtures/animated_solarized.gif +0 -0
- data/spec/fixtures/invalid-image.jpg +1 -0
- data/spec/fixtures/kerb.jpg +0 -0
- data/spec/fixtures/kerb_solarized.jpg +0 -0
- data/spec/fixtures/netscape.gif +0 -0
- data/spec/fixtures/skanthak.png +0 -0
- data/spec/image_science_manipulator_spec.rb +195 -0
- data/spec/integration_spec.rb +668 -0
- data/spec/magic_columns_spec.rb +120 -0
- data/spec/rmagick_manipulator_spec.rb +186 -0
- data/spec/sanitized_file_spec.rb +496 -0
- data/spec/spec_helper.rb +90 -0
- data/spec/upload_column_spec.rb +65 -0
- data/spec/uploaded_file_spec.rb +1053 -0
- metadata +108 -0
data/.gitignore
ADDED
data/CHANGELOG
ADDED
@@ -0,0 +1,123 @@
|
|
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.
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
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.
|
data/README.rdoc
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
= UploadColumn Gem
|
2
|
+
|
3
|
+
UploadColumn is a gem for the Ruby on Rails framework that enables easy
|
4
|
+
uploading of files, especially images. Most other versions of this code are
|
5
|
+
packaged for use as a Rails plugin, but this fork of the
|
6
|
+
original code (see http://github.com/jnicklas/uploadcolumn) is designed to be used
|
7
|
+
as a gem. Other than that, it's just the same code you've been using to
|
8
|
+
upload your files for many, many years.
|
9
|
+
|
10
|
+
== Installation
|
11
|
+
|
12
|
+
First, install the gem:
|
13
|
+
|
14
|
+
sudo gem install uploadcolumn
|
15
|
+
|
16
|
+
Next, require it in your Rails application:
|
17
|
+
|
18
|
+
config.gem 'uploadcolumn', :lib => 'upload_column'
|
19
|
+
|
20
|
+
== Usage
|
21
|
+
|
22
|
+
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.
|
23
|
+
|
24
|
+
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:
|
25
|
+
|
26
|
+
class User < ActiveRecord::Base
|
27
|
+
upload_column :picture
|
28
|
+
end
|
29
|
+
|
30
|
+
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.
|
31
|
+
|
32
|
+
Uploading files is no fun without a user interface, so get going and make one:
|
33
|
+
|
34
|
+
add an upload_column_field to your form, maybe like this:
|
35
|
+
|
36
|
+
<p><label for="user_picture">Picture</label><br/>
|
37
|
+
<%= upload_column_field 'user', 'picture' %></p>
|
38
|
+
|
39
|
+
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.
|
40
|
+
|
41
|
+
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:
|
42
|
+
|
43
|
+
<%= upload_form_tag( :action => 'create' ) %>
|
44
|
+
|
45
|
+
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!
|
46
|
+
|
47
|
+
== Storage Path
|
48
|
+
|
49
|
+
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 <tt>:store_dir</tt> key to the upload_column declaration, this will override the default mechanism and always use that directory as the basis.
|
50
|
+
|
51
|
+
upload_column :picture, :store_dir => "pictures"
|
52
|
+
|
53
|
+
might be sensible in our case. Note that this way, all files will be stored in the same directory.
|
54
|
+
|
55
|
+
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:
|
56
|
+
|
57
|
+
upload_column :picture, :store_dir => proc{|record, file| "images/#{record.category.name}/#{record.id}/#{file.extension}"}
|
58
|
+
|
59
|
+
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 <tt>:picture</tt>).
|
60
|
+
|
61
|
+
You can change the <tt>:tmp_dir</tt> in the same way.
|
62
|
+
|
63
|
+
== Filename
|
64
|
+
|
65
|
+
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:
|
66
|
+
|
67
|
+
upload_column :picture, :filename => "donkey.png"
|
68
|
+
|
69
|
+
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.
|
70
|
+
|
71
|
+
upload_column :picture, :filename => proc{|record, file| "avatar#{record.id}.#{file.extension}"}
|
72
|
+
|
73
|
+
The Proc will be passed two parameters, the current instance, and the file itself.
|
74
|
+
|
75
|
+
== Manipulators
|
76
|
+
|
77
|
+
UploadColumn allows you to use manipulators on your file, that in some way transform your file, or perform any kind
|
78
|
+
of operations on it. There are currently two manipulators bundled, the RMagick manipulator and the ImageScience
|
79
|
+
manipulator, but writing your own is very easy. There are further instructions on the website.
|
80
|
+
|
81
|
+
== Manipulating Images with RMagick
|
82
|
+
|
83
|
+
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:
|
84
|
+
|
85
|
+
class User < ActiveRecord::Base
|
86
|
+
upload_column :picture, :manipulator => UploadColumn::Manipulators::RMagick
|
87
|
+
|
88
|
+
def picture_after_assign
|
89
|
+
|
90
|
+
picture.process! do |img|
|
91
|
+
img.solarize
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
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:
|
98
|
+
|
99
|
+
class User < ActiveRecord::Base
|
100
|
+
upload_column :picture, :process => '800x600', :manipulator => UploadColumn::Manipulators::RMagick
|
101
|
+
end
|
102
|
+
|
103
|
+
the previous example with solarize could be written shorter as:
|
104
|
+
|
105
|
+
class User < ActiveRecord::Base
|
106
|
+
upload_column :picture, :process => proc{|img| img.solarize }, :manipulator => UploadColumn::Manipulators::RMagick
|
107
|
+
end
|
108
|
+
|
109
|
+
Or maybe we want different versions of our image, then we could simply specify:
|
110
|
+
|
111
|
+
class User < ActiveRecord::Base
|
112
|
+
upload_column :picture, :versions => [ :solarized, :sepiatoned ], :manipulator => UploadColumn::Manipulators::RMagick
|
113
|
+
|
114
|
+
def picture_after_assign
|
115
|
+
picture.solarized.process! do |img|
|
116
|
+
img.solarize
|
117
|
+
end
|
118
|
+
picture.sepiatoned.process! do |img|
|
119
|
+
img.sepiatone
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
you can also use a Hash for versions and pass a dimension or a proc to it, so you can do:
|
125
|
+
|
126
|
+
class User < ActiveRecord::Base
|
127
|
+
upload_column :picture, :versions => { :thumb => "c100x100", :large => "200x300", :sepiatoned => proc{ |img| img.sepiatone } }, :manipulator => UploadColumn::Manipulators::RMagick
|
128
|
+
end
|
129
|
+
|
130
|
+
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:
|
131
|
+
|
132
|
+
class User < ActiveRecord::Base
|
133
|
+
image_column :picture, :versions => { :thumb => "c100x100", :large => "200x300", :sepiatoned => proc{ |img| img.sepiatone } }
|
134
|
+
end
|
135
|
+
|
136
|
+
This also puts your images in public/images instead of public, which is neat!
|
137
|
+
|
138
|
+
== Runtime rendering
|
139
|
+
|
140
|
+
You can manipulate images at runtime (it's a huge performance hit though!). In your controller add an action and use UploadColumnRenderHelper.render_image.
|
141
|
+
|
142
|
+
def sepiatone
|
143
|
+
@user = User.find(parms[:id])
|
144
|
+
render_image @user.picture do |img|
|
145
|
+
img.sepiatone
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
And that's it!
|
150
|
+
|
151
|
+
In your view, you can use UploadColumnHelper.image to easily create an image tag for your action:
|
152
|
+
|
153
|
+
<%= image :action => "sepiatone", :id => 5 %>
|
154
|
+
|
155
|
+
== Views
|
156
|
+
|
157
|
+
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.
|
158
|
+
|
159
|
+
<%= link_to "Guitar Tablature", @song.tab.url %>
|
160
|
+
|
161
|
+
<%= image_tag @user.picture.url %>
|
162
|
+
|
163
|
+
== Magic Columns
|
164
|
+
|
165
|
+
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:
|
166
|
+
|
167
|
+
add_column :users, :picture_content_type
|
168
|
+
|
169
|
+
And if our model looks like this:
|
170
|
+
|
171
|
+
class User < ActiveRecord::Base
|
172
|
+
upload_column :picture
|
173
|
+
end
|
174
|
+
|
175
|
+
The column <tt>picture_content_type</tt> will now automatically be filled with the file's content-type (or at least with UploadColumn's best guess ;).
|
176
|
+
|
177
|
+
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.
|
178
|
+
|
179
|
+
You can also do <tt>picture_exif_date_time</tt> or <tt>picture_exif_model</tt>, 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 <tt>gem install exifr</tt>.
|
180
|
+
|
181
|
+
== Validations
|
182
|
+
|
183
|
+
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.
|
184
|
+
|
185
|
+
If you want to only allow the upload of XHTML and XML files, so you can manipulate them with XSLT you could do:
|
186
|
+
|
187
|
+
upload_column :xml, :extensions => %w(xml html htm), :manipulator => MyXSLTProcessor
|
188
|
+
|
189
|
+
validate_integrity_of :xml
|
190
|
+
|
191
|
+
You can also use some of Rails' validations with UploadColumn.
|
192
|
+
|
193
|
+
validates_presence_of and validates_size_of have been verified to work.
|
194
|
+
|
195
|
+
validates_size_of :image, :maximum => 200000, :message => "is too big, must be smaller than 200kB!"
|
196
|
+
|
197
|
+
Remember to change the error message, the default one sounds a bit stupid with UploadColumn.
|
198
|
+
|
199
|
+
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
|
200
|
+
|
201
|
+
validates_each :your_upload_column do |record, attr, value|
|
202
|
+
record.errors.add attr, 'already exists!' if YourModel.find( :first, :conditions => ["#{attr.to_s} = ?", value ] )
|
203
|
+
end
|
204
|
+
|
205
|
+
It's not elegant I know, but it should work.
|
206
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'spec/rake/spectask'
|
5
|
+
|
6
|
+
|
7
|
+
begin
|
8
|
+
require 'jeweler'
|
9
|
+
Jeweler::Tasks.new do |gemspec|
|
10
|
+
gemspec.name = "uploadcolumn"
|
11
|
+
gemspec.summary = "Enables easy uploading of files, especially images."
|
12
|
+
gemspec.description = "UploadColumn is a gem/plugin for the Ruby on Rails framework that enables easy uploading of files, especially images."
|
13
|
+
gemspec.email = "dave.hrycyszyn@headlondon.com"
|
14
|
+
gemspec.homepage = "http://github.com/futurechimp/uploadcolumn"
|
15
|
+
gemspec.authors = ["Dave Hrycyszyn", "Jonas Nicklas", "Sebastian Kanthak"]
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
file_list = FileList['spec/*_spec.rb']
|
25
|
+
|
26
|
+
namespace :spec do
|
27
|
+
desc "Run all examples with RCov"
|
28
|
+
Spec::Rake::SpecTask.new('rcov') do |t|
|
29
|
+
t.spec_files = file_list
|
30
|
+
t.rcov = true
|
31
|
+
t.rcov_dir = "doc/coverage"
|
32
|
+
t.rcov_opts = ['--exclude', 'spec']
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Generate an html report"
|
36
|
+
Spec::Rake::SpecTask.new('report') do |t|
|
37
|
+
t.spec_files = file_list
|
38
|
+
t.rcov = true
|
39
|
+
t.rcov_dir = "doc/coverage"
|
40
|
+
t.rcov_opts = ['--exclude', 'spec']
|
41
|
+
t.spec_opts = ["--format", "html:doc/reports/specs.html"]
|
42
|
+
t.fail_on_error = false
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "heckle all"
|
46
|
+
task :heckle => [ 'spec:heckle:uploaded_file', 'spec:heckle:sanitized_file' ]
|
47
|
+
|
48
|
+
namespace :heckle do
|
49
|
+
desc "Heckle UploadedFile"
|
50
|
+
Spec::Rake::SpecTask.new('uploaded_file') do |t|
|
51
|
+
t.spec_files = [ File.join(File.dirname(__FILE__), *%w[spec uploaded_file_spec.rb]) ]
|
52
|
+
t.spec_opts = ["--heckle", "UploadColumn::UploadedFile"]
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Heckle SanitizedFile"
|
56
|
+
Spec::Rake::SpecTask.new('sanitized_file') do |t|
|
57
|
+
t.spec_files = [ File.join(File.dirname(__FILE__), *%w[spec uploaded_file_spec.rb]) ]
|
58
|
+
t.spec_opts = ["--heckle", "UploadColumn::SanitizedFile"]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
desc 'Default: run unit tests.'
|
66
|
+
task :default => 'spec:rcov'
|
67
|
+
|
68
|
+
namespace "doc" do
|
69
|
+
|
70
|
+
desc 'Generate documentation for the UploadColumn plugin.'
|
71
|
+
Rake::RDocTask.new(:normal) do |rdoc|
|
72
|
+
rdoc.rdoc_dir = 'doc/rdoc'
|
73
|
+
rdoc.title = 'UploadColumn'
|
74
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
75
|
+
rdoc.rdoc_files.include('README')
|
76
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
77
|
+
end
|
78
|
+
|
79
|
+
desc 'Generate documentation for the UploadColumn plugin using the allison template.'
|
80
|
+
Rake::RDocTask.new(:allison) do |rdoc|
|
81
|
+
rdoc.rdoc_dir = 'doc/rdoc'
|
82
|
+
rdoc.title = 'UploadColumn'
|
83
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
84
|
+
rdoc.rdoc_files.include('README')
|
85
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
86
|
+
rdoc.main = "README" # page to start on
|
87
|
+
rdoc.template = "~/Projects/allison2/allison/allison.rb"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.3.0
|
data/init.rb
ADDED
@@ -0,0 +1,15 @@
|
|
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
|
15
|
+
|
@@ -0,0 +1,154 @@
|
|
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 <tt>:keep</tt>, <tt>:delete</tt> and <tt>:replace</tt>. If set to <tt>:keep</tt> 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
|
@@ -0,0 +1,49 @@
|
|
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
|