thoughtbot-paperclip 2.2.8 → 2.2.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +9 -7
- data/Rakefile +29 -7
- data/lib/paperclip.rb +62 -30
- data/lib/paperclip/attachment.rb +64 -55
- data/lib/paperclip/geometry.rb +1 -1
- data/lib/paperclip/interpolations.rb +105 -0
- data/lib/paperclip/processor.rb +7 -6
- data/lib/paperclip/storage.rb +11 -9
- data/test/attachment_test.rb +29 -3
- data/test/fixtures/s3.yml +4 -0
- data/test/geometry_test.rb +9 -0
- data/test/helper.rb +21 -3
- data/test/interpolations_test.rb +120 -0
- data/test/paperclip_test.rb +58 -0
- data/test/storage_test.rb +12 -7
- metadata +4 -16
data/README.rdoc
CHANGED
@@ -79,8 +79,7 @@ validates_attachment_size.
|
|
79
79
|
|
80
80
|
The files that are assigned as attachments are, by default, placed in the
|
81
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
|
82
|
+
location is ":rails_root/public/system/:attachment/:id/:style/:filename". This
|
84
83
|
location was chosen because on standard Capistrano deployments, the
|
85
84
|
public/system directory is symlinked to the app's shared directory, meaning it
|
86
85
|
will survive between deployments. For example, using that :path, you may have a
|
@@ -89,7 +88,7 @@ file at
|
|
89
88
|
/data/myapp/releases/20081229172410/public/system/avatars/13/small/my_pic.png
|
90
89
|
|
91
90
|
NOTE: This is a change from previous versions of Paperclip, but is overall a
|
92
|
-
safer choice for the
|
91
|
+
safer choice for the default file store.
|
93
92
|
|
94
93
|
You may also choose to store your files using Amazon's S3 service. You can find
|
95
94
|
more information about S3 storage at the description for
|
@@ -104,9 +103,9 @@ variables.
|
|
104
103
|
|
105
104
|
==Post Processing
|
106
105
|
|
107
|
-
Paperclip supports an
|
106
|
+
Paperclip supports an extensible selection of post-processors. When you define
|
108
107
|
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
|
108
|
+
"styles" are actually "thumbnails". However, you can do much more than just
|
110
109
|
thumbnail images. By defining a subclass of Paperclip::Processor, you can
|
111
110
|
perform any processing you want on the files that are attached. Any file in
|
112
111
|
your Rails app's lib/paperclip_processors directory is automatically loaded by
|
@@ -141,7 +140,10 @@ For example, assuming we had this definition:
|
|
141
140
|
|
142
141
|
then both the :rotator processor and the :ocr processor would receive the
|
143
142
|
options "{ :quality => :better }". This parameter may not mean anything to one
|
144
|
-
or more or the processors, and they are
|
143
|
+
or more or the processors, and they are expected to ignore it.
|
144
|
+
|
145
|
+
NOTE: Because processors operate by turning the original attachment into the
|
146
|
+
styles, no processors will be run if there are no styles defined.
|
145
147
|
|
146
148
|
==Events
|
147
149
|
|
@@ -157,7 +159,7 @@ will halt. Returning false in an after_ filter will not halt anything, but you
|
|
157
159
|
can access the model and the attachment if necessary.
|
158
160
|
|
159
161
|
NOTE: Post processing will not even *start* if the attachment is not valid
|
160
|
-
according to the validations. Your callbacks
|
162
|
+
according to the validations. Your callbacks and processors will *only* be
|
161
163
|
called with valid attachments.
|
162
164
|
|
163
165
|
==Contributing
|
data/Rakefile
CHANGED
@@ -42,8 +42,23 @@ task :clean do |t|
|
|
42
42
|
FileUtils.rm_rf "pkg"
|
43
43
|
FileUtils.rm "test/debug.log" rescue nil
|
44
44
|
FileUtils.rm "test/paperclip.db" rescue nil
|
45
|
+
Dir.glob("paperclip-*.gem").each{|f| FileUtils.rm f }
|
45
46
|
end
|
46
47
|
|
48
|
+
include_file_globs = ["README*",
|
49
|
+
"LICENSE",
|
50
|
+
"Rakefile",
|
51
|
+
"init.rb",
|
52
|
+
"{generators,lib,tasks,test,shoulda_macros}/**/*"]
|
53
|
+
exclude_file_globs = ["test/s3.yml",
|
54
|
+
"test/debug.log",
|
55
|
+
"test/paperclip.db",
|
56
|
+
"test/doc",
|
57
|
+
"test/doc/*",
|
58
|
+
"test/pkg",
|
59
|
+
"test/pkg/*",
|
60
|
+
"test/tmp",
|
61
|
+
"test/tmp/*"]
|
47
62
|
spec = Gem::Specification.new do |s|
|
48
63
|
s.name = "paperclip"
|
49
64
|
s.version = Paperclip::VERSION
|
@@ -52,11 +67,7 @@ spec = Gem::Specification.new do |s|
|
|
52
67
|
s.homepage = "http://www.thoughtbot.com/projects/paperclip"
|
53
68
|
s.platform = Gem::Platform::RUBY
|
54
69
|
s.summary = "File attachments as attributes for ActiveRecord"
|
55
|
-
s.files = FileList[
|
56
|
-
"LICENSE",
|
57
|
-
"Rakefile",
|
58
|
-
"init.rb",
|
59
|
-
"{generators,lib,tasks,test,shoulda_macros}/**/*"].to_a
|
70
|
+
s.files = FileList[include_file_globs].to_a - FileList[exclude_file_globs].to_a
|
60
71
|
s.require_path = "lib"
|
61
72
|
s.test_files = FileList["test/**/test_*.rb"].to_a
|
62
73
|
s.rubyforge_project = "paperclip"
|
@@ -64,14 +75,25 @@ spec = Gem::Specification.new do |s|
|
|
64
75
|
s.extra_rdoc_files = FileList["README*"].to_a
|
65
76
|
s.rdoc_options << '--line-numbers' << '--inline-source'
|
66
77
|
s.requirements << "ImageMagick"
|
67
|
-
s.add_runtime_dependency 'right_aws'
|
68
78
|
s.add_development_dependency 'thoughtbot-shoulda'
|
69
79
|
s.add_development_dependency 'mocha'
|
70
80
|
end
|
81
|
+
|
82
|
+
desc "Print a list of the files to be put into the gem"
|
83
|
+
task :manifest => :clean do
|
84
|
+
spec.files.each do |file|
|
85
|
+
puts file
|
86
|
+
end
|
87
|
+
end
|
71
88
|
|
72
89
|
desc "Generate a gemspec file for GitHub"
|
73
|
-
task :gemspec do
|
90
|
+
task :gemspec => :clean do
|
74
91
|
File.open("#{spec.name}.gemspec", 'w') do |f|
|
75
92
|
f.write spec.to_ruby
|
76
93
|
end
|
94
|
+
end
|
95
|
+
|
96
|
+
desc "Build the gem into the current directory"
|
97
|
+
task :gem => :gemspec do
|
98
|
+
`gem build #{spec.name}.gemspec`
|
77
99
|
end
|
data/lib/paperclip.rb
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
# columns to your table.
|
6
6
|
#
|
7
7
|
# Author:: Jon Yurek
|
8
|
-
# Copyright:: Copyright (c) 2008 thoughtbot, inc.
|
8
|
+
# Copyright:: Copyright (c) 2008-2009 thoughtbot, inc.
|
9
9
|
# License:: MIT License (http://www.opensource.org/licenses/mit-license.php)
|
10
10
|
#
|
11
11
|
# Paperclip defines an attachment as any file, though it makes special considerations
|
@@ -32,6 +32,7 @@ require 'paperclip/geometry'
|
|
32
32
|
require 'paperclip/processor'
|
33
33
|
require 'paperclip/thumbnail'
|
34
34
|
require 'paperclip/storage'
|
35
|
+
require 'paperclip/interpolations'
|
35
36
|
require 'paperclip/attachment'
|
36
37
|
if defined? RAILS_ROOT
|
37
38
|
Dir.glob(File.join(File.expand_path(RAILS_ROOT), "lib", "paperclip_processors", "*.rb")).each do |processor|
|
@@ -43,11 +44,11 @@ end
|
|
43
44
|
# documentation for Paperclip::ClassMethods for more useful information.
|
44
45
|
module Paperclip
|
45
46
|
|
46
|
-
VERSION = "2.2.
|
47
|
+
VERSION = "2.2.9.1"
|
47
48
|
|
48
49
|
class << self
|
49
50
|
# Provides configurability to Paperclip. There are a number of options available, such as:
|
50
|
-
# *
|
51
|
+
# * whiny: Will raise an error if Paperclip cannot process thumbnails of
|
51
52
|
# an uploaded image. Defaults to true.
|
52
53
|
# * log: Logs progress to the Rails log. Uses ActiveRecord's logger, so honors
|
53
54
|
# log levels, etc. Defaults to true.
|
@@ -57,10 +58,11 @@ module Paperclip
|
|
57
58
|
# * image_magick_path: Deprecated alias of command_path.
|
58
59
|
def options
|
59
60
|
@options ||= {
|
60
|
-
:
|
61
|
+
:whiny => true,
|
61
62
|
:image_magick_path => nil,
|
62
63
|
:command_path => nil,
|
63
64
|
:log => true,
|
65
|
+
:log_command => false,
|
64
66
|
:swallow_stderr => true
|
65
67
|
}
|
66
68
|
end
|
@@ -74,7 +76,7 @@ module Paperclip
|
|
74
76
|
end
|
75
77
|
|
76
78
|
def interpolates key, &block
|
77
|
-
Paperclip::
|
79
|
+
Paperclip::Interpolations[key] = block
|
78
80
|
end
|
79
81
|
|
80
82
|
# The run method takes a command to execute and a string of parameters
|
@@ -86,9 +88,14 @@ module Paperclip
|
|
86
88
|
# If the command returns with a result code that is not one of the
|
87
89
|
# expected_outcodes, a PaperclipCommandLineError will be raised. Generally
|
88
90
|
# a code of 0 is expected, but a list of codes may be passed if necessary.
|
91
|
+
#
|
92
|
+
# This method can log the command being run when
|
93
|
+
# Paperclip.options[:log_command] is set to true (defaults to false). This
|
94
|
+
# will only log if logging in general is set to true as well.
|
89
95
|
def run cmd, params = "", expected_outcodes = 0
|
90
96
|
command = %Q<#{%Q[#{path_for_command(cmd)} #{params}].gsub(/\s+/, " ")}>
|
91
97
|
command = "#{command} 2>#{bit_bucket}" if Paperclip.options[:swallow_stderr]
|
98
|
+
Paperclip.log(command) if Paperclip.options[:log_command]
|
92
99
|
output = `#{command}`
|
93
100
|
unless [expected_outcodes].flatten.include?($?.exitstatus)
|
94
101
|
raise PaperclipCommandLineError, "Error while running #{cmd}"
|
@@ -115,6 +122,20 @@ module Paperclip
|
|
115
122
|
end
|
116
123
|
processor
|
117
124
|
end
|
125
|
+
|
126
|
+
# Log a paperclip-specific line. Uses ActiveRecord::Base.logger
|
127
|
+
# by default. Set Paperclip.options[:log] to false to turn off.
|
128
|
+
def log message
|
129
|
+
logger.info("[paperclip] #{message}") if logging?
|
130
|
+
end
|
131
|
+
|
132
|
+
def logger #:nodoc:
|
133
|
+
ActiveRecord::Base.logger
|
134
|
+
end
|
135
|
+
|
136
|
+
def logging? #:nodoc:
|
137
|
+
options[:log]
|
138
|
+
end
|
118
139
|
end
|
119
140
|
|
120
141
|
class PaperclipError < StandardError #:nodoc:
|
@@ -125,6 +146,9 @@ module Paperclip
|
|
125
146
|
|
126
147
|
class NotIdentifiedByImageMagickError < PaperclipError #:nodoc:
|
127
148
|
end
|
149
|
+
|
150
|
+
class InfiniteInterpolationError < PaperclipError #:nodoc:
|
151
|
+
end
|
128
152
|
|
129
153
|
module ClassMethods
|
130
154
|
# +has_attached_file+ gives the class it is called on an attribute that maps to a file. This
|
@@ -141,9 +165,9 @@ module Paperclip
|
|
141
165
|
# that can control permissions. You can specify the full domain and path, but usually
|
142
166
|
# just an absolute path is sufficient. The leading slash *must* be included manually for
|
143
167
|
# absolute paths. The default value is
|
144
|
-
# "/system/:attachment/:id/:style/:
|
168
|
+
# "/system/:attachment/:id/:style/:filename". See
|
145
169
|
# Paperclip::Attachment#interpolate for more information on variable interpolaton.
|
146
|
-
# :url => "/:class/:attachment/:id/:style_:
|
170
|
+
# :url => "/:class/:attachment/:id/:style_:filename"
|
147
171
|
# :url => "http://some.other.host/stuff/:class/:id_:extension"
|
148
172
|
# * +default_url+: The URL that will be returned if there is no attachment assigned.
|
149
173
|
# This field is interpolated just as the url is. The default value is
|
@@ -161,9 +185,10 @@ module Paperclip
|
|
161
185
|
# has_attached_file :avatar, :styles => { :normal => "100x100#" },
|
162
186
|
# :default_style => :normal
|
163
187
|
# user.avatar.url # => "/avatars/23/normal_me.png"
|
164
|
-
# * +
|
188
|
+
# * +whiny+: Will raise an error if Paperclip cannot post_process an uploaded file due
|
165
189
|
# to a command line error. This will override the global setting for this attachment.
|
166
|
-
# Defaults to true.
|
190
|
+
# Defaults to true. This option used to be called :whiny_thumbanils, but this is
|
191
|
+
# deprecated.
|
167
192
|
# * +convert_options+: When creating thumbnails, use this free-form options
|
168
193
|
# field to pass in various convert command options. Typical options are "-strip" to
|
169
194
|
# remove all Exif data from the image (save space for thumbnails and avatars) or
|
@@ -179,6 +204,9 @@ module Paperclip
|
|
179
204
|
# :all => "-strip",
|
180
205
|
# :negative => "-negate"
|
181
206
|
# }
|
207
|
+
# NOTE: While not deprecated yet, it is not recommended to specify options this way.
|
208
|
+
# It is recommended that :convert_options option be included in the hash passed to each
|
209
|
+
# :styles for compatability with future versions.
|
182
210
|
# * +storage+: Chooses the storage backend where the files will be stored. The current
|
183
211
|
# choices are :filesystem and :s3. The default is :filesystem. Make sure you read the
|
184
212
|
# documentation for Paperclip::Storage::Filesystem and Paperclip::Storage::S3
|
@@ -187,7 +215,7 @@ module Paperclip
|
|
187
215
|
include InstanceMethods
|
188
216
|
|
189
217
|
write_inheritable_attribute(:attachment_definitions, {}) if attachment_definitions.nil?
|
190
|
-
attachment_definitions[name] = {:validations =>
|
218
|
+
attachment_definitions[name] = {:validations => []}.merge(options)
|
191
219
|
|
192
220
|
after_save :save_attached_files
|
193
221
|
before_destroy :destroy_attached_files
|
@@ -220,30 +248,39 @@ module Paperclip
|
|
220
248
|
# * +less_than+: equivalent to :in => 0..options[:less_than]
|
221
249
|
# * +greater_than+: equivalent to :in => options[:greater_than]..Infinity
|
222
250
|
# * +message+: error message to display, use :min and :max as replacements
|
251
|
+
# * +if+: A lambda or name of a method on the instance. Validation will only
|
252
|
+
# be run is this lambda or method returns true.
|
253
|
+
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
223
254
|
def validates_attachment_size name, options = {}
|
224
255
|
min = options[:greater_than] || (options[:in] && options[:in].first) || 0
|
225
256
|
max = options[:less_than] || (options[:in] && options[:in].last) || (1.0/0)
|
226
257
|
range = (min..max)
|
227
258
|
message = options[:message] || "file size must be between :min and :max bytes."
|
228
259
|
|
229
|
-
attachment_definitions[name][:validations][:size
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
end
|
260
|
+
attachment_definitions[name][:validations] << [:size, {:range => range,
|
261
|
+
:message => message,
|
262
|
+
:if => options[:if],
|
263
|
+
:unless => options[:unless]}]
|
234
264
|
end
|
235
265
|
|
236
266
|
# Adds errors if thumbnail creation fails. The same as specifying :whiny_thumbnails => true.
|
237
267
|
def validates_attachment_thumbnails name, options = {}
|
268
|
+
warn('[DEPRECATION] validates_attachment_thumbnail is deprecated. ' +
|
269
|
+
'This validation is on by default and will be removed from future versions. ' +
|
270
|
+
'If you wish to turn it off, supply :whiny => false in your definition.')
|
238
271
|
attachment_definitions[name][:whiny_thumbnails] = true
|
239
272
|
end
|
240
273
|
|
241
274
|
# Places ActiveRecord-style validations on the presence of a file.
|
275
|
+
# Options:
|
276
|
+
# * +if+: A lambda or name of a method on the instance. Validation will only
|
277
|
+
# be run is this lambda or method returns true.
|
278
|
+
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
242
279
|
def validates_attachment_presence name, options = {}
|
243
280
|
message = options[:message] || "must be set."
|
244
|
-
attachment_definitions[name][:validations][:presence
|
245
|
-
|
246
|
-
|
281
|
+
attachment_definitions[name][:validations] << [:presence, {:message => message,
|
282
|
+
:if => options[:if],
|
283
|
+
:unless => options[:unless]}]
|
247
284
|
end
|
248
285
|
|
249
286
|
# Places ActiveRecord-style validations on the content type of the file
|
@@ -256,22 +293,17 @@ module Paperclip
|
|
256
293
|
# match. Allows all by default.
|
257
294
|
# * +message+: The message to display when the uploaded file has an invalid
|
258
295
|
# content type.
|
296
|
+
# * +if+: A lambda or name of a method on the instance. Validation will only
|
297
|
+
# be run is this lambda or method returns true.
|
298
|
+
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
259
299
|
# NOTE: If you do not specify an [attachment]_content_type field on your
|
260
300
|
# model, content_type validation will work _ONLY upon assignment_ and
|
261
301
|
# re-validation after the instance has been reloaded will always succeed.
|
262
302
|
def validates_attachment_content_type name, options = {}
|
263
|
-
attachment_definitions[name][:validations][:content_type
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
unless valid_types.blank?
|
268
|
-
content_type = attachment.instance_read(:content_type)
|
269
|
-
unless valid_types.any?{|t| content_type.nil? || t === content_type }
|
270
|
-
options[:message] || "is not one of the allowed file types."
|
271
|
-
end
|
272
|
-
end
|
273
|
-
end
|
274
|
-
end
|
303
|
+
attachment_definitions[name][:validations] << [:content_type, {:content_type => options[:content_type],
|
304
|
+
:message => options[:message],
|
305
|
+
:if => options[:if],
|
306
|
+
:unless => options[:unless]}]
|
275
307
|
end
|
276
308
|
|
277
309
|
# Returns the attachment definitions defined by each call to
|
data/lib/paperclip/attachment.rb
CHANGED
@@ -6,17 +6,18 @@ module Paperclip
|
|
6
6
|
|
7
7
|
def self.default_options
|
8
8
|
@default_options ||= {
|
9
|
-
:url => "/system/:attachment/:id/:style/:
|
10
|
-
:path => ":rails_root/public
|
9
|
+
:url => "/system/:attachment/:id/:style/:filename",
|
10
|
+
:path => ":rails_root/public:url",
|
11
11
|
:styles => {},
|
12
12
|
:default_url => "/:attachment/:style/missing.png",
|
13
13
|
:default_style => :original,
|
14
|
-
:validations =>
|
15
|
-
:storage => :filesystem
|
14
|
+
:validations => [],
|
15
|
+
:storage => :filesystem,
|
16
|
+
:whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails]
|
16
17
|
}
|
17
18
|
end
|
18
19
|
|
19
|
-
attr_reader :name, :instance, :styles, :default_style, :convert_options, :queued_for_write
|
20
|
+
attr_reader :name, :instance, :styles, :default_style, :convert_options, :queued_for_write, :options
|
20
21
|
|
21
22
|
# Creates an Attachment object. +name+ is the name of the attachment,
|
22
23
|
# +instance+ is the ActiveRecord object instance it's attached to, and
|
@@ -37,7 +38,7 @@ module Paperclip
|
|
37
38
|
@validations = options[:validations]
|
38
39
|
@default_style = options[:default_style]
|
39
40
|
@storage = options[:storage]
|
40
|
-
@whiny = options[:whiny_thumbnails]
|
41
|
+
@whiny = options[:whiny_thumbnails] || options[:whiny]
|
41
42
|
@convert_options = options[:convert_options] || {}
|
42
43
|
@processors = options[:processors] || [:thumbnail]
|
43
44
|
@options = options
|
@@ -60,11 +61,7 @@ module Paperclip
|
|
60
61
|
# If the file that is assigned is not valid, the processing (i.e.
|
61
62
|
# thumbnailing, etc) will NOT be run.
|
62
63
|
def assign uploaded_file
|
63
|
-
|
64
|
-
unless @instance.class.column_names.include?("#{name}_#{field}")
|
65
|
-
raise PaperclipError.new("#{@instance.class} model does not have required column '#{name}_#{field}'")
|
66
|
-
end
|
67
|
-
end
|
64
|
+
ensure_required_accessors!
|
68
65
|
|
69
66
|
if uploaded_file.is_a?(Paperclip::Attachment)
|
70
67
|
uploaded_file = uploaded_file.to_file(:original)
|
@@ -111,7 +108,7 @@ module Paperclip
|
|
111
108
|
# file is stored in the filesystem the path refers to the path of the file
|
112
109
|
# on disk. If the file is stored in S3, the path is the "key" part of the
|
113
110
|
# URL, and the :bucket option refers to the S3 bucket.
|
114
|
-
def path style =
|
111
|
+
def path style = default_style
|
115
112
|
original_filename.nil? ? nil : interpolate(@path, style)
|
116
113
|
end
|
117
114
|
|
@@ -192,32 +189,16 @@ module Paperclip
|
|
192
189
|
time && time.to_i
|
193
190
|
end
|
194
191
|
|
195
|
-
#
|
196
|
-
#
|
197
|
-
#
|
198
|
-
#
|
199
|
-
#
|
192
|
+
# Paths and URLs can have a number of variables interpolated into them
|
193
|
+
# to vary the storage location based on name, id, style, class, etc.
|
194
|
+
# This method is a deprecated access into supplying and retrieving these
|
195
|
+
# interpolations. Future access should use either Paperclip.interpolates
|
196
|
+
# or extend the Paperclip::Interpolations module directly.
|
200
197
|
def self.interpolations
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
attachment.instance.class.name.underscore.pluralize
|
206
|
-
end,
|
207
|
-
:basename => lambda do |attachment,style|
|
208
|
-
attachment.original_filename.gsub(/#{File.extname(attachment.original_filename)}$/, "")
|
209
|
-
end,
|
210
|
-
:extension => lambda do |attachment,style|
|
211
|
-
((style = attachment.styles[style]) && style[:format]) ||
|
212
|
-
File.extname(attachment.original_filename).gsub(/^\.+/, "")
|
213
|
-
end,
|
214
|
-
:id => lambda{|attachment,style| attachment.instance.id },
|
215
|
-
:id_partition => lambda do |attachment, style|
|
216
|
-
("%09d" % attachment.instance.id).scan(/\d{3}/).join("/")
|
217
|
-
end,
|
218
|
-
:attachment => lambda{|attachment,style| attachment.name.to_s.downcase.pluralize },
|
219
|
-
:style => lambda{|attachment,style| style || attachment.default_style },
|
220
|
-
}
|
198
|
+
warn('[DEPRECATION] Paperclip::Attachment.interpolations is deprecated ' +
|
199
|
+
'and will be removed from future versions. ' +
|
200
|
+
'Use Paperclip.interpolates instead')
|
201
|
+
Paperclip::Interpolations
|
221
202
|
end
|
222
203
|
|
223
204
|
# This method really shouldn't be called that often. It's expected use is
|
@@ -269,16 +250,16 @@ module Paperclip
|
|
269
250
|
|
270
251
|
private
|
271
252
|
|
272
|
-
def
|
273
|
-
|
253
|
+
def ensure_required_accessors! #:nodoc:
|
254
|
+
%w(file_name).each do |field|
|
255
|
+
unless @instance.respond_to?("#{name}_#{field}") && @instance.respond_to?("#{name}_#{field}=")
|
256
|
+
raise PaperclipError.new("#{@instance.class} model missing required attr_accessor for '#{name}_#{field}'")
|
257
|
+
end
|
258
|
+
end
|
274
259
|
end
|
275
260
|
|
276
261
|
def log message #:nodoc:
|
277
|
-
|
278
|
-
end
|
279
|
-
|
280
|
-
def logging? #:nodoc:
|
281
|
-
Paperclip.options[:log]
|
262
|
+
Paperclip.log(message)
|
282
263
|
end
|
283
264
|
|
284
265
|
def valid_assignment? file #:nodoc:
|
@@ -288,8 +269,8 @@ module Paperclip
|
|
288
269
|
def validate #:nodoc:
|
289
270
|
unless @validation_errors
|
290
271
|
@validation_errors = @validations.inject({}) do |errors, validation|
|
291
|
-
name,
|
292
|
-
errors[name] =
|
272
|
+
name, options = validation
|
273
|
+
errors[name] = send(:"validate_#{name}", options) if allow_validation?(options)
|
293
274
|
errors
|
294
275
|
end
|
295
276
|
@validation_errors.reject!{|k,v| v == nil }
|
@@ -298,6 +279,40 @@ module Paperclip
|
|
298
279
|
@validation_errors
|
299
280
|
end
|
300
281
|
|
282
|
+
def allow_validation? options #:nodoc:
|
283
|
+
(options[:if].nil? || check_guard(options[:if])) && (options[:unless].nil? || !check_guard(options[:unless]))
|
284
|
+
end
|
285
|
+
|
286
|
+
def check_guard guard #:nodoc:
|
287
|
+
if guard.respond_to? :call
|
288
|
+
guard.call(instance)
|
289
|
+
elsif ! guard.blank?
|
290
|
+
instance.send(guard.to_s)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def validate_size options #:nodoc:
|
295
|
+
if file? && !options[:range].include?(size.to_i)
|
296
|
+
options[:message].gsub(/:min/, options[:min].to_s).gsub(/:max/, options[:max].to_s)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def validate_presence options #:nodoc:
|
301
|
+
options[:message] unless file?
|
302
|
+
end
|
303
|
+
|
304
|
+
def validate_content_type options #:nodoc:
|
305
|
+
valid_types = [options[:content_type]].flatten
|
306
|
+
unless original_filename.blank?
|
307
|
+
unless valid_types.blank?
|
308
|
+
content_type = instance_read(:content_type)
|
309
|
+
unless valid_types.any?{|t| content_type.nil? || t === content_type }
|
310
|
+
options[:message] || "is not one of the allowed file types."
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
301
316
|
def normalize_style_definition #:nodoc:
|
302
317
|
@styles.each do |name, args|
|
303
318
|
unless args.is_a? Hash
|
@@ -349,7 +364,7 @@ module Paperclip
|
|
349
364
|
return if fire_events(:after)
|
350
365
|
end
|
351
366
|
|
352
|
-
def fire_events(which)
|
367
|
+
def fire_events(which) #:nodoc:
|
353
368
|
return true if callback(:"#{which}_post_process") == false
|
354
369
|
return true if callback(:"#{which}_#{name}_post_process") == false
|
355
370
|
end
|
@@ -358,7 +373,7 @@ module Paperclip
|
|
358
373
|
instance.run_callbacks(which, @queued_for_write){|result, obj| result == false }
|
359
374
|
end
|
360
375
|
|
361
|
-
def post_process_styles
|
376
|
+
def post_process_styles #:nodoc:
|
362
377
|
@styles.each do |name, args|
|
363
378
|
begin
|
364
379
|
raise RuntimeError.new("Style #{name} has no processors defined.") if args[:processors].blank?
|
@@ -373,13 +388,7 @@ module Paperclip
|
|
373
388
|
end
|
374
389
|
|
375
390
|
def interpolate pattern, style = default_style #:nodoc:
|
376
|
-
|
377
|
-
interpolations.reverse.inject( pattern.dup ) do |result, interpolation|
|
378
|
-
tag, blk = interpolation
|
379
|
-
result.gsub(/:#{tag}/) do |match|
|
380
|
-
blk.call( self, style )
|
381
|
-
end
|
382
|
-
end
|
391
|
+
Paperclip::Interpolations.interpolate(pattern, self, style)
|
383
392
|
end
|
384
393
|
|
385
394
|
def queue_existing_for_delete #:nodoc:
|
data/lib/paperclip/geometry.rb
CHANGED
@@ -26,7 +26,7 @@ module Paperclip
|
|
26
26
|
|
27
27
|
# Parses a "WxH" formatted string, where W is the width and H is the height.
|
28
28
|
def self.parse string
|
29
|
-
if match = (string && string.match(/\b(\d*)x?(\d*)\b([\>\<\#\@\%^!])?/))
|
29
|
+
if match = (string && string.match(/\b(\d*)x?(\d*)\b([\>\<\#\@\%^!])?/i))
|
30
30
|
Geometry.new(*match[1,3])
|
31
31
|
end
|
32
32
|
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Paperclip
|
2
|
+
# This module contains all the methods that are available for interpolation
|
3
|
+
# in paths and urls. To add your own (or override an existing one), you
|
4
|
+
# can either open this module and define it, or call the
|
5
|
+
# Paperclip.interpolates method.
|
6
|
+
module Interpolations
|
7
|
+
extend self
|
8
|
+
|
9
|
+
# Hash assignment of interpolations. Included only for compatability,
|
10
|
+
# and is not intended for normal use.
|
11
|
+
def self.[]= name, block
|
12
|
+
define_method(name, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Hash access of interpolations. Included only for compatability,
|
16
|
+
# and is not intended for normal use.
|
17
|
+
def self.[] name
|
18
|
+
method(name)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns a sorted list of all interpolations.
|
22
|
+
def self.all
|
23
|
+
self.instance_methods(false).sort
|
24
|
+
end
|
25
|
+
|
26
|
+
# Perform the actual interpolation. Takes the pattern to interpolate
|
27
|
+
# and the arguments to pass, which are the attachment and style name.
|
28
|
+
def self.interpolate pattern, *args
|
29
|
+
all.reverse.inject( pattern.dup ) do |result, tag|
|
30
|
+
result.gsub(/:#{tag}/) do |match|
|
31
|
+
send( tag, *args )
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the filename, the same way as ":basename.:extension" would.
|
37
|
+
def filename attachment, style
|
38
|
+
"#{basename(attachment, style)}.#{extension(attachment, style)}"
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the interpolated URL. Will raise an error if the url itself
|
42
|
+
# contains ":url" to prevent infinite recursion. This interpolation
|
43
|
+
# is used in the default :path to ease default specifications.
|
44
|
+
def url attachment, style
|
45
|
+
raise InfiniteInterpolationError if attachment.options[:url].include?(":url")
|
46
|
+
attachment.url(style, false)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns the timestamp as defined by the <attachment>_updated_at field
|
50
|
+
def timestamp attachment, style
|
51
|
+
attachment.instance_read(:updated_at).to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the RAILS_ROOT constant.
|
55
|
+
def rails_root attachment, style
|
56
|
+
RAILS_ROOT
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns the RAILS_ENV constant.
|
60
|
+
def rails_env attachment, style
|
61
|
+
RAILS_ENV
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns the underscored, pluralized version of the class name.
|
65
|
+
# e.g. "users" for the User class.
|
66
|
+
def class attachment, style
|
67
|
+
attachment.instance.class.to_s.underscore.pluralize
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the basename of the file. e.g. "file" for "file.jpg"
|
71
|
+
def basename attachment, style
|
72
|
+
attachment.original_filename.gsub(/#{File.extname(attachment.original_filename)}$/, "")
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns the extension of the file. e.g. "jpg" for "file.jpg"
|
76
|
+
# If the style has a format defined, it will return the format instead
|
77
|
+
# of the actual extension.
|
78
|
+
def extension attachment, style
|
79
|
+
((style = attachment.styles[style]) && style[:format]) ||
|
80
|
+
File.extname(attachment.original_filename).gsub(/^\.+/, "")
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns the id of the instance.
|
84
|
+
def id attachment, style
|
85
|
+
attachment.instance.id
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns the id of the instance in a split path form. e.g. returns
|
89
|
+
# 000/001/234 for an id of 1234.
|
90
|
+
def id_partition attachment, style
|
91
|
+
("%09d" % attachment.instance.id).scan(/\d{3}/).join("/")
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the pluralized form of the attachment name. e.g.
|
95
|
+
# "avatars" for an attachment of :avatar
|
96
|
+
def attachment attachment, style
|
97
|
+
attachment.name.to_s.downcase.pluralize
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns the style, or the default style if nil is supplied.
|
101
|
+
def style attachment, style
|
102
|
+
style || attachment.default_style
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/lib/paperclip/processor.rb
CHANGED
@@ -5,13 +5,14 @@ module Paperclip
|
|
5
5
|
# are not required to follow suit.
|
6
6
|
#
|
7
7
|
# Processors are required to be defined inside the Paperclip module and
|
8
|
-
# are also required to be a subclass of Paperclip::Processor. There
|
9
|
-
# only
|
10
|
-
# #
|
11
|
-
# be operated on (which is an instance of
|
12
|
-
# that were defined in has_attached_file's
|
8
|
+
# are also required to be a subclass of Paperclip::Processor. There is
|
9
|
+
# only one method you *must* implement to properly be a subclass:
|
10
|
+
# #make, but #initialize may also be of use. Both methods accept 3
|
11
|
+
# arguments: the file that will be operated on (which is an instance of
|
12
|
+
# File), a hash of options that were defined in has_attached_file's
|
13
|
+
# style hash, and the Paperclip::Attachment itself.
|
13
14
|
#
|
14
|
-
# All #make needs to
|
15
|
+
# All #make needs to return is an instance of File (Tempfile is
|
15
16
|
# acceptable) which contains the results of the processing.
|
16
17
|
#
|
17
18
|
# See Paperclip.run for more information about using command-line
|
data/lib/paperclip/storage.rb
CHANGED
@@ -39,7 +39,7 @@ module Paperclip
|
|
39
39
|
@queued_for_write.each do |style, file|
|
40
40
|
file.close
|
41
41
|
FileUtils.mkdir_p(File.dirname(path(style)))
|
42
|
-
|
42
|
+
log("saving #{path(style)}")
|
43
43
|
FileUtils.mv(file.path, path(style))
|
44
44
|
FileUtils.chmod(0644, path(style))
|
45
45
|
end
|
@@ -49,7 +49,7 @@ module Paperclip
|
|
49
49
|
def flush_deletes #:nodoc:
|
50
50
|
@queued_for_delete.each do |path|
|
51
51
|
begin
|
52
|
-
|
52
|
+
log("deleting #{path}")
|
53
53
|
FileUtils.rm(path) if File.exist?(path)
|
54
54
|
rescue Errno::ENOENT => e
|
55
55
|
# ignore file-not-found, let everything else pass
|
@@ -62,7 +62,7 @@ module Paperclip
|
|
62
62
|
rescue Errno::EEXIST, Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL, Errno::ENOTDIR
|
63
63
|
# Stop trying to remove parent directories
|
64
64
|
rescue SystemCallError => e
|
65
|
-
|
65
|
+
log("There was an unexpected error while deleting directories: #{e.class}")
|
66
66
|
# Ignore it
|
67
67
|
end
|
68
68
|
end
|
@@ -128,6 +128,8 @@ module Paperclip
|
|
128
128
|
# separate parts of your file name.
|
129
129
|
module S3
|
130
130
|
def self.extended base
|
131
|
+
warn('[DEPRECATION] S3 support through RightAWS is deprecated. S3 support will ' +
|
132
|
+
'be changed to AWS::S3 in a future version.')
|
131
133
|
require 'right_aws'
|
132
134
|
base.instance_eval do
|
133
135
|
@s3_credentials = parse_credentials(@options[:s3_credentials])
|
@@ -140,13 +142,13 @@ module Paperclip
|
|
140
142
|
@s3_host_alias = @options[:s3_host_alias]
|
141
143
|
@url = ":s3_path_url" unless @url.to_s.match(/^:s3.*url$/)
|
142
144
|
end
|
143
|
-
|
145
|
+
Paperclip.interpolates(:s3_alias_url) do |attachment, style|
|
144
146
|
"#{attachment.s3_protocol}://#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
|
145
147
|
end
|
146
|
-
|
148
|
+
Paperclip.interpolates(:s3_path_url) do |attachment, style|
|
147
149
|
"#{attachment.s3_protocol}://s3.amazonaws.com/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
|
148
150
|
end
|
149
|
-
|
151
|
+
Paperclip.interpolates(:s3_domain_url) do |attachment, style|
|
150
152
|
"#{attachment.s3_protocol}://#{attachment.bucket_name}.s3.amazonaws.com/#{attachment.path(style).gsub(%r{^/}, "")}"
|
151
153
|
end
|
152
154
|
end
|
@@ -171,7 +173,7 @@ module Paperclip
|
|
171
173
|
|
172
174
|
def parse_credentials creds
|
173
175
|
creds = find_credentials(creds).stringify_keys
|
174
|
-
(creds[
|
176
|
+
(creds[RAILS_ENV] || creds).symbolize_keys
|
175
177
|
end
|
176
178
|
|
177
179
|
def exists?(style = default_style)
|
@@ -192,7 +194,7 @@ module Paperclip
|
|
192
194
|
def flush_writes #:nodoc:
|
193
195
|
@queued_for_write.each do |style, file|
|
194
196
|
begin
|
195
|
-
|
197
|
+
log("saving #{path(style)}")
|
196
198
|
key = s3_bucket.key(path(style))
|
197
199
|
key.data = file
|
198
200
|
key.put(nil, @s3_permissions, {'Content-type' => instance_read(:content_type)}.merge(@s3_headers))
|
@@ -206,7 +208,7 @@ module Paperclip
|
|
206
208
|
def flush_deletes #:nodoc:
|
207
209
|
@queued_for_delete.each do |path|
|
208
210
|
begin
|
209
|
-
|
211
|
+
log("deleting #{path}")
|
210
212
|
if file = s3_bucket.key(path)
|
211
213
|
file.delete
|
212
214
|
end
|
data/test/attachment_test.rb
CHANGED
@@ -5,6 +5,14 @@ class Dummy
|
|
5
5
|
end
|
6
6
|
|
7
7
|
class AttachmentTest < Test::Unit::TestCase
|
8
|
+
should "return the path based on the url by default" do
|
9
|
+
@attachment = attachment :url => "/:class/:id/:basename"
|
10
|
+
@model = @attachment.instance
|
11
|
+
@model.id = 1234
|
12
|
+
@model.avatar_file_name = "fake.jpg"
|
13
|
+
assert_equal "#{RAILS_ROOT}/public/fake_models/1234/fake", @attachment.path
|
14
|
+
end
|
15
|
+
|
8
16
|
context "Attachment default_options" do
|
9
17
|
setup do
|
10
18
|
rebuild_model
|
@@ -106,6 +114,20 @@ class AttachmentTest < Test::Unit::TestCase
|
|
106
114
|
end
|
107
115
|
end
|
108
116
|
|
117
|
+
context "An attachment with a default style and an extension interpolation" do
|
118
|
+
setup do
|
119
|
+
@attachment = attachment :path => ":basename.:extension",
|
120
|
+
:styles => { :default => ["100x100", :png] },
|
121
|
+
:default_style => :default
|
122
|
+
@file = StringIO.new("...")
|
123
|
+
@file.expects(:original_filename).returns("file.jpg")
|
124
|
+
end
|
125
|
+
should "return the right extension for the path" do
|
126
|
+
@attachment.assign(@file)
|
127
|
+
assert_equal "file.png", @attachment.path
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
109
131
|
context "An attachment with :convert_options" do
|
110
132
|
setup do
|
111
133
|
rebuild_model :styles => {
|
@@ -319,13 +341,13 @@ class AttachmentTest < Test::Unit::TestCase
|
|
319
341
|
setup { @dummy.avatar = @file }
|
320
342
|
|
321
343
|
before_should "call #make on all specified processors" do
|
322
|
-
expected_params = @style_params[:once].merge({:processors => [:thumbnail, :test], :whiny =>
|
344
|
+
expected_params = @style_params[:once].merge({:processors => [:thumbnail, :test], :whiny => true, :convert_options => ""})
|
323
345
|
Paperclip::Thumbnail.expects(:make).with(@file, expected_params, @dummy.avatar).returns(@file)
|
324
346
|
Paperclip::Test.expects(:make).with(@file, expected_params, @dummy.avatar).returns(@file)
|
325
347
|
end
|
326
348
|
|
327
349
|
before_should "call #make with attachment passed as third argument" do
|
328
|
-
expected_params = @style_params[:once].merge({:processors => [:thumbnail, :test], :whiny =>
|
350
|
+
expected_params = @style_params[:once].merge({:processors => [:thumbnail, :test], :whiny => true, :convert_options => ""})
|
329
351
|
Paperclip::Test.expects(:make).with(@file, expected_params, @dummy.avatar).returns(@file)
|
330
352
|
end
|
331
353
|
end
|
@@ -456,6 +478,7 @@ class AttachmentTest < Test::Unit::TestCase
|
|
456
478
|
|
457
479
|
context "An attachment" do
|
458
480
|
setup do
|
481
|
+
@old_defaults = Paperclip::Attachment.default_options.dup
|
459
482
|
Paperclip::Attachment.default_options.merge!({
|
460
483
|
:path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
|
461
484
|
})
|
@@ -468,7 +491,10 @@ class AttachmentTest < Test::Unit::TestCase
|
|
468
491
|
"5k.png"), 'rb')
|
469
492
|
end
|
470
493
|
|
471
|
-
teardown
|
494
|
+
teardown do
|
495
|
+
@file.close
|
496
|
+
Paperclip::Attachment.default_options.merge!(@old_defaults)
|
497
|
+
end
|
472
498
|
|
473
499
|
should "raise if there are not the correct columns when you try to assign" do
|
474
500
|
@other_attachment = Paperclip::Attachment.new(:not_here, @instance)
|
data/test/geometry_test.rb
CHANGED
@@ -49,6 +49,15 @@ class GeometryTest < Test::Unit::TestCase
|
|
49
49
|
assert_nil @geo.modifier
|
50
50
|
end
|
51
51
|
|
52
|
+
should "treat x and X the same in geometries" do
|
53
|
+
@lower = Paperclip::Geometry.parse("123x456")
|
54
|
+
@upper = Paperclip::Geometry.parse("123X456")
|
55
|
+
assert_equal 123, @lower.width
|
56
|
+
assert_equal 123, @upper.width
|
57
|
+
assert_equal 456, @lower.height
|
58
|
+
assert_equal 456, @upper.height
|
59
|
+
end
|
60
|
+
|
52
61
|
['>', '<', '#', '@', '%', '^', '!', nil].each do |mod|
|
53
62
|
should "ensure the modifier #{mod.inspect} is preserved" do
|
54
63
|
assert @geo = Paperclip::Geometry.parse("123x456#{mod}")
|
data/test/helper.rb
CHANGED
@@ -17,6 +17,7 @@ end
|
|
17
17
|
|
18
18
|
ROOT = File.join(File.dirname(__FILE__), '..')
|
19
19
|
RAILS_ROOT = ROOT
|
20
|
+
RAILS_ENV = "test"
|
20
21
|
|
21
22
|
$LOAD_PATH << File.join(ROOT, 'lib')
|
22
23
|
$LOAD_PATH << File.join(ROOT, 'lib', 'paperclip')
|
@@ -25,8 +26,6 @@ require File.join(ROOT, 'lib', 'paperclip.rb')
|
|
25
26
|
|
26
27
|
require 'shoulda_macros/paperclip'
|
27
28
|
|
28
|
-
ENV['RAILS_ENV'] ||= 'test'
|
29
|
-
|
30
29
|
FIXTURES_DIR = File.join(File.dirname(__FILE__), "fixtures")
|
31
30
|
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
32
31
|
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
@@ -71,7 +70,7 @@ def rebuild_class options = {}
|
|
71
70
|
end
|
72
71
|
|
73
72
|
def temporary_rails_env(new_env)
|
74
|
-
old_env =
|
73
|
+
old_env = Object.const_defined?("RAILS_ENV") ? RAILS_ENV : nil
|
75
74
|
silence_warnings do
|
76
75
|
Object.const_set("RAILS_ENV", new_env)
|
77
76
|
end
|
@@ -80,3 +79,22 @@ def temporary_rails_env(new_env)
|
|
80
79
|
Object.const_set("RAILS_ENV", old_env)
|
81
80
|
end
|
82
81
|
end
|
82
|
+
|
83
|
+
class FakeModel
|
84
|
+
attr_accessor :avatar_file_name,
|
85
|
+
:avatar_file_size,
|
86
|
+
:avatar_last_updated,
|
87
|
+
:avatar_content_type,
|
88
|
+
:id
|
89
|
+
|
90
|
+
def errors
|
91
|
+
@errors ||= []
|
92
|
+
end
|
93
|
+
|
94
|
+
def run_callbacks name, *args
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def attachment options
|
99
|
+
Paperclip::Attachment.new(:avatar, FakeModel.new, options)
|
100
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'test/helper'
|
2
|
+
|
3
|
+
class InterpolationsTest < Test::Unit::TestCase
|
4
|
+
should "return all methods but the infrastructure when sent #all" do
|
5
|
+
methods = Paperclip::Interpolations.all
|
6
|
+
assert ! methods.include?(:[])
|
7
|
+
assert ! methods.include?(:[]=)
|
8
|
+
assert ! methods.include?(:all)
|
9
|
+
methods.each do |m|
|
10
|
+
assert Paperclip::Interpolations.respond_to? m
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
should "return the RAILS_ROOT" do
|
15
|
+
assert_equal RAILS_ROOT, Paperclip::Interpolations.rails_root(:attachment, :style)
|
16
|
+
end
|
17
|
+
|
18
|
+
should "return the RAILS_ENV" do
|
19
|
+
assert_equal RAILS_ENV, Paperclip::Interpolations.rails_env(:attachment, :style)
|
20
|
+
end
|
21
|
+
|
22
|
+
should "return the class of the instance" do
|
23
|
+
attachment = mock
|
24
|
+
attachment.expects(:instance).returns(attachment)
|
25
|
+
attachment.expects(:class).returns("Thing")
|
26
|
+
assert_equal "things", Paperclip::Interpolations.class(attachment, :style)
|
27
|
+
end
|
28
|
+
|
29
|
+
should "return the basename of the file" do
|
30
|
+
attachment = mock
|
31
|
+
attachment.expects(:original_filename).returns("one.jpg").times(2)
|
32
|
+
assert_equal "one", Paperclip::Interpolations.basename(attachment, :style)
|
33
|
+
end
|
34
|
+
|
35
|
+
should "return the extension of the file" do
|
36
|
+
attachment = mock
|
37
|
+
attachment.expects(:original_filename).returns("one.jpg")
|
38
|
+
attachment.expects(:styles).returns({})
|
39
|
+
assert_equal "jpg", Paperclip::Interpolations.extension(attachment, :style)
|
40
|
+
end
|
41
|
+
|
42
|
+
should "return the extension of the file as the format if defined in the style" do
|
43
|
+
attachment = mock
|
44
|
+
attachment.expects(:original_filename).never
|
45
|
+
attachment.expects(:styles).returns({:style => {:format => "png"}})
|
46
|
+
assert_equal "png", Paperclip::Interpolations.extension(attachment, :style)
|
47
|
+
end
|
48
|
+
|
49
|
+
should "return the id of the attachment" do
|
50
|
+
attachment = mock
|
51
|
+
attachment.expects(:id).returns(23)
|
52
|
+
attachment.expects(:instance).returns(attachment)
|
53
|
+
assert_equal 23, Paperclip::Interpolations.id(attachment, :style)
|
54
|
+
end
|
55
|
+
|
56
|
+
should "return the partitioned id of the attachment" do
|
57
|
+
attachment = mock
|
58
|
+
attachment.expects(:id).returns(23)
|
59
|
+
attachment.expects(:instance).returns(attachment)
|
60
|
+
assert_equal "000/000/023", Paperclip::Interpolations.id_partition(attachment, :style)
|
61
|
+
end
|
62
|
+
|
63
|
+
should "return the name of the attachment" do
|
64
|
+
attachment = mock
|
65
|
+
attachment.expects(:name).returns("file")
|
66
|
+
assert_equal "files", Paperclip::Interpolations.attachment(attachment, :style)
|
67
|
+
end
|
68
|
+
|
69
|
+
should "return the style" do
|
70
|
+
assert_equal :style, Paperclip::Interpolations.style(:attachment, :style)
|
71
|
+
end
|
72
|
+
|
73
|
+
should "return the default style" do
|
74
|
+
attachment = mock
|
75
|
+
attachment.expects(:default_style).returns(:default_style)
|
76
|
+
assert_equal :default_style, Paperclip::Interpolations.style(attachment, nil)
|
77
|
+
end
|
78
|
+
|
79
|
+
should "reinterpolate :url" do
|
80
|
+
attachment = mock
|
81
|
+
attachment.expects(:options).returns({:url => ":id"})
|
82
|
+
attachment.expects(:url).with(:style, false).returns("1234")
|
83
|
+
assert_equal "1234", Paperclip::Interpolations.url(attachment, :style)
|
84
|
+
end
|
85
|
+
|
86
|
+
should "raise if infinite loop detcted reinterpolating :url" do
|
87
|
+
attachment = mock
|
88
|
+
attachment.expects(:options).returns({:url => ":url"})
|
89
|
+
assert_raises(Paperclip::InfiniteInterpolationError){ Paperclip::Interpolations.url(attachment, :style) }
|
90
|
+
end
|
91
|
+
|
92
|
+
should "return the filename as basename.extension" do
|
93
|
+
attachment = mock
|
94
|
+
attachment.expects(:styles).returns({})
|
95
|
+
attachment.expects(:original_filename).returns("one.jpg").times(3)
|
96
|
+
assert_equal "one.jpg", Paperclip::Interpolations.filename(attachment, :style)
|
97
|
+
end
|
98
|
+
|
99
|
+
should "return the filename as basename.extension when format supplied" do
|
100
|
+
attachment = mock
|
101
|
+
attachment.expects(:styles).returns({:style => {:format => :png}})
|
102
|
+
attachment.expects(:original_filename).returns("one.jpg").times(2)
|
103
|
+
assert_equal "one.png", Paperclip::Interpolations.filename(attachment, :style)
|
104
|
+
end
|
105
|
+
|
106
|
+
should "return the timestamp" do
|
107
|
+
now = Time.now
|
108
|
+
attachment = mock
|
109
|
+
attachment.expects(:instance_read).with(:updated_at).returns(now)
|
110
|
+
assert_equal now.to_s, Paperclip::Interpolations.timestamp(attachment, :style)
|
111
|
+
end
|
112
|
+
|
113
|
+
should "call all expected interpolations with the given arguments" do
|
114
|
+
Paperclip::Interpolations.expects(:id).with(:attachment, :style).returns(1234)
|
115
|
+
Paperclip::Interpolations.expects(:attachment).with(:attachment, :style).returns("attachments")
|
116
|
+
Paperclip::Interpolations.expects(:notreal).never
|
117
|
+
value = Paperclip::Interpolations.interpolate(":notreal/:id/:attachment", :attachment, :style)
|
118
|
+
assert_equal ":notreal/1234/attachments", value
|
119
|
+
end
|
120
|
+
end
|
data/test/paperclip_test.rb
CHANGED
@@ -30,6 +30,14 @@ class PaperclipTest < Test::Unit::TestCase
|
|
30
30
|
Paperclip.expects(:"`").with("convert one.jpg two.jpg 2>/dev/null")
|
31
31
|
Paperclip.run("convert", "one.jpg two.jpg")
|
32
32
|
end
|
33
|
+
|
34
|
+
should "log the command when :log_command is set" do
|
35
|
+
Paperclip.options[:log_command] = true
|
36
|
+
Paperclip.expects(:bit_bucket).returns("/dev/null")
|
37
|
+
Paperclip.expects(:log).with("this is the command 2>/dev/null")
|
38
|
+
Paperclip.expects(:"`").with("this is the command 2>/dev/null")
|
39
|
+
Paperclip.run("this","is the command")
|
40
|
+
end
|
33
41
|
end
|
34
42
|
|
35
43
|
should "raise when sent #processor and the name of a class that exists but isn't a subclass of Processor" do
|
@@ -44,6 +52,18 @@ class PaperclipTest < Test::Unit::TestCase
|
|
44
52
|
assert_equal ::Paperclip::Thumbnail, Paperclip.processor(:thumbnail)
|
45
53
|
end
|
46
54
|
|
55
|
+
should "call a proc sent to check_guard" do
|
56
|
+
@dummy = Dummy.new
|
57
|
+
@dummy.expects(:one).returns(:one)
|
58
|
+
assert_equal :one, @dummy.avatar.send(:check_guard, lambda{|x| x.one })
|
59
|
+
end
|
60
|
+
|
61
|
+
should "call a method name sent to check_guard" do
|
62
|
+
@dummy = Dummy.new
|
63
|
+
@dummy.expects(:one).returns(:one)
|
64
|
+
assert_equal :one, @dummy.avatar.send(:check_guard, :one)
|
65
|
+
end
|
66
|
+
|
47
67
|
context "Paperclip.bit_bucket" do
|
48
68
|
context "on systems without /dev/null" do
|
49
69
|
setup do
|
@@ -167,6 +187,44 @@ class PaperclipTest < Test::Unit::TestCase
|
|
167
187
|
end
|
168
188
|
end
|
169
189
|
|
190
|
+
context "a validation with an if guard clause" do
|
191
|
+
setup do
|
192
|
+
Dummy.send(:"validates_attachment_presence", :avatar, :if => lambda{|i| i.foo })
|
193
|
+
@dummy = Dummy.new
|
194
|
+
end
|
195
|
+
|
196
|
+
should "attempt validation if the guard returns true" do
|
197
|
+
@dummy.expects(:foo).returns(true)
|
198
|
+
@dummy.avatar.expects(:validate_presence).returns(nil)
|
199
|
+
@dummy.valid?
|
200
|
+
end
|
201
|
+
|
202
|
+
should "not attempt validation if the guard returns false" do
|
203
|
+
@dummy.expects(:foo).returns(false)
|
204
|
+
@dummy.avatar.expects(:validate_presence).never
|
205
|
+
@dummy.valid?
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
context "a validation with an unless guard clause" do
|
210
|
+
setup do
|
211
|
+
Dummy.send(:"validates_attachment_presence", :avatar, :unless => lambda{|i| i.foo })
|
212
|
+
@dummy = Dummy.new
|
213
|
+
end
|
214
|
+
|
215
|
+
should "attempt validation if the guard returns true" do
|
216
|
+
@dummy.expects(:foo).returns(false)
|
217
|
+
@dummy.avatar.expects(:validate_presence).returns(nil)
|
218
|
+
@dummy.valid?
|
219
|
+
end
|
220
|
+
|
221
|
+
should "not attempt validation if the guard returns false" do
|
222
|
+
@dummy.expects(:foo).returns(true)
|
223
|
+
@dummy.avatar.expects(:validate_presence).never
|
224
|
+
@dummy.valid?
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
170
228
|
def self.should_validate validation, options, valid_file, invalid_file
|
171
229
|
context "with #{validation} validation and #{options.inspect} options" do
|
172
230
|
setup do
|
data/test/storage_test.rb
CHANGED
@@ -10,29 +10,29 @@ class StorageTest < Test::Unit::TestCase
|
|
10
10
|
@dummy = Dummy.new
|
11
11
|
@avatar = @dummy.avatar
|
12
12
|
|
13
|
-
@current_env =
|
13
|
+
@current_env = RAILS_ENV
|
14
14
|
end
|
15
15
|
|
16
16
|
teardown do
|
17
|
-
|
17
|
+
Object.const_set("RAILS_ENV", @current_env)
|
18
18
|
end
|
19
19
|
|
20
20
|
should "get the correct credentials when RAILS_ENV is production" do
|
21
|
-
|
21
|
+
Object.const_set('RAILS_ENV', "production")
|
22
22
|
assert_equal({:key => "12345"},
|
23
23
|
@avatar.parse_credentials('production' => {:key => '12345'},
|
24
24
|
:development => {:key => "54321"}))
|
25
25
|
end
|
26
26
|
|
27
27
|
should "get the correct credentials when RAILS_ENV is development" do
|
28
|
-
|
28
|
+
Object.const_set('RAILS_ENV', "development")
|
29
29
|
assert_equal({:key => "54321"},
|
30
30
|
@avatar.parse_credentials('production' => {:key => '12345'},
|
31
31
|
:development => {:key => "54321"}))
|
32
32
|
end
|
33
33
|
|
34
34
|
should "return the argument if the key does not exist" do
|
35
|
-
|
35
|
+
Object.const_set('RAILS_ENV', "not really an env")
|
36
36
|
assert_equal({:test => "12345"}, @avatar.parse_credentials(:test => "12345"))
|
37
37
|
end
|
38
38
|
end
|
@@ -94,13 +94,18 @@ class StorageTest < Test::Unit::TestCase
|
|
94
94
|
:development => { :bucket => "dev_bucket" }
|
95
95
|
}
|
96
96
|
@dummy = Dummy.new
|
97
|
+
@old_env = RAILS_ENV
|
97
98
|
end
|
98
99
|
|
99
|
-
|
100
|
+
teardown{ Object.const_set("RAILS_ENV", @old_env) }
|
101
|
+
|
102
|
+
should "get the right bucket in production" do
|
103
|
+
Object.const_set("RAILS_ENV", "production")
|
100
104
|
assert_equal "prod_bucket", @dummy.avatar.bucket_name
|
101
105
|
end
|
102
106
|
|
103
|
-
should "get the right bucket in development"
|
107
|
+
should "get the right bucket in development" do
|
108
|
+
Object.const_set("RAILS_ENV", "development")
|
104
109
|
assert_equal "dev_bucket", @dummy.avatar.bucket_name
|
105
110
|
end
|
106
111
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: thoughtbot-paperclip
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.2.
|
4
|
+
version: 2.2.9.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Yurek
|
@@ -9,19 +9,9 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-05-15 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
|
-
- !ruby/object:Gem::Dependency
|
16
|
-
name: right_aws
|
17
|
-
type: :runtime
|
18
|
-
version_requirement:
|
19
|
-
version_requirements: !ruby/object:Gem::Requirement
|
20
|
-
requirements:
|
21
|
-
- - ">="
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: "0"
|
24
|
-
version:
|
25
15
|
- !ruby/object:Gem::Dependency
|
26
16
|
name: thoughtbot-shoulda
|
27
17
|
type: :development
|
@@ -64,6 +54,7 @@ files:
|
|
64
54
|
- lib/paperclip/attachment.rb
|
65
55
|
- lib/paperclip/callback_compatability.rb
|
66
56
|
- lib/paperclip/geometry.rb
|
57
|
+
- lib/paperclip/interpolations.rb
|
67
58
|
- lib/paperclip/iostream.rb
|
68
59
|
- lib/paperclip/matchers
|
69
60
|
- lib/paperclip/matchers/have_attached_file_matcher.rb
|
@@ -79,7 +70,6 @@ files:
|
|
79
70
|
- tasks/paperclip_tasks.rake
|
80
71
|
- test/attachment_test.rb
|
81
72
|
- test/database.yml
|
82
|
-
- test/debug.log
|
83
73
|
- test/fixtures
|
84
74
|
- test/fixtures/12k.png
|
85
75
|
- test/fixtures/50x50.png
|
@@ -91,6 +81,7 @@ files:
|
|
91
81
|
- test/geometry_test.rb
|
92
82
|
- test/helper.rb
|
93
83
|
- test/integration_test.rb
|
84
|
+
- test/interpolations_test.rb
|
94
85
|
- test/iostream_test.rb
|
95
86
|
- test/matchers
|
96
87
|
- test/matchers/have_attached_file_matcher_test.rb
|
@@ -99,11 +90,8 @@ files:
|
|
99
90
|
- test/matchers/validate_attachment_size_matcher_test.rb
|
100
91
|
- test/paperclip_test.rb
|
101
92
|
- test/processor_test.rb
|
102
|
-
- test/s3.yml
|
103
93
|
- test/storage_test.rb
|
104
94
|
- test/thumbnail_test.rb
|
105
|
-
- test/tmp
|
106
|
-
- test/tmp/storage.txt
|
107
95
|
- shoulda_macros/paperclip.rb
|
108
96
|
has_rdoc: true
|
109
97
|
homepage: http://www.thoughtbot.com/projects/paperclip
|