uploadcolumn 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ doc
2
+ spec/public
3
+ spec/db
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