dm-paperclip 2.1.2.1 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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