dm-paperclip 2.1.2.1 → 2.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- h1. DataMapper Paperclip
1
+ =DataMapper Paperclip
2
2
 
3
3
  DM-Paperclip is a port of Thoughtbot's Paperclip plugin to work with DataMapper 0.9. This plugin is fully compatible with
4
4
  the original ActiveRecord-oriented Paperclip. You could take an existing ActiveRecord database and use it with DataMapper.
@@ -14,11 +14,11 @@ It processes the thumbnails through the command-line applications instead of usi
14
14
 
15
15
  See the documentation for the +has_attached_file+ method for options.
16
16
 
17
- h2. Code
17
+ ==Code
18
18
 
19
19
  The code DM-Paperclip is available at Github:
20
20
 
21
- git clone git://github.com/krobertson/paperclip.git
21
+ git clone git://github.com/krobertson/dm-paperclip.git
22
22
 
23
23
  It is regularly updated to keep in sync with the latest from Thoughtbot.
24
24
 
@@ -27,7 +27,7 @@ packaged as a gem through Rubyforge:
27
27
 
28
28
  sudo gem install dm-paperclip
29
29
 
30
- h2. Usage
30
+ ==Usage
31
31
 
32
32
  In your model:
33
33
 
@@ -41,8 +41,9 @@ In your model:
41
41
  :thumb => "100x100>" }
42
42
  end
43
43
 
44
- Your database will need to add three columns, +avatar_file_name+ (varchar), +avatar_content_type+ (varchar), and
45
- +avatar_file_size+ (integer). You can either add these manually, auto-migrate, or use the following migration:
44
+ Your database will need to add four columns, avatar_file_name (varchar), avatar_content_type (varchar), and
45
+ avatar_file_size (integer), and avatar_updated_at (datetime). You can either add these manually, auto-
46
+ migrate, or use the following migration:
46
47
 
47
48
  migration( 1, :add_user_paperclip_fields ) do
48
49
  up do
@@ -50,11 +51,12 @@ Your database will need to add three columns, +avatar_file_name+ (varchar), +ava
50
51
  add_column :avatar_file_name, "varchar(255)"
51
52
  add_column :avatar_content_type, "varchar(255)"
52
53
  add_column :avatar_file_size, "integer"
54
+ add_column :avatar_updated_at, "datetime"
53
55
  end
54
56
  end
55
57
  down do
56
58
  modify_table :users do
57
- drop_columns :avatar_file_name, :avatar_content_type, :avatar_file_size
59
+ drop_columns :avatar_file_name, :avatar_content_type, :avatar_file_size, :avatar_updated_at
58
60
  end
59
61
  end
60
62
  end
data/Rakefile CHANGED
@@ -14,7 +14,7 @@ task :default => [:clean, :test]
14
14
  desc 'Test the DM-Paperclip library.'
15
15
  Rake::TestTask.new(:test) do |t|
16
16
  t.libs << 'dm-paperclip'
17
- t.pattern = 'test/**/test_*.rb'
17
+ t.pattern = 'test/**/*_test.rb'
18
18
  t.verbose = true
19
19
  end
20
20
 
@@ -30,7 +30,7 @@ Rake::RDocTask.new(:doc) do |rdoc|
30
30
  rdoc.rdoc_dir = 'doc'
31
31
  rdoc.title = 'DM-Paperclip'
32
32
  rdoc.options << '--line-numbers' << '--inline-source'
33
- rdoc.rdoc_files.include('README.textile')
33
+ rdoc.rdoc_files.include('README.rdoc')
34
34
  rdoc.rdoc_files.include('lib/**/*.rb')
35
35
  end
36
36
 
@@ -55,11 +55,11 @@ spec = Gem::Specification.new do |s|
55
55
  s.name = "dm-paperclip"
56
56
  s.version = Paperclip::VERSION
57
57
  s.author = "Ken Robertson"
58
- s.email = "ken@invalidlogic.com jyurek@thoughtbot.com"
58
+ s.email = "ken@invalidlogic.com"
59
59
  s.homepage = "http://invalidlogic.com/dm-paperclip/"
60
60
  s.platform = Gem::Platform::RUBY
61
61
  s.summary = "File attachments as attributes for DataMapper, based on the original Paperclip by Jon Yurek at Thoughtbot"
62
- s.files = FileList["README.textile",
62
+ s.files = FileList["README.rdoc",
63
63
  "LICENSE",
64
64
  "Rakefile",
65
65
  "init.rb",
@@ -68,7 +68,7 @@ spec = Gem::Specification.new do |s|
68
68
  s.test_files = FileList["test/**/test_*.rb"].to_a
69
69
  s.rubyforge_project = "dm-paperclip"
70
70
  s.has_rdoc = true
71
- s.extra_rdoc_files = ["README.textile"]
71
+ s.extra_rdoc_files = ["README.rdoc"]
72
72
  s.rdoc_options << '--line-numbers' << '--inline-source'
73
73
  s.requirements << "ImageMagick"
74
74
  s.requirements << "data_mapper"
data/lib/dm-paperclip.rb CHANGED
@@ -37,7 +37,7 @@ require File.join(File.dirname(__FILE__), 'dm-paperclip', 'attachment')
37
37
  require File.join(File.dirname(__FILE__), 'dm-paperclip', 'validations') unless defined?(DataMapper::Validate).nil?
38
38
 
39
39
  module Paperclip
40
- VERSION = "2.1.2.1"
40
+ VERSION = "2.1.4"
41
41
  class << self
42
42
  # Provides configurability to Paperclip. There are a number of options available, such as:
43
43
  # * whiny_thumbnails: Will raise an error if Paperclip cannot process thumbnails of
@@ -66,12 +66,21 @@ module Paperclip
66
66
 
67
67
  module Resource
68
68
  def self.included(base)
69
+ base.class_eval <<-RUBY, __FILE__, __LINE__ + 1
70
+ class_variable_set(:@@attachment_definitions,nil) unless class_variable_defined?(:@@attachment_definitions)
71
+ def self.attachment_definitions
72
+ @@attachment_definitions
73
+ end
74
+
75
+ def self.attachment_definitions=(obj)
76
+ @@attachment_definitions = obj
77
+ end
78
+ RUBY
69
79
  base.extend Paperclip::ClassMethods
70
80
  end
71
81
  end
72
82
 
73
83
  module ClassMethods
74
- @@attachment_definitions = {}
75
84
 
76
85
  # +has_attached_file+ gives the class it is called on an attribute that maps to a file. This
77
86
  # is typically a file stored somewhere on the filesystem and has been uploaded by a user.
@@ -106,30 +115,40 @@ module Paperclip
106
115
  # has_attached_file :avatar, :styles => { :normal => "100x100#" },
107
116
  # :default_style => :normal
108
117
  # user.avatar.url # => "/avatars/23/normal_me.png"
109
- # * +path+: The location of the repository of attachments on disk. This can be coordinated
110
- # with the value of the +url+ option to allow files to be saved into a place where Apache
111
- # can serve them without hitting your app. Defaults to
112
- # ":merb_root/public/:class/:attachment/:id/:style_:filename".
113
- # By default this places the files in the app's public directory which can be served
114
- # directly. If you are using capistrano for deployment, a good idea would be to
115
- # make a symlink to the capistrano-created system directory from inside your app's
116
- # public directory.
117
- # See Paperclip::Attachment#interpolate for more information on variable interpolaton.
118
- # :path => "/var/app/attachments/:class/:id/:style/:filename"
119
118
  # * +whiny_thumbnails+: Will raise an error if Paperclip cannot process thumbnails of an
120
119
  # uploaded image. This will ovrride the global setting for this attachment.
121
120
  # Defaults to true.
121
+ # * +convert_options+: When creating thumbnails, use this free-form options
122
+ # field to pass in various convert command options. Typical options are "-strip" to
123
+ # remove all Exif data from the image (save space for thumbnails and avatars) or
124
+ # "-depth 8" to specify the bit depth of the resulting conversion. See ImageMagick
125
+ # convert documentation for more options: (http://www.imagemagick.org/script/convert.php)
126
+ # Note that this option takes a hash of options, each of which correspond to the style
127
+ # of thumbnail being generated. You can also specify :all as a key, which will apply
128
+ # to all of the thumbnails being generated. If you specify options for the :original,
129
+ # it would be best if you did not specify destructive options, as the intent of keeping
130
+ # the original around is to regenerate all the thumbnails then requirements change.
131
+ # has_attached_file :avatar, :styles => { :large => "300x300", :negative => "100x100" }
132
+ # :convert_options => {
133
+ # :all => "-strip",
134
+ # :negative => "-negate"
135
+ # }
136
+ # * +storage+: Chooses the storage backend where the files will be stored. The current
137
+ # choices are :filesystem and :s3. The default is :filesystem. Make sure you read the
138
+ # documentation for Paperclip::Storage::Filesystem and Paperclip::Storage::S3
139
+ # for backend-specific options.
122
140
  def has_attached_file name, options = {}
123
141
  include InstanceMethods
124
142
 
125
- @@attachment_definitions = {} if @@attachment_definitions.nil?
126
- @@attachment_definitions[name] = {:validations => []}.merge(options)
143
+ self.attachment_definitions = {} if self.attachment_definitions.nil?
144
+ self.attachment_definitions[name] = {:validations => []}.merge(options)
127
145
 
128
146
  property_options = options.delete_if { |k,v| ![ :public, :protected, :private, :accessor, :reader, :writer ].include?(key) }
129
147
 
130
148
  property "#{name}_file_name".to_sym, String, property_options
131
149
  property "#{name}_content_type".to_sym, String, property_options
132
150
  property "#{name}_file_size".to_sym, Integer, property_options
151
+ property "#{name}_updated_at".to_sym, DateTime, property_options
133
152
 
134
153
  after :save, :save_attached_files
135
154
  before :destroy, :destroy_attached_files
@@ -167,7 +186,7 @@ module Paperclip
167
186
 
168
187
  # Adds errors if thumbnail creation fails. The same as specifying :whiny_thumbnails => true.
169
188
  def validates_attachment_thumbnails name, options = {}
170
- @@attachment_definitions[name][:whiny_thumbnails] = true
189
+ self.attachment_definitions[name][:whiny_thumbnails] = true
171
190
  end
172
191
 
173
192
  # Places ActiveRecord-style validations on the presence of a file.
@@ -187,11 +206,6 @@ module Paperclip
187
206
 
188
207
  end
189
208
 
190
- # Returns the attachment definitions defined by each call to has_attached_file.
191
- def attachment_definitions
192
- @@attachment_definitions
193
- end
194
-
195
209
  end
196
210
 
197
211
  module InstanceMethods #:nodoc:
@@ -207,12 +221,14 @@ module Paperclip
207
221
  end
208
222
 
209
223
  def save_attached_files
224
+ #logger.info("[paperclip] Saving attachments.")
210
225
  each_attachment do |name, attachment|
211
226
  attachment.send(:save)
212
227
  end
213
228
  end
214
229
 
215
230
  def destroy_attached_files
231
+ #logger.info("[paperclip] Deleting attachments.")
216
232
  each_attachment do |name, attachment|
217
233
  attachment.send(:queue_existing_for_delete)
218
234
  attachment.send(:flush_deletes)
@@ -15,7 +15,7 @@ module Paperclip
15
15
  }
16
16
  end
17
17
 
18
- attr_reader :name, :instance, :styles, :default_style
18
+ attr_reader :name, :instance, :styles, :default_style, :convert_options
19
19
 
20
20
  # Creates an Attachment object. +name+ is the name of the attachment, +instance+ is the
21
21
  # ActiveRecord object instance it's attached to, and +options+ is the same as the hash
@@ -34,6 +34,7 @@ module Paperclip
34
34
  @default_style = options[:default_style]
35
35
  @storage = options[:storage]
36
36
  @whiny_thumbnails = options[:whiny_thumbnails]
37
+ @convert_options = options[:convert_options] || {}
37
38
  @options = options
38
39
  @queued_for_delete = []
39
40
  @queued_for_write = {}
@@ -43,13 +44,22 @@ module Paperclip
43
44
 
44
45
  normalize_style_definition
45
46
  initialize_storage
47
+
48
+ #logger.info("[paperclip] Paperclip attachment #{name} on #{instance.class} initialized.")
46
49
  end
47
50
 
48
51
  # What gets called when you call instance.attachment = File. It clears errors,
49
52
  # assigns attributes, processes the file, and runs validations. It also queues up
50
53
  # the previous file for deletion, to be flushed away on #save of its host.
54
+ # In addition to form uploads, you can also assign another Paperclip attachment:
55
+ # new_user.avatar = old_user.avatar
51
56
  def assign uploaded_file
57
+ if uploaded_file.is_a?(Paperclip::Attachment)
58
+ uploaded_file = uploaded_file.to_file(:original)
59
+ end
60
+
52
61
  return nil unless valid_assignment?(uploaded_file)
62
+ #logger.info("[paperclip] Assigning #{uploaded_file.inspect} to #{name}")
53
63
 
54
64
  queue_existing_for_delete
55
65
  @errors = []
@@ -57,23 +67,36 @@ module Paperclip
57
67
 
58
68
  return nil if uploaded_file.nil?
59
69
 
70
+ #logger.info("[paperclip] Writing attributes for #{name}")
71
+ newvals = {}
60
72
  if uploaded_file.is_a?(Mash)
61
73
  @queued_for_write[:original] = uploaded_file['tempfile']
62
- newvals = { :"#{@name}_file_name" => uploaded_file['filename'],
63
- :"#{@name}_content_type" => uploaded_file['content_type'],
64
- :"#{@name}_file_size" => uploaded_file['size'] }
65
- @instance.update_attributes(newvals)
74
+ newvals = { :"#{@name}_file_name" => uploaded_file['filename'].strip.gsub(/[^\w\d\.\-]+/, '_'),
75
+ :"#{@name}_content_type" => uploaded_file['content_type'].strip,
76
+ :"#{@name}_file_size" => uploaded_file['size'],
77
+ :"#{@name}_updated_at" => Time.now }
66
78
  else
67
79
  @queued_for_write[:original] = uploaded_file.to_tempfile
68
- newvals = { :"#{@name}_file_name" => uploaded_file.original_filename,
69
- :"#{@name}_content_type" => uploaded_file.content_type,
70
- :"#{@name}_file_size" => uploaded_file.size }
71
- @instance.update_attributes(newvals)
80
+ newvals = { :"#{@name}_file_name" => uploaded_file.original_filename.strip.gsub(/[^\w\d\.\-]+/, '_'),
81
+ :"#{@name}_content_type" => uploaded_file.content_type.strip,
82
+ :"#{@name}_file_size" => uploaded_file.size,
83
+ :"#{@name}_updated_at" => Time.now }
72
84
  end
73
85
 
86
+ post_process
74
87
  @dirty = true
75
88
 
76
- post_process
89
+ # Reset the file size if the original file was reprocessed.
90
+ #newvals[:"#{@name}_file_size"] = uploaded_file.size.to_i
91
+ if @styles[:original]
92
+ newvals[:"#{@name}_file_size"] = @queued_for_write[:original].size.to_i
93
+ end
94
+
95
+ begin
96
+ @instance.attributes = newvals
97
+ rescue NameError
98
+ raise PaperclipError, "There was an error processing this attachment"
99
+ end
77
100
  ensure
78
101
  validate
79
102
  end
@@ -84,12 +107,13 @@ module Paperclip
84
107
  # This is not recommended if you don't need the security, however, for
85
108
  # performance reasons.
86
109
  def url style = default_style
87
- original_filename.nil? ? interpolate(@default_url, style) : interpolate(@url, style)
110
+ url = original_filename.nil? ? interpolate(@default_url, style) : interpolate(@url, style)
111
+ updated_at ? [url, updated_at].compact.join(url.include?("?") ? "&" : "?") : url
88
112
  end
89
113
 
90
- # Returns the path of the attachment as defined by the :path optionn. If the
114
+ # Returns the path of the attachment as defined by the :path option. If the
91
115
  # file is stored in the filesystem the path refers to the path of the file on
92
- # disk. If the file is stored in S3, the path is the "key" part of th URL,
116
+ # disk. If the file is stored in S3, the path is the "key" part of the URL,
93
117
  # and the :bucket option refers to the S3 bucket.
94
118
  def path style = nil #:nodoc:
95
119
  interpolate(@path, style)
@@ -100,7 +124,7 @@ module Paperclip
100
124
  url(style)
101
125
  end
102
126
 
103
- # Returns true if there are any errors on this attachment.
127
+ # Returns true if there are no errors on this attachment.
104
128
  def valid?
105
129
  @errors.length == 0
106
130
  end
@@ -119,11 +143,13 @@ module Paperclip
119
143
  # the instance's errors and returns false, cancelling the save.
120
144
  def save
121
145
  if valid?
146
+ #logger.info("[paperclip] Saving files for #{name}")
122
147
  flush_deletes
123
148
  flush_writes
124
149
  @dirty = false
125
150
  true
126
151
  else
152
+ #logger.info("[paperclip] Errors on #{name}. Not saving.")
127
153
  flush_errors
128
154
  false
129
155
  end
@@ -132,7 +158,16 @@ module Paperclip
132
158
  # Returns the name of the file as originally assigned, and as lives in the
133
159
  # <attachment>_file_name attribute of the model.
134
160
  def original_filename
135
- @instance.attribute_get(:"#{name}_file_name")
161
+ begin
162
+ @instance.attribute_get(:"#{name}_file_name")
163
+ rescue ArgumentError
164
+ nil
165
+ end
166
+ end
167
+
168
+ def updated_at
169
+ time = @instance.attribute_get(:"#{name}_updated_at")
170
+ time && "#{time.year}#{time.month}#{time.day}#{time.hour}#{time.min}#{time.sec}"
136
171
  end
137
172
 
138
173
  # A hash of procs that are run during the interpolation of a path or url.
@@ -143,6 +178,7 @@ module Paperclip
143
178
  def self.interpolations
144
179
  @interpolations ||= {
145
180
  :merb_root => lambda{|attachment,style| Merb.root },
181
+ :merb_env => lambda{|attachment,style| Merb.env },
146
182
  :class => lambda do |attachment,style|
147
183
  underscore(attachment.instance.class.name.pluralize)
148
184
  end,
@@ -168,14 +204,23 @@ module Paperclip
168
204
  # again.
169
205
  def reprocess!
170
206
  new_original = Tempfile.new("paperclip-reprocess")
171
- old_original = to_file(:original)
172
- new_original.write( old_original.read )
173
- new_original.rewind
207
+ if old_original = to_file(:original)
208
+ new_original.write( old_original.read )
209
+ new_original.rewind
174
210
 
175
- @queued_for_write = { :original => new_original }
176
- post_process
211
+ @queued_for_write = { :original => new_original }
212
+ post_process
213
+
214
+ old_original.close if old_original.respond_to?(:close)
177
215
 
178
- old_original.close if old_original.respond_to?(:close)
216
+ save
217
+ else
218
+ true
219
+ end
220
+ end
221
+
222
+ def file?
223
+ !original_filename.blank?
179
224
  end
180
225
 
181
226
  private
@@ -188,6 +233,10 @@ module Paperclip
188
233
  downcase
189
234
  end
190
235
 
236
+ def logger
237
+ instance.logger
238
+ end
239
+
191
240
  def valid_assignment? file #:nodoc:
192
241
  return true if file.nil?
193
242
  if(file.is_a?(File))
@@ -204,6 +253,7 @@ module Paperclip
204
253
  end.flatten.compact.uniq
205
254
  @errors += @validation_errors
206
255
  end
256
+ @validation_errors
207
257
  end
208
258
 
209
259
  def normalize_style_definition
@@ -219,14 +269,21 @@ module Paperclip
219
269
  self.extend(@storage_module)
220
270
  end
221
271
 
272
+ def extra_options_for(style) #:nodoc:
273
+ [ convert_options[style], convert_options[:all] ].compact.join(" ")
274
+ end
275
+
222
276
  def post_process #:nodoc:
223
277
  return if @queued_for_write[:original].nil?
278
+ #logger.info("[paperclip] Post-processing #{name}")
224
279
  @styles.each do |name, args|
225
280
  begin
226
281
  dimensions, format = args
282
+ dimensions = dimensions.call(instance) if dimensions.respond_to? :call
227
283
  @queued_for_write[name] = Thumbnail.make(@queued_for_write[:original],
228
284
  dimensions,
229
285
  format,
286
+ extra_options_for(name),
230
287
  @whiny_thumnails)
231
288
  rescue PaperclipError => e
232
289
  @errors << e.message if @whiny_thumbnails
@@ -245,14 +302,15 @@ module Paperclip
245
302
  end
246
303
 
247
304
  def queue_existing_for_delete #:nodoc:
248
- return if original_filename.blank?
305
+ return unless file?
306
+ #logger.info("[paperclip] Queueing the existing files for #{name} for deletion.")
249
307
  @queued_for_delete += [:original, *@styles.keys].uniq.map do |style|
250
308
  path(style) if exists?(style)
251
309
  end.compact
252
310
  newvals = { :"#{@name}_file_name" => nil,
253
311
  :"#{@name}_content_type" => nil,
254
312
  :"#{@name}_file_size" => nil }
255
- @instance.update_attributes(newvals)
313
+ @instance.attributes = newvals
256
314
  end
257
315
 
258
316
  def flush_errors #:nodoc:
@@ -76,9 +76,9 @@ module Paperclip
76
76
  # overhanging image would be cropped. Useful for square thumbnail images. The cropping
77
77
  # is weighted at the center of the Geometry.
78
78
  def transformation_to dst, crop = false
79
- ratio = Geometry.new( dst.width / self.width, dst.height / self.height )
80
79
 
81
80
  if crop
81
+ ratio = Geometry.new( dst.width / self.width, dst.height / self.height )
82
82
  scale_geometry, scale = scaling(dst, ratio)
83
83
  crop_geometry = cropping(dst, ratio, scale)
84
84
  else