thoughtbot-paperclip 2.1.5 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +123 -10
- data/Rakefile +1 -5
- data/lib/paperclip.rb +63 -24
- data/lib/paperclip/attachment.rb +90 -48
- data/lib/paperclip/callback_compatability.rb +33 -0
- data/lib/paperclip/iostream.rb +1 -1
- data/lib/paperclip/processor.rb +47 -0
- data/lib/paperclip/storage.rb +11 -1
- data/lib/paperclip/thumbnail.rb +13 -31
- data/shoulda_macros/paperclip.rb +131 -10
- data/test/attachment_test.rb +126 -32
- data/test/helper.rb +2 -0
- data/test/integration_test.rb +64 -1
- data/test/iostream_test.rb +2 -0
- data/test/paperclip_test.rb +39 -3
- data/test/processor_test.rb +10 -0
- data/test/storage_test.rb +48 -0
- data/test/thumbnail_test.rb +12 -6
- metadata +5 -2
data/README.rdoc
CHANGED
@@ -1,10 +1,21 @@
|
|
1
1
|
=Paperclip
|
2
2
|
|
3
|
-
Paperclip is intended as an easy file attachment library for ActiveRecord. The
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
Paperclip is intended as an easy file attachment library for ActiveRecord. The
|
4
|
+
intent behind it was to keep setup as easy as possible and to treat files as
|
5
|
+
much like other attributes as possible. This means they aren't saved to their
|
6
|
+
final locations on disk, nor are they deleted if set to nil, until
|
7
|
+
ActiveRecord::Base#save is called. It manages validations based on size and
|
8
|
+
presence, if required. It can transform its assigned image into thumbnails if
|
9
|
+
needed, and the prerequisites are as simple as installing ImageMagick (which,
|
10
|
+
for most modern Unix-based systems, is as easy as installing the right
|
11
|
+
packages). Attached files are saved to the filesystem and referenced in the
|
12
|
+
browser by an easily understandable specification, which has sensible and
|
13
|
+
useful defaults.
|
14
|
+
|
15
|
+
See the documentation for +has_attached_file+ in Paperclip::ClassMethods for
|
16
|
+
more detailed options.
|
17
|
+
|
18
|
+
==Quick Start
|
8
19
|
|
9
20
|
In your model:
|
10
21
|
|
@@ -48,12 +59,114 @@ In your show view:
|
|
48
59
|
<%= image_tag @user.avatar.url(:medium) %>
|
49
60
|
<%= image_tag @user.avatar.url(:thumb) %>
|
50
61
|
|
62
|
+
==Usage
|
63
|
+
|
64
|
+
The basics of paperclip are quite simple: Declare that your model has an
|
65
|
+
attachment with the has_attached_file method, and give it a name. Paperclip
|
66
|
+
will wrap up up to four attributes (all prefixed with that attachment's name,
|
67
|
+
so you can have multiple attachments per model if you wish) and give the a
|
68
|
+
friendly front end. The attributes are <attachment>_file_name,
|
69
|
+
<attachment>_file_size, <attachment>_content_type, and <attachment>_updated_at.
|
70
|
+
Only <attachment>_file_name is required for paperclip to operate. More
|
71
|
+
information about the options to has_attached_file is available in the
|
72
|
+
documentation of Paperclip::ClassMethods.
|
73
|
+
|
74
|
+
Attachments can be validated with Paperclip's validation methods,
|
75
|
+
validates_attachment_presence, validates_attachment_content_type, and
|
76
|
+
validates_attachment_size.
|
77
|
+
|
78
|
+
==Storage
|
79
|
+
|
80
|
+
The files that are assigned as attachments are, by default, placed in the
|
81
|
+
directory specified by the :path option to has_attached_file. By default, this
|
82
|
+
location is
|
83
|
+
":rails_root/public/system/:attachment/:id/:style/:basename.:extension". This
|
84
|
+
location was chosen because on standard Capistrano deployments, the
|
85
|
+
public/system directory is symlinked to the app's shared directory, meaning it
|
86
|
+
will survive between deployments. For example, using that :path, you may have a
|
87
|
+
file at
|
88
|
+
|
89
|
+
/data/myapp/releases/20081229172410/public/system/avatars/13/small/my_pic.png
|
90
|
+
|
91
|
+
NOTE: This is a change from previous versions of Paperclip, but is overall a
|
92
|
+
safer choice for the defaul file store.
|
93
|
+
|
94
|
+
You may also choose to store your files using Amazon's S3 service. You can find
|
95
|
+
more information about S3 storage at the description for
|
96
|
+
Paperclip::Storage::S3.
|
97
|
+
|
98
|
+
Files on the local filesystem (and in the Rails app's public directory) will be
|
99
|
+
available to the internet at large. If you require access control, it's
|
100
|
+
possible to place your files in a different location. You will need to change
|
101
|
+
both the :path and :url options in order to make sure the files are unavailable
|
102
|
+
to the public. Both :path and :url allow the same set of interpolated
|
103
|
+
variables.
|
104
|
+
|
105
|
+
==Post Processing
|
106
|
+
|
107
|
+
Paperclip supports an extendible selection of post-processors. When you define
|
108
|
+
a set of styles for an attachment, by default it is expected that those
|
109
|
+
"styles" are actually "thumbnails". However, you can do more than just
|
110
|
+
thumbnail images. By defining a subclass of Paperclip::Processor, you can
|
111
|
+
perform any processing you want on the files that are attached. Any file in
|
112
|
+
your Rails app's lib/paperclip_processor directory is automatically loaded by
|
113
|
+
paperclip, allowing you to easily define custom processors. You can specify a
|
114
|
+
processor with the :processors option to has_attached_file:
|
115
|
+
|
116
|
+
has_attached_file :scan, :styles => { :text => { :quality => :better } },
|
117
|
+
:processors => [:ocr]
|
118
|
+
|
119
|
+
This would load the hypothetical class Paperclip::Ocr, which would have the
|
120
|
+
hash "{ :quality => :better }" passed to it along with the uploaded file. For
|
121
|
+
more information about defining processors, see Paperclip::Processor.
|
122
|
+
|
123
|
+
The default processor is Paperclip::Thumbnail. For backwards compatability
|
124
|
+
reasons, you can pass a single geometry string or an array containing a
|
125
|
+
geometry and a format, which the file will be converted to, like so:
|
126
|
+
|
127
|
+
has_attached_file :avatar, :styles => { :thumb => ["32x32#", :png] }
|
128
|
+
|
129
|
+
This will convert the "thumb" style to a 32x32 square in png format, regardless
|
130
|
+
of what was uploaded. If the format is not specified, it is kept the same (i.e.
|
131
|
+
jpgs will remain jpgs).
|
132
|
+
|
133
|
+
Multiple processors can be specified, and they will be invoked in the order
|
134
|
+
they are defined in the :processors array. Each successive processor will
|
135
|
+
be given the result of the previous processor's execution. All processors will
|
136
|
+
receive the same parameters, which are what you define in the :styles hash.
|
137
|
+
For example, assuming we had this definition:
|
138
|
+
|
139
|
+
has_attached_file :scan, :styles => { :text => { :quality => :better } },
|
140
|
+
:processors => [:rotator, :ocr]
|
141
|
+
|
142
|
+
then both the :rotator processor and the :ocr processor would receive the
|
143
|
+
options "{ :quality => :better }". This parameter may not mean anything to one
|
144
|
+
or more or the processors, and they are free to ignore it.
|
145
|
+
|
146
|
+
==Events
|
147
|
+
|
148
|
+
Before and after the Post Processing step, Paperclip calls back to the model
|
149
|
+
with a few callbacks, allowing the model to change or cancel the processing
|
150
|
+
step. The callbacks are "before_post_process" and "after_post_process" (which
|
151
|
+
are called before and after the processing of each attachment), and the
|
152
|
+
attachment-specific "before_<attachment>_post_process" and
|
153
|
+
"after_<attachment>_post_process". The callbacks are intended to be as close to
|
154
|
+
normal ActiveRecord callbacks as possible, so if you return false (specifically
|
155
|
+
-- returning nil is not the same) in a before_ filter, the post processing step
|
156
|
+
will halt. Returning false in an after_ filter will not halt anything, but you
|
157
|
+
can access the model and the attachment if necessary.
|
158
|
+
|
159
|
+
NOTE: Post processing will not even *start* if the attachment is not valid
|
160
|
+
according to the validations. Your callbacks (and processors) will only be
|
161
|
+
called with valid attachments.
|
162
|
+
|
51
163
|
==Contributing
|
52
164
|
|
53
|
-
If you'd like to contribute a feature or bugfix
|
54
|
-
has a high chance of being
|
165
|
+
If you'd like to contribute a feature or bugfix: Thanks! To make sure your
|
166
|
+
fix/feature has a high chance of being included, please read the following
|
167
|
+
guidelines:
|
55
168
|
|
56
169
|
1. Ask on the mailing list, or post a ticket in Lighthouse.
|
57
|
-
2. Make sure there are tests!
|
58
|
-
It's a rare time when explicit tests aren't needed. If you have questions
|
59
|
-
writing tests for paperclip, please ask the mailing list.
|
170
|
+
2. Make sure there are tests! We will not accept any patch that is not tested.
|
171
|
+
It's a rare time when explicit tests aren't needed. If you have questions
|
172
|
+
about writing tests for paperclip, please ask the mailing list.
|
data/Rakefile
CHANGED
@@ -27,7 +27,7 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
|
|
27
27
|
rdoc.rdoc_dir = 'doc'
|
28
28
|
rdoc.title = 'Paperclip'
|
29
29
|
rdoc.options << '--line-numbers' << '--inline-source'
|
30
|
-
rdoc.rdoc_files.include('README')
|
30
|
+
rdoc.rdoc_files.include('README*')
|
31
31
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
32
32
|
end
|
33
33
|
|
@@ -70,10 +70,6 @@ spec = Gem::Specification.new do |s|
|
|
70
70
|
s.add_development_dependency 'mocha'
|
71
71
|
end
|
72
72
|
|
73
|
-
Rake::GemPackageTask.new(spec) do |pkg|
|
74
|
-
pkg.need_tar = true
|
75
|
-
end
|
76
|
-
|
77
73
|
desc "Release new version"
|
78
74
|
task :release => [:test, :sync_docs, :gem] do
|
79
75
|
require 'rubygems'
|
data/lib/paperclip.rb
CHANGED
@@ -29,35 +29,57 @@ require 'tempfile'
|
|
29
29
|
require 'paperclip/upfile'
|
30
30
|
require 'paperclip/iostream'
|
31
31
|
require 'paperclip/geometry'
|
32
|
+
require 'paperclip/processor'
|
32
33
|
require 'paperclip/thumbnail'
|
33
34
|
require 'paperclip/storage'
|
34
35
|
require 'paperclip/attachment'
|
36
|
+
if defined? RAILS_ROOT
|
37
|
+
Dir.glob(File.join(File.expand_path(RAILS_ROOT), "lib", "paperclip_processors", "*.rb")).each do |processor|
|
38
|
+
require processor
|
39
|
+
end
|
40
|
+
end
|
35
41
|
|
36
42
|
# The base module that gets included in ActiveRecord::Base. See the
|
37
43
|
# documentation for Paperclip::ClassMethods for more useful information.
|
38
44
|
module Paperclip
|
39
45
|
|
40
|
-
VERSION = "2.
|
46
|
+
VERSION = "2.2.0"
|
41
47
|
|
42
48
|
class << self
|
43
49
|
# Provides configurability to Paperclip. There are a number of options available, such as:
|
44
50
|
# * whiny_thumbnails: Will raise an error if Paperclip cannot process thumbnails of
|
45
51
|
# an uploaded image. Defaults to true.
|
46
|
-
# *
|
52
|
+
# * command_path: Defines the path at which to find the command line
|
47
53
|
# programs if they are not visible to Rails the system's search path. Defaults to
|
48
|
-
# nil, which uses the first executable found in the search path.
|
54
|
+
# nil, which uses the first executable found in the user's search path.
|
55
|
+
# * image_magick_path: Deprecated alias of command_path.
|
49
56
|
def options
|
50
57
|
@options ||= {
|
51
58
|
:whiny_thumbnails => true,
|
52
|
-
:image_magick_path => nil
|
59
|
+
:image_magick_path => nil,
|
60
|
+
:command_path => nil
|
53
61
|
}
|
54
62
|
end
|
55
63
|
|
56
64
|
def path_for_command command #:nodoc:
|
57
|
-
|
65
|
+
if options[:image_magick_path]
|
66
|
+
ActiveSupport::Deprecation.warn(":image_magick_path is deprecated and "+
|
67
|
+
"will be removed. Use :command_path "+
|
68
|
+
"instead")
|
69
|
+
end
|
70
|
+
path = [options[:image_magick_path] || options[:command_path], command].compact
|
58
71
|
File.join(*path)
|
59
72
|
end
|
60
73
|
|
74
|
+
# The run method takes a command to execute and a string of parameters
|
75
|
+
# that get passed to it. The command is prefixed with the :command_path
|
76
|
+
# option from Paperclip.options. If you have many commands to run and
|
77
|
+
# they are in different paths, the suggested course of action is to
|
78
|
+
# symlink them so they are all in the same directory.
|
79
|
+
#
|
80
|
+
# If the command returns with a result code that is not one of the
|
81
|
+
# expected_outcodes, a PaperclipCommandLineError will be raised. Generally
|
82
|
+
# a code of 0 is expected, but a list of codes may be passed if necessary.
|
61
83
|
def run cmd, params = "", expected_outcodes = 0
|
62
84
|
output = `#{%Q[#{path_for_command(cmd)} #{params} 2>#{bit_bucket}].gsub(/\s+/, " ")}`
|
63
85
|
unless [expected_outcodes].flatten.include?($?.exitstatus)
|
@@ -66,12 +88,24 @@ module Paperclip
|
|
66
88
|
output
|
67
89
|
end
|
68
90
|
|
69
|
-
def bit_bucket
|
91
|
+
def bit_bucket #:nodoc:
|
70
92
|
File.exists?("/dev/null") ? "/dev/null" : "NUL"
|
71
93
|
end
|
72
94
|
|
73
95
|
def included base #:nodoc:
|
74
96
|
base.extend ClassMethods
|
97
|
+
unless base.respond_to?(:define_callbacks)
|
98
|
+
base.send(:include, Paperclip::CallbackCompatability)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def processor name #:nodoc:
|
103
|
+
name = name.to_s.camelize
|
104
|
+
processor = Paperclip.const_get(name)
|
105
|
+
unless processor.ancestors.include?(Paperclip::Processor)
|
106
|
+
raise PaperclipError.new("Processor #{name} was not found")
|
107
|
+
end
|
108
|
+
processor
|
75
109
|
end
|
76
110
|
end
|
77
111
|
|
@@ -97,15 +131,15 @@ module Paperclip
|
|
97
131
|
# * +url+: The full URL of where the attachment is publically accessible. This can just
|
98
132
|
# as easily point to a directory served directly through Apache as it can to an action
|
99
133
|
# that can control permissions. You can specify the full domain and path, but usually
|
100
|
-
# just an absolute path is sufficient. The leading slash must be included manually for
|
134
|
+
# just an absolute path is sufficient. The leading slash *must* be included manually for
|
101
135
|
# absolute paths. The default value is
|
102
|
-
# "/:
|
136
|
+
# "/system/:attachment/:id/:style/:basename.:extension". See
|
103
137
|
# Paperclip::Attachment#interpolate for more information on variable interpolaton.
|
104
|
-
# :url => "/:attachment/:id/:style_:basename
|
138
|
+
# :url => "/:class/:attachment/:id/:style_:basename.:extension"
|
105
139
|
# :url => "http://some.other.host/stuff/:class/:id_:extension"
|
106
140
|
# * +default_url+: The URL that will be returned if there is no attachment assigned.
|
107
141
|
# This field is interpolated just as the url is. The default value is
|
108
|
-
# "/:
|
142
|
+
# "/:attachment/:style/missing.png"
|
109
143
|
# has_attached_file :avatar, :default_url => "/images/default_:style_avatar.png"
|
110
144
|
# User.new.avatar_url(:small) # => "/images/default_small_avatar.png"
|
111
145
|
# * +styles+: A hash of thumbnail styles and their geometries. You can find more about
|
@@ -119,8 +153,8 @@ module Paperclip
|
|
119
153
|
# has_attached_file :avatar, :styles => { :normal => "100x100#" },
|
120
154
|
# :default_style => :normal
|
121
155
|
# user.avatar.url # => "/avatars/23/normal_me.png"
|
122
|
-
# * +whiny_thumbnails+: Will raise an error if Paperclip cannot
|
123
|
-
#
|
156
|
+
# * +whiny_thumbnails+: Will raise an error if Paperclip cannot post_process an uploaded file due
|
157
|
+
# to a command line error. This will override the global setting for this attachment.
|
124
158
|
# Defaults to true.
|
125
159
|
# * +convert_options+: When creating thumbnails, use this free-form options
|
126
160
|
# field to pass in various convert command options. Typical options are "-strip" to
|
@@ -150,6 +184,9 @@ module Paperclip
|
|
150
184
|
after_save :save_attached_files
|
151
185
|
before_destroy :destroy_attached_files
|
152
186
|
|
187
|
+
define_callbacks :before_post_process, :after_post_process
|
188
|
+
define_callbacks :"before_#{name}_post_process", :"after_#{name}_post_process"
|
189
|
+
|
153
190
|
define_method name do |*args|
|
154
191
|
a = attachment_for(name)
|
155
192
|
(args.length > 0) ? a.to_s(args.first) : a
|
@@ -200,14 +237,16 @@ module Paperclip
|
|
200
237
|
end
|
201
238
|
end
|
202
239
|
|
203
|
-
# Places ActiveRecord-style validations on the content type of the file
|
204
|
-
# possible options are:
|
205
|
-
# * +content_type+: Allowed content types. Can be a single content type
|
206
|
-
# Each type can be a String or a Regexp. It should be
|
207
|
-
#
|
208
|
-
#
|
209
|
-
#
|
210
|
-
#
|
240
|
+
# Places ActiveRecord-style validations on the content type of the file
|
241
|
+
# assigned. The possible options are:
|
242
|
+
# * +content_type+: Allowed content types. Can be a single content type
|
243
|
+
# or an array. Each type can be a String or a Regexp. It should be
|
244
|
+
# noted that Internet Explorer upload files with content_types that you
|
245
|
+
# may not expect. For example, JPEG images are given image/pjpeg and
|
246
|
+
# PNGs are image/x-png, so keep that in mind when determining how you
|
247
|
+
# match. Allows all by default.
|
248
|
+
# * +message+: The message to display when the uploaded file has an invalid
|
249
|
+
# content type.
|
211
250
|
def validates_attachment_content_type name, options = {}
|
212
251
|
attachment_definitions[name][:validations][:content_type] = lambda do |attachment, instance|
|
213
252
|
valid_types = [options[:content_type]].flatten
|
@@ -223,17 +262,17 @@ module Paperclip
|
|
223
262
|
end
|
224
263
|
end
|
225
264
|
|
226
|
-
# Returns the attachment definitions defined by each call to
|
265
|
+
# Returns the attachment definitions defined by each call to
|
266
|
+
# has_attached_file.
|
227
267
|
def attachment_definitions
|
228
268
|
read_inheritable_attribute(:attachment_definitions)
|
229
269
|
end
|
230
|
-
|
231
270
|
end
|
232
271
|
|
233
272
|
module InstanceMethods #:nodoc:
|
234
273
|
def attachment_for name
|
235
|
-
@
|
236
|
-
@
|
274
|
+
@_paperclip_attachments ||= {}
|
275
|
+
@_paperclip_attachments[name] ||= Attachment.new(name, self, self.class.attachment_definitions[name])
|
237
276
|
end
|
238
277
|
|
239
278
|
def each_attachment
|
data/lib/paperclip/attachment.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
module Paperclip
|
2
|
-
# The Attachment class manages the files for a given attachment. It saves
|
3
|
-
# deletes when the model is destroyed, and processes
|
2
|
+
# The Attachment class manages the files for a given attachment. It saves
|
3
|
+
# when the model saves, deletes when the model is destroyed, and processes
|
4
|
+
# the file upon assignment.
|
4
5
|
class Attachment
|
5
6
|
|
6
7
|
def self.default_options
|
7
8
|
@default_options ||= {
|
8
|
-
:url => "/:attachment/:id/:style/:basename.:extension",
|
9
|
-
:path => ":rails_root/public/:attachment/:id/:style/:basename.:extension",
|
9
|
+
:url => "/system/:attachment/:id/:style/:basename.:extension",
|
10
|
+
:path => ":rails_root/public/system/:attachment/:id/:style/:basename.:extension",
|
10
11
|
:styles => {},
|
11
12
|
:default_url => "/:attachment/:style/missing.png",
|
12
13
|
:default_style => :original,
|
@@ -15,11 +16,11 @@ module Paperclip
|
|
15
16
|
}
|
16
17
|
end
|
17
18
|
|
18
|
-
attr_reader :name, :instance, :styles, :default_style, :convert_options
|
19
|
+
attr_reader :name, :instance, :styles, :default_style, :convert_options, :queued_for_write
|
19
20
|
|
20
|
-
# Creates an Attachment object. +name+ is the name of the attachment,
|
21
|
-
# ActiveRecord object instance it's attached to, and
|
22
|
-
# passed to +has_attached_file+.
|
21
|
+
# Creates an Attachment object. +name+ is the name of the attachment,
|
22
|
+
# +instance+ is the ActiveRecord object instance it's attached to, and
|
23
|
+
# +options+ is the same as the hash passed to +has_attached_file+.
|
23
24
|
def initialize name, instance, options = {}
|
24
25
|
@name = name
|
25
26
|
@instance = instance
|
@@ -33,8 +34,9 @@ module Paperclip
|
|
33
34
|
@validations = options[:validations]
|
34
35
|
@default_style = options[:default_style]
|
35
36
|
@storage = options[:storage]
|
36
|
-
@
|
37
|
+
@whiny = options[:whiny_thumbnails]
|
37
38
|
@convert_options = options[:convert_options] || {}
|
39
|
+
@processors = options[:processors] || [:thumbnail]
|
38
40
|
@options = options
|
39
41
|
@queued_for_delete = []
|
40
42
|
@queued_for_write = {}
|
@@ -45,14 +47,17 @@ module Paperclip
|
|
45
47
|
normalize_style_definition
|
46
48
|
initialize_storage
|
47
49
|
|
48
|
-
|
50
|
+
log("Paperclip attachment #{name} on #{instance.class} initialized.")
|
49
51
|
end
|
50
52
|
|
51
|
-
# What gets called when you call instance.attachment = File. It clears
|
52
|
-
# assigns attributes, processes the file, and runs validations. It
|
53
|
-
# the previous file for deletion, to be flushed away on
|
54
|
-
# In addition to form uploads, you can also assign
|
53
|
+
# What gets called when you call instance.attachment = File. It clears
|
54
|
+
# errors, assigns attributes, processes the file, and runs validations. It
|
55
|
+
# also queues up the previous file for deletion, to be flushed away on
|
56
|
+
# #save of its host. In addition to form uploads, you can also assign
|
57
|
+
# another Paperclip attachment:
|
55
58
|
# new_user.avatar = old_user.avatar
|
59
|
+
# If the file that is assigned is not valid, the processing (i.e.
|
60
|
+
# thumbnailing, etc) will NOT be run.
|
56
61
|
def assign uploaded_file
|
57
62
|
%w(file_name).each do |field|
|
58
63
|
unless @instance.class.column_names.include?("#{name}_#{field}")
|
@@ -65,7 +70,7 @@ module Paperclip
|
|
65
70
|
end
|
66
71
|
|
67
72
|
return nil unless valid_assignment?(uploaded_file)
|
68
|
-
|
73
|
+
log("Assigning #{uploaded_file.inspect} to #{name}")
|
69
74
|
|
70
75
|
uploaded_file.binmode if uploaded_file.respond_to? :binmode
|
71
76
|
queue_existing_for_delete
|
@@ -74,7 +79,7 @@ module Paperclip
|
|
74
79
|
|
75
80
|
return nil if uploaded_file.nil?
|
76
81
|
|
77
|
-
|
82
|
+
log("Writing attributes for #{name}")
|
78
83
|
@queued_for_write[:original] = uploaded_file.to_tempfile
|
79
84
|
instance_write(:file_name, uploaded_file.original_filename.strip.gsub(/[^\w\d\.\-]+/, '_'))
|
80
85
|
instance_write(:content_type, uploaded_file.content_type.to_s.strip)
|
@@ -83,7 +88,7 @@ module Paperclip
|
|
83
88
|
|
84
89
|
@dirty = true
|
85
90
|
|
86
|
-
post_process
|
91
|
+
post_process if valid?
|
87
92
|
|
88
93
|
# Reset the file size if the original file was reprocessed.
|
89
94
|
instance_write(:file_size, uploaded_file.size.to_i)
|
@@ -91,20 +96,22 @@ module Paperclip
|
|
91
96
|
validate
|
92
97
|
end
|
93
98
|
|
94
|
-
# Returns the public URL of the attachment, with a given style. Note that
|
95
|
-
# does not necessarily need to point to a file that your web server
|
96
|
-
# and can point to an action in your app, if you need fine
|
97
|
-
# This is not recommended if you don't need the
|
98
|
-
# performance reasons.
|
99
|
-
|
99
|
+
# Returns the public URL of the attachment, with a given style. Note that
|
100
|
+
# this does not necessarily need to point to a file that your web server
|
101
|
+
# can access and can point to an action in your app, if you need fine
|
102
|
+
# grained security. This is not recommended if you don't need the
|
103
|
+
# security, however, for performance reasons. set
|
104
|
+
# include_updated_timestamp to false if you want to stop the attachment
|
105
|
+
# update time appended to the url
|
106
|
+
def url style = default_style, include_updated_timestamp = true
|
100
107
|
url = original_filename.nil? ? interpolate(@default_url, style) : interpolate(@url, style)
|
101
|
-
updated_at ? [url, updated_at].compact.join(url.include?("?") ? "&" : "?") : url
|
108
|
+
include_updated_timestamp && updated_at ? [url, updated_at].compact.join(url.include?("?") ? "&" : "?") : url
|
102
109
|
end
|
103
110
|
|
104
111
|
# Returns the path of the attachment as defined by the :path option. If the
|
105
|
-
# file is stored in the filesystem the path refers to the path of the file
|
106
|
-
# disk. If the file is stored in S3, the path is the "key" part of the
|
107
|
-
# and the :bucket option refers to the S3 bucket.
|
112
|
+
# file is stored in the filesystem the path refers to the path of the file
|
113
|
+
# on disk. If the file is stored in S3, the path is the "key" part of the
|
114
|
+
# URL, and the :bucket option refers to the S3 bucket.
|
108
115
|
def path style = nil #:nodoc:
|
109
116
|
original_filename.nil? ? nil : interpolate(@path, style)
|
110
117
|
end
|
@@ -134,32 +141,38 @@ module Paperclip
|
|
134
141
|
# the instance's errors and returns false, cancelling the save.
|
135
142
|
def save
|
136
143
|
if valid?
|
137
|
-
|
144
|
+
log("Saving files for #{name}")
|
138
145
|
flush_deletes
|
139
146
|
flush_writes
|
140
147
|
@dirty = false
|
141
148
|
true
|
142
149
|
else
|
143
|
-
|
150
|
+
log("Errors on #{name}. Not saving.")
|
144
151
|
flush_errors
|
145
152
|
false
|
146
153
|
end
|
147
154
|
end
|
148
155
|
|
149
|
-
# Returns the name of the file as originally assigned, and
|
156
|
+
# Returns the name of the file as originally assigned, and lives in the
|
150
157
|
# <attachment>_file_name attribute of the model.
|
151
158
|
def original_filename
|
152
159
|
instance_read(:file_name)
|
153
160
|
end
|
154
161
|
|
162
|
+
# Returns the size of the file as originally assigned, and lives in the
|
163
|
+
# <attachment>_file_size attribute of the model.
|
155
164
|
def size
|
156
165
|
instance_read(:file_size) || (@queued_for_write[:original] && @queued_for_write[:original].size)
|
157
166
|
end
|
158
167
|
|
168
|
+
# Returns the content_type of the file as originally assigned, and lives
|
169
|
+
# in the <attachment>_content_type attribute of the model.
|
159
170
|
def content_type
|
160
171
|
instance_read(:content_type)
|
161
172
|
end
|
162
173
|
|
174
|
+
# Returns the last modified time of the file as originally assigned, and
|
175
|
+
# lives in the <attachment>_updated_at attribute of the model.
|
163
176
|
def updated_at
|
164
177
|
time = instance_read(:updated_at)
|
165
178
|
time && time.to_i
|
@@ -181,7 +194,7 @@ module Paperclip
|
|
181
194
|
attachment.original_filename.gsub(/#{File.extname(attachment.original_filename)}$/, "")
|
182
195
|
end,
|
183
196
|
:extension => lambda do |attachment,style|
|
184
|
-
((style = attachment.styles[style]) && style
|
197
|
+
((style = attachment.styles[style]) && style[:format]) ||
|
185
198
|
File.extname(attachment.original_filename).gsub(/^\.+/, "")
|
186
199
|
end,
|
187
200
|
:id => lambda{|attachment,style| attachment.instance.id },
|
@@ -193,10 +206,10 @@ module Paperclip
|
|
193
206
|
}
|
194
207
|
end
|
195
208
|
|
196
|
-
# This method really shouldn't be called that often. It's expected use is
|
197
|
-
# paperclip:refresh rake task and that's it. It will regenerate all
|
198
|
-
# forcefully, by reobtaining the original file and going through
|
199
|
-
# again.
|
209
|
+
# This method really shouldn't be called that often. It's expected use is
|
210
|
+
# in the paperclip:refresh rake task and that's it. It will regenerate all
|
211
|
+
# thumbnails forcefully, by reobtaining the original file and going through
|
212
|
+
# the post-process again.
|
200
213
|
def reprocess!
|
201
214
|
new_original = Tempfile.new("paperclip-reprocess")
|
202
215
|
if old_original = to_file(:original)
|
@@ -214,16 +227,22 @@ module Paperclip
|
|
214
227
|
end
|
215
228
|
end
|
216
229
|
|
230
|
+
# Returns true if a file has been assigned.
|
217
231
|
def file?
|
218
232
|
!original_filename.blank?
|
219
233
|
end
|
220
234
|
|
235
|
+
# Writes the attachment-specific attribute on the instance. For example,
|
236
|
+
# instance_write(:file_name, "me.jpg") will write "me.jpg" to the instance's
|
237
|
+
# "avatar_file_name" field (assuming the attachment is called avatar).
|
221
238
|
def instance_write(attr, value)
|
222
239
|
setter = :"#{name}_#{attr}="
|
223
240
|
responds = instance.respond_to?(setter)
|
224
241
|
instance.send(setter, value) if responds || attr.to_s == "file_name"
|
225
242
|
end
|
226
243
|
|
244
|
+
# Reads the attachment-specific attribute on the instance. See instance_write
|
245
|
+
# for more details.
|
227
246
|
def instance_read(attr)
|
228
247
|
getter = :"#{name}_#{attr}"
|
229
248
|
responds = instance.respond_to?(getter)
|
@@ -236,6 +255,10 @@ module Paperclip
|
|
236
255
|
instance.logger
|
237
256
|
end
|
238
257
|
|
258
|
+
def log message
|
259
|
+
logger.info("[paperclip] #{message}")
|
260
|
+
end
|
261
|
+
|
239
262
|
def valid_assignment? file #:nodoc:
|
240
263
|
file.nil? || (file.respond_to?(:original_filename) && file.respond_to?(:content_type))
|
241
264
|
end
|
@@ -255,9 +278,23 @@ module Paperclip
|
|
255
278
|
|
256
279
|
def normalize_style_definition
|
257
280
|
@styles.each do |name, args|
|
258
|
-
|
259
|
-
|
260
|
-
|
281
|
+
unless args.is_a? Hash
|
282
|
+
dimensions, format = [args, nil].flatten[0..1]
|
283
|
+
format = nil if format.blank?
|
284
|
+
@styles[name] = {
|
285
|
+
:processors => @processors,
|
286
|
+
:geometry => dimensions,
|
287
|
+
:format => format,
|
288
|
+
:whiny => @whiny,
|
289
|
+
:convert_options => extra_options_for(name)
|
290
|
+
}
|
291
|
+
else
|
292
|
+
@styles[name] = {
|
293
|
+
:processors => @processors,
|
294
|
+
:whiny => @whiny,
|
295
|
+
:convert_options => extra_options_for(name)
|
296
|
+
}.merge(@styles[name])
|
297
|
+
end
|
261
298
|
end
|
262
299
|
end
|
263
300
|
|
@@ -272,20 +309,25 @@ module Paperclip
|
|
272
309
|
|
273
310
|
def post_process #:nodoc:
|
274
311
|
return if @queued_for_write[:original].nil?
|
275
|
-
|
312
|
+
return if callback(:before_post_process) == false
|
313
|
+
return if callback(:"before_#{name}_post_process") == false
|
314
|
+
log("Post-processing #{name}")
|
276
315
|
@styles.each do |name, args|
|
277
316
|
begin
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
format,
|
283
|
-
extra_options_for(name),
|
284
|
-
@whiny_thumbnails)
|
317
|
+
@queued_for_write[name] = @queued_for_write[:original]
|
318
|
+
args[:processors].each do |processor|
|
319
|
+
@queued_for_write[name] = Paperclip.processor(processor).make(@queued_for_write[name], args)
|
320
|
+
end
|
285
321
|
rescue PaperclipError => e
|
286
|
-
(@errors[:processing] ||= []) << e.message if @
|
322
|
+
(@errors[:processing] ||= []) << e.message if @whiny
|
287
323
|
end
|
288
324
|
end
|
325
|
+
callback(:"after_#{name}_post_process")
|
326
|
+
callback(:after_post_process)
|
327
|
+
end
|
328
|
+
|
329
|
+
def callback which
|
330
|
+
instance.run_callbacks(which, @queued_for_write){|result, obj| result == false }
|
289
331
|
end
|
290
332
|
|
291
333
|
def interpolate pattern, style = default_style #:nodoc:
|
@@ -300,7 +342,7 @@ module Paperclip
|
|
300
342
|
|
301
343
|
def queue_existing_for_delete #:nodoc:
|
302
344
|
return unless file?
|
303
|
-
|
345
|
+
log("Queueing the existing files for #{name} for deletion.")
|
304
346
|
@queued_for_delete += [:original, *@styles.keys].uniq.map do |style|
|
305
347
|
path(style) if exists?(style)
|
306
348
|
end.compact
|