paperclip 2.3.11 → 2.3.12

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of paperclip might be problematic. Click here for more details.

data/README.md CHANGED
@@ -36,13 +36,23 @@ In development mode, you might add this line to `config/environments/development
36
36
  Installation
37
37
  ------------
38
38
 
39
+ Paperclip is distributed as a gem, which is how it should be used in your app. It's
40
+ technically still installable as a plugin, but that's discouraged, as Rails plays
41
+ well with gems.
42
+
39
43
  Include the gem in your Gemfile:
40
44
 
41
45
  gem "paperclip", "~> 2.3"
42
46
 
43
- Or as a plugin:
47
+ Or, if you don't use Bundler (though you probably should, even in Rails 2), with config.gem
44
48
 
45
- ruby script/plugin install git://github.com/thoughtbot/paperclip.git
49
+ # In config/environment.rb
50
+ ...
51
+ Rails::Initializer.run do |config|
52
+ ...
53
+ config.gem "paperclip", :version => "~> 2.3"
54
+ ...
55
+ end
46
56
 
47
57
  Quick Start
48
58
  -----------
@@ -95,7 +105,7 @@ Usage
95
105
  The basics of paperclip are quite simple: Declare that your model has an
96
106
  attachment with the has_attached_file method, and give it a name. Paperclip
97
107
  will wrap up up to four attributes (all prefixed with that attachment's name,
98
- so you can have multiple attachments per model if you wish) and give the a
108
+ so you can have multiple attachments per model if you wish) and give them a
99
109
  friendly front end. The attributes are `<attachment>_file_name`,
100
110
  `<attachment>_file_size`, `<attachment>_content_type`, and `<attachment>_updated_at`.
101
111
  Only `<attachment>_file_name` is required for paperclip to operate. More
@@ -178,6 +188,9 @@ or more or the processors, and they are expected to ignore it.
178
188
  _NOTE: Because processors operate by turning the original attachment into the
179
189
  styles, no processors will be run if there are no styles defined._
180
190
 
191
+ If you're interested in caching your thumbnail's width, height and size in the
192
+ database, take a look at the [paperclip-meta](https://github.com/y8/paperclip-meta) gem.
193
+
181
194
  Events
182
195
  ------
183
196
 
@@ -196,11 +209,19 @@ _NOTE: Post processing will not even *start* if the attachment is not valid
196
209
  according to the validations. Your callbacks and processors will *only* be
197
210
  called with valid attachments._
198
211
 
212
+ URI Obfuscation
213
+ ---------------
214
+
215
+ Paperclip has an interpolation called `:hash` for obfuscating filenames of publicly-available files. For more on this feature read author's own explanation.
216
+
217
+ [https://github.com/thoughtbot/paperclip/pull/416](https://github.com/thoughtbot/paperclip/pull/416)
218
+
199
219
  Testing
200
220
  -------
201
221
 
202
222
  Paperclip provides rspec-compatible matchers for testing attachments. See the
203
- documentation on Paperclip::Shoulda::Matchers for more information.
223
+ documentation on [Paperclip::Shoulda::Matchers](http://rubydoc.info/gems/paperclip/Paperclip/Shoulda/Matchers)
224
+ for more information.
204
225
 
205
226
  Contributing
206
227
  ------------
data/init.rb CHANGED
@@ -1 +1,4 @@
1
1
  require File.join(File.dirname(__FILE__), "lib", "paperclip")
2
+ require 'paperclip/railtie'
3
+
4
+ Paperclip::Railtie.insert
@@ -5,7 +5,7 @@
5
5
  # columns to your table.
6
6
  #
7
7
  # Author:: Jon Yurek
8
- # Copyright:: Copyright (c) 2008-2009 thoughtbot, inc.
8
+ # Copyright:: Copyright (c) 2008-2011 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
@@ -38,14 +38,9 @@ require 'paperclip/interpolations'
38
38
  require 'paperclip/style'
39
39
  require 'paperclip/attachment'
40
40
  require 'paperclip/storage'
41
- require 'paperclip/callback_compatability'
42
- require 'paperclip/command_line'
41
+ require 'paperclip/callback_compatibility'
43
42
  require 'paperclip/railtie'
44
- if defined?(Rails.root) && Rails.root
45
- Dir.glob(File.join(File.expand_path(Rails.root), "lib", "paperclip_processors", "*.rb")).each do |processor|
46
- require processor
47
- end
48
- end
43
+ require 'cocaine'
49
44
 
50
45
  # The base module that gets included in ActiveRecord::Base. See the
51
46
  # documentation for Paperclip::ClassMethods for more useful information.
@@ -87,7 +82,7 @@ module Paperclip
87
82
  # symlink them so they are all in the same directory.
88
83
  #
89
84
  # If the command returns with a result code that is not one of the
90
- # expected_outcodes, a PaperclipCommandLineError will be raised. Generally
85
+ # expected_outcodes, a Cocaine::CommandLineError will be raised. Generally
91
86
  # a code of 0 is expected, but a list of codes may be passed if necessary.
92
87
  # These codes should be passed as a hash as the last argument, like so:
93
88
  #
@@ -100,12 +95,13 @@ module Paperclip
100
95
  if options[:image_magick_path]
101
96
  Paperclip.log("[DEPRECATION] :image_magick_path is deprecated and will be removed. Use :command_path instead")
102
97
  end
103
- CommandLine.path = options[:command_path] || options[:image_magick_path]
104
- CommandLine.new(cmd, *params).run
98
+ Cocaine::CommandLine.path = options[:command_path] || options[:image_magick_path]
99
+ Cocaine::CommandLine.new(cmd, *params).run
105
100
  end
106
101
 
107
102
  def processor name #:nodoc:
108
103
  name = name.to_s.camelize
104
+ load_processor(name) unless Paperclip.const_defined?(name)
109
105
  processor = Paperclip.const_get(name)
110
106
  unless processor.ancestors.include?(Paperclip::Processor)
111
107
  raise PaperclipError.new("Processor #{name} was not found")
@@ -113,8 +109,14 @@ module Paperclip
113
109
  processor
114
110
  end
115
111
 
112
+ def load_processor(name)
113
+ if defined?(Rails.root) && Rails.root
114
+ require File.expand_path(Rails.root.join("lib", "paperclip_processors", "#{name.underscore}.rb"))
115
+ end
116
+ end
117
+
116
118
  def each_instance_with_attachment(klass, name)
117
- Object.const_get(klass).all.each do |instance|
119
+ class_for(klass).all.each do |instance|
118
120
  yield(instance) if instance.send(:"#{name}?")
119
121
  end
120
122
  end
@@ -126,23 +128,40 @@ module Paperclip
126
128
  end
127
129
 
128
130
  def logger #:nodoc:
129
- ActiveRecord::Base.logger
131
+ defined?(ActiveRecord::Base) ? ActiveRecord::Base.logger : Rails.logger
130
132
  end
131
133
 
132
134
  def logging? #:nodoc:
133
135
  options[:log]
134
136
  end
135
- end
136
137
 
137
- class PaperclipError < StandardError #:nodoc:
138
+ def class_for(class_name)
139
+ # Ruby 1.9 introduces an inherit argument for Module#const_get and
140
+ # #const_defined? and changes their default behavior.
141
+ # https://github.com/rails/rails/blob/v3.0.9/activesupport/lib/active_support/inflector/methods.rb#L89
142
+ if Module.method(:const_get).arity == 1
143
+ class_name.split('::').inject(Object) do |klass, partial_class_name|
144
+ klass.const_defined?(partial_class_name) ? klass.const_get(partial_class_name) : klass.const_missing(partial_class_name)
145
+ end
146
+ else
147
+ class_name.split('::').inject(Object) do |klass, partial_class_name|
148
+ klass.const_defined?(partial_class_name) ? klass.const_get(partial_class_name, false) : klass.const_missing(partial_class_name)
149
+ end
150
+ end
151
+ rescue ArgumentError => e
152
+ # Sadly, we need to capture ArguementError here because Rails 2.3.x
153
+ # Active Support dependency's management will try to the constant inherited
154
+ # from Object, and fail misably with "Object is not missing constant X" error
155
+ # https://github.com/rails/rails/blob/v2.3.12/activesupport/lib/active_support/dependencies.rb#L124
156
+ if e.message =~ /is not missing constant/
157
+ raise NameError, "uninitialized constant #{class_name}"
158
+ else
159
+ raise e
160
+ end
161
+ end
138
162
  end
139
163
 
140
- class PaperclipCommandLineError < PaperclipError #:nodoc:
141
- attr_accessor :output
142
- def initialize(msg = nil, output = nil)
143
- super(msg)
144
- @output = output
145
- end
164
+ class PaperclipError < StandardError #:nodoc:
146
165
  end
147
166
 
148
167
  class StorageMethodNotFound < PaperclipError
@@ -160,6 +179,7 @@ module Paperclip
160
179
  module Glue
161
180
  def self.included base #:nodoc:
162
181
  base.extend ClassMethods
182
+ base.class_attribute :attachment_definitions
163
183
  if base.respond_to?("set_callback")
164
184
  base.send :include, Paperclip::CallbackCompatability::Rails3
165
185
  else
@@ -224,7 +244,7 @@ module Paperclip
224
244
  # }
225
245
  # NOTE: While not deprecated yet, it is not recommended to specify options this way.
226
246
  # It is recommended that :convert_options option be included in the hash passed to each
227
- # :styles for compatability with future versions.
247
+ # :styles for compatibility with future versions.
228
248
  # NOTE: Strings supplied to :convert_options are split on space in order to undergo
229
249
  # shell quoting for safety. If your options require a space, please pre-split them
230
250
  # and pass an array to :convert_options instead.
@@ -235,7 +255,14 @@ module Paperclip
235
255
  def has_attached_file name, options = {}
236
256
  include InstanceMethods
237
257
 
238
- write_inheritable_attribute(:attachment_definitions, {}) if attachment_definitions.nil?
258
+ if attachment_definitions.nil?
259
+ if respond_to?(:class_attribute)
260
+ self.attachment_definitions = {}
261
+ else
262
+ write_inheritable_attribute(:attachment_definitions, {})
263
+ end
264
+ end
265
+
239
266
  attachment_definitions[name] = {:validations => []}.merge(options)
240
267
 
241
268
  after_save :save_attached_files
@@ -343,7 +370,11 @@ module Paperclip
343
370
  # Returns the attachment definitions defined by each call to
344
371
  # has_attached_file.
345
372
  def attachment_definitions
346
- read_inheritable_attribute(:attachment_definitions)
373
+ if respond_to?(:class_attribute)
374
+ self.attachment_definitions
375
+ else
376
+ read_inheritable_attribute(:attachment_definitions)
377
+ end
347
378
  end
348
379
  end
349
380
 
@@ -11,6 +11,7 @@ module Paperclip
11
11
  :url => "/system/:attachment/:id/:style/:filename",
12
12
  :path => ":rails_root/public:url",
13
13
  :styles => {},
14
+ :only_process => [],
14
15
  :processors => [:thumbnail],
15
16
  :convert_options => {},
16
17
  :default_url => "/:attachment/:style/missing.png",
@@ -41,6 +42,7 @@ module Paperclip
41
42
  @path = options[:path]
42
43
  @path = @path.call(self) if @path.is_a?(Proc)
43
44
  @styles = options[:styles]
45
+ @only_process = options[:only_process]
44
46
  @normalized_styles = nil
45
47
  @default_url = options[:default_url]
46
48
  @default_style = options[:default_style]
@@ -64,7 +66,7 @@ module Paperclip
64
66
  end
65
67
 
66
68
  def styles
67
- unless @normalized_styles
69
+ if @styles.respond_to?(:call) || !@normalized_styles
68
70
  @normalized_styles = {}
69
71
  (@styles.respond_to?(:call) ? @styles.call(self) : @styles).each do |name, args|
70
72
  @normalized_styles[name] = Paperclip::Style.new(name, args.dup, self)
@@ -107,7 +109,7 @@ module Paperclip
107
109
 
108
110
  @dirty = true
109
111
 
110
- post_process if @post_processing
112
+ post_process(*@only_process) if @post_processing
111
113
 
112
114
  # Reset the file size if the original file was reprocessed.
113
115
  instance_write(:file_size, @queued_for_write[:original].size.to_i)
@@ -123,7 +125,8 @@ module Paperclip
123
125
  # security, however, for performance reasons. Set use_timestamp to false
124
126
  # if you want to stop the attachment update time appended to the url
125
127
  def url(style_name = default_style, use_timestamp = @use_timestamp)
126
- url = original_filename.nil? ? interpolate(@default_url, style_name) : interpolate(@url, style_name)
128
+ default_url = @default_url.is_a?(Proc) ? @default_url.call(self) : @default_url
129
+ url = original_filename.nil? ? interpolate(default_url, style_name) : interpolate(@url, style_name)
127
130
  use_timestamp && updated_at ? [url, updated_at].compact.join(url.include?("?") ? "&" : "?") : url
128
131
  end
129
132
 
@@ -268,6 +271,8 @@ module Paperclip
268
271
  def file?
269
272
  !original_filename.blank?
270
273
  end
274
+
275
+ alias :present? :file?
271
276
 
272
277
  # Writes the attachment-specific attribute on the instance. For example,
273
278
  # instance_write(:file_name, "me.jpg") will write "me.jpg" to the instance's
@@ -15,10 +15,13 @@ module Paperclip
15
15
  # File or path.
16
16
  def self.from_file file
17
17
  file = file.path if file.respond_to? "path"
18
+ raise(Paperclip::NotIdentifiedByImageMagickError.new("Cannot find the geometry of a file with a blank name")) if file.blank?
18
19
  geometry = begin
19
20
  Paperclip.run("identify", "-format %wx%h :file", :file => "#{file}[0]")
20
- rescue PaperclipCommandLineError
21
+ rescue Cocaine::ExitStatusError
21
22
  ""
23
+ rescue Cocaine::CommandNotFoundError => e
24
+ raise Paperclip::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.")
22
25
  end
23
26
  parse(geometry) ||
24
27
  raise(NotIdentifiedByImageMagickError.new("#{file} is not recognized by the 'identify' command."))
@@ -6,13 +6,13 @@ module Paperclip
6
6
  module Interpolations
7
7
  extend self
8
8
 
9
- # Hash assignment of interpolations. Included only for compatability,
9
+ # Hash assignment of interpolations. Included only for compatibility,
10
10
  # and is not intended for normal use.
11
11
  def self.[]= name, block
12
12
  define_method(name, &block)
13
13
  end
14
14
 
15
- # Hash access of interpolations. Included only for compatability,
15
+ # Hash access of interpolations. Included only for compatibility,
16
16
  # and is not intended for normal use.
17
17
  def self.[] name
18
18
  method(name)
@@ -35,7 +35,7 @@ module Paperclip
35
35
 
36
36
  # Returns the filename, the same way as ":basename.:extension" would.
37
37
  def filename attachment, style_name
38
- "#{basename(attachment, style_name)}.#{extension(attachment, style_name)}"
38
+ [ basename(attachment, style_name), extension(attachment, style_name) ].reject(&:blank?).join(".")
39
39
  end
40
40
 
41
41
  # Returns the interpolated URL. Will raise an error if the url itself
@@ -99,6 +99,11 @@ module Paperclip
99
99
  attachment.instance.id
100
100
  end
101
101
 
102
+ # Returns the #to_param of the instance.
103
+ def param attachment, style_name
104
+ attachment.instance.to_param
105
+ end
106
+
102
107
  # Returns the fingerprint of the instance.
103
108
  def fingerprint attachment, style_name
104
109
  attachment.fingerprint
@@ -1,5 +1,35 @@
1
1
  module Paperclip
2
2
  module Storage
3
+ # fog is a modern and versatile cloud computing library for Ruby.
4
+ # Among others, it supports Amazon S3 to store your files. In
5
+ # contrast to the outdated AWS-S3 gem it is actively maintained and
6
+ # supports multiple locations.
7
+ # Amazon's S3 file hosting service is a scalable, easy place to
8
+ # store files for distribution. You can find out more about it at
9
+ # http://aws.amazon.com/s3 There are a few fog-specific options for
10
+ # has_attached_file, which will be explained using S3 as an example:
11
+ # * +fog_credentials+: Takes a Hash with your credentials. For S3,
12
+ # you can use the following format:
13
+ # aws_access_key_id: '<your aws_access_key_id>'
14
+ # aws_secret_access_key: '<your aws_secret_access_key>'
15
+ # provider: 'AWS'
16
+ # region: 'eu-west-1'
17
+ # * +fog_directory+: This is the name of the S3 bucket that will
18
+ # store your files. Remember that the bucket must be unique across
19
+ # all of Amazon S3. If the bucket does not exist, Paperclip will
20
+ # attempt to create it.
21
+ # * +path+: This is the key under the bucket in which the file will
22
+ # be stored. The URL will be constructed from the bucket and the
23
+ # path. This is what you will want to interpolate. Keys should be
24
+ # unique, like filenames, and despite the fact that S3 (strictly
25
+ # speaking) does not support directories, you can still use a / to
26
+ # separate parts of your file name.
27
+ # * +fog_public+: (optional, defaults to true) Should the uploaded
28
+ # files be public or not? (true/false)
29
+ # * +fog_host+: (optional) The fully-qualified domain name (FQDN)
30
+ # that is the alias to the S3 domain of your bucket, e.g.
31
+ # 'http://images.example.com'. This can also be used in
32
+ # conjunction with Cloudfront (http://aws.amazon.com/cloudfront)
3
33
 
4
34
  module Fog
5
35
  def self.extended base
@@ -71,7 +101,8 @@ module Paperclip
71
101
 
72
102
  def public_url(style = default_style)
73
103
  if @fog_host
74
- "#{@fog_host}/#{path(style)}"
104
+ host = (@fog_host =~ /%d/) ? @fog_host % (path(style).hash % 4) : @fog_host
105
+ "#{host}/#{path(style)}"
75
106
  else
76
107
  directory.files.new(:key => path(style)).public_url
77
108
  end
@@ -39,9 +39,9 @@ module Paperclip
39
39
  # * +s3_host_alias+: The fully-qualified domain name (FQDN) that is the alias to the
40
40
  # S3 domain of your bucket. Used with the :s3_alias_url url interpolation. See the
41
41
  # link in the +url+ entry for more information about S3 domains and buckets.
42
- # * +url+: There are three options for the S3 url. You can choose to have the bucket's name
42
+ # * +url+: There are four options for the S3 url. You can choose to have the bucket's name
43
43
  # placed domain-style (bucket.s3.amazonaws.com) or path-style (s3.amazonaws.com/bucket).
44
- # Lastly, you can specify a CNAME (which requires the CNAME to be specified as
44
+ # You can also specify a CNAME (which requires the CNAME to be specified as
45
45
  # :s3_alias_url. You can read more about CNAMEs and S3 at
46
46
  # http://docs.amazonwebservices.com/AmazonS3/latest/index.html?VirtualHosting.html
47
47
  # Normally, this won't matter in the slightest and you can leave the default (which is
@@ -50,7 +50,9 @@ module Paperclip
50
50
  # NOTE: If you use a CNAME for use with CloudFront, you can NOT specify https as your
51
51
  # :s3_protocol; This is *not supported* by S3/CloudFront. Finally, when using the host
52
52
  # alias, the :bucket parameter is ignored, as the hostname is used as the bucket name
53
- # by S3.
53
+ # by S3. The fourth option for the S3 url is :asset_host, which uses Rails' built-in
54
+ # asset_host settings. NOTE: To get the full url from a paperclip'd object, use the
55
+ # image_path helper; this is what image_tag uses to generate the url for an img tag.
54
56
  # * +path+: This is the key under the bucket in which the file will be stored. The
55
57
  # URL will be constructed from the bucket and the path. This is what you will want
56
58
  # to interpolate. Keys should be unique, like filenames, and despite the fact that
@@ -74,10 +76,12 @@ module Paperclip
74
76
  @s3_protocol = @options[:s3_protocol] || (@s3_permissions == :public_read ? 'http' : 'https')
75
77
  @s3_headers = @options[:s3_headers] || {}
76
78
  @s3_host_alias = @options[:s3_host_alias]
79
+ @s3_host_alias = @s3_host_alias.call(self) if @s3_host_alias.is_a?(Proc)
77
80
  unless @url.to_s.match(/^:s3.*url$/)
78
81
  @path = @path.gsub(/:url/, @url)
79
82
  @url = ":s3_path_url"
80
83
  end
84
+ @url = ":asset_host" if @options[:url].to_s == ":asset_host"
81
85
  AWS::S3::Base.establish_connection!( @s3_options.merge(
82
86
  :access_key_id => @s3_credentials[:access_key_id],
83
87
  :secret_access_key => @s3_credentials[:secret_access_key]
@@ -92,6 +96,9 @@ module Paperclip
92
96
  Paperclip.interpolates(:s3_domain_url) do |attachment, style|
93
97
  "#{attachment.s3_protocol}://#{attachment.bucket_name}.s3.amazonaws.com/#{attachment.path(style).gsub(%r{^/}, "")}"
94
98
  end unless Paperclip::Interpolations.respond_to? :s3_domain_url
99
+ Paperclip.interpolates(:asset_host) do |attachment, style|
100
+ "#{attachment.path(style).gsub(%r{^/}, "")}"
101
+ end unless Paperclip::Interpolations.respond_to? :asset_host
95
102
  end
96
103
 
97
104
  def expiring_url(time = 3600, style_name = default_style)
@@ -148,7 +155,7 @@ module Paperclip
148
155
  AWS::S3::S3Object.store(path(style),
149
156
  file,
150
157
  bucket_name,
151
- {:content_type => instance_read(:content_type),
158
+ {:content_type => file.content_type.to_s.strip,
152
159
  :access => @s3_permissions,
153
160
  }.merge(@s3_headers))
154
161
  rescue AWS::S3::NoSuchBucket => e
@@ -30,8 +30,9 @@ module Paperclip
30
30
  # (which method (in the attachment) will call any supplied procs)
31
31
  # There is an important change of interface here: a style rule can set its own processors
32
32
  # by default we behave as before, though.
33
+ # if a proc has been supplied, we call it here
33
34
  def processors
34
- @processors || attachment.processors
35
+ @processors.respond_to?(:call) ? @processors.call(attachment.instance) : (@processors || attachment.processors)
35
36
  end
36
37
 
37
38
  # retrieves from the attachment the whiny setting
@@ -4,6 +4,9 @@ module Paperclip
4
4
 
5
5
  attr_accessor :current_geometry, :target_geometry, :format, :whiny, :convert_options, :source_file_options
6
6
 
7
+ # List of formats that we need to preserve animation
8
+ ANIMATED_FORMATS = %w(gif)
9
+
7
10
  # Creates a Thumbnail object set to work on the +file+ given. It
8
11
  # will attempt to transform the image into one defined by +target_geometry+
9
12
  # which is a "WxH"-style string. +format+ will be inferred from the +file+
@@ -58,9 +61,11 @@ module Paperclip
58
61
 
59
62
  parameters = parameters.flatten.compact.join(" ").strip.squeeze(" ")
60
63
 
61
- success = Paperclip.run("convert", parameters, :source => "#{File.expand_path(src.path)}[0]", :dest => File.expand_path(dst.path))
62
- rescue PaperclipCommandLineError => e
64
+ success = Paperclip.run("convert", parameters, :source => "#{File.expand_path(src.path)}#{'[0]' unless animated?}", :dest => File.expand_path(dst.path))
65
+ rescue Cocaine::ExitStatusError => e
63
66
  raise PaperclipError, "There was an error processing the thumbnail for #{@basename}" if @whiny
67
+ rescue Cocaine::CommandNotFoundError => e
68
+ raise Paperclip::CommandNotFoundError.new("Could not run the `convert` command. Please install ImageMagick.")
64
69
  end
65
70
 
66
71
  dst
@@ -75,5 +80,12 @@ module Paperclip
75
80
  trans << "-crop" << %["#{crop}"] << "+repage" if crop
76
81
  trans
77
82
  end
83
+
84
+ protected
85
+
86
+ # Return true if the format is animated
87
+ def animated?
88
+ ANIMATED_FORMATS.include?(@current_format[1..-1]) && (ANIMATED_FORMATS.include?(@format.to_s) || @format.blank?)
89
+ end
78
90
  end
79
91
  end