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 +20 -0
- data/README.markdown +354 -0
- data/Rakefile +9 -0
- data/init.rb +1 -0
- data/lib/milton/attachment.rb +203 -0
- data/lib/milton/core/file.rb +22 -0
- data/lib/milton/core/tempfile.rb +38 -0
- data/lib/milton/derivatives/derivative.rb +95 -0
- data/lib/milton/derivatives/thumbnail/crop_calculator.rb +64 -0
- data/lib/milton/derivatives/thumbnail/image.rb +49 -0
- data/lib/milton/derivatives/thumbnail.rb +46 -0
- data/lib/milton/storage/disk_file.rb +78 -0
- data/lib/milton/storage/s3_file.rb +89 -0
- data/lib/milton/storage/stored_file.rb +47 -0
- data/lib/milton/uploading.rb +82 -0
- data/lib/milton.rb +95 -0
- data/test/fixtures/big-milton.jpg +0 -0
- data/test/fixtures/milton.jpg +0 -0
- data/test/fixtures/mini-milton.jpg +0 -0
- data/test/fixtures/unsanitary .milton.jpg +0 -0
- data/test/milton/attachment_test.rb +329 -0
- data/test/milton/milton_test.rb +13 -0
- data/test/milton/resizing_test.rb +70 -0
- data/test/schema.rb +13 -0
- data/test/test_helper.rb +62 -0
- metadata +31 -7
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
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
|