paperclip 3.0.3 → 3.5.1
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.
- checksums.yaml +15 -0
- data/.gitignore +2 -1
- data/.travis.yml +3 -0
- data/Appraisals +8 -3
- data/Gemfile +1 -1
- data/LICENSE +1 -1
- data/NEWS +198 -35
- data/README.md +332 -113
- data/features/basic_integration.feature +24 -12
- data/features/migration.feature +94 -0
- data/features/rake_tasks.feature +2 -3
- data/features/step_definitions/attachment_steps.rb +28 -0
- data/features/step_definitions/rails_steps.rb +94 -8
- data/features/step_definitions/s3_steps.rb +1 -1
- data/features/step_definitions/web_steps.rb +3 -3
- data/features/support/fakeweb.rb +4 -1
- data/features/support/file_helpers.rb +10 -0
- data/features/support/rails.rb +18 -2
- data/gemfiles/3.0.gemfile +2 -2
- data/gemfiles/3.1.gemfile +2 -2
- data/gemfiles/3.2.gemfile +2 -2
- data/gemfiles/4.0.gemfile +11 -0
- data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +4 -8
- data/lib/paperclip/attachment.rb +96 -43
- data/lib/paperclip/attachment_registry.rb +57 -0
- data/lib/paperclip/callbacks.rb +2 -2
- data/lib/paperclip/content_type_detector.rb +78 -0
- data/lib/paperclip/file_command_content_type_detector.rb +32 -0
- data/lib/paperclip/filename_cleaner.rb +16 -0
- data/lib/paperclip/geometry.rb +66 -30
- data/lib/paperclip/geometry_detector_factory.rb +41 -0
- data/lib/paperclip/geometry_parser_factory.rb +31 -0
- data/lib/paperclip/glue.rb +2 -8
- data/lib/paperclip/has_attached_file.rb +99 -0
- data/lib/paperclip/helpers.rb +12 -15
- data/lib/paperclip/interpolations/plural_cache.rb +17 -0
- data/lib/paperclip/interpolations.rb +15 -5
- data/lib/paperclip/io_adapters/abstract_adapter.rb +45 -0
- data/lib/paperclip/io_adapters/attachment_adapter.rb +14 -49
- data/lib/paperclip/io_adapters/data_uri_adapter.rb +27 -0
- data/lib/paperclip/io_adapters/empty_string_adapter.rb +18 -0
- data/lib/paperclip/io_adapters/file_adapter.rb +8 -69
- data/lib/paperclip/io_adapters/identity_adapter.rb +1 -1
- data/lib/paperclip/io_adapters/nil_adapter.rb +2 -2
- data/lib/paperclip/io_adapters/stringio_adapter.rb +16 -45
- data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +17 -40
- data/lib/paperclip/io_adapters/uri_adapter.rb +44 -0
- data/lib/paperclip/matchers/have_attached_file_matcher.rb +1 -5
- data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +36 -17
- data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +5 -1
- data/lib/paperclip/matchers.rb +3 -3
- data/lib/paperclip/missing_attachment_styles.rb +11 -16
- data/lib/paperclip/processor.rb +12 -0
- data/lib/paperclip/railtie.rb +5 -1
- data/lib/paperclip/schema.rb +59 -23
- data/lib/paperclip/storage/filesystem.rb +23 -5
- data/lib/paperclip/storage/fog.rb +64 -25
- data/lib/paperclip/storage/s3.rb +93 -52
- data/lib/paperclip/style.rb +2 -2
- data/lib/paperclip/tempfile_factory.rb +21 -0
- data/lib/paperclip/thumbnail.rb +18 -3
- data/lib/paperclip/validators/attachment_content_type_validator.rb +38 -10
- data/lib/paperclip/validators/attachment_presence_validator.rb +8 -8
- data/lib/paperclip/validators/attachment_size_validator.rb +12 -7
- data/lib/paperclip/validators.rb +21 -2
- data/lib/paperclip/version.rb +1 -1
- data/lib/paperclip.rb +15 -44
- data/lib/tasks/paperclip.rake +26 -7
- data/paperclip.gemspec +11 -7
- data/test/attachment_definitions_test.rb +12 -0
- data/test/attachment_processing_test.rb +83 -0
- data/test/attachment_registry_test.rb +77 -0
- data/test/attachment_test.rb +253 -44
- data/test/content_type_detector_test.rb +50 -0
- data/test/file_command_content_type_detector_test.rb +25 -0
- data/test/filename_cleaner_test.rb +14 -0
- data/test/fixtures/animated +0 -0
- data/test/fixtures/animated.unknown +0 -0
- data/test/fixtures/rotated.jpg +0 -0
- data/test/generator_test.rb +26 -24
- data/test/geometry_detector_test.rb +24 -0
- data/test/geometry_parser_test.rb +73 -0
- data/test/geometry_test.rb +55 -4
- data/test/has_attached_file_test.rb +125 -0
- data/test/helper.rb +38 -7
- data/test/integration_test.rb +105 -89
- data/test/interpolations_test.rb +12 -0
- data/test/io_adapters/abstract_adapter_test.rb +58 -0
- data/test/io_adapters/attachment_adapter_test.rb +120 -33
- data/test/io_adapters/data_uri_adapter_test.rb +60 -0
- data/test/io_adapters/empty_string_adapter_test.rb +17 -0
- data/test/io_adapters/file_adapter_test.rb +32 -1
- data/test/io_adapters/stringio_adapter_test.rb +29 -10
- data/test/io_adapters/uploaded_file_adapter_test.rb +53 -5
- data/test/io_adapters/uri_adapter_test.rb +102 -0
- data/test/matchers/validate_attachment_presence_matcher_test.rb +22 -0
- data/test/meta_class_test.rb +32 -0
- data/test/paperclip_missing_attachment_styles_test.rb +4 -8
- data/test/paperclip_test.rb +27 -51
- data/test/plural_cache_test.rb +36 -0
- data/test/processor_test.rb +16 -0
- data/test/rake_test.rb +103 -0
- data/test/schema_test.rb +179 -77
- data/test/storage/filesystem_test.rb +26 -3
- data/test/storage/fog_test.rb +181 -3
- data/test/storage/s3_test.rb +239 -4
- data/test/style_test.rb +18 -14
- data/test/tempfile_factory_test.rb +13 -0
- data/test/thumbnail_test.rb +96 -16
- data/test/validators/attachment_content_type_validator_test.rb +181 -55
- data/test/validators/attachment_size_validator_test.rb +10 -0
- data/test/validators_test.rb +8 -1
- metadata +126 -92
- data/Gemfile.lock +0 -157
- data/features/support/fixtures/.boot_config.rb.swo +0 -0
- data/images.rake +0 -21
- data/lib/.DS_Store +0 -0
- data/lib/paperclip/.DS_Store +0 -0
- data/lib/paperclip/attachment_options.rb +0 -9
- data/lib/paperclip/instance_methods.rb +0 -35
- data/test/attachment_options_test.rb +0 -27
@@ -1,7 +1,8 @@
|
|
1
1
|
module Paperclip
|
2
|
-
class UploadedFileAdapter
|
2
|
+
class UploadedFileAdapter < AbstractAdapter
|
3
3
|
def initialize(target)
|
4
4
|
@target = target
|
5
|
+
cache_current_values
|
5
6
|
|
6
7
|
if @target.respond_to?(:tempfile)
|
7
8
|
@tempfile = copy_to_tempfile(@target.tempfile)
|
@@ -10,52 +11,28 @@ module Paperclip
|
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
13
|
-
|
14
|
-
|
14
|
+
class << self
|
15
|
+
attr_accessor :content_type_detector
|
15
16
|
end
|
16
17
|
|
17
|
-
|
18
|
-
@target.content_type
|
19
|
-
end
|
20
|
-
|
21
|
-
def fingerprint
|
22
|
-
@fingerprint ||= Digest::MD5.file(path).to_s
|
23
|
-
end
|
24
|
-
|
25
|
-
def size
|
26
|
-
File.size(path)
|
27
|
-
end
|
28
|
-
|
29
|
-
def nil?
|
30
|
-
false
|
31
|
-
end
|
32
|
-
|
33
|
-
def read(length = nil, buffer = nil)
|
34
|
-
@tempfile.read(length, buffer)
|
35
|
-
end
|
36
|
-
|
37
|
-
# We don't use this directly, but aws/sdk does.
|
38
|
-
def rewind
|
39
|
-
@tempfile.rewind
|
40
|
-
end
|
18
|
+
private
|
41
19
|
|
42
|
-
def
|
43
|
-
@
|
20
|
+
def cache_current_values
|
21
|
+
self.original_filename = @target.original_filename
|
22
|
+
@content_type = determine_content_type
|
23
|
+
@size = File.size(@target.path)
|
44
24
|
end
|
45
25
|
|
46
|
-
def
|
47
|
-
|
26
|
+
def content_type_detector
|
27
|
+
self.class.content_type_detector
|
48
28
|
end
|
49
29
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
dest.binmode
|
57
|
-
FileUtils.cp(src.path, dest.path)
|
58
|
-
dest
|
30
|
+
def determine_content_type
|
31
|
+
content_type = @target.content_type.to_s.strip
|
32
|
+
if content_type_detector
|
33
|
+
content_type = content_type_detector.new(@target.path).detect
|
34
|
+
end
|
35
|
+
content_type
|
59
36
|
end
|
60
37
|
end
|
61
38
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
module Paperclip
|
4
|
+
class UriAdapter < AbstractAdapter
|
5
|
+
def initialize(target)
|
6
|
+
@target = target
|
7
|
+
@content = download_content
|
8
|
+
cache_current_values
|
9
|
+
@tempfile = copy_to_tempfile(@content)
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_writer :content_type
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def download_content
|
17
|
+
open(@target)
|
18
|
+
end
|
19
|
+
|
20
|
+
def cache_current_values
|
21
|
+
@original_filename = @target.path.split("/").last
|
22
|
+
@original_filename ||= "index.html"
|
23
|
+
self.original_filename = @original_filename.strip
|
24
|
+
|
25
|
+
@content_type = @content.content_type if @content.respond_to?(:content_type)
|
26
|
+
@content_type ||= "text/html"
|
27
|
+
|
28
|
+
@size = @content.size
|
29
|
+
end
|
30
|
+
|
31
|
+
def copy_to_tempfile(src)
|
32
|
+
while data = src.read(16*1024)
|
33
|
+
destination.write(data)
|
34
|
+
end
|
35
|
+
src.close
|
36
|
+
destination.rewind
|
37
|
+
destination
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
Paperclip.io_adapters.register Paperclip::UriAdapter do |target|
|
43
|
+
target.kind_of?(URI)
|
44
|
+
end
|
@@ -20,7 +20,7 @@ module Paperclip
|
|
20
20
|
def matches? subject
|
21
21
|
@subject = subject
|
22
22
|
@subject = @subject.class unless Class === @subject
|
23
|
-
responds? && has_column?
|
23
|
+
responds? && has_column?
|
24
24
|
end
|
25
25
|
|
26
26
|
def failure_message
|
@@ -47,10 +47,6 @@ module Paperclip
|
|
47
47
|
def has_column?
|
48
48
|
@subject.column_names.include?("#{@attachment_name}_file_name")
|
49
49
|
end
|
50
|
-
|
51
|
-
def included?
|
52
|
-
@subject.ancestors.include?(Paperclip::InstanceMethods)
|
53
|
-
end
|
54
50
|
end
|
55
51
|
end
|
56
52
|
end
|
@@ -39,18 +39,10 @@ module Paperclip
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def failure_message
|
42
|
-
"".tap do |
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def negative_failure_message
|
50
|
-
"".tap do |str|
|
51
|
-
str << "Content types #{@allowed_types.join(", ")} should be rejected" if @allowed_types.present?
|
52
|
-
str << "\n" if @allowed_types.present? && @rejected_types.present?
|
53
|
-
str << "Content types #{@rejected_types.join(", ")} should be accepted by #{@attachment_name}" if @rejected_types.present?
|
42
|
+
"#{expected_attachment}\n".tap do |message|
|
43
|
+
message << accepted_types_and_failures
|
44
|
+
message << "\n\n" if @allowed_types.present? && @rejected_types.present?
|
45
|
+
message << rejected_types_and_failures
|
54
46
|
end
|
55
47
|
end
|
56
48
|
|
@@ -60,20 +52,47 @@ module Paperclip
|
|
60
52
|
|
61
53
|
protected
|
62
54
|
|
55
|
+
def accepted_types_and_failures
|
56
|
+
if @allowed_types.present?
|
57
|
+
"Accept content types: #{@allowed_types.join(", ")}\n".tap do |message|
|
58
|
+
if @missing_allowed_types.any?
|
59
|
+
message << " #{@missing_allowed_types.join(", ")} were rejected."
|
60
|
+
else
|
61
|
+
message << " All were accepted successfully."
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
def rejected_types_and_failures
|
67
|
+
if @rejected_types.present?
|
68
|
+
"Reject content types: #{@rejected_types.join(", ")}\n".tap do |message|
|
69
|
+
if @missing_rejected_types.any?
|
70
|
+
message << " #{@missing_rejected_types.join(", ")} were accepted."
|
71
|
+
else
|
72
|
+
message << " All were rejected successfully."
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def expected_attachment
|
79
|
+
"Expected #{@attachment_name}:\n"
|
80
|
+
end
|
81
|
+
|
63
82
|
def type_allowed?(type)
|
64
|
-
|
65
|
-
file.content_type = type
|
66
|
-
@subject.attachment_for(@attachment_name).assign(file)
|
83
|
+
@subject.send("#{@attachment_name}_content_type=", type)
|
67
84
|
@subject.valid?
|
68
85
|
@subject.errors[:"#{@attachment_name}_content_type"].blank?
|
69
86
|
end
|
70
87
|
|
71
88
|
def allowed_types_allowed?
|
72
|
-
@allowed_types.
|
89
|
+
@missing_allowed_types ||= @allowed_types.reject { |type| type_allowed?(type) }
|
90
|
+
@missing_allowed_types.none?
|
73
91
|
end
|
74
92
|
|
75
93
|
def rejected_types_rejected?
|
76
|
-
|
94
|
+
@missing_rejected_types ||= @rejected_types.select { |type| type_allowed?(type) }
|
95
|
+
@missing_rejected_types.none?
|
77
96
|
end
|
78
97
|
end
|
79
98
|
end
|
@@ -46,7 +46,11 @@ module Paperclip
|
|
46
46
|
@file = StringIO.new(".")
|
47
47
|
@subject.send(@attachment_name).assign(@file)
|
48
48
|
@subject.valid?
|
49
|
-
|
49
|
+
expected_message = [
|
50
|
+
@attachment_name.to_s.titleize,
|
51
|
+
I18n.t(:blank, scope: [:errors, :messages])
|
52
|
+
].join(' ')
|
53
|
+
@subject.errors.full_messages.exclude?(expected_message)
|
50
54
|
end
|
51
55
|
end
|
52
56
|
end
|
data/lib/paperclip/matchers.rb
CHANGED
@@ -15,7 +15,7 @@ module Paperclip
|
|
15
15
|
#
|
16
16
|
# And _include_ the module:
|
17
17
|
#
|
18
|
-
#
|
18
|
+
# RSpec.configure do |config|
|
19
19
|
# config.include Paperclip::Shoulda::Matchers
|
20
20
|
# end
|
21
21
|
#
|
@@ -41,7 +41,7 @@ module Paperclip
|
|
41
41
|
#
|
42
42
|
# class ActiveSupport::TestCase
|
43
43
|
# extend Paperclip::Shoulda::Matchers
|
44
|
-
#
|
44
|
+
#
|
45
45
|
# #...other initializers...#
|
46
46
|
# end
|
47
47
|
#
|
@@ -57,7 +57,7 @@ module Paperclip
|
|
57
57
|
# should validate_attachment_size(:avatar).
|
58
58
|
# less_than(2.megabytes)
|
59
59
|
# end
|
60
|
-
#
|
60
|
+
#
|
61
61
|
module Matchers
|
62
62
|
end
|
63
63
|
end
|
@@ -1,16 +1,14 @@
|
|
1
|
-
|
1
|
+
require 'paperclip/attachment_registry'
|
2
2
|
require 'set'
|
3
|
+
|
3
4
|
module Paperclip
|
4
5
|
class << self
|
5
|
-
attr_accessor :classes_with_attachments
|
6
6
|
attr_writer :registered_attachments_styles_path
|
7
7
|
def registered_attachments_styles_path
|
8
8
|
@registered_attachments_styles_path ||= Rails.root.join('public/system/paperclip_attachments.yml').to_s
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
self.classes_with_attachments = Set.new
|
13
|
-
|
14
12
|
# Get list of styles saved on previous deploy (running rake paperclip:refresh:missing_styles)
|
15
13
|
def self.get_registered_attachments_styles
|
16
14
|
YAML.load_file(Paperclip.registered_attachments_styles_path)
|
@@ -36,18 +34,15 @@ module Paperclip
|
|
36
34
|
# }
|
37
35
|
def self.current_attachments_styles
|
38
36
|
Hash.new.tap do |current_styles|
|
39
|
-
Paperclip.
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
current_styles[klass_sym][attachment_name.to_sym] << style_name.to_sym
|
49
|
-
current_styles[klass_sym][attachment_name.to_sym].map!(&:to_s).sort!.map!(&:to_sym).uniq!
|
50
|
-
end
|
37
|
+
Paperclip::AttachmentRegistry.each_definition do |klass, attachment_name, attachment_attributes|
|
38
|
+
# TODO: is it even possible to take into account Procs?
|
39
|
+
next if attachment_attributes[:styles].kind_of?(Proc)
|
40
|
+
attachment_attributes[:styles].try(:keys).try(:each) do |style_name|
|
41
|
+
klass_sym = klass.to_s.to_sym
|
42
|
+
current_styles[klass_sym] ||= Hash.new
|
43
|
+
current_styles[klass_sym][attachment_name.to_sym] ||= Array.new
|
44
|
+
current_styles[klass_sym][attachment_name.to_sym] << style_name.to_sym
|
45
|
+
current_styles[klass_sym][attachment_name.to_sym].map!(&:to_s).sort!.map!(&:to_sym).uniq!
|
51
46
|
end
|
52
47
|
end
|
53
48
|
end
|
data/lib/paperclip/processor.rb
CHANGED
@@ -32,6 +32,18 @@ module Paperclip
|
|
32
32
|
def self.make file, options = {}, attachment = nil
|
33
33
|
new(file, options, attachment).make
|
34
34
|
end
|
35
|
+
|
36
|
+
# The convert method runs the convert binary with the provided arguments.
|
37
|
+
# See Paperclip.run for the available options.
|
38
|
+
def convert(arguments = "", local_options = {})
|
39
|
+
Paperclip.run('convert', arguments, local_options)
|
40
|
+
end
|
41
|
+
|
42
|
+
# The identify method runs the identify binary with the provided arguments.
|
43
|
+
# See Paperclip.run for the available options.
|
44
|
+
def identify(arguments = "", local_options = {})
|
45
|
+
Paperclip.run('identify', arguments, local_options)
|
46
|
+
end
|
35
47
|
end
|
36
48
|
|
37
49
|
module ProcessorHelpers
|
data/lib/paperclip/railtie.rb
CHANGED
@@ -5,10 +5,14 @@ module Paperclip
|
|
5
5
|
require 'rails'
|
6
6
|
|
7
7
|
class Railtie < Rails::Railtie
|
8
|
-
initializer 'paperclip.insert_into_active_record' do
|
8
|
+
initializer 'paperclip.insert_into_active_record' do |app|
|
9
9
|
ActiveSupport.on_load :active_record do
|
10
10
|
Paperclip::Railtie.insert
|
11
11
|
end
|
12
|
+
|
13
|
+
if app.config.respond_to?(:paperclip_defaults)
|
14
|
+
Paperclip::Attachment.default_options.merge!(app.config.paperclip_defaults)
|
15
|
+
end
|
12
16
|
end
|
13
17
|
|
14
18
|
rake_tasks { load "tasks/paperclip.rake" }
|
data/lib/paperclip/schema.rb
CHANGED
@@ -1,39 +1,75 @@
|
|
1
|
+
require 'active_support/deprecation'
|
2
|
+
|
1
3
|
module Paperclip
|
2
|
-
# Provides
|
3
|
-
#
|
4
|
-
# In order to use this module, the target class should implement a
|
5
|
-
# +column+ method that takes the column name and type, both as symbols,
|
6
|
-
# as well as a +remove_column+ method that takes a table and column name,
|
7
|
-
# also both symbols.
|
4
|
+
# Provides helper methods that can be used in migrations.
|
8
5
|
module Schema
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
COLUMNS = {:file_name => :string,
|
7
|
+
:content_type => :string,
|
8
|
+
:file_size => :integer,
|
9
|
+
:updated_at => :datetime}
|
10
|
+
|
11
|
+
def self.included(base)
|
12
|
+
ActiveRecord::ConnectionAdapters::Table.send :include, TableDefinition
|
13
|
+
ActiveRecord::ConnectionAdapters::TableDefinition.send :include, TableDefinition
|
14
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.send :include, Statements
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
column(column_name, column_type)
|
16
|
+
if defined?(ActiveRecord::Migration::CommandRecorder) # Rails 3.1+
|
17
|
+
ActiveRecord::Migration::CommandRecorder.send :include, CommandRecorder
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
module Statements
|
22
|
+
def add_attachment(table_name, *attachment_names)
|
23
|
+
raise ArgumentError, "Please specify attachment name in your add_attachment call in your migration." if attachment_names.empty?
|
24
|
+
|
25
|
+
attachment_names.each do |attachment_name|
|
26
|
+
COLUMNS.each_pair do |column_name, column_type|
|
27
|
+
add_column(table_name, "#{attachment_name}_#{column_name}", column_type)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def remove_attachment(table_name, *attachment_names)
|
33
|
+
raise ArgumentError, "Please specify attachment name in your remove_attachment call in your migration." if attachment_names.empty?
|
34
|
+
|
35
|
+
attachment_names.each do |attachment_name|
|
36
|
+
COLUMNS.each_pair do |column_name, column_type|
|
37
|
+
remove_column(table_name, "#{attachment_name}_#{column_name}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def drop_attached_file(*args)
|
43
|
+
ActiveSupport::Deprecation.warn "Method `drop_attached_file` in the migration has been deprecated and will be replaced by `remove_attachment`."
|
44
|
+
remove_attachment(*args)
|
23
45
|
end
|
24
46
|
end
|
25
47
|
|
26
|
-
|
48
|
+
module TableDefinition
|
49
|
+
def attachment(*attachment_names)
|
50
|
+
attachment_names.each do |attachment_name|
|
51
|
+
COLUMNS.each_pair do |column_name, column_type|
|
52
|
+
column("#{attachment_name}_#{column_name}", column_type)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
27
56
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
yield column_name, column_type
|
57
|
+
def has_attached_file(*attachment_names)
|
58
|
+
ActiveSupport::Deprecation.warn "Method `t.has_attached_file` in the migration has been deprecated and will be replaced by `t.attachment`."
|
59
|
+
attachment(*attachment_names)
|
32
60
|
end
|
33
61
|
end
|
34
62
|
|
35
|
-
|
36
|
-
|
63
|
+
module CommandRecorder
|
64
|
+
def add_attachment(*args)
|
65
|
+
record(:add_attachment, args)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def invert_add_attachment(args)
|
71
|
+
[:remove_attachment, args]
|
72
|
+
end
|
37
73
|
end
|
38
74
|
end
|
39
75
|
end
|
@@ -3,7 +3,7 @@ module Paperclip
|
|
3
3
|
# The default place to store attachments is in the filesystem. Files on the local
|
4
4
|
# filesystem can be very easily served by Apache without requiring a hit to your app.
|
5
5
|
# They also can be processed more easily after they've been saved, as they're just
|
6
|
-
# normal files. There
|
6
|
+
# normal files. There are two Filesystem-specific options for has_attached_file:
|
7
7
|
# * +path+: The location of the repository of attachments on disk. This can (and, in
|
8
8
|
# almost all cases, should) be coordinated with the value of the +url+ option to
|
9
9
|
# allow files to be saved into a place where Apache can serve them without
|
@@ -15,6 +15,12 @@ module Paperclip
|
|
15
15
|
# public directory.
|
16
16
|
# See Paperclip::Attachment#interpolate for more information on variable interpolaton.
|
17
17
|
# :path => "/var/app/attachments/:class/:id/:style/:basename.:extension"
|
18
|
+
# * +override_file_permissions+: This allows you to override the file permissions for files
|
19
|
+
# saved by paperclip. If you set this to an explicit octal value (0755, 0644, etc) then
|
20
|
+
# that value will be used to set the permissions for an uploaded file. The default is 0666.
|
21
|
+
# If you set :override_file_permissions to false, the chmod will be skipped. This allows
|
22
|
+
# you to use paperclip on filesystems that don't understand unix file permissions, and has the
|
23
|
+
# added benefit of using the storage directories default umask on those that do.
|
18
24
|
module Filesystem
|
19
25
|
def self.extended base
|
20
26
|
end
|
@@ -30,12 +36,20 @@ module Paperclip
|
|
30
36
|
def flush_writes #:nodoc:
|
31
37
|
@queued_for_write.each do |style_name, file|
|
32
38
|
FileUtils.mkdir_p(File.dirname(path(style_name)))
|
33
|
-
|
34
|
-
|
35
|
-
|
39
|
+
begin
|
40
|
+
FileUtils.mv(file.path, path(style_name))
|
41
|
+
rescue SystemCallError
|
42
|
+
File.open(path(style_name), "wb") do |new_file|
|
43
|
+
while chunk = file.read(16 * 1024)
|
44
|
+
new_file.write(chunk)
|
45
|
+
end
|
36
46
|
end
|
37
47
|
end
|
38
|
-
|
48
|
+
unless @options[:override_file_permissions] == false
|
49
|
+
resolved_chmod = (@options[:override_file_permissions] &~ 0111) || (0666 &~ File.umask)
|
50
|
+
FileUtils.chmod( resolved_chmod, path(style_name) )
|
51
|
+
end
|
52
|
+
file.rewind
|
39
53
|
end
|
40
54
|
|
41
55
|
after_flush_writes # allows attachment to clean up temp files
|
@@ -66,6 +80,10 @@ module Paperclip
|
|
66
80
|
end
|
67
81
|
@queued_for_delete = []
|
68
82
|
end
|
83
|
+
|
84
|
+
def copy_to_local_file(style, local_dest_path)
|
85
|
+
FileUtils.cp(path(style), local_dest_path)
|
86
|
+
end
|
69
87
|
end
|
70
88
|
|
71
89
|
end
|
@@ -18,6 +18,8 @@ module Paperclip
|
|
18
18
|
# store your files. Remember that the bucket must be unique across
|
19
19
|
# all of Amazon S3. If the bucket does not exist, Paperclip will
|
20
20
|
# attempt to create it.
|
21
|
+
# * +fog_file*: This can be hash or lambda returning hash. The
|
22
|
+
# value is used as base properties for new uploaded file.
|
21
23
|
# * +path+: This is the key under the bucket in which the file will
|
22
24
|
# be stored. The URL will be constructed from the bucket and the
|
23
25
|
# path. This is what you will want to interpolate. Keys should be
|
@@ -42,7 +44,7 @@ module Paperclip
|
|
42
44
|
|
43
45
|
base.instance_eval do
|
44
46
|
unless @options[:url].to_s.match(/^:fog.*url$/)
|
45
|
-
@options[:path] = @options[:path].gsub(/:url/, @options[:url])
|
47
|
+
@options[:path] = @options[:path].gsub(/:url/, @options[:url]).gsub(/^:rails_root\/public\/system\//, '')
|
46
48
|
@options[:url] = ':fog_public_url'
|
47
49
|
end
|
48
50
|
Paperclip.interpolates(:fog_public_url) do |attachment, style|
|
@@ -66,12 +68,28 @@ module Paperclip
|
|
66
68
|
end
|
67
69
|
|
68
70
|
def fog_file
|
69
|
-
@fog_file ||=
|
71
|
+
@fog_file ||= begin
|
72
|
+
value = @options[:fog_file]
|
73
|
+
if !value
|
74
|
+
{}
|
75
|
+
elsif value.respond_to?(:call)
|
76
|
+
value.call(self)
|
77
|
+
else
|
78
|
+
value
|
79
|
+
end
|
80
|
+
end
|
70
81
|
end
|
71
82
|
|
72
|
-
def fog_public
|
73
|
-
|
74
|
-
|
83
|
+
def fog_public(style = default_style)
|
84
|
+
if @options.has_key?(:fog_public)
|
85
|
+
if @options[:fog_public].respond_to?(:has_key?) && @options[:fog_public].has_key?(style)
|
86
|
+
@options[:fog_public][style]
|
87
|
+
else
|
88
|
+
@options[:fog_public]
|
89
|
+
end
|
90
|
+
else
|
91
|
+
true
|
92
|
+
end
|
75
93
|
end
|
76
94
|
|
77
95
|
def flush_writes
|
@@ -82,7 +100,7 @@ module Paperclip
|
|
82
100
|
directory.files.create(fog_file.merge(
|
83
101
|
:body => file,
|
84
102
|
:key => path(style),
|
85
|
-
:public => fog_public,
|
103
|
+
:public => fog_public(style),
|
86
104
|
:content_type => file.content_type
|
87
105
|
))
|
88
106
|
rescue Excon::Errors::NotFound
|
@@ -90,6 +108,8 @@ module Paperclip
|
|
90
108
|
retried = true
|
91
109
|
directory.save
|
92
110
|
retry
|
111
|
+
ensure
|
112
|
+
file.rewind
|
93
113
|
end
|
94
114
|
end
|
95
115
|
|
@@ -108,27 +128,30 @@ module Paperclip
|
|
108
128
|
|
109
129
|
def public_url(style = default_style)
|
110
130
|
if @options[:fog_host]
|
111
|
-
|
112
|
-
@options[:fog_host].call(self)
|
113
|
-
else
|
114
|
-
(@options[:fog_host] =~ /%d/) ? @options[:fog_host] % (path(style).hash % 4) : @options[:fog_host]
|
115
|
-
end
|
116
|
-
|
117
|
-
"#{host}/#{path(style)}"
|
131
|
+
"#{dynamic_fog_host_for_style(style)}/#{path(style)}"
|
118
132
|
else
|
119
133
|
if fog_credentials[:provider] == 'AWS'
|
120
|
-
|
121
|
-
"https://#{@options[:fog_directory]}.s3.amazonaws.com/#{path(style)}"
|
122
|
-
else
|
123
|
-
# directory is not a valid subdomain, so use path style for access
|
124
|
-
"https://s3.amazonaws.com/#{@options[:fog_directory]}/#{path(style)}"
|
125
|
-
end
|
134
|
+
"https://#{host_name_for_directory}/#{path(style)}"
|
126
135
|
else
|
127
136
|
directory.files.new(:key => path(style)).public_url
|
128
137
|
end
|
129
138
|
end
|
130
139
|
end
|
131
140
|
|
141
|
+
def expiring_url(time = (Time.now + 3600), style = default_style)
|
142
|
+
if directory.files.respond_to?(:get_http_url)
|
143
|
+
expiring_url = directory.files.get_http_url(path(style), time)
|
144
|
+
|
145
|
+
if @options[:fog_host]
|
146
|
+
expiring_url.gsub!(/#{host_name_for_directory}/, dynamic_fog_host_for_style(style))
|
147
|
+
end
|
148
|
+
else
|
149
|
+
expiring_url = public_url
|
150
|
+
end
|
151
|
+
|
152
|
+
return expiring_url
|
153
|
+
end
|
154
|
+
|
132
155
|
def parse_credentials(creds)
|
133
156
|
creds = find_credentials(creds).stringify_keys
|
134
157
|
env = Object.const_defined?(:Rails) ? Rails.env : nil
|
@@ -137,17 +160,33 @@ module Paperclip
|
|
137
160
|
|
138
161
|
def copy_to_local_file(style, local_dest_path)
|
139
162
|
log("copying #{path(style)} to local file #{local_dest_path}")
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
rescue Fog::Errors::Error => e
|
163
|
+
::File.open(local_dest_path, 'wb') do |local_file|
|
164
|
+
file = directory.files.get(path(style))
|
165
|
+
local_file.write(file.body)
|
166
|
+
end
|
167
|
+
rescue ::Fog::Errors::Error => e
|
145
168
|
warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
|
146
169
|
false
|
147
170
|
end
|
148
171
|
|
149
172
|
private
|
150
173
|
|
174
|
+
def dynamic_fog_host_for_style(style)
|
175
|
+
if @options[:fog_host].respond_to?(:call)
|
176
|
+
@options[:fog_host].call(self)
|
177
|
+
else
|
178
|
+
(@options[:fog_host] =~ /%d/) ? @options[:fog_host] % (path(style).hash % 4) : @options[:fog_host]
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def host_name_for_directory
|
183
|
+
if @options[:fog_directory].to_s =~ Fog::AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX
|
184
|
+
"#{@options[:fog_directory]}.s3.amazonaws.com"
|
185
|
+
else
|
186
|
+
"s3.amazonaws.com/#{@options[:fog_directory]}"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
151
190
|
def find_credentials(creds)
|
152
191
|
case creds
|
153
192
|
when File
|
@@ -175,7 +214,7 @@ module Paperclip
|
|
175
214
|
else
|
176
215
|
@options[:fog_directory]
|
177
216
|
end
|
178
|
-
|
217
|
+
|
179
218
|
@directory ||= connection.directories.new(:key => dir)
|
180
219
|
end
|
181
220
|
end
|