paperclip 2.4.5 → 2.5.0
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/.gitignore +22 -0
- data/.travis.yml +13 -0
- data/Appraisals +14 -0
- data/CONTRIBUTING.md +38 -0
- data/Gemfile +5 -0
- data/NEWS +23 -0
- data/README.md +72 -42
- data/Rakefile +1 -46
- data/cucumber/paperclip_steps.rb +6 -0
- data/features/basic_integration.feature +46 -0
- data/features/rake_tasks.feature +63 -0
- data/features/step_definitions/attachment_steps.rb +65 -0
- data/features/step_definitions/html_steps.rb +15 -0
- data/features/step_definitions/rails_steps.rb +182 -0
- data/features/step_definitions/s3_steps.rb +14 -0
- data/features/step_definitions/web_steps.rb +209 -0
- data/features/support/env.rb +8 -0
- data/features/support/fakeweb.rb +3 -0
- data/features/support/fixtures/.boot_config.rb.swo +0 -0
- data/features/support/fixtures/boot_config.txt +15 -0
- data/features/support/fixtures/gemfile.txt +5 -0
- data/features/support/fixtures/preinitializer.txt +20 -0
- data/features/support/paths.rb +28 -0
- data/features/support/rails.rb +46 -0
- data/features/support/selectors.rb +19 -0
- data/gemfiles/rails2.gemfile +9 -0
- data/gemfiles/rails3.gemfile +9 -0
- data/gemfiles/rails3_1.gemfile +9 -0
- data/lib/paperclip.rb +26 -19
- data/lib/paperclip/attachment.rb +123 -109
- data/lib/paperclip/interpolations.rb +7 -4
- data/lib/paperclip/matchers.rb +33 -2
- data/lib/paperclip/missing_attachment_styles.rb +1 -1
- data/lib/paperclip/railtie.rb +5 -0
- data/lib/paperclip/schema.rb +39 -0
- data/lib/paperclip/storage/fog.rb +21 -10
- data/lib/paperclip/storage/s3.rb +107 -40
- data/lib/paperclip/style.rb +13 -5
- data/lib/paperclip/url_generator.rb +64 -0
- data/lib/paperclip/version.rb +1 -1
- data/lib/tasks/paperclip.rake +1 -1
- data/paperclip.gemspec +41 -0
- data/test/.gitignore +1 -0
- data/test/attachment_test.rb +155 -168
- data/test/fixtures/question?mark.png +0 -0
- data/test/helper.rb +24 -1
- data/test/interpolations_test.rb +16 -2
- data/test/paperclip_missing_attachment_styles_test.rb +16 -0
- data/test/paperclip_test.rb +72 -22
- data/test/schema_test.rb +98 -0
- data/test/storage/filesystem_test.rb +2 -2
- data/test/{fog_test.rb → storage/fog_test.rb} +35 -8
- data/test/storage/s3_live_test.rb +63 -13
- data/test/storage/s3_test.rb +394 -91
- data/test/style_test.rb +50 -21
- data/test/support/mock_attachment.rb +22 -0
- data/test/support/mock_interpolator.rb +24 -0
- data/test/support/mock_model.rb +2 -0
- data/test/support/mock_url_generator_builder.rb +27 -0
- data/test/url_generator_test.rb +187 -0
- metadata +307 -125
- data/lib/paperclip/options.rb +0 -78
- data/test/options_test.rb +0 -75
Binary file
|
@@ -0,0 +1,20 @@
|
|
1
|
+
begin
|
2
|
+
require "rubygems"
|
3
|
+
require "bundler"
|
4
|
+
rescue LoadError
|
5
|
+
raise "Could not load the bundler gem. Install it with `gem install bundler`."
|
6
|
+
end
|
7
|
+
|
8
|
+
if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.24")
|
9
|
+
raise RuntimeError, "Your bundler version is too old for Rails 2.3." +
|
10
|
+
"Run `gem install bundler` to upgrade."
|
11
|
+
end
|
12
|
+
|
13
|
+
begin
|
14
|
+
# Set up load paths for all bundled gems
|
15
|
+
ENV["BUNDLE_GEMFILE"] = File.expand_path("../../Gemfile", __FILE__)
|
16
|
+
Bundler.setup
|
17
|
+
rescue Bundler::GemNotFound
|
18
|
+
raise RuntimeError, "Bundler couldn't find some gems." +
|
19
|
+
"Did you run `bundle install`?"
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module NavigationHelpers
|
2
|
+
# Maps a name to a path. Used by the
|
3
|
+
#
|
4
|
+
# When /^I go to (.+)$/ do |page_name|
|
5
|
+
#
|
6
|
+
# step definition in web_steps.rb
|
7
|
+
#
|
8
|
+
def path_to(page_name)
|
9
|
+
case page_name
|
10
|
+
|
11
|
+
when /the home\s?page/
|
12
|
+
'/'
|
13
|
+
when /the new user page/
|
14
|
+
'/users/new'
|
15
|
+
else
|
16
|
+
begin
|
17
|
+
page_name =~ /the (.*) page/
|
18
|
+
path_components = $1.split(/\s+/)
|
19
|
+
self.send(path_components.push('path').join('_').to_sym)
|
20
|
+
rescue Object => e
|
21
|
+
raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
|
22
|
+
"Now, go and add a mapping in #{__FILE__}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
World(NavigationHelpers)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')).freeze
|
2
|
+
APP_NAME = 'testapp'.freeze
|
3
|
+
BUNDLE_ENV_VARS = %w(RUBYOPT BUNDLE_PATH BUNDLE_BIN_PATH BUNDLE_GEMFILE)
|
4
|
+
ORIGINAL_BUNDLE_VARS = Hash[ENV.select{ |key,value| BUNDLE_ENV_VARS.include?(key) }]
|
5
|
+
|
6
|
+
ENV['RAILS_ENV'] = 'test'
|
7
|
+
|
8
|
+
Before do
|
9
|
+
ENV['BUNDLE_GEMFILE'] = File.join(Dir.pwd, ENV['BUNDLE_GEMFILE']) unless ENV['BUNDLE_GEMFILE'].start_with?(Dir.pwd)
|
10
|
+
@framework_version = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
After do
|
14
|
+
ORIGINAL_BUNDLE_VARS.each_pair do |key, value|
|
15
|
+
ENV[key] = value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
When /^I reset Bundler environment variable$/ do
|
20
|
+
BUNDLE_ENV_VARS.each do |key|
|
21
|
+
ENV[key] = nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module RailsCommandHelpers
|
26
|
+
def framework_version?(version_string)
|
27
|
+
framework_version =~ /^#{version_string}/
|
28
|
+
end
|
29
|
+
|
30
|
+
def framework_version
|
31
|
+
@framework_version ||= `rails -v`[/^Rails (.+)$/, 1]
|
32
|
+
end
|
33
|
+
|
34
|
+
def new_application_command
|
35
|
+
framework_version?("3") ? "rails new" : "rails"
|
36
|
+
end
|
37
|
+
|
38
|
+
def generator_command
|
39
|
+
framework_version?("3") ? "script/rails generate" : "script/generate"
|
40
|
+
end
|
41
|
+
|
42
|
+
def runner_command
|
43
|
+
framework_version?("3") ? "script/rails runner" : "script/runner"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
World(RailsCommandHelpers)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module HtmlSelectorsHelpers
|
2
|
+
# Maps a name to a selector. Used primarily by the
|
3
|
+
#
|
4
|
+
# When /^(.+) within (.+)$/ do |step, scope|
|
5
|
+
#
|
6
|
+
# step definitions in web_steps.rb
|
7
|
+
#
|
8
|
+
def selector_for(locator)
|
9
|
+
case locator
|
10
|
+
when "the page"
|
11
|
+
"html > body"
|
12
|
+
else
|
13
|
+
raise "Can't find mapping from \"#{locator}\" to a selector.\n" +
|
14
|
+
"Now, go and add a mapping in #{__FILE__}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
World(HtmlSelectorsHelpers)
|
data/lib/paperclip.rb
CHANGED
@@ -28,7 +28,6 @@
|
|
28
28
|
require 'erb'
|
29
29
|
require 'digest'
|
30
30
|
require 'tempfile'
|
31
|
-
require 'paperclip/options'
|
32
31
|
require 'paperclip/version'
|
33
32
|
require 'paperclip/upfile'
|
34
33
|
require 'paperclip/iostream'
|
@@ -50,7 +49,7 @@ require 'cocaine'
|
|
50
49
|
module Paperclip
|
51
50
|
|
52
51
|
class << self
|
53
|
-
# Provides configurability to Paperclip.
|
52
|
+
# Provides configurability to Paperclip. The options available are:
|
54
53
|
# * whiny: Will raise an error if Paperclip cannot process thumbnails of
|
55
54
|
# an uploaded image. Defaults to true.
|
56
55
|
# * log: Logs progress to the Rails log. Uses ActiveRecord's logger, so honors
|
@@ -140,8 +139,11 @@ module Paperclip
|
|
140
139
|
# Find all instances of the given Active Record model +klass+ with attachment +name+.
|
141
140
|
# This method is used by the refresh rake tasks.
|
142
141
|
def each_instance_with_attachment(klass, name)
|
143
|
-
class_for(klass).
|
144
|
-
|
142
|
+
unscope_method = class_for(klass).respond_to?(:unscoped) ? :unscoped : :with_exclusive_scope
|
143
|
+
class_for(klass).send(unscope_method) do
|
144
|
+
class_for(klass).find(:all, :order => 'id').each do |instance|
|
145
|
+
yield(instance) if instance.send(:"#{name}?")
|
146
|
+
end
|
145
147
|
end
|
146
148
|
end
|
147
149
|
|
@@ -177,9 +179,9 @@ module Paperclip
|
|
177
179
|
end
|
178
180
|
end
|
179
181
|
rescue ArgumentError => e
|
180
|
-
# Sadly, we need to capture
|
181
|
-
#
|
182
|
-
# from Object, and fail
|
182
|
+
# Sadly, we need to capture ArgumentError here because Rails 2.3.x
|
183
|
+
# ActiveSupport dependency management will try to the constant inherited
|
184
|
+
# from Object, and fail miserably with "Object is not missing constant X" error
|
183
185
|
# https://github.com/rails/rails/blob/v2.3.12/activesupport/lib/active_support/dependencies.rb#L124
|
184
186
|
if e.message =~ /is not missing constant/
|
185
187
|
raise NameError, "uninitialized constant #{class_name}"
|
@@ -191,7 +193,7 @@ module Paperclip
|
|
191
193
|
def check_for_url_clash(name,url,klass)
|
192
194
|
@names_url ||= {}
|
193
195
|
default_url = url || Attachment.default_options[:url]
|
194
|
-
if @names_url[name] && @names_url[name][:url] == default_url && @names_url[name][:class] != klass
|
196
|
+
if @names_url[name] && @names_url[name][:url] == default_url && @names_url[name][:class] != klass && @names_url[name][:url] !~ /:class/
|
195
197
|
log("Duplicate URL for #{name} with #{default_url}. This will clash with attachment defined in #{@names_url[name][:class]} class")
|
196
198
|
end
|
197
199
|
@names_url[name] = {:url => default_url, :class => klass}
|
@@ -264,6 +266,9 @@ module Paperclip
|
|
264
266
|
# has_attached_file :avatar, :styles => { :normal => "100x100#" },
|
265
267
|
# :default_style => :normal
|
266
268
|
# user.avatar.url # => "/avatars/23/normal_me.png"
|
269
|
+
# * +keep_old_files+: Keep the existing attachment files (original + resized) from
|
270
|
+
# being automatically deleted when an attachment is cleared or updated.
|
271
|
+
# Defaults to +false+.#
|
267
272
|
# * +whiny+: Will raise an error if Paperclip cannot post_process an uploaded file due
|
268
273
|
# to a command line error. This will override the global setting for this attachment.
|
269
274
|
# Defaults to true. This option used to be called :whiny_thumbanils, but this is
|
@@ -290,11 +295,11 @@ module Paperclip
|
|
290
295
|
# shell quoting for safety. If your options require a space, please pre-split them
|
291
296
|
# and pass an array to :convert_options instead.
|
292
297
|
# * +storage+: Chooses the storage backend where the files will be stored. The current
|
293
|
-
# choices are :filesystem and :s3. The default is :filesystem. Make sure you read the
|
294
|
-
# documentation for Paperclip::Storage::Filesystem and Paperclip::Storage::S3
|
298
|
+
# choices are :filesystem, :fog and :s3. The default is :filesystem. Make sure you read the
|
299
|
+
# documentation for Paperclip::Storage::Filesystem, Paperclip::Storage::Fog and Paperclip::Storage::S3
|
295
300
|
# for backend-specific options.
|
296
301
|
#
|
297
|
-
# It's also possible for you to
|
302
|
+
# It's also possible for you to dynamically define your interpolation string for :url,
|
298
303
|
# :default_url, and :path in your model by passing a method name as a symbol as a argument
|
299
304
|
# for your has_attached_file definition:
|
300
305
|
#
|
@@ -316,6 +321,8 @@ module Paperclip
|
|
316
321
|
else
|
317
322
|
write_inheritable_attribute(:attachment_definitions, {})
|
318
323
|
end
|
324
|
+
else
|
325
|
+
self.attachment_definitions = self.attachment_definitions.dup
|
319
326
|
end
|
320
327
|
|
321
328
|
attachment_definitions[name] = {:validations => []}.merge(options)
|
@@ -353,8 +360,8 @@ module Paperclip
|
|
353
360
|
# * +less_than+: equivalent to :in => 0..options[:less_than]
|
354
361
|
# * +greater_than+: equivalent to :in => options[:greater_than]..Infinity
|
355
362
|
# * +message+: error message to display, use :min and :max as replacements
|
356
|
-
# * +if+: A lambda or name of
|
357
|
-
# be run
|
363
|
+
# * +if+: A lambda or name of an instance method. Validation will only
|
364
|
+
# be run if this lambda or method returns true.
|
358
365
|
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
359
366
|
def validates_attachment_size name, options = {}
|
360
367
|
min = options[:greater_than] || (options[:in] && options[:in].first) || 0
|
@@ -382,14 +389,14 @@ module Paperclip
|
|
382
389
|
|
383
390
|
# Places ActiveRecord-style validations on the presence of a file.
|
384
391
|
# Options:
|
385
|
-
# * +if+: A lambda or name of
|
386
|
-
# be run
|
392
|
+
# * +if+: A lambda or name of an instance method. Validation will only
|
393
|
+
# be run if this lambda or method returns true.
|
387
394
|
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
388
395
|
def validates_attachment_presence name, options = {}
|
389
396
|
message = options[:message] || :empty
|
390
397
|
validates_each :"#{name}_file_name" do |record, attr, value|
|
391
|
-
if_clause_passed = options[:if].nil? || (options[:if].call(record) != false)
|
392
|
-
unless_clause_passed = options[:unless].nil? || (!!options[:unless].call(record) == false)
|
398
|
+
if_clause_passed = options[:if].nil? || (options[:if].respond_to?(:call) ? options[:if].call(record) != false : record.send(options[:if]))
|
399
|
+
unless_clause_passed = options[:unless].nil? || (options[:unless].respond_to?(:call) ? !!options[:unless].call(record) == false : !record.send(options[:unless]))
|
393
400
|
if if_clause_passed && unless_clause_passed && value.blank?
|
394
401
|
record.errors.add(name, message)
|
395
402
|
record.errors.add("#{name}_file_name", message)
|
@@ -401,13 +408,13 @@ module Paperclip
|
|
401
408
|
# assigned. The possible options are:
|
402
409
|
# * +content_type+: Allowed content types. Can be a single content type
|
403
410
|
# or an array. Each type can be a String or a Regexp. It should be
|
404
|
-
# noted that Internet Explorer
|
411
|
+
# noted that Internet Explorer uploads files with content_types that you
|
405
412
|
# may not expect. For example, JPEG images are given image/pjpeg and
|
406
413
|
# PNGs are image/x-png, so keep that in mind when determining how you
|
407
414
|
# match. Allows all by default.
|
408
415
|
# * +message+: The message to display when the uploaded file has an invalid
|
409
416
|
# content type.
|
410
|
-
# * +if+: A lambda or name of
|
417
|
+
# * +if+: A lambda or name of an instance method. Validation will only
|
411
418
|
# be run is this lambda or method returns true.
|
412
419
|
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
413
420
|
# NOTE: If you do not specify an [attachment]_content_type field on your
|
data/lib/paperclip/attachment.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'uri'
|
3
|
+
require 'paperclip/url_generator'
|
3
4
|
|
4
5
|
module Paperclip
|
5
6
|
# The Attachment class manages the files for a given attachment. It saves
|
@@ -25,11 +26,14 @@ module Paperclip
|
|
25
26
|
:use_default_time_zone => true,
|
26
27
|
:hash_digest => "SHA1",
|
27
28
|
:hash_data => ":class/:attachment/:id/:style/:updated_at",
|
28
|
-
:preserve_files => false
|
29
|
+
:preserve_files => false,
|
30
|
+
:interpolator => Paperclip::Interpolations,
|
31
|
+
:url_generator => Paperclip::UrlGenerator
|
29
32
|
}
|
30
33
|
end
|
31
34
|
|
32
35
|
attr_reader :name, :instance, :default_style, :convert_options, :queued_for_write, :whiny, :options, :interpolator
|
36
|
+
attr_reader :source_file_options, :whiny
|
33
37
|
attr_accessor :post_processing
|
34
38
|
|
35
39
|
# Creates an Attachment object. +name+ is the name of the attachment,
|
@@ -38,49 +42,45 @@ module Paperclip
|
|
38
42
|
#
|
39
43
|
# Options include:
|
40
44
|
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
|
45
|
+
# +url+ - a relative URL of the attachment. This is interpolated using +interpolator+
|
46
|
+
# +path+ - where on the filesystem to store the attachment. This is interpolated using +interpolator+
|
47
|
+
# +styles+ - a hash of options for processing the attachment. See +has_attached_file+ for the details
|
48
|
+
# +only_process+ - style args to be run through the post-processor. This defaults to the empty list
|
49
|
+
# +default_url+ - a URL for the missing image
|
50
|
+
# +default_style+ - the style to use when don't specify an argument to e.g. #url, #path
|
51
|
+
# +storage+ - the storage mechanism. Defaults to :filesystem
|
52
|
+
# +use_timestamp+ - whether to append an anti-caching timestamp to image URLs. Defaults to true
|
53
|
+
# +whiny+, +whiny_thumbnails+ - whether to raise when thumbnailing fails
|
54
|
+
# +use_default_time_zone+ - related to +use_timestamp+. Defaults to true
|
55
|
+
# +hash_digest+ - a string representing a class that will be used to hash URLs for obfuscation
|
56
|
+
# +hash_data+ - the relative URL for the hash data. This is interpolated using +interpolator+
|
57
|
+
# +hash_secret+ - a secret passed to the +hash_digest+
|
58
|
+
# +convert_options+ - flags passed to the +convert+ command for processing
|
59
|
+
# +source_file_options+ - flags passed to the +convert+ command that controls how the file is read
|
60
|
+
# +processors+ - classes that transform the attachment. Defaults to [:thumbnail]
|
61
|
+
# +preserve_files+ - whether to keep files on the filesystem when deleting to clearing the attachment. Defaults to false
|
62
|
+
# +interpolator+ - the object used to interpolate filenames and URLs. Defaults to Paperclip::Interpolations
|
63
|
+
# +url_generator+ - the object used to generate URLs, using the interpolator. Defaults to Paperclip::UrlGenerator
|
64
|
+
def initialize(name, instance, options = {})
|
60
65
|
@name = name
|
61
66
|
@instance = instance
|
62
67
|
|
63
68
|
options = self.class.default_options.merge(options)
|
64
69
|
|
65
|
-
@options =
|
70
|
+
@options = options
|
66
71
|
@post_processing = true
|
67
72
|
@queued_for_delete = []
|
68
73
|
@queued_for_write = {}
|
69
74
|
@errors = {}
|
70
75
|
@dirty = false
|
71
|
-
@interpolator =
|
76
|
+
@interpolator = options[:interpolator]
|
77
|
+
@url_generator = options[:url_generator].new(self, @options)
|
78
|
+
@source_file_options = options[:source_file_options]
|
79
|
+
@whiny = options[:whiny]
|
72
80
|
|
73
81
|
initialize_storage
|
74
82
|
end
|
75
83
|
|
76
|
-
# [:url, :path, :only_process, :normalized_styles, :default_url, :default_style,
|
77
|
-
# :storage, :use_timestamp, :whiny, :use_default_time_zone, :hash_digest, :hash_secret,
|
78
|
-
# :convert_options, :preserve_files].each do |field|
|
79
|
-
# define_method field do
|
80
|
-
# @options.send(field)
|
81
|
-
# end
|
82
|
-
# end
|
83
|
-
|
84
84
|
# What gets called when you call instance.attachment = File. It clears
|
85
85
|
# errors, assigns attributes, and processes the file. It
|
86
86
|
# also queues up the previous file for deletion, to be flushed away on
|
@@ -106,37 +106,55 @@ module Paperclip
|
|
106
106
|
return nil if uploaded_file.nil?
|
107
107
|
|
108
108
|
uploaded_filename ||= uploaded_file.original_filename
|
109
|
+
stores_fingerprint = @instance.respond_to?("#{name}_fingerprint".to_sym)
|
109
110
|
@queued_for_write[:original] = to_tempfile(uploaded_file)
|
110
111
|
instance_write(:file_name, uploaded_filename.strip)
|
111
112
|
instance_write(:content_type, uploaded_file.content_type.to_s.strip)
|
112
113
|
instance_write(:file_size, uploaded_file.size.to_i)
|
113
|
-
instance_write(:fingerprint, generate_fingerprint(uploaded_file))
|
114
|
+
instance_write(:fingerprint, generate_fingerprint(uploaded_file)) if stores_fingerprint
|
114
115
|
instance_write(:updated_at, Time.now)
|
115
116
|
|
116
117
|
@dirty = true
|
117
118
|
|
118
|
-
post_process(*@options
|
119
|
+
post_process(*@options[:only_process]) if post_processing
|
119
120
|
|
120
121
|
# Reset the file size if the original file was reprocessed.
|
121
122
|
instance_write(:file_size, @queued_for_write[:original].size.to_i)
|
122
|
-
instance_write(:fingerprint, generate_fingerprint(@queued_for_write[:original]))
|
123
|
+
instance_write(:fingerprint, generate_fingerprint(@queued_for_write[:original])) if stores_fingerprint
|
123
124
|
ensure
|
124
125
|
uploaded_file.close if close_uploaded_file
|
125
126
|
end
|
126
127
|
|
127
|
-
# Returns the public URL of the attachment
|
128
|
-
#
|
129
|
-
#
|
130
|
-
#
|
131
|
-
#
|
132
|
-
#
|
128
|
+
# Returns the public URL of the attachment with a given style. This does
|
129
|
+
# not necessarily need to point to a file that your Web server can access
|
130
|
+
# and can instead point to an action in your app, for example for fine grained
|
131
|
+
# security; this has a serious performance tradeoff.
|
132
|
+
#
|
133
|
+
# Options:
|
134
|
+
#
|
135
|
+
# +timestamp+ - Add a timestamp to the end of the URL. Default: true.
|
136
|
+
# +escape+ - Perform URI escaping to the URL. Default: true.
|
137
|
+
#
|
138
|
+
# Global controls (set on has_attached_file):
|
139
|
+
#
|
140
|
+
# +interpolator+ - The object that fills in a URL pattern's variables.
|
141
|
+
# +default_url+ - The image to show when the attachment has no image.
|
142
|
+
# +url+ - The URL for a saved image.
|
143
|
+
# +url_generator+ - The object that generates a URL. Default: Paperclip::UrlGenerator.
|
144
|
+
#
|
145
|
+
# As mentioned just above, the object that generates this URL can be passed
|
146
|
+
# in, for finer control. This object must respond to two methods:
|
147
|
+
#
|
148
|
+
# +#new(Paperclip::Attachment, options_hash)+
|
149
|
+
# +#for(style_name, options_hash)+
|
133
150
|
def url(style_name = default_style, options = {})
|
134
|
-
|
135
|
-
url = interpolate(most_appropriate_url, style_name)
|
151
|
+
default_options = {:timestamp => @options[:use_timestamp], :escape => true}
|
136
152
|
|
137
|
-
|
138
|
-
|
139
|
-
|
153
|
+
if options == true || options == false # Backwards compatibility.
|
154
|
+
@url_generator.for(style_name, default_options.merge(:timestamp => options))
|
155
|
+
else
|
156
|
+
@url_generator.for(style_name, default_options.merge(options))
|
157
|
+
end
|
140
158
|
end
|
141
159
|
|
142
160
|
# Returns the path of the attachment as defined by the :path option. If the
|
@@ -144,7 +162,7 @@ module Paperclip
|
|
144
162
|
# on disk. If the file is stored in S3, the path is the "key" part of the
|
145
163
|
# URL, and the :bucket option refers to the S3 bucket.
|
146
164
|
def path(style_name = default_style)
|
147
|
-
path = original_filename.nil? ? nil : interpolate(
|
165
|
+
path = original_filename.nil? ? nil : interpolate(path_option, style_name)
|
148
166
|
path.respond_to?(:unescape) ? path.unescape : path
|
149
167
|
end
|
150
168
|
|
@@ -154,11 +172,28 @@ module Paperclip
|
|
154
172
|
end
|
155
173
|
|
156
174
|
def default_style
|
157
|
-
@options
|
175
|
+
@options[:default_style]
|
158
176
|
end
|
159
177
|
|
160
178
|
def styles
|
161
|
-
@options
|
179
|
+
styling_option = @options[:styles]
|
180
|
+
if styling_option.respond_to?(:call) || !@normalized_styles
|
181
|
+
@normalized_styles = ActiveSupport::OrderedHash.new
|
182
|
+
(styling_option.respond_to?(:call) ? styling_option.call(self) : styling_option).each do |name, args|
|
183
|
+
@normalized_styles[name] = Paperclip::Style.new(name, args.dup, self)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
@normalized_styles
|
187
|
+
end
|
188
|
+
|
189
|
+
def processors
|
190
|
+
processing_option = @options[:processors]
|
191
|
+
|
192
|
+
if processing_option.respond_to?(:call)
|
193
|
+
processing_option.call(instance)
|
194
|
+
else
|
195
|
+
processing_option
|
196
|
+
end
|
162
197
|
end
|
163
198
|
|
164
199
|
# Returns an array containing the errors on this attachment.
|
@@ -174,7 +209,7 @@ module Paperclip
|
|
174
209
|
# Saves the file, if there are no errors. If there are, it flushes them to
|
175
210
|
# the instance's errors and returns false, cancelling the save.
|
176
211
|
def save
|
177
|
-
flush_deletes
|
212
|
+
flush_deletes unless @options[:keep_old_files]
|
178
213
|
flush_writes
|
179
214
|
@dirty = false
|
180
215
|
true
|
@@ -193,7 +228,7 @@ module Paperclip
|
|
193
228
|
# nil to the attachment *and saving*. This is permanent. If you wish to
|
194
229
|
# wipe out the existing attachment but not save, use #clear.
|
195
230
|
def destroy
|
196
|
-
unless @options
|
231
|
+
unless @options[:preserve_files]
|
197
232
|
clear
|
198
233
|
save
|
199
234
|
end
|
@@ -219,7 +254,13 @@ module Paperclip
|
|
219
254
|
# Returns the hash of the file as originally assigned, and lives in the
|
220
255
|
# <attachment>_fingerprint attribute of the model.
|
221
256
|
def fingerprint
|
222
|
-
instance_read(:fingerprint)
|
257
|
+
if instance_read(:fingerprint)
|
258
|
+
instance_read(:fingerprint)
|
259
|
+
elsif @instance.respond_to?("#{name}_fingerprint".to_sym)
|
260
|
+
@queued_for_write[:original] && generate_fingerprint(@queued_for_write[:original])
|
261
|
+
else
|
262
|
+
nil
|
263
|
+
end
|
223
264
|
end
|
224
265
|
|
225
266
|
# Returns the content_type of the file as originally assigned, and lives
|
@@ -238,16 +279,16 @@ module Paperclip
|
|
238
279
|
# The time zone to use for timestamp interpolation. Using the default
|
239
280
|
# time zone ensures that results are consistent across all threads.
|
240
281
|
def time_zone
|
241
|
-
@options
|
282
|
+
@options[:use_default_time_zone] ? Time.zone_default : Time.zone
|
242
283
|
end
|
243
284
|
|
244
285
|
# Returns a unique hash suitable for obfuscating the URL of an otherwise
|
245
286
|
# publicly viewable attachment.
|
246
|
-
def
|
247
|
-
raise ArgumentError, "Unable to generate hash without :hash_secret" unless @options
|
287
|
+
def hash_key(style_name = default_style)
|
288
|
+
raise ArgumentError, "Unable to generate hash without :hash_secret" unless @options[:hash_secret]
|
248
289
|
require 'openssl' unless defined?(OpenSSL)
|
249
|
-
data = interpolate(@options
|
250
|
-
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@options
|
290
|
+
data = interpolate(@options[:hash_data], style_name)
|
291
|
+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@options[:hash_digest]).new, @options[:hash_secret], data)
|
251
292
|
end
|
252
293
|
|
253
294
|
def generate_fingerprint(source)
|
@@ -328,42 +369,8 @@ module Paperclip
|
|
328
369
|
|
329
370
|
private
|
330
371
|
|
331
|
-
def
|
332
|
-
|
333
|
-
options = {} if options == true || options == false
|
334
|
-
options[:timestamp] = timestamp
|
335
|
-
options[:escape] = true if options[:escape].nil?
|
336
|
-
options
|
337
|
-
end
|
338
|
-
|
339
|
-
def extract_timestamp(options)
|
340
|
-
possibilities = [((options == true || options == false) ? options : nil),
|
341
|
-
(options.respond_to?(:[]) ? options[:timestamp] : nil),
|
342
|
-
@options.use_timestamp]
|
343
|
-
possibilities.find{|n| !n.nil? }
|
344
|
-
end
|
345
|
-
|
346
|
-
def default_url
|
347
|
-
return @options.default_url.call(self) if @options.default_url.is_a?(Proc)
|
348
|
-
@options.default_url
|
349
|
-
end
|
350
|
-
|
351
|
-
def most_appropriate_url
|
352
|
-
if original_filename.nil?
|
353
|
-
default_url
|
354
|
-
else
|
355
|
-
@options.url
|
356
|
-
end
|
357
|
-
end
|
358
|
-
|
359
|
-
def url_timestamp(url)
|
360
|
-
return url unless updated_at
|
361
|
-
delimiter_char = url.include?("?") ? "&" : "?"
|
362
|
-
"#{url}#{delimiter_char}#{updated_at.to_s}"
|
363
|
-
end
|
364
|
-
|
365
|
-
def escape_url(url)
|
366
|
-
url.respond_to?(:escape) ? url.escape : URI.escape(url)
|
372
|
+
def path_option
|
373
|
+
@options[:path].respond_to?(:call) ? @options[:path].call(self) : @options[:path]
|
367
374
|
end
|
368
375
|
|
369
376
|
def ensure_required_accessors! #:nodoc:
|
@@ -383,7 +390,7 @@ module Paperclip
|
|
383
390
|
end
|
384
391
|
|
385
392
|
def initialize_storage #:nodoc:
|
386
|
-
storage_class_name = @options
|
393
|
+
storage_class_name = @options[:storage].to_s.downcase.camelize
|
387
394
|
begin
|
388
395
|
storage_module = Paperclip::Storage.const_get(storage_class_name)
|
389
396
|
rescue NameError
|
@@ -393,18 +400,18 @@ module Paperclip
|
|
393
400
|
end
|
394
401
|
|
395
402
|
def extra_options_for(style) #:nodoc:
|
396
|
-
all_options = @options
|
403
|
+
all_options = @options[:convert_options][:all]
|
397
404
|
all_options = all_options.call(instance) if all_options.respond_to?(:call)
|
398
|
-
style_options = @options
|
405
|
+
style_options = @options[:convert_options][style]
|
399
406
|
style_options = style_options.call(instance) if style_options.respond_to?(:call)
|
400
407
|
|
401
408
|
[ style_options, all_options ].compact.join(" ")
|
402
409
|
end
|
403
410
|
|
404
411
|
def extra_source_file_options_for(style) #:nodoc:
|
405
|
-
all_options = @options
|
412
|
+
all_options = @options[:source_file_options][:all]
|
406
413
|
all_options = all_options.call(instance) if all_options.respond_to?(:call)
|
407
|
-
style_options = @options
|
414
|
+
style_options = @options[:source_file_options][style]
|
408
415
|
style_options = style_options.call(instance) if style_options.respond_to?(:call)
|
409
416
|
|
410
417
|
[ style_options, all_options ].compact.join(" ")
|
@@ -420,28 +427,35 @@ module Paperclip
|
|
420
427
|
end
|
421
428
|
|
422
429
|
def post_process_styles(*style_args) #:nodoc:
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
(
|
430
|
+
post_process_style(:original, styles[:original]) if styles.include?(:original) && process_style?(:original, style_args)
|
431
|
+
styles.reject{ |name, style| name == :original }.each do |name, style|
|
432
|
+
post_process_style(name, style) if process_style?(name, style_args)
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
def post_process_style(name, style) #:nodoc:
|
437
|
+
begin
|
438
|
+
raise RuntimeError.new("Style #{name} has no processors defined.") if style.processors.blank?
|
439
|
+
@queued_for_write[name] = style.processors.inject(@queued_for_write[:original]) do |file, processor|
|
440
|
+
Paperclip.processor(processor).make(file, style.processor_options, self)
|
434
441
|
end
|
442
|
+
rescue PaperclipError => e
|
443
|
+
log("An error was received while processing: #{e.inspect}")
|
444
|
+
(@errors[:processing] ||= []) << e.message if @options[:whiny]
|
435
445
|
end
|
436
446
|
end
|
437
447
|
|
448
|
+
def process_style?(style_name, style_args) #:nodoc:
|
449
|
+
style_args.empty? || style_args.include?(style_name)
|
450
|
+
end
|
451
|
+
|
438
452
|
def interpolate(pattern, style_name = default_style) #:nodoc:
|
439
453
|
interpolator.interpolate(pattern, self, style_name)
|
440
454
|
end
|
441
455
|
|
442
456
|
def queue_existing_for_delete #:nodoc:
|
443
|
-
return if @options
|
444
|
-
@queued_for_delete += [:original,
|
457
|
+
return if @options[:preserve_files] || !file?
|
458
|
+
@queued_for_delete += [:original, *styles.keys].uniq.map do |style|
|
445
459
|
path(style) if exists?(style)
|
446
460
|
end.compact
|
447
461
|
instance_write(:file_name, nil)
|