citrusbyte-milton 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Citrusbyte, LLC
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,354 @@
1
+ Milton
2
+ ======
3
+
4
+ Milton is an upload and attachment handling plugin for Rails. It is built for
5
+ extensibility and has support for resizing images and Amazon S3 built in.
6
+
7
+ Description
8
+ -----------
9
+
10
+ Milton is an upload handling plugin for Rails. It is meant to handle multiple
11
+ types of file uploads. It includes support for thumbnailing of image uploads,
12
+ and more processors can easily be added for handling other file types. It also
13
+ supports multiple file stores including disk storage and Amazon S3. Milton also
14
+ supports on-demand file processing for prototyping.
15
+
16
+ Getting Started
17
+ ---------------
18
+
19
+ You can get started with Milton with the default settings by simply calling
20
+ `is_attachment` on your ActiveRecord model you'll be handling files with. It is
21
+ expected that your underlying table will have a `filename` (string) column. If
22
+ you'd like you can also define `content_type` (string) and `size` (integer)
23
+ columns to have that info automatically stored as well.
24
+
25
+ ### Example
26
+
27
+ An `Asset` model:
28
+
29
+ class Asset < ActiveRecord::Base
30
+ is_attachment
31
+ end
32
+
33
+ Your new `Asset` form:
34
+
35
+ - form_for Asset.new, :html => { :enctype => 'multipart/form-data' } do |f|
36
+ = f.file_field :file
37
+ = f.submit 'Upload'
38
+
39
+ **Note:** don't forget to add `:html => { :enctype => 'multipart/form-data' }`
40
+
41
+ Your `AssetsController`:
42
+
43
+ class AssetsController < ApplicationController
44
+ def create
45
+ @asset = Asset.create params[:asset]
46
+ end
47
+ end
48
+
49
+ Resizing Images
50
+ ---------------
51
+
52
+ Currently Milton relies on ImageMagick (but not RMagick!). Once you have
53
+ ImageMagick installed, add the `:thumbnail` processor to your configuration
54
+ and define your processing recipes:
55
+
56
+ class Image < ActiveRecord::Base
57
+ is_attachment :recipes => { :thumb => [{ :thumbnail => { :size => '100x100', :crop => true } }] }
58
+ end
59
+
60
+ @image.path => .../000/000/000/001/milton.jpg
61
+ @image.path(:thumb) => .../000/000/000/001/milton.crop_size-100x100.jpg
62
+
63
+ ### Thumbnail Options
64
+
65
+ **Note:** currently the only options supported are `:size` and `:crop`.
66
+
67
+ `:size` takes a geometry string similar to other image-manipulation
68
+ plugins (based off ImageMagick's geometry strings).
69
+
70
+ `:size => '50x50'` will resize the larger dimension down to 50px and maintain aspect ratio (you
71
+ can use `:crop` for forced zoom/cropping).
72
+
73
+ `:size => '50x'` will resize the width to 50px and maintain aspect ratio.
74
+
75
+ `:size => 'x50'` will resize the height to 50px and maintain aspect ratio.
76
+
77
+ Then you can throw in `:crop` to get zoom/cropping functionality:
78
+
79
+ @image.path(:thumbnail => { :size => '50x50', :crop => true })
80
+
81
+ This will create a 50px x 50px version of the image regardless of the source
82
+ aspect-ratio. It will *not* distort the source image, rather it will resize the
83
+ image as close to fitting as possible without distorting, then crop off the
84
+ remainder.
85
+
86
+ By default `:crop` uses a North/Center gravity -- so the remainder will be
87
+ cropped from the bottom and equally from both sides.
88
+
89
+ **Note:** the `:size` option is required when resizing.
90
+
91
+ ### Embedding Images
92
+
93
+ `#path` will always return the full path to the image, in your views
94
+ you probably want to refer to the "public" path -- the portion of the
95
+ path from your `/public` folder up for embedding your images. For
96
+ now there is a helper method that gets attached to your model called
97
+ `#public_path` that simply gives you the path from `public` on.
98
+
99
+ @image.public_path => '/assets/000/000/001/234/milton.jpg'
100
+
101
+ As opposed to:
102
+
103
+ @image.path(:thumb) => '/var/www/site/public/assets/000/000/001/234/milton.jpg'
104
+
105
+ **Note:** if you use the `:file_system_path` option to upload your files to
106
+ somewhere outside of your `public` folder this will no longer work. You can
107
+ pass a different folder to `public_path` to use as an alternate base.
108
+
109
+ @image.public_path(:thumb, 'uploads')
110
+
111
+ Processors
112
+ ----------
113
+
114
+ Processors are defined in recipes and are loaded when Milton is loaded. In the
115
+ above example we're telling Milton to load the thumbnail processor (which comes
116
+ with Milton) and then telling it to pass
117
+ `{ :size => '100x100', :crop => true }` to the thumbnail processor in order to
118
+ create a derivative called `:thumb` for all uploaded Images.
119
+
120
+ More processors can be loaded and combined into the recipe, to be run in the
121
+ order specified (hence the array syntax), i.e.:
122
+
123
+ class Image < ActiveRecord::Base
124
+ is_attachment :recipes => {
125
+ :watermarked_thumb => [{ :watermark => 'Milton' }, { :thumbnail => { :size => '100x100', :crop => true } }]
126
+ }
127
+ end
128
+
129
+ This recipe would create a watermarked version of the originally uploaded file,
130
+ then create a 100x100, cropped thumbnail of the watermarked version.
131
+
132
+ When processors would create the same derivative they use the already created
133
+ derivative and do not recreate it, so if you had:
134
+
135
+ class Image < ActiveRecord::Base
136
+ is_attachment :processors => [ :thumbnail, :watermark ], :recipes => {
137
+ :thumb => [{ :watermark => 'Milton' }, { :thumbnail => { :size => '100x100', :crop => true } }],
138
+ :small => [{ :watermark => 'Milton' }, { :thumbnail => { :size => '250x' } }]
139
+ }
140
+ end
141
+
142
+ The watermarking would only be done once and both thumbnails would be created
143
+ from the same watermarked version of the original image.
144
+
145
+ **Note:** There is no `:watermark` processor, just an example of how processors
146
+ can be combined.
147
+
148
+ Post-processing
149
+ ---------------
150
+
151
+ Post-processing allows you to create derivatives by running processors on
152
+ demand instead of when the file is uploaded. This is particularly useful for
153
+ prototyping or early-on in development when processing options are changing
154
+ rapidly and you want to play with results immediately.
155
+
156
+ You can pass `:postprocessing => true` to `is_attachment` in order to turn on
157
+ post-processing of files. This is recommended only for prototyping as it works
158
+ by checking the existence of the requested derivative every time the derivative
159
+ is requested to determine if it should be processed or not. With disk storage
160
+ in development this can be quite fast, but when using S3 or in production mode
161
+ it is definitely not recommended.
162
+
163
+ Post-processing allows you to pass recipes to `path`, i.e.:
164
+
165
+ @image.path(:thumbnail => { :size => '100x100', :crop => true })
166
+
167
+ If the particular derivative (size of 100x100 and cropped) doesn't exist it
168
+ will be created.
169
+
170
+ **Note:** Without post-processing turned on the call to `path` above would
171
+ still return the same path, it just wouldn't create the underlying file.
172
+
173
+ Amazon S3
174
+ ---------
175
+
176
+ Milton comes with support for Amazon S3. When using S3 uploads and derivatives
177
+ are stored locally in temp files then sent to S3. To use S3 you need to pass a
178
+ few options to `is_attachment`:
179
+
180
+ class Asset < ActiveRecord::Base
181
+ is_attachment :storage => :s3, :storage_options => { :api_access_key => '123', :secret_access_key => '456', :bucket => 'assets' }
182
+ end
183
+
184
+ Where `:api_access_key` and `:secret_access_key` are your API access key and
185
+ secret access key from your Amazon AWS account and `:bucket` is the S3 bucket
186
+ you would like your files to be stored in.
187
+
188
+ When using S3 files are stored in folders according to the associated model's
189
+ ID. So in the above example the URL to a stored file might be:
190
+
191
+ @image.path => http://assets.amazonaws.com/1/milton.jpg
192
+
193
+ Storage Options
194
+ ---------------
195
+
196
+ By default Milton uses your local disk for storing files. Additional storage
197
+ methods can be used by passing the `:storage` option to `is_attachment` (as in
198
+ the S3 example above). Milton comes included with `:s3` and `:disk` storage.
199
+
200
+ Disk Storage Options
201
+ --------------------
202
+
203
+ When using disk storage (default) the following options can be passed in
204
+ `:storage_options`:
205
+
206
+ * `:root` (default `<Rails.root>/public/<table name>`) -- is the root path to where files are/will be stored on your file system. The partitioned portion of the path is then added onto this root to generate the full path. You can do some useful stuff with this like pulling your assets out of /public if you want them to be non-web-accessible.
207
+ * `:chmod` (default `0755`) -- is the mode to set on on created folders and uploaded files.
208
+
209
+ **Note:** If you're using Capistrano for deployment with disk storage remember to put your `:root` path in `shared` and link it up on deploy so you don't lose your uploads between deployments!
210
+
211
+ General Options
212
+ ---------------
213
+
214
+ * `:separator` (default `'.'`) -- is the character used to separate the options from the filename in cached derivative files (i.e. resized images). It will be stripped from the filename of any incoming file.
215
+ * `:replacement` (default `'-'`) -- is the character used to replace `:separator` after stripping it from the incoming filename.
216
+ `:tempfile_path` (default `<Rails.root>/tmp/milton`) -- is the path used for Milton's temporary storage.
217
+
218
+ Installation
219
+ ------------
220
+
221
+ ### Gem
222
+
223
+ $ gem install citrusbyte-milton --source http://gems.github.com
224
+
225
+ ### Ruby on Rails gem plugin:
226
+
227
+ Add to your environment.rb:
228
+
229
+ config.gem "citrusbyte-milton", :source => "http://gems.github.com", :lib => "milton"
230
+
231
+ Then run:
232
+
233
+ $ rake gems:install
234
+
235
+ ### Ruby on Rails plugin
236
+
237
+ script/plugin install git://github.com/citrusbyte/milton.git
238
+
239
+ You will also need to install ImageMagick to use image resizing.
240
+
241
+ Dependencies
242
+ ------------
243
+
244
+ * Ruby on Rails w/ ActiveRecord
245
+ * A filesystem (hopefully this one is covered...)
246
+ * ImageMagick (only if using :thumbnail processor)
247
+ * mimetype-fu (not required, but if available will be used to recognized mime
248
+ types of incoming files)
249
+ * right_aws (if using S3 storage option)
250
+
251
+ Migrating
252
+ ---------
253
+
254
+ ### From 0.2.4
255
+
256
+ Milton is initialized with only `is_attachment` as of 0.3.0, so
257
+ `is_resizeable`, `is_uploadable`, etc... are no longer used.
258
+
259
+ `:postprocessing` is off by default and should only be used in development now.
260
+ Recipes should be used instead of post-processing -- see Processors above.
261
+
262
+ Extended Usage Examples
263
+ -----------------------
264
+
265
+ ### Basic User Avatar
266
+
267
+ class User < ActiveRecord::Base
268
+ has_one :avatar, :dependent => :destroy
269
+ end
270
+
271
+ class Avatar < ActiveRecord::Base
272
+ is_attachment :recipes => { :small => [{ :thumbnail => { :size => '100x100', :crop => true } }] }
273
+ belongs_to :user
274
+ end
275
+
276
+ Allow user to upload an avatar when creating
277
+
278
+ class UsersController < ActiveRecord::Base
279
+ def create
280
+ @user = User.new params[:user]
281
+ @user.avatar = Avatar.new(params[:avatar]) if params[:avatar] && params[:avatar][:file]
282
+
283
+ if @user.save
284
+ ...
285
+ else
286
+ ...
287
+ end
288
+
289
+ ...
290
+ end
291
+ end
292
+
293
+ Allow user to upload a new avatar, note that we don't care about updating files
294
+ in this `has_one` case, we're just gonna set a new relationship (which will
295
+ destroy the existing one)
296
+
297
+ class AvatarsController < ActiveRecord::Base
298
+ def create
299
+ @user = User.find params[:user_id]
300
+
301
+ # setting a has_one on a saved object saves the new related object
302
+ if @user.avatar = Avatar.new(params[:avatar])
303
+ ...
304
+ else
305
+ ...
306
+ end
307
+
308
+ ...
309
+ end
310
+ end
311
+
312
+ User's profile snippet (in Haml)
313
+
314
+ #profile
315
+ = image_tag(@user.avatar.public_path(:small))
316
+ = @user.name
317
+
318
+ Contributors
319
+ ------------
320
+
321
+ <<<<<<< HEAD:README.markdown
322
+ Milton is based on AttachmentPow by Ari Lerner (auser)
323
+ =======
324
+ * Upload and file handling based on AttachmentPow by Ari Lerner (auser)
325
+ * Cropping and thumbnailing calculations based on code by Nicolás Sanguinetti (foca)
326
+ * Damian Janowski (djanowski)
327
+ * Michel Martens (soveran)
328
+ >>>>>>> s3:README.markdown
329
+
330
+ License
331
+ -------
332
+
333
+ Copyright (c) 2009 Ben Alavi for Citrusbyte
334
+
335
+ Permission is hereby granted, free of charge, to any person
336
+ obtaining a copy of this software and associated documentation
337
+ files (the "Software"), to deal in the Software without
338
+ restriction, including without limitation the rights to use,
339
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
340
+ copies of the Software, and to permit persons to whom the
341
+ Software is furnished to do so, subject to the following
342
+ conditions:
343
+
344
+ The above copyright notice and this permission notice shall be
345
+ included in all copies or substantial portions of the Software.
346
+
347
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
348
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
349
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
350
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
351
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
352
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
353
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
354
+ OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |task|
5
+ task.libs << 'lib'
6
+ task.libs << "test"
7
+ task.pattern = 'test/**/*_test.rb'
8
+ task.verbose = false
9
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'milton'
@@ -0,0 +1,203 @@
1
+ require 'ftools'
2
+ require 'fileutils'
3
+ require 'milton/derivatives/derivative'
4
+
5
+ module Milton
6
+ module Attachment
7
+ # Call is_attachment with your options in order to add attachment
8
+ # functionality to your ActiveRecord model.
9
+ #
10
+ # TODO: list options
11
+ def is_attachment(options={})
12
+ # Check to see that it hasn't already been extended so that subclasses
13
+ # can redefine is_attachment from their superclasses and overwrite
14
+ # options w/o losing the superclass options.
15
+ unless respond_to?(:has_attachment_methods)
16
+ extend Milton::Attachment::AttachmentMethods
17
+ class_inheritable_accessor :milton_options
18
+ end
19
+ has_attachment_methods(options)
20
+ end
21
+
22
+ module AttachmentMethods
23
+ def require_column(column, message)
24
+ begin
25
+ raise message unless column_names.include?(column)
26
+ rescue ActiveRecord::StatementInvalid => i
27
+ # table doesn't exist yet, i.e. hasn't been migrated in...
28
+ end
29
+ end
30
+
31
+ def has_attachment_methods(options={})
32
+ require_column 'filename', "Milton requires a filename column on #{class_name} table"
33
+
34
+ # It's possible that this is being included from a class and a sub
35
+ # class of that class, in which case we need to merge the existing
36
+ # options up.
37
+ self.milton_options ||= {}
38
+ milton_options.merge!(options)
39
+
40
+ # Character used to seperate a filename from its derivative options, this
41
+ # character will be stripped from all incoming filenames and replaced by
42
+ # replacement
43
+ milton_options[:separator] ||= '.'
44
+ milton_options[:replacement] ||= '-'
45
+ milton_options[:tempfile_path] ||= File.join(Rails.root, "tmp", "milton")
46
+ milton_options[:storage] ||= :disk
47
+ milton_options[:storage_options] ||= {}
48
+ milton_options[:processors] ||= {}
49
+ milton_options[:uploading] ||= true
50
+
51
+ # Set to true to allow on-demand processing of derivatives. This can
52
+ # be rediculously slow because it requires that the existance of the
53
+ # derivative is checked each time it's requested -- throw in S3 and
54
+ # that becomes a huge lag. Reccommended only for prototyping.
55
+ milton_options[:postproccess] ||= false
56
+
57
+ # TODO: Write about recipes
58
+ # * They're refered to by name in #path
59
+ # * They're an order of derivations to make against this attachment
60
+ # * They run in the order defined
61
+ # * They are created and run when the AR model is created
62
+ # * They're necessary when +:postprocessing+ is turned off
63
+ milton_options[:recipes] ||= {}
64
+ milton_options[:recipes].each do |name, steps|
65
+ steps = [steps] unless steps.is_a?(Array)
66
+ steps.each do |step|
67
+ step.each { |processor, options| Milton.try_require "milton/derivatives/#{processor}", "No '#{processor}' processor found for Milton" }
68
+ end
69
+ end
70
+
71
+ # TODO: Write about storage options
72
+ # * Late binding (so right_aws is only req'd if you use S3)
73
+ Milton.try_require "milton/storage/#{milton_options[:storage]}_file", "No '#{milton_options[:storage]}' storage found for Milton"
74
+
75
+ # TODO: initialize these options in DiskFile
76
+ if milton_options[:storage] == :disk
77
+ # root of where the underlying files are stored (or will be stored)
78
+ # on the file system
79
+ milton_options[:storage_options][:root] ||= File.join(Rails.root, "public", table_name)
80
+ milton_options[:storage_options][:root] = File.expand_path(milton_options[:storage_options][:root])
81
+ # mode to set on stored files and created directories
82
+ milton_options[:storage_options][:chmod] ||= 0755
83
+ end
84
+
85
+ validates_presence_of :filename
86
+
87
+ after_destroy :destroy_attached_file
88
+ after_create :create_derivatives
89
+
90
+ include Milton::Attachment::InstanceMethods
91
+
92
+ if milton_options[:uploading]
93
+ require 'milton/uploading'
94
+ extend Milton::Uploading::ClassMethods
95
+ include Milton::Uploading::InstanceMethods
96
+ end
97
+ end
98
+ end
99
+
100
+ # These get mixed in to your model when you use Milton
101
+ module InstanceMethods
102
+ # Sets the filename to the given filename (sanitizes the given filename
103
+ # as well)
104
+ #
105
+ # TODO: change the filename on the underlying file system on save so as
106
+ # not to orphan the file
107
+ def filename=(name)
108
+ write_attribute :filename, Storage::StoredFile.sanitize_filename(name, self.class.milton_options)
109
+ end
110
+
111
+ # Returns the content_type of this attachment, tries to determine it if
112
+ # hasn't been determined yet or is not saved to the database
113
+ def content_type
114
+ return self[:content_type] unless self[:content_type].blank?
115
+ self.content_type = attached_file.mime_type
116
+ end
117
+
118
+ # Sets the content type to the given type
119
+ def content_type=(type)
120
+ write_attribute :content_type, type.to_s.strip
121
+ end
122
+
123
+ # Simple helper, same as path except returns the directory from
124
+ # .../public/ on, i.e. for showing images in your views.
125
+ #
126
+ # @asset.path => /var/www/site/public/assets/000/000/001/313/milton.jpg
127
+ # @asset.public_path => /assets/000/000/001/313/milton.jpg
128
+ #
129
+ # Can send a different base path than public if you want to give the
130
+ # path from that base on, useful if you change your root path to
131
+ # somewhere else.
132
+ def public_path(options={}, base='public')
133
+ path(options).gsub(/.*?\/#{base}/, '')
134
+ end
135
+
136
+ # The path to the file.
137
+ def path(options=nil)
138
+ return attached_file.path if options.nil?
139
+ process(options).path
140
+ end
141
+
142
+ protected
143
+
144
+ # Meant to be used as an after_create filter -- loops over all the
145
+ # recipes and processes them to create the derivatives.
146
+ def create_derivatives
147
+ milton_options[:recipes].each{ |name, recipe| process(name, true) } if milton_options[:recipes].any?
148
+ end
149
+
150
+ # Process the given options to produce a final derivative. +options+
151
+ # takes a Hash of options to process or the name of a pre-defined
152
+ # recipe which will be looked up and processed.
153
+ #
154
+ # Pass +force = true+ to force processing regardless of if
155
+ # +:postprocessing+ is turned on or not.
156
+ #
157
+ # Returns the final Derivative of all processors in the recipe.
158
+ def process(options, force=false)
159
+ options = milton_options[:recipes][options] unless options.is_a?(Hash)
160
+ options = [options] unless options.is_a?(Array)
161
+
162
+ source = attached_file
163
+ options.each do |recipe|
164
+ recipe.each do |processor, opts|
165
+ source = Derivative.factory(processor, source, opts, self.class.milton_options).process_if(process? || force).file
166
+ end
167
+ end
168
+ source
169
+ end
170
+
171
+ # Returns true if derivaties of the attachment should be processed,
172
+ # returns false if no processing should be done when a derivative is
173
+ # requested.
174
+ #
175
+ # No processing also means the derivative won't be checked for
176
+ # existance (since that can be slow) so w/o postprocessing things will
177
+ # be much faster but #path will happily return the paths to Derivatives
178
+ # which don't exist.
179
+ #
180
+ # It is highly recommended that you turn +:postprocessing+ off for
181
+ # anything but prototyping, and instead use recipes and refer to them
182
+ # via #path. +:postprocessing+ relies on checking for existance which
183
+ # will kill any real application.
184
+ def process?
185
+ self.class.milton_options[:postprocessing]
186
+ end
187
+
188
+ # A reference to the attached file, this is probably what you want to
189
+ # overwrite to introduce a new behavior
190
+ #
191
+ # i.e.
192
+ # have attached_file return a ResizeableFile, or a TranscodableFile
193
+ def attached_file
194
+ @attached_file ||= Storage::StoredFile.adapter(self.class.milton_options[:storage]).new(filename, id, self.class.milton_options)
195
+ end
196
+
197
+ # Clean the file from the filesystem
198
+ def destroy_attached_file
199
+ attached_file.destroy
200
+ end
201
+ end
202
+ end
203
+ end