dm-paperclip 2.1.4 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +13 -2
- data/Rakefile +6 -0
- data/lib/dm-paperclip.rb +187 -61
- data/lib/dm-paperclip/attachment.rb +240 -149
- data/lib/dm-paperclip/callback_compatability.rb +33 -0
- data/lib/dm-paperclip/geometry.rb +22 -14
- data/lib/dm-paperclip/interpolations.rb +123 -0
- data/lib/dm-paperclip/iostream.rb +17 -2
- data/lib/dm-paperclip/processor.rb +49 -0
- data/lib/dm-paperclip/storage.rb +72 -55
- data/lib/dm-paperclip/thumbnail.rb +18 -36
- data/lib/dm-paperclip/upfile.rb +12 -1
- data/lib/dm-paperclip/validations.rb +35 -0
- data/test/attachment_test.rb +24 -32
- data/test/geometry_test.rb +1 -1
- data/test/helper.rb +6 -0
- data/test/storage_test.rb +3 -3
- data/test/thumbnail_test.rb +17 -15
- metadata +19 -9
data/README.rdoc
CHANGED
@@ -34,13 +34,24 @@ In your model:
|
|
34
34
|
class User
|
35
35
|
include DataMapper::Resource
|
36
36
|
include Paperclip::Resource
|
37
|
-
property :id,
|
37
|
+
property :id, Serial
|
38
38
|
property :username, String
|
39
39
|
has_attached_file :avatar,
|
40
40
|
:styles => { :medium => "300x300>",
|
41
41
|
:thumb => "100x100>" }
|
42
42
|
end
|
43
43
|
|
44
|
+
You will need to add an initializer to configure Paperclip. If on Rails, can add a config/initializers/paperclip.rb, on Merb
|
45
|
+
can use config/init.rb and add it to the Merb::BootLoader.after_app_loads section. Can also use environment configs, rackup
|
46
|
+
file, Rake task, wherever.
|
47
|
+
|
48
|
+
Paperclip.configure do |config|
|
49
|
+
config.root = Rails.root # the application root to anchor relative urls (defaults to Dir.pwd)
|
50
|
+
config.env = Rails.env # server env support, defaults to ENV['RACK_ENV'] or 'development'
|
51
|
+
config.use_dm_validations = true # validate attachment sizes and such, defaults to false
|
52
|
+
config.processors_path = 'lib/pc' # relative path to look for processors, defaults to 'lib/paperclip_processors'
|
53
|
+
end
|
54
|
+
|
44
55
|
Your database will need to add four columns, avatar_file_name (varchar), avatar_content_type (varchar), and
|
45
56
|
avatar_file_size (integer), and avatar_updated_at (datetime). You can either add these manually, auto-
|
46
57
|
migrate, or use the following migration:
|
@@ -96,7 +107,7 @@ into your mode:
|
|
96
107
|
include DataMapper::Resource
|
97
108
|
include DataMapper::Validate
|
98
109
|
include Paperclip::Resource
|
99
|
-
property :id,
|
110
|
+
property :id, Serial
|
100
111
|
property :username, String
|
101
112
|
has_attached_file :avatar,
|
102
113
|
:styles => { :medium => "300x300>",
|
data/Rakefile
CHANGED
@@ -4,6 +4,7 @@ require 'rake/rdoctask'
|
|
4
4
|
require 'rake/gempackagetask'
|
5
5
|
|
6
6
|
$LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
|
7
|
+
require 'dm-core'
|
7
8
|
require 'dm-validations'
|
8
9
|
require 'dm-paperclip'
|
9
10
|
|
@@ -78,6 +79,11 @@ Rake::GemPackageTask.new(spec) do |pkg|
|
|
78
79
|
pkg.need_tar = true
|
79
80
|
end
|
80
81
|
|
82
|
+
desc 'Generate gemspec'
|
83
|
+
task :gemspec do
|
84
|
+
File.open("#{spec.name}.gemspec", 'w') { |f| f.puts(spec.to_ruby) }
|
85
|
+
end
|
86
|
+
|
81
87
|
WIN32 = (PLATFORM =~ /win32|cygwin/) rescue nil
|
82
88
|
SUDO = WIN32 ? '' : ('sudo' unless ENV['SUDOLESS'])
|
83
89
|
|
data/lib/dm-paperclip.rb
CHANGED
@@ -26,46 +26,188 @@
|
|
26
26
|
# See the +has_attached_file+ documentation for more details.
|
27
27
|
|
28
28
|
require 'tempfile'
|
29
|
-
require File.join(File.dirname(__FILE__), 'dm-paperclip', 'upfile')
|
30
|
-
require File.join(File.dirname(__FILE__), 'dm-paperclip', 'iostream')
|
31
|
-
require File.join(File.dirname(__FILE__), 'dm-paperclip', 'geometry')
|
32
|
-
require File.join(File.dirname(__FILE__), 'dm-paperclip', 'thumbnail')
|
33
|
-
require File.join(File.dirname(__FILE__), 'dm-paperclip', 'storage')
|
34
|
-
require File.join(File.dirname(__FILE__), 'dm-paperclip', 'attachment')
|
35
29
|
|
36
|
-
|
37
|
-
require File.join(File.dirname(__FILE__), 'dm-paperclip', 'validations') unless defined?(DataMapper::Validate).nil?
|
30
|
+
require 'dm-core'
|
38
31
|
|
32
|
+
require 'dm-paperclip/upfile'
|
33
|
+
require 'dm-paperclip/iostream'
|
34
|
+
require 'dm-paperclip/geometry'
|
35
|
+
require 'dm-paperclip/processor'
|
36
|
+
require 'dm-paperclip/thumbnail'
|
37
|
+
require 'dm-paperclip/storage'
|
38
|
+
require 'dm-paperclip/interpolations'
|
39
|
+
require 'dm-paperclip/attachment'
|
40
|
+
|
41
|
+
# The base module that gets included in ActiveRecord::Base. See the
|
42
|
+
# documentation for Paperclip::ClassMethods for more useful information.
|
39
43
|
module Paperclip
|
40
|
-
|
44
|
+
|
45
|
+
VERSION = "2.3.0"
|
46
|
+
|
47
|
+
# To configure Paperclip, put this code in an initializer, Rake task, or wherever:
|
48
|
+
#
|
49
|
+
# Paperclip.configure do |config|
|
50
|
+
# config.root = Rails.root # the application root to anchor relative urls (defaults to Dir.pwd)
|
51
|
+
# config.env = Rails.env # server env support, defaults to ENV['RACK_ENV'] or 'development'
|
52
|
+
# config.use_dm_validations = true # validate attachment sizes and such, defaults to false
|
53
|
+
# config.processors_path = 'lib/pc' # relative path to look for processors, defaults to 'lib/paperclip_processors'
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
def self.configure
|
57
|
+
yield @config = Configuration.new
|
58
|
+
Paperclip.config = @config
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.config=(config)
|
62
|
+
@config = config
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.config
|
66
|
+
@config ||= Configuration.new
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.require_processors
|
70
|
+
return if @processors_already_required
|
71
|
+
Dir.glob(File.expand_path("#{Paperclip.config.processors_path}/*.rb")).sort.each do |processor|
|
72
|
+
require processor
|
73
|
+
end
|
74
|
+
@processors_already_required = true
|
75
|
+
end
|
76
|
+
|
77
|
+
class Configuration
|
78
|
+
|
79
|
+
DEFAULT_PROCESSORS_PATH = 'lib/paperclip_processors'
|
80
|
+
|
81
|
+
attr_writer :root, :env
|
82
|
+
attr_accessor :use_dm_validations
|
83
|
+
|
84
|
+
def root
|
85
|
+
@root ||= Dir.pwd
|
86
|
+
end
|
87
|
+
|
88
|
+
def env
|
89
|
+
@env ||= (ENV['RACK_ENV'] || 'development')
|
90
|
+
end
|
91
|
+
|
92
|
+
def processors_path=(path)
|
93
|
+
@processors_path = File.expand_path(path, root)
|
94
|
+
end
|
95
|
+
|
96
|
+
def processors_path
|
97
|
+
@processors_path ||= File.expand_path("../#{DEFAULT_PROCESSORS_PATH}", root)
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
41
102
|
class << self
|
103
|
+
|
42
104
|
# Provides configurability to Paperclip. There are a number of options available, such as:
|
43
|
-
# *
|
105
|
+
# * whiny: Will raise an error if Paperclip cannot process thumbnails of
|
44
106
|
# an uploaded image. Defaults to true.
|
45
|
-
# *
|
107
|
+
# * log: Logs progress to the Rails log. Uses ActiveRecord's logger, so honors
|
108
|
+
# log levels, etc. Defaults to true.
|
109
|
+
# * command_path: Defines the path at which to find the command line
|
46
110
|
# programs if they are not visible to Rails the system's search path. Defaults to
|
47
|
-
# nil, which uses the first executable found in the search path.
|
111
|
+
# nil, which uses the first executable found in the user's search path.
|
112
|
+
# * image_magick_path: Deprecated alias of command_path.
|
48
113
|
def options
|
49
114
|
@options ||= {
|
50
|
-
:
|
51
|
-
:image_magick_path => nil
|
115
|
+
:whiny => true,
|
116
|
+
:image_magick_path => nil,
|
117
|
+
:command_path => nil,
|
118
|
+
:log => true,
|
119
|
+
:log_command => false,
|
120
|
+
:swallow_stderr => true
|
52
121
|
}
|
53
122
|
end
|
54
123
|
|
55
124
|
def path_for_command command #:nodoc:
|
56
|
-
|
125
|
+
if options[:image_magick_path]
|
126
|
+
warn("[DEPRECATION] :image_magick_path is deprecated and will be removed. Use :command_path instead")
|
127
|
+
end
|
128
|
+
path = [options[:command_path] || options[:image_magick_path], command].compact
|
57
129
|
File.join(*path)
|
58
130
|
end
|
131
|
+
|
132
|
+
def interpolates key, &block
|
133
|
+
Paperclip::Interpolations[key] = block
|
134
|
+
end
|
135
|
+
|
136
|
+
# The run method takes a command to execute and a string of parameters
|
137
|
+
# that get passed to it. The command is prefixed with the :command_path
|
138
|
+
# option from Paperclip.options. If you have many commands to run and
|
139
|
+
# they are in different paths, the suggested course of action is to
|
140
|
+
# symlink them so they are all in the same directory.
|
141
|
+
#
|
142
|
+
# If the command returns with a result code that is not one of the
|
143
|
+
# expected_outcodes, a PaperclipCommandLineError will be raised. Generally
|
144
|
+
# a code of 0 is expected, but a list of codes may be passed if necessary.
|
145
|
+
#
|
146
|
+
# This method can log the command being run when
|
147
|
+
# Paperclip.options[:log_command] is set to true (defaults to false). This
|
148
|
+
# will only log if logging in general is set to true as well.
|
149
|
+
def run cmd, params = "", expected_outcodes = 0
|
150
|
+
command = %Q<#{%Q[#{path_for_command(cmd)} #{params}].gsub(/\s+/, " ")}>
|
151
|
+
command = "#{command} 2>#{bit_bucket}" if Paperclip.options[:swallow_stderr]
|
152
|
+
Paperclip.log(command) if Paperclip.options[:log_command]
|
153
|
+
output = `#{command}`
|
154
|
+
unless [expected_outcodes].flatten.include?($?.exitstatus)
|
155
|
+
raise PaperclipCommandLineError, "Error while running #{cmd}"
|
156
|
+
end
|
157
|
+
output
|
158
|
+
end
|
159
|
+
|
160
|
+
def bit_bucket #:nodoc:
|
161
|
+
File.exists?("/dev/null") ? "/dev/null" : "NUL"
|
162
|
+
end
|
163
|
+
|
164
|
+
def included base #:nodoc:
|
165
|
+
base.extend ClassMethods
|
166
|
+
unless base.respond_to?(:define_callbacks)
|
167
|
+
base.send(:include, Paperclip::CallbackCompatability)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def processor name #:nodoc:
|
172
|
+
name = name.to_s.camel_case
|
173
|
+
processor = Paperclip.const_get(name)
|
174
|
+
unless processor.ancestors.include?(Paperclip::Processor)
|
175
|
+
raise PaperclipError.new("[paperclip] Processor #{name} was not found")
|
176
|
+
end
|
177
|
+
processor
|
178
|
+
end
|
179
|
+
|
180
|
+
# Log a paperclip-specific line. Uses ActiveRecord::Base.logger
|
181
|
+
# by default. Set Paperclip.options[:log] to false to turn off.
|
182
|
+
def log message
|
183
|
+
logger.info("[paperclip] #{message}") if logging?
|
184
|
+
end
|
185
|
+
|
186
|
+
def logger #:nodoc:
|
187
|
+
DataMapper.logger
|
188
|
+
end
|
189
|
+
|
190
|
+
def logging? #:nodoc:
|
191
|
+
options[:log]
|
192
|
+
end
|
59
193
|
end
|
60
194
|
|
61
195
|
class PaperclipError < StandardError #:nodoc:
|
62
196
|
end
|
63
197
|
|
64
|
-
class
|
198
|
+
class PaperclipCommandLineError < StandardError #:nodoc:
|
65
199
|
end
|
66
200
|
|
201
|
+
class NotIdentifiedByImageMagickError < PaperclipError #:nodoc:
|
202
|
+
end
|
203
|
+
|
204
|
+
class InfiniteInterpolationError < PaperclipError #:nodoc:
|
205
|
+
end
|
206
|
+
|
67
207
|
module Resource
|
208
|
+
|
68
209
|
def self.included(base)
|
210
|
+
|
69
211
|
base.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
70
212
|
class_variable_set(:@@attachment_definitions,nil) unless class_variable_defined?(:@@attachment_definitions)
|
71
213
|
def self.attachment_definitions
|
@@ -76,12 +218,24 @@ module Paperclip
|
|
76
218
|
@@attachment_definitions = obj
|
77
219
|
end
|
78
220
|
RUBY
|
221
|
+
|
79
222
|
base.extend Paperclip::ClassMethods
|
223
|
+
|
224
|
+
# Done at this time to ensure that the user
|
225
|
+
# had a chance to configure the app in an initializer
|
226
|
+
if Paperclip.config.use_dm_validations
|
227
|
+
require 'dm-validations'
|
228
|
+
require 'dm-paperclip/validations'
|
229
|
+
base.extend Paperclip::Validate::ClassMethods
|
230
|
+
end
|
231
|
+
|
232
|
+
Paperclip.require_processors
|
233
|
+
|
80
234
|
end
|
235
|
+
|
81
236
|
end
|
82
237
|
|
83
238
|
module ClassMethods
|
84
|
-
|
85
239
|
# +has_attached_file+ gives the class it is called on an attribute that maps to a file. This
|
86
240
|
# is typically a file stored somewhere on the filesystem and has been uploaded by a user.
|
87
241
|
# The attribute returns a Paperclip::Attachment object which handles the management of
|
@@ -145,13 +299,17 @@ module Paperclip
|
|
145
299
|
|
146
300
|
property_options = options.delete_if { |k,v| ![ :public, :protected, :private, :accessor, :reader, :writer ].include?(key) }
|
147
301
|
|
148
|
-
property "#{name}_file_name"
|
149
|
-
property "#{name}_content_type"
|
150
|
-
property "#{name}_file_size"
|
151
|
-
property "#{name}_updated_at"
|
302
|
+
property :"#{name}_file_name", String, property_options.merge(:length => 255)
|
303
|
+
property :"#{name}_content_type", String, property_options.merge(:length => 255)
|
304
|
+
property :"#{name}_file_size", Integer, property_options
|
305
|
+
property :"#{name}_updated_at", DateTime, property_options
|
152
306
|
|
153
307
|
after :save, :save_attached_files
|
154
308
|
before :destroy, :destroy_attached_files
|
309
|
+
|
310
|
+
# not needed with extlib just do before :post_process, or after :post_process
|
311
|
+
# define_callbacks :before_post_process, :after_post_process
|
312
|
+
# define_callbacks :"before_#{name}_post_process", :"after_#{name}_post_process"
|
155
313
|
|
156
314
|
define_method name do |*args|
|
157
315
|
a = attachment_for(name)
|
@@ -166,46 +324,17 @@ module Paperclip
|
|
166
324
|
! attachment_for(name).original_filename.blank?
|
167
325
|
end
|
168
326
|
|
169
|
-
|
327
|
+
if Paperclip.config.use_dm_validations
|
170
328
|
add_validator_to_context(opts_from_validator_args([name]), [name], Paperclip::Validate::CopyAttachmentErrors)
|
171
329
|
end
|
172
|
-
end
|
173
330
|
|
174
|
-
unless defined?(DataMapper::Validate).nil?
|
175
|
-
|
176
|
-
# Places ActiveRecord-style validations on the size of the file assigned. The
|
177
|
-
# possible options are:
|
178
|
-
# * +in+: a Range of bytes (i.e. +1..1.megabyte+),
|
179
|
-
# * +less_than+: equivalent to :in => 0..options[:less_than]
|
180
|
-
# * +greater_than+: equivalent to :in => options[:greater_than]..Infinity
|
181
|
-
# * +message+: error message to display, use :min and :max as replacements
|
182
|
-
def validates_attachment_size(*fields)
|
183
|
-
opts = opts_from_validator_args(fields)
|
184
|
-
add_validator_to_context(opts, fields, Paperclip::Validate::SizeValidator)
|
185
331
|
end
|
186
332
|
|
187
|
-
#
|
188
|
-
|
189
|
-
|
333
|
+
# Returns the attachment definitions defined by each call to
|
334
|
+
# has_attached_file.
|
335
|
+
def attachment_definitions
|
336
|
+
read_inheritable_attribute(:attachment_definitions)
|
190
337
|
end
|
191
|
-
|
192
|
-
# Places ActiveRecord-style validations on the presence of a file.
|
193
|
-
def validates_attachment_presence(*fields)
|
194
|
-
opts = opts_from_validator_args(fields)
|
195
|
-
add_validator_to_context(opts, fields, Paperclip::Validate::RequiredFieldValidator)
|
196
|
-
end
|
197
|
-
|
198
|
-
# Places ActiveRecord-style validations on the content type of the file assigned. The
|
199
|
-
# possible options are:
|
200
|
-
# * +content_type+: Allowed content types. Can be a single content type or an array. Allows all by default.
|
201
|
-
# * +message+: The message to display when the uploaded file has an invalid content type.
|
202
|
-
def validates_attachment_content_type(*fields)
|
203
|
-
opts = opts_from_validator_args(fields)
|
204
|
-
add_validator_to_context(opts, fields, Paperclip::Validate::ContentTypeValidator)
|
205
|
-
end
|
206
|
-
|
207
|
-
end
|
208
|
-
|
209
338
|
end
|
210
339
|
|
211
340
|
module InstanceMethods #:nodoc:
|
@@ -213,7 +342,7 @@ module Paperclip
|
|
213
342
|
@attachments ||= {}
|
214
343
|
@attachments[name] ||= Attachment.new(name, self, self.class.attachment_definitions[name])
|
215
344
|
end
|
216
|
-
|
345
|
+
|
217
346
|
def each_attachment
|
218
347
|
self.class.attachment_definitions.each do |name, definition|
|
219
348
|
yield(name, attachment_for(name))
|
@@ -221,21 +350,18 @@ module Paperclip
|
|
221
350
|
end
|
222
351
|
|
223
352
|
def save_attached_files
|
224
|
-
|
353
|
+
Paperclip.log("Saving attachments.")
|
225
354
|
each_attachment do |name, attachment|
|
226
355
|
attachment.send(:save)
|
227
356
|
end
|
228
357
|
end
|
229
358
|
|
230
359
|
def destroy_attached_files
|
231
|
-
|
360
|
+
Paperclip.log("Deleting attachments.")
|
232
361
|
each_attachment do |name, attachment|
|
233
362
|
attachment.send(:queue_existing_for_delete)
|
234
363
|
attachment.send(:flush_deletes)
|
235
364
|
end
|
236
365
|
end
|
237
366
|
end
|
238
|
-
|
239
367
|
end
|
240
|
-
|
241
|
-
File.send(:include, Paperclip::Upfile)
|
@@ -1,25 +1,27 @@
|
|
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/:
|
9
|
-
:path => ":
|
9
|
+
:url => "/system/:attachment/:id/:style/:filename",
|
10
|
+
:path => ":rails_root/public:url",
|
10
11
|
:styles => {},
|
11
12
|
:default_url => "/:attachment/:style/missing.png",
|
12
13
|
:default_style => :original,
|
13
14
|
:validations => [],
|
14
|
-
:storage => :filesystem
|
15
|
+
:storage => :filesystem,
|
16
|
+
:whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails]
|
15
17
|
}
|
16
18
|
end
|
17
19
|
|
18
|
-
attr_reader :name, :instance, :styles, :default_style, :convert_options
|
20
|
+
attr_reader :name, :instance, :styles, :default_style, :convert_options, :queued_for_write, :options
|
19
21
|
|
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+.
|
22
|
+
# Creates an Attachment object. +name+ is the name of the attachment,
|
23
|
+
# +instance+ is the ActiveRecord object instance it's attached to, and
|
24
|
+
# +options+ is the same as the hash passed to +has_attached_file+.
|
23
25
|
def initialize name, instance, options = {}
|
24
26
|
@name = name
|
25
27
|
@instance = instance
|
@@ -27,96 +29,99 @@ module Paperclip
|
|
27
29
|
options = self.class.default_options.merge(options)
|
28
30
|
|
29
31
|
@url = options[:url]
|
32
|
+
@url = @url.call(self) if @url.is_a?(Proc)
|
30
33
|
@path = options[:path]
|
34
|
+
@path = @path.call(self) if @path.is_a?(Proc)
|
31
35
|
@styles = options[:styles]
|
36
|
+
@styles = @styles.call(self) if @styles.is_a?(Proc)
|
32
37
|
@default_url = options[:default_url]
|
33
38
|
@validations = options[:validations]
|
34
39
|
@default_style = options[:default_style]
|
35
40
|
@storage = options[:storage]
|
36
|
-
@
|
41
|
+
@whiny = options[:whiny_thumbnails] || options[:whiny]
|
37
42
|
@convert_options = options[:convert_options] || {}
|
43
|
+
@processors = options[:processors] || [:thumbnail]
|
38
44
|
@options = options
|
39
45
|
@queued_for_delete = []
|
40
46
|
@queued_for_write = {}
|
41
|
-
@errors =
|
47
|
+
@errors = {}
|
42
48
|
@validation_errors = nil
|
43
49
|
@dirty = false
|
44
50
|
|
45
51
|
normalize_style_definition
|
46
52
|
initialize_storage
|
47
|
-
|
48
|
-
#logger.info("[paperclip] Paperclip attachment #{name} on #{instance.class} initialized.")
|
49
53
|
end
|
50
54
|
|
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
|
55
|
+
# What gets called when you call instance.attachment = File. It clears
|
56
|
+
# errors, assigns attributes, processes the file, and runs validations. It
|
57
|
+
# also queues up the previous file for deletion, to be flushed away on
|
58
|
+
# #save of its host. In addition to form uploads, you can also assign
|
59
|
+
# another Paperclip attachment:
|
55
60
|
# new_user.avatar = old_user.avatar
|
61
|
+
# If the file that is assigned is not valid, the processing (i.e.
|
62
|
+
# thumbnailing, etc) will NOT be run.
|
56
63
|
def assign uploaded_file
|
64
|
+
|
65
|
+
ensure_required_accessors!
|
66
|
+
|
57
67
|
if uploaded_file.is_a?(Paperclip::Attachment)
|
58
68
|
uploaded_file = uploaded_file.to_file(:original)
|
69
|
+
close_uploaded_file = uploaded_file.respond_to?(:close)
|
59
70
|
end
|
60
71
|
|
61
72
|
return nil unless valid_assignment?(uploaded_file)
|
62
|
-
#logger.info("[paperclip] Assigning #{uploaded_file.inspect} to #{name}")
|
63
73
|
|
64
|
-
|
65
|
-
|
66
|
-
@validation_errors = nil
|
74
|
+
uploaded_file.binmode if uploaded_file.respond_to? :binmode
|
75
|
+
self.clear
|
67
76
|
|
68
77
|
return nil if uploaded_file.nil?
|
69
78
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
@queued_for_write[:original]
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
79
|
+
if uploaded_file.respond_to?(:[])
|
80
|
+
uploaded_file = uploaded_file.to_mash
|
81
|
+
|
82
|
+
@queued_for_write[:original] = uploaded_file['tempfile']
|
83
|
+
instance_write(:file_name, uploaded_file['filename'].strip.gsub(/[^\w\d\.\-]+/, '_'))
|
84
|
+
instance_write(:content_type, uploaded_file['content_type'] ? uploaded_file['content_type'].strip : uploaded_file['tempfile'].content_type.to_s.strip)
|
85
|
+
instance_write(:file_size, uploaded_file['size'] ? uploaded_file['size'].to_i : uploaded_file['tempfile'].size.to_i)
|
86
|
+
instance_write(:updated_at, Time.now)
|
78
87
|
else
|
79
|
-
@queued_for_write[:original]
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
88
|
+
@queued_for_write[:original] = uploaded_file.to_tempfile
|
89
|
+
instance_write(:file_name, uploaded_file.original_filename.strip.gsub(/[^\w\d\.\-]+/, '_'))
|
90
|
+
instance_write(:content_type, uploaded_file.content_type.to_s.strip)
|
91
|
+
instance_write(:file_size, uploaded_file.size.to_i)
|
92
|
+
instance_write(:updated_at, Time.now)
|
84
93
|
end
|
85
94
|
|
86
|
-
post_process
|
87
95
|
@dirty = true
|
88
96
|
|
97
|
+
post_process if valid?
|
98
|
+
|
89
99
|
# Reset the file size if the original file was reprocessed.
|
90
|
-
|
91
|
-
if @styles[:original]
|
92
|
-
newvals[:"#{@name}_file_size"] = @queued_for_write[:original].size.to_i
|
93
|
-
end
|
100
|
+
instance_write(:file_size, @queued_for_write[:original].size.to_i)
|
94
101
|
|
95
|
-
begin
|
96
|
-
@instance.attributes = newvals
|
97
|
-
rescue NameError
|
98
|
-
raise PaperclipError, "There was an error processing this attachment"
|
99
|
-
end
|
100
102
|
ensure
|
103
|
+
uploaded_file.close if close_uploaded_file
|
101
104
|
validate
|
102
105
|
end
|
103
106
|
|
104
|
-
# Returns the public URL of the attachment, with a given style. Note that
|
105
|
-
# does not necessarily need to point to a file that your web server
|
106
|
-
# and can point to an action in your app, if you need fine
|
107
|
-
# This is not recommended if you don't need the
|
108
|
-
# performance reasons.
|
109
|
-
|
110
|
-
|
111
|
-
|
107
|
+
# Returns the public URL of the attachment, with a given style. Note that
|
108
|
+
# this does not necessarily need to point to a file that your web server
|
109
|
+
# can access and can point to an action in your app, if you need fine
|
110
|
+
# grained security. This is not recommended if you don't need the
|
111
|
+
# security, however, for performance reasons. set
|
112
|
+
# include_updated_timestamp to false if you want to stop the attachment
|
113
|
+
# update time appended to the url
|
114
|
+
def url style = default_style, include_updated_timestamp = true
|
115
|
+
the_url = original_filename.nil? ? interpolate(@default_url, style) : interpolate(@url, style)
|
116
|
+
include_updated_timestamp && updated_at ? [the_url, updated_at.to_i].compact.join(the_url.include?("?") ? "&" : "?") : the_url
|
112
117
|
end
|
113
118
|
|
114
119
|
# Returns the path of the attachment as defined by the :path option. If the
|
115
|
-
# file is stored in the filesystem the path refers to the path of the file
|
116
|
-
# disk. If the file is stored in S3, the path is the "key" part of the
|
117
|
-
# and the :bucket option refers to the S3 bucket.
|
118
|
-
def path style =
|
119
|
-
interpolate(@path, style)
|
120
|
+
# file is stored in the filesystem the path refers to the path of the file
|
121
|
+
# on disk. If the file is stored in S3, the path is the "key" part of the
|
122
|
+
# URL, and the :bucket option refers to the S3 bucket.
|
123
|
+
def path style = default_style
|
124
|
+
original_filename.nil? ? nil : interpolate(@path, style)
|
120
125
|
end
|
121
126
|
|
122
127
|
# Alias to +url+
|
@@ -126,12 +131,13 @@ module Paperclip
|
|
126
131
|
|
127
132
|
# Returns true if there are no errors on this attachment.
|
128
133
|
def valid?
|
129
|
-
|
134
|
+
validate
|
135
|
+
errors.empty?
|
130
136
|
end
|
131
137
|
|
132
138
|
# Returns an array containing the errors on this attachment.
|
133
139
|
def errors
|
134
|
-
@errors
|
140
|
+
@errors
|
135
141
|
end
|
136
142
|
|
137
143
|
# Returns true if there are changes that need to be saved.
|
@@ -143,67 +149,76 @@ module Paperclip
|
|
143
149
|
# the instance's errors and returns false, cancelling the save.
|
144
150
|
def save
|
145
151
|
if valid?
|
146
|
-
#logger.info("[paperclip] Saving files for #{name}")
|
147
152
|
flush_deletes
|
148
153
|
flush_writes
|
149
154
|
@dirty = false
|
150
155
|
true
|
151
156
|
else
|
152
|
-
#logger.info("[paperclip] Errors on #{name}. Not saving.")
|
153
157
|
flush_errors
|
154
158
|
false
|
155
159
|
end
|
156
160
|
end
|
157
161
|
|
158
|
-
#
|
162
|
+
# Clears out the attachment. Has the same effect as previously assigning
|
163
|
+
# nil to the attachment. Does NOT save. If you wish to clear AND save,
|
164
|
+
# use #destroy.
|
165
|
+
def clear
|
166
|
+
queue_existing_for_delete
|
167
|
+
@errors = {}
|
168
|
+
@validation_errors = nil
|
169
|
+
end
|
170
|
+
|
171
|
+
# Destroys the attachment. Has the same effect as previously assigning
|
172
|
+
# nil to the attachment *and saving*. This is permanent. If you wish to
|
173
|
+
# wipe out the existing attachment but not save, use #clear.
|
174
|
+
def destroy
|
175
|
+
clear
|
176
|
+
save
|
177
|
+
end
|
178
|
+
|
179
|
+
# Returns the name of the file as originally assigned, and lives in the
|
159
180
|
# <attachment>_file_name attribute of the model.
|
160
181
|
def original_filename
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
182
|
+
instance_read(:file_name)
|
183
|
+
end
|
184
|
+
|
185
|
+
# Returns the size of the file as originally assigned, and lives in the
|
186
|
+
# <attachment>_file_size attribute of the model.
|
187
|
+
def size
|
188
|
+
instance_read(:file_size) || (@queued_for_write[:original] && @queued_for_write[:original].size)
|
166
189
|
end
|
167
|
-
|
190
|
+
|
191
|
+
# Returns the content_type of the file as originally assigned, and lives
|
192
|
+
# in the <attachment>_content_type attribute of the model.
|
193
|
+
def content_type
|
194
|
+
instance_read(:content_type)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Returns the last modified time of the file as originally assigned, and
|
198
|
+
# lives in the <attachment>_updated_at attribute of the model.
|
168
199
|
def updated_at
|
169
|
-
|
170
|
-
time && "#{time.year}#{time.month}#{time.day}#{time.hour}#{time.min}#{time.sec}"
|
200
|
+
instance_read(:updated_at)
|
171
201
|
end
|
172
202
|
|
173
|
-
#
|
174
|
-
#
|
175
|
-
#
|
176
|
-
#
|
177
|
-
#
|
203
|
+
# Paths and URLs can have a number of variables interpolated into them
|
204
|
+
# to vary the storage location based on name, id, style, class, etc.
|
205
|
+
# This method is a deprecated access into supplying and retrieving these
|
206
|
+
# interpolations. Future access should use either Paperclip.interpolates
|
207
|
+
# or extend the Paperclip::Interpolations module directly.
|
178
208
|
def self.interpolations
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
underscore(attachment.instance.class.name.pluralize)
|
184
|
-
end,
|
185
|
-
:basename => lambda do |attachment,style|
|
186
|
-
attachment.original_filename.gsub(File.extname(attachment.original_filename), "")
|
187
|
-
end,
|
188
|
-
:extension => lambda do |attachment,style|
|
189
|
-
((style = attachment.styles[style]) && style.last) ||
|
190
|
-
File.extname(attachment.original_filename).gsub(/^\.+/, "")
|
191
|
-
end,
|
192
|
-
:id => lambda{|attachment,style| attachment.instance.id },
|
193
|
-
:id_partition => lambda do |attachment, style|
|
194
|
-
("%09d" % attachment.instance.id).scan(/\d{3}/).join("/")
|
195
|
-
end,
|
196
|
-
:attachment => lambda{|attachment,style| attachment.name.to_s.downcase.pluralize },
|
197
|
-
:style => lambda{|attachment,style| style || attachment.default_style },
|
198
|
-
}
|
209
|
+
warn('[DEPRECATION] Paperclip::Attachment.interpolations is deprecated ' +
|
210
|
+
'and will be removed from future versions. ' +
|
211
|
+
'Use Paperclip.interpolates instead')
|
212
|
+
Paperclip::Interpolations
|
199
213
|
end
|
200
214
|
|
201
|
-
# This method really shouldn't be called that often. It's expected use is
|
202
|
-
# paperclip:refresh rake task and that's it. It will regenerate all
|
203
|
-
# forcefully, by reobtaining the original file and going through
|
204
|
-
# again.
|
215
|
+
# This method really shouldn't be called that often. It's expected use is
|
216
|
+
# in the paperclip:refresh rake task and that's it. It will regenerate all
|
217
|
+
# thumbnails forcefully, by reobtaining the original file and going through
|
218
|
+
# the post-process again.
|
205
219
|
def reprocess!
|
206
220
|
new_original = Tempfile.new("paperclip-reprocess")
|
221
|
+
new_original.binmode
|
207
222
|
if old_original = to_file(:original)
|
208
223
|
new_original.write( old_original.read )
|
209
224
|
new_original.rewind
|
@@ -218,107 +233,183 @@ module Paperclip
|
|
218
233
|
true
|
219
234
|
end
|
220
235
|
end
|
221
|
-
|
236
|
+
|
237
|
+
# Returns true if a file has been assigned.
|
222
238
|
def file?
|
223
239
|
!original_filename.blank?
|
224
240
|
end
|
225
241
|
|
242
|
+
# Writes the attachment-specific attribute on the instance. For example,
|
243
|
+
# instance_write(:file_name, "me.jpg") will write "me.jpg" to the instance's
|
244
|
+
# "avatar_file_name" field (assuming the attachment is called avatar).
|
245
|
+
def instance_write(attr, value)
|
246
|
+
setter = :"#{name}_#{attr}="
|
247
|
+
responds = instance.respond_to?(setter)
|
248
|
+
self.instance_variable_set("@_#{setter.to_s.chop}", value)
|
249
|
+
instance.send(setter, value) if responds || attr.to_s == "file_name"
|
250
|
+
end
|
251
|
+
|
252
|
+
# Reads the attachment-specific attribute on the instance. See instance_write
|
253
|
+
# for more details.
|
254
|
+
def instance_read(attr)
|
255
|
+
getter = :"#{name}_#{attr}"
|
256
|
+
responds = instance.respond_to?(getter)
|
257
|
+
cached = self.instance_variable_get("@_#{getter}")
|
258
|
+
return cached if cached
|
259
|
+
instance.send(getter) if responds || attr.to_s == "file_name"
|
260
|
+
end
|
261
|
+
|
226
262
|
private
|
227
263
|
|
228
|
-
def
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
264
|
+
def ensure_required_accessors! #:nodoc:
|
265
|
+
%w(file_name).each do |field|
|
266
|
+
unless @instance.respond_to?("#{name}_#{field}") && @instance.respond_to?("#{name}_#{field}=")
|
267
|
+
raise PaperclipError.new("#{@instance.class} model missing required attr_accessor for '#{name}_#{field}'")
|
268
|
+
end
|
269
|
+
end
|
234
270
|
end
|
235
271
|
|
236
|
-
def
|
237
|
-
|
272
|
+
def log message #:nodoc:
|
273
|
+
Paperclip.log(message)
|
238
274
|
end
|
239
275
|
|
240
276
|
def valid_assignment? file #:nodoc:
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
(file.include?('tempfile') && file.include?('content_type') && file.include?('size') && file.include?('filename'))
|
277
|
+
if file.respond_to?(:[])
|
278
|
+
file[:filename] || file['filename']
|
279
|
+
else
|
280
|
+
file.nil? || (file.respond_to?(:original_filename) && file.respond_to?(:content_type))
|
246
281
|
end
|
247
282
|
end
|
248
283
|
|
249
284
|
def validate #:nodoc:
|
250
285
|
unless @validation_errors
|
251
|
-
@validation_errors = @validations.
|
252
|
-
|
253
|
-
|
254
|
-
|
286
|
+
@validation_errors = @validations.inject({}) do |errors, validation|
|
287
|
+
name, options = validation
|
288
|
+
errors[name] = send(:"validate_#{name}", options) if allow_validation?(options)
|
289
|
+
errors
|
290
|
+
end
|
291
|
+
@validation_errors.reject!{|k,v| v == nil }
|
292
|
+
@errors.merge!(@validation_errors)
|
255
293
|
end
|
256
294
|
@validation_errors
|
257
295
|
end
|
258
296
|
|
259
|
-
def
|
297
|
+
def allow_validation? options #:nodoc:
|
298
|
+
(options[:if].nil? || check_guard(options[:if])) && (options[:unless].nil? || !check_guard(options[:unless]))
|
299
|
+
end
|
300
|
+
|
301
|
+
def check_guard guard #:nodoc:
|
302
|
+
if guard.respond_to? :call
|
303
|
+
guard.call(instance)
|
304
|
+
elsif ! guard.blank?
|
305
|
+
instance.send(guard.to_s)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def validate_size options #:nodoc:
|
310
|
+
if file? && !options[:range].include?(size.to_i)
|
311
|
+
options[:message].gsub(/:min/, options[:min].to_s).gsub(/:max/, options[:max].to_s)
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def validate_presence options #:nodoc:
|
316
|
+
options[:message] unless file?
|
317
|
+
end
|
318
|
+
|
319
|
+
def validate_content_type options #:nodoc:
|
320
|
+
valid_types = [options[:content_type]].flatten
|
321
|
+
unless original_filename.blank?
|
322
|
+
unless valid_types.blank?
|
323
|
+
content_type = instance_read(:content_type)
|
324
|
+
unless valid_types.any?{|t| content_type.nil? || t === content_type }
|
325
|
+
options[:message] || "is not one of the allowed file types."
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def normalize_style_definition #:nodoc:
|
332
|
+
@styles.each do |name, args|
|
333
|
+
unless args.is_a? Hash
|
334
|
+
dimensions, format = [args, nil].flatten[0..1]
|
335
|
+
format = nil if format.blank?
|
336
|
+
@styles[name] = {
|
337
|
+
:processors => @processors,
|
338
|
+
:geometry => dimensions,
|
339
|
+
:format => format,
|
340
|
+
:whiny => @whiny,
|
341
|
+
:convert_options => extra_options_for(name)
|
342
|
+
}
|
343
|
+
else
|
344
|
+
@styles[name] = {
|
345
|
+
:processors => @processors,
|
346
|
+
:whiny => @whiny,
|
347
|
+
:convert_options => extra_options_for(name)
|
348
|
+
}.merge(@styles[name])
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def solidify_style_definitions #:nodoc:
|
260
354
|
@styles.each do |name, args|
|
261
|
-
|
262
|
-
|
263
|
-
@styles[name] = [dimensions, format]
|
355
|
+
@styles[name][:geometry] = @styles[name][:geometry].call(instance) if @styles[name][:geometry].respond_to?(:call)
|
356
|
+
@styles[name][:processors] = @styles[name][:processors].call(instance) if @styles[name][:processors].respond_to?(:call)
|
264
357
|
end
|
265
358
|
end
|
266
359
|
|
267
|
-
def initialize_storage
|
360
|
+
def initialize_storage #:nodoc:
|
268
361
|
@storage_module = Paperclip::Storage.const_get(@storage.to_s.capitalize)
|
269
362
|
self.extend(@storage_module)
|
270
363
|
end
|
271
364
|
|
272
365
|
def extra_options_for(style) #:nodoc:
|
273
|
-
|
366
|
+
all_options = convert_options[:all]
|
367
|
+
all_options = all_options.call(instance) if all_options.respond_to?(:call)
|
368
|
+
style_options = convert_options[style]
|
369
|
+
style_options = style_options.call(instance) if style_options.respond_to?(:call)
|
370
|
+
|
371
|
+
[ style_options, all_options ].compact.join(" ")
|
274
372
|
end
|
275
373
|
|
276
374
|
def post_process #:nodoc:
|
277
375
|
return if @queued_for_write[:original].nil?
|
278
|
-
|
376
|
+
solidify_style_definitions
|
377
|
+
post_process_styles
|
378
|
+
end
|
379
|
+
|
380
|
+
def post_process_styles #:nodoc:
|
279
381
|
@styles.each do |name, args|
|
280
382
|
begin
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
format,
|
286
|
-
extra_options_for(name),
|
287
|
-
@whiny_thumnails)
|
383
|
+
raise RuntimeError.new("Style #{name} has no processors defined.") if args[:processors].blank?
|
384
|
+
@queued_for_write[name] = args[:processors].inject(@queued_for_write[:original]) do |file, processor|
|
385
|
+
Paperclip.processor(processor).make(file, args, self)
|
386
|
+
end
|
288
387
|
rescue PaperclipError => e
|
289
|
-
|
388
|
+
log("An error was received while processing: #{e.inspect}")
|
389
|
+
(@errors[:processing] ||= []) << e.message if @whiny
|
290
390
|
end
|
291
391
|
end
|
292
392
|
end
|
293
393
|
|
294
394
|
def interpolate pattern, style = default_style #:nodoc:
|
295
|
-
|
296
|
-
interpolations.reverse.inject( pattern.dup ) do |result, interpolation|
|
297
|
-
tag, blk = interpolation
|
298
|
-
result.gsub(/:#{tag}/) do |match|
|
299
|
-
blk.call( self, style )
|
300
|
-
end
|
301
|
-
end
|
395
|
+
Paperclip::Interpolations.interpolate(pattern, self, style)
|
302
396
|
end
|
303
397
|
|
304
398
|
def queue_existing_for_delete #:nodoc:
|
305
399
|
return unless file?
|
306
|
-
#logger.info("[paperclip] Queueing the existing files for #{name} for deletion.")
|
307
400
|
@queued_for_delete += [:original, *@styles.keys].uniq.map do |style|
|
308
401
|
path(style) if exists?(style)
|
309
402
|
end.compact
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
403
|
+
instance_write(:file_name, nil)
|
404
|
+
instance_write(:content_type, nil)
|
405
|
+
instance_write(:file_size, nil)
|
406
|
+
instance_write(:updated_at, nil)
|
314
407
|
end
|
315
408
|
|
316
409
|
def flush_errors #:nodoc:
|
317
|
-
@errors.each do |error|
|
318
|
-
|
410
|
+
@errors.each do |error, message|
|
411
|
+
[message].flatten.each {|m| instance.errors.add(name, m) }
|
319
412
|
end
|
320
413
|
end
|
321
|
-
|
322
414
|
end
|
323
415
|
end
|
324
|
-
|