kt-paperclip 6.2.0

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.
Files changed (191) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +17 -0
  3. data/.github/issue_template.md +3 -0
  4. data/.gitignore +19 -0
  5. data/.hound.yml +1050 -0
  6. data/.rubocop.yml +1 -0
  7. data/.travis.yml +47 -0
  8. data/Appraisals +24 -0
  9. data/CONTRIBUTING.md +86 -0
  10. data/Gemfile +18 -0
  11. data/LICENSE +24 -0
  12. data/NEWS +515 -0
  13. data/README.md +1053 -0
  14. data/RELEASING.md +17 -0
  15. data/Rakefile +52 -0
  16. data/UPGRADING +17 -0
  17. data/features/basic_integration.feature +85 -0
  18. data/features/migration.feature +29 -0
  19. data/features/rake_tasks.feature +62 -0
  20. data/features/step_definitions/attachment_steps.rb +110 -0
  21. data/features/step_definitions/html_steps.rb +15 -0
  22. data/features/step_definitions/rails_steps.rb +257 -0
  23. data/features/step_definitions/s3_steps.rb +14 -0
  24. data/features/step_definitions/web_steps.rb +106 -0
  25. data/features/support/env.rb +12 -0
  26. data/features/support/fakeweb.rb +11 -0
  27. data/features/support/file_helpers.rb +34 -0
  28. data/features/support/fixtures/boot_config.txt +15 -0
  29. data/features/support/fixtures/gemfile.txt +5 -0
  30. data/features/support/fixtures/preinitializer.txt +20 -0
  31. data/features/support/paths.rb +28 -0
  32. data/features/support/rails.rb +39 -0
  33. data/features/support/selectors.rb +19 -0
  34. data/gemfiles/4.2.gemfile +20 -0
  35. data/gemfiles/5.0.gemfile +20 -0
  36. data/gemfiles/5.1.gemfile +20 -0
  37. data/gemfiles/5.2.gemfile +20 -0
  38. data/gemfiles/6.0.gemfile +20 -0
  39. data/lib/generators/paperclip/USAGE +8 -0
  40. data/lib/generators/paperclip/paperclip_generator.rb +36 -0
  41. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
  42. data/lib/paperclip.rb +215 -0
  43. data/lib/paperclip/attachment.rb +617 -0
  44. data/lib/paperclip/attachment_registry.rb +60 -0
  45. data/lib/paperclip/callbacks.rb +42 -0
  46. data/lib/paperclip/content_type_detector.rb +80 -0
  47. data/lib/paperclip/errors.rb +34 -0
  48. data/lib/paperclip/file_command_content_type_detector.rb +28 -0
  49. data/lib/paperclip/filename_cleaner.rb +15 -0
  50. data/lib/paperclip/geometry.rb +157 -0
  51. data/lib/paperclip/geometry_detector_factory.rb +45 -0
  52. data/lib/paperclip/geometry_parser_factory.rb +31 -0
  53. data/lib/paperclip/glue.rb +17 -0
  54. data/lib/paperclip/has_attached_file.rb +116 -0
  55. data/lib/paperclip/helpers.rb +60 -0
  56. data/lib/paperclip/interpolations.rb +201 -0
  57. data/lib/paperclip/interpolations/plural_cache.rb +18 -0
  58. data/lib/paperclip/io_adapters/abstract_adapter.rb +75 -0
  59. data/lib/paperclip/io_adapters/attachment_adapter.rb +47 -0
  60. data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
  61. data/lib/paperclip/io_adapters/empty_string_adapter.rb +19 -0
  62. data/lib/paperclip/io_adapters/file_adapter.rb +26 -0
  63. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +16 -0
  64. data/lib/paperclip/io_adapters/identity_adapter.rb +17 -0
  65. data/lib/paperclip/io_adapters/nil_adapter.rb +37 -0
  66. data/lib/paperclip/io_adapters/registry.rb +36 -0
  67. data/lib/paperclip/io_adapters/stringio_adapter.rb +36 -0
  68. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +44 -0
  69. data/lib/paperclip/io_adapters/uri_adapter.rb +68 -0
  70. data/lib/paperclip/locales/en.yml +18 -0
  71. data/lib/paperclip/logger.rb +21 -0
  72. data/lib/paperclip/matchers.rb +64 -0
  73. data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
  74. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +101 -0
  75. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
  76. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +97 -0
  77. data/lib/paperclip/media_type_spoof_detector.rb +90 -0
  78. data/lib/paperclip/missing_attachment_styles.rb +84 -0
  79. data/lib/paperclip/processor.rb +56 -0
  80. data/lib/paperclip/processor_helpers.rb +52 -0
  81. data/lib/paperclip/rails_environment.rb +21 -0
  82. data/lib/paperclip/railtie.rb +31 -0
  83. data/lib/paperclip/schema.rb +81 -0
  84. data/lib/paperclip/storage.rb +3 -0
  85. data/lib/paperclip/storage/filesystem.rb +99 -0
  86. data/lib/paperclip/storage/fog.rb +252 -0
  87. data/lib/paperclip/storage/s3.rb +461 -0
  88. data/lib/paperclip/style.rb +106 -0
  89. data/lib/paperclip/tempfile.rb +42 -0
  90. data/lib/paperclip/tempfile_factory.rb +22 -0
  91. data/lib/paperclip/thumbnail.rb +131 -0
  92. data/lib/paperclip/url_generator.rb +76 -0
  93. data/lib/paperclip/validators.rb +73 -0
  94. data/lib/paperclip/validators/attachment_content_type_validator.rb +88 -0
  95. data/lib/paperclip/validators/attachment_file_name_validator.rb +75 -0
  96. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +28 -0
  97. data/lib/paperclip/validators/attachment_presence_validator.rb +28 -0
  98. data/lib/paperclip/validators/attachment_size_validator.rb +109 -0
  99. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +29 -0
  100. data/lib/paperclip/version.rb +3 -0
  101. data/lib/tasks/paperclip.rake +140 -0
  102. data/paperclip.gemspec +50 -0
  103. data/shoulda_macros/paperclip.rb +134 -0
  104. data/spec/database.yml +4 -0
  105. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  106. data/spec/paperclip/attachment_processing_spec.rb +79 -0
  107. data/spec/paperclip/attachment_registry_spec.rb +158 -0
  108. data/spec/paperclip/attachment_spec.rb +1590 -0
  109. data/spec/paperclip/content_type_detector_spec.rb +47 -0
  110. data/spec/paperclip/file_command_content_type_detector_spec.rb +40 -0
  111. data/spec/paperclip/filename_cleaner_spec.rb +13 -0
  112. data/spec/paperclip/geometry_detector_spec.rb +38 -0
  113. data/spec/paperclip/geometry_parser_spec.rb +73 -0
  114. data/spec/paperclip/geometry_spec.rb +255 -0
  115. data/spec/paperclip/glue_spec.rb +42 -0
  116. data/spec/paperclip/has_attached_file_spec.rb +78 -0
  117. data/spec/paperclip/integration_spec.rb +702 -0
  118. data/spec/paperclip/interpolations_spec.rb +270 -0
  119. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +160 -0
  120. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +140 -0
  121. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +88 -0
  122. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  123. data/spec/paperclip/io_adapters/file_adapter_spec.rb +131 -0
  124. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +137 -0
  125. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
  126. data/spec/paperclip/io_adapters/nil_adapter_spec.rb +25 -0
  127. data/spec/paperclip/io_adapters/registry_spec.rb +35 -0
  128. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +64 -0
  129. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +146 -0
  130. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +221 -0
  131. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
  132. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +108 -0
  133. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
  134. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
  135. data/spec/paperclip/media_type_spoof_detector_spec.rb +120 -0
  136. data/spec/paperclip/meta_class_spec.rb +30 -0
  137. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +88 -0
  138. data/spec/paperclip/paperclip_spec.rb +196 -0
  139. data/spec/paperclip/plural_cache_spec.rb +37 -0
  140. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  141. data/spec/paperclip/processor_spec.rb +26 -0
  142. data/spec/paperclip/rails_environment_spec.rb +30 -0
  143. data/spec/paperclip/rake_spec.rb +103 -0
  144. data/spec/paperclip/schema_spec.rb +252 -0
  145. data/spec/paperclip/storage/filesystem_spec.rb +79 -0
  146. data/spec/paperclip/storage/fog_spec.rb +560 -0
  147. data/spec/paperclip/storage/s3_live_spec.rb +188 -0
  148. data/spec/paperclip/storage/s3_spec.rb +1695 -0
  149. data/spec/paperclip/style_spec.rb +251 -0
  150. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  151. data/spec/paperclip/tempfile_spec.rb +35 -0
  152. data/spec/paperclip/thumbnail_spec.rb +504 -0
  153. data/spec/paperclip/url_generator_spec.rb +221 -0
  154. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
  155. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +159 -0
  156. data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
  157. data/spec/paperclip/validators/attachment_size_validator_spec.rb +235 -0
  158. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +48 -0
  159. data/spec/paperclip/validators_spec.rb +164 -0
  160. data/spec/spec_helper.rb +45 -0
  161. data/spec/support/assertions.rb +84 -0
  162. data/spec/support/fake_model.rb +24 -0
  163. data/spec/support/fake_rails.rb +12 -0
  164. data/spec/support/fixtures/12k.png +0 -0
  165. data/spec/support/fixtures/50x50.png +0 -0
  166. data/spec/support/fixtures/5k.png +0 -0
  167. data/spec/support/fixtures/animated +0 -0
  168. data/spec/support/fixtures/animated.gif +0 -0
  169. data/spec/support/fixtures/animated.unknown +0 -0
  170. data/spec/support/fixtures/bad.png +1 -0
  171. data/spec/support/fixtures/empty.html +1 -0
  172. data/spec/support/fixtures/empty.xlsx +0 -0
  173. data/spec/support/fixtures/fog.yml +8 -0
  174. data/spec/support/fixtures/rotated.jpg +0 -0
  175. data/spec/support/fixtures/s3.yml +8 -0
  176. data/spec/support/fixtures/spaced file.jpg +0 -0
  177. data/spec/support/fixtures/spaced file.png +0 -0
  178. data/spec/support/fixtures/text.txt +1 -0
  179. data/spec/support/fixtures/twopage.pdf +0 -0
  180. data/spec/support/fixtures/uppercase.PNG +0 -0
  181. data/spec/support/matchers/accept.rb +5 -0
  182. data/spec/support/matchers/exist.rb +5 -0
  183. data/spec/support/matchers/have_column.rb +23 -0
  184. data/spec/support/mock_attachment.rb +24 -0
  185. data/spec/support/mock_interpolator.rb +24 -0
  186. data/spec/support/mock_url_generator_builder.rb +26 -0
  187. data/spec/support/model_reconstruction.rb +72 -0
  188. data/spec/support/reporting.rb +11 -0
  189. data/spec/support/test_data.rb +13 -0
  190. data/spec/support/version_helper.rb +9 -0
  191. metadata +586 -0
@@ -0,0 +1,84 @@
1
+ require "paperclip/attachment_registry"
2
+ require "set"
3
+
4
+ module Paperclip
5
+ class << self
6
+ attr_writer :registered_attachments_styles_path
7
+ def registered_attachments_styles_path
8
+ @registered_attachments_styles_path ||= Rails.root.join("public/system/paperclip_attachments.yml").to_s
9
+ end
10
+ end
11
+
12
+ # Get list of styles saved on previous deploy (running rake paperclip:refresh:missing_styles)
13
+ def self.get_registered_attachments_styles
14
+ YAML.load_file(Paperclip.registered_attachments_styles_path)
15
+ rescue Errno::ENOENT
16
+ nil
17
+ end
18
+ private_class_method :get_registered_attachments_styles
19
+
20
+ def self.save_current_attachments_styles!
21
+ File.open(Paperclip.registered_attachments_styles_path, "w") do |f|
22
+ YAML.dump(current_attachments_styles, f)
23
+ end
24
+ end
25
+
26
+ # Returns hash with styles for all classes using Paperclip.
27
+ # Unfortunately current version does not work with lambda styles:(
28
+ # {
29
+ # :User => {:avatar => [:small, :big]},
30
+ # :Book => {
31
+ # :cover => [:thumb, :croppable]},
32
+ # :sample => [:thumb, :big]},
33
+ # }
34
+ # }
35
+ def self.current_attachments_styles
36
+ Hash.new.tap do |current_styles|
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].is_a?(Proc)
40
+
41
+ attachment_attributes[:styles].try(:keys).try(:each) do |style_name|
42
+ klass_sym = klass.to_s.to_sym
43
+ current_styles[klass_sym] ||= Hash.new
44
+ current_styles[klass_sym][attachment_name.to_sym] ||= Array.new
45
+ current_styles[klass_sym][attachment_name.to_sym] << style_name.to_sym
46
+ current_styles[klass_sym][attachment_name.to_sym].map!(&:to_s).sort!.map!(&:to_sym).uniq!
47
+ end
48
+ end
49
+ end
50
+ end
51
+ private_class_method :current_attachments_styles
52
+
53
+ # Returns hash with styles missing from recent run of rake paperclip:refresh:missing_styles
54
+ # {
55
+ # :User => {:avatar => [:big]},
56
+ # :Book => {
57
+ # :cover => [:croppable]},
58
+ # }
59
+ # }
60
+ def self.missing_attachments_styles
61
+ current_styles = current_attachments_styles
62
+ registered_styles = get_registered_attachments_styles
63
+
64
+ Hash.new.tap do |missing_styles|
65
+ current_styles.each do |klass, attachment_definitions|
66
+ attachment_definitions.each do |attachment_name, styles|
67
+ registered = begin
68
+ registered_styles[klass][attachment_name] || []
69
+ rescue StandardError
70
+ []
71
+ end
72
+ missed = styles - registered
73
+ if missed.present?
74
+ klass_sym = klass.to_s.to_sym
75
+ missing_styles[klass_sym] ||= Hash.new
76
+ missing_styles[klass_sym][attachment_name.to_sym] ||= Array.new
77
+ missing_styles[klass_sym][attachment_name.to_sym].concat(missed.to_a)
78
+ missing_styles[klass_sym][attachment_name.to_sym].map!(&:to_s).sort!.map!(&:to_sym).uniq!
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,56 @@
1
+ module Paperclip
2
+ # Paperclip processors allow you to modify attached files when they are
3
+ # attached in any way you are able. Paperclip itself uses command-line
4
+ # programs for its included Thumbnail processor, but custom processors
5
+ # are not required to follow suit.
6
+ #
7
+ # Processors are required to be defined inside the Paperclip module and
8
+ # are also required to be a subclass of Paperclip::Processor. There is
9
+ # only one method you *must* implement to properly be a subclass:
10
+ # #make, but #initialize may also be of use. #initialize accepts 3
11
+ # arguments: the file that will be operated on (which is an instance of
12
+ # File), a hash of options that were defined in has_attached_file's
13
+ # style hash, and the Paperclip::Attachment itself. These are set as
14
+ # instance variables that can be used within `#make`.
15
+ #
16
+ # #make must return an instance of File (Tempfile is acceptable) which
17
+ # contains the results of the processing.
18
+ #
19
+ # See Paperclip.run for more information about using command-line
20
+ # utilities from within Processors.
21
+ class Processor
22
+ attr_accessor :file, :options, :attachment
23
+
24
+ def initialize(file, options = {}, attachment = nil)
25
+ @file = file
26
+ @options = options
27
+ @attachment = attachment
28
+ end
29
+
30
+ def make; end
31
+
32
+ def self.make(file, options = {}, attachment = nil)
33
+ new(file, options, attachment).make
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(
40
+ Paperclip.options[:is_windows] ? "magick convert" : "convert",
41
+ arguments,
42
+ local_options
43
+ )
44
+ end
45
+
46
+ # The identify method runs the identify binary with the provided arguments.
47
+ # See Paperclip.run for the available options.
48
+ def identify(arguments = "", local_options = {})
49
+ Paperclip.run(
50
+ Paperclip.options[:is_windows] ? "magick identify" : "identify",
51
+ arguments,
52
+ local_options
53
+ )
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,52 @@
1
+ module Paperclip
2
+ module ProcessorHelpers
3
+ class NoSuchProcessor < StandardError; end
4
+
5
+ def processor(name) #:nodoc:
6
+ @known_processors ||= {}
7
+ if @known_processors[name.to_s]
8
+ @known_processors[name.to_s]
9
+ else
10
+ name = name.to_s.camelize
11
+ load_processor(name) unless Paperclip.const_defined?(name)
12
+ processor = Paperclip.const_get(name)
13
+ @known_processors[name.to_s] = processor
14
+ end
15
+ end
16
+
17
+ def load_processor(name)
18
+ if defined?(Rails.root) && Rails.root
19
+ filename = "#{name.to_s.underscore}.rb"
20
+ directories = %w(lib/paperclip lib/paperclip_processors)
21
+
22
+ required = directories.map do |directory|
23
+ pathname = File.expand_path(Rails.root.join(directory, filename))
24
+ file_exists = File.exist?(pathname)
25
+ require pathname if file_exists
26
+ file_exists
27
+ end
28
+
29
+ unless required.any?
30
+ raise LoadError, "Could not find the '#{name}' processor in any of these paths: #{directories.join(', ')}"
31
+ end
32
+ end
33
+ end
34
+
35
+ def clear_processors!
36
+ @known_processors.try(:clear)
37
+ end
38
+
39
+ # You can add your own processor via the Paperclip configuration. Normally
40
+ # Paperclip will load all processors from the
41
+ # Rails.root/lib/paperclip_processors directory, but here you can add any
42
+ # existing class using this mechanism.
43
+ #
44
+ # Paperclip.configure do |c|
45
+ # c.register_processor :watermarker, WatermarkingProcessor.new
46
+ # end
47
+ def register_processor(name, processor)
48
+ @known_processors ||= {}
49
+ @known_processors[name.to_s] = processor
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,21 @@
1
+ module Paperclip
2
+ class RailsEnvironment
3
+ def self.get
4
+ new.get
5
+ end
6
+
7
+ def get
8
+ Rails.env if rails_exists? && rails_environment_exists?
9
+ end
10
+
11
+ private
12
+
13
+ def rails_exists?
14
+ Object.const_defined?(:Rails)
15
+ end
16
+
17
+ def rails_environment_exists?
18
+ Rails.respond_to?(:env)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,31 @@
1
+ require "paperclip"
2
+ require "paperclip/schema"
3
+
4
+ module Paperclip
5
+ require "rails"
6
+
7
+ class Railtie < Rails::Railtie
8
+ initializer "paperclip.insert_into_active_record" do |app|
9
+ ActiveSupport.on_load :active_record do
10
+ Paperclip::Railtie.insert
11
+ end
12
+
13
+ if app.config.respond_to?(:paperclip_defaults)
14
+ Paperclip::Attachment.default_options.merge!(app.config.paperclip_defaults)
15
+ end
16
+ end
17
+
18
+ rake_tasks { load "tasks/paperclip.rake" }
19
+ end
20
+
21
+ class Railtie
22
+ def self.insert
23
+ Paperclip.options[:logger] = Rails.logger
24
+
25
+ if defined?(ActiveRecord)
26
+ Paperclip.options[:logger] = ActiveRecord::Base.logger
27
+ ActiveRecord::Base.include Paperclip::Glue
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,81 @@
1
+ require "active_support/deprecation"
2
+
3
+ module Paperclip
4
+ # Provides helper methods that can be used in migrations.
5
+ module Schema
6
+ COLUMNS = { file_name: :string,
7
+ content_type: :string,
8
+ file_size: :bigint,
9
+ updated_at: :datetime }.freeze
10
+
11
+ def self.included(_base)
12
+ ActiveRecord::ConnectionAdapters::Table.include TableDefinition
13
+ ActiveRecord::ConnectionAdapters::TableDefinition.include TableDefinition
14
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.include Statements
15
+ ActiveRecord::Migration::CommandRecorder.include CommandRecorder
16
+ end
17
+
18
+ module Statements
19
+ def add_attachment(table_name, *attachment_names)
20
+ if attachment_names.empty?
21
+ raise ArgumentError, "Please specify attachment name in your add_attachment call in your migration."
22
+ end
23
+
24
+ options = attachment_names.extract_options!
25
+
26
+ attachment_names.each do |attachment_name|
27
+ COLUMNS.each_pair do |column_name, column_type|
28
+ column_options = options.merge(options[column_name.to_sym] || {})
29
+ add_column(table_name, "#{attachment_name}_#{column_name}", column_type, column_options)
30
+ end
31
+ end
32
+ end
33
+
34
+ def remove_attachment(table_name, *attachment_names)
35
+ if attachment_names.empty?
36
+ raise ArgumentError, "Please specify attachment name in your remove_attachment call in your migration."
37
+ end
38
+
39
+ attachment_names.each do |attachment_name|
40
+ COLUMNS.keys.each do |column_name|
41
+ remove_column(table_name, "#{attachment_name}_#{column_name}")
42
+ end
43
+ end
44
+ end
45
+
46
+ def drop_attached_file(*args)
47
+ ActiveSupport::Deprecation.warn "Method `drop_attached_file` in the migration has been deprecated and will be replaced by `remove_attachment`."
48
+ remove_attachment(*args)
49
+ end
50
+ end
51
+
52
+ module TableDefinition
53
+ def attachment(*attachment_names)
54
+ options = attachment_names.extract_options!
55
+ attachment_names.each do |attachment_name|
56
+ COLUMNS.each_pair do |column_name, column_type|
57
+ column_options = options.merge(options[column_name.to_sym] || {})
58
+ column("#{attachment_name}_#{column_name}", column_type, column_options)
59
+ end
60
+ end
61
+ end
62
+
63
+ def has_attached_file(*attachment_names)
64
+ ActiveSupport::Deprecation.warn "Method `t.has_attached_file` in the migration has been deprecated and will be replaced by `t.attachment`."
65
+ attachment(*attachment_names)
66
+ end
67
+ end
68
+
69
+ module CommandRecorder
70
+ def add_attachment(*args)
71
+ record(:add_attachment, args)
72
+ end
73
+
74
+ private
75
+
76
+ def invert_add_attachment(args)
77
+ [:remove_attachment, args]
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,3 @@
1
+ require "paperclip/storage/filesystem"
2
+ require "paperclip/storage/fog"
3
+ require "paperclip/storage/s3"
@@ -0,0 +1,99 @@
1
+ module Paperclip
2
+ module Storage
3
+ # The default place to store attachments is in the filesystem. Files on the local
4
+ # filesystem can be very easily served by Apache without requiring a hit to your app.
5
+ # They also can be processed more easily after they've been saved, as they're just
6
+ # normal files. There are two Filesystem-specific options for has_attached_file:
7
+ # * +path+: The location of the repository of attachments on disk. This can (and, in
8
+ # almost all cases, should) be coordinated with the value of the +url+ option to
9
+ # allow files to be saved into a place where Apache can serve them without
10
+ # hitting your app. Defaults to
11
+ # ":rails_root/public/:attachment/:id/:style/:basename.:extension"
12
+ # By default this places the files in the app's public directory which can be served
13
+ # directly. If you are using capistrano for deployment, a good idea would be to
14
+ # make a symlink to the capistrano-created system directory from inside your app's
15
+ # public directory.
16
+ # See Paperclip::Attachment#interpolate for more information on variable interpolaton.
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.
24
+ module Filesystem
25
+ def self.extended(base); end
26
+
27
+ def exists?(style_name = default_style)
28
+ if original_filename
29
+ File.exist?(path(style_name))
30
+ else
31
+ false
32
+ end
33
+ end
34
+
35
+ def flush_writes #:nodoc:
36
+ @queued_for_write.each do |style_name, file|
37
+ FileUtils.mkdir_p(File.dirname(path(style_name)))
38
+ begin
39
+ move_file(file.path, path(style_name))
40
+ rescue SystemCallError
41
+ File.open(path(style_name), "wb") do |new_file|
42
+ while chunk = file.read(16 * 1024)
43
+ new_file.write(chunk)
44
+ end
45
+ end
46
+ end
47
+ unless @options[:override_file_permissions] == false
48
+ resolved_chmod = (@options[:override_file_permissions] & ~0o111) || (0o666 & ~File.umask)
49
+ FileUtils.chmod(resolved_chmod, path(style_name))
50
+ end
51
+ file.rewind
52
+ end
53
+
54
+ after_flush_writes # allows attachment to clean up temp files
55
+
56
+ @queued_for_write = {}
57
+ end
58
+
59
+ def flush_deletes #:nodoc:
60
+ @queued_for_delete.each do |path|
61
+ begin
62
+ log("deleting #{path}")
63
+ FileUtils.rm(path) if File.exist?(path)
64
+ rescue Errno::ENOENT => e
65
+ # ignore file-not-found, let everything else pass
66
+ end
67
+ begin
68
+ loop do
69
+ path = File.dirname(path)
70
+ FileUtils.rmdir(path)
71
+ break if File.exist?(path) # Ruby 1.9.2 does not raise if the removal failed.
72
+ end
73
+ rescue Errno::EEXIST, Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL, Errno::ENOTDIR, Errno::EACCES
74
+ # Stop trying to remove parent directories
75
+ rescue SystemCallError => e
76
+ log("There was an unexpected error while deleting directories: #{e.class}")
77
+ # Ignore it
78
+ end
79
+ end
80
+ @queued_for_delete = []
81
+ end
82
+
83
+ def copy_to_local_file(style, local_dest_path)
84
+ FileUtils.cp(path(style), local_dest_path)
85
+ end
86
+
87
+ private
88
+
89
+ def move_file(src, dest)
90
+ # Support hardlinked files
91
+ if File.identical?(src, dest)
92
+ File.unlink(src)
93
+ else
94
+ FileUtils.mv(src, dest)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,252 @@
1
+ module Paperclip
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
+ # scheme: 'https'
18
+ # * +fog_directory+: This is the name of the S3 bucket that will
19
+ # store your files. Remember that the bucket must be unique across
20
+ # all of Amazon S3. If the bucket does not exist, Paperclip will
21
+ # attempt to create it.
22
+ # * +fog_file+: This can be hash or lambda returning hash. The
23
+ # value is used as base properties for new uploaded file.
24
+ # * +path+: This is the key under the bucket in which the file will
25
+ # be stored. The URL will be constructed from the bucket and the
26
+ # path. This is what you will want to interpolate. Keys should be
27
+ # unique, like filenames, and despite the fact that S3 (strictly
28
+ # speaking) does not support directories, you can still use a / to
29
+ # separate parts of your file name.
30
+ # * +fog_public+: (optional, defaults to true) Should the uploaded
31
+ # files be public or not? (true/false)
32
+ # * +fog_host+: (optional) The fully-qualified domain name (FQDN)
33
+ # that is the alias to the S3 domain of your bucket, e.g.
34
+ # 'http://images.example.com'. This can also be used in
35
+ # conjunction with Cloudfront (http://aws.amazon.com/cloudfront)
36
+ # * +fog_options+: (optional) A hash of options that are passed
37
+ # to fog when the file is created. For example, you could set
38
+ # the multipart-chunk size to 100MB with a hash:
39
+ # { :multipart_chunk_size => 104857600 }
40
+
41
+ module Fog
42
+ def self.extended(base)
43
+ unless defined?(Fog)
44
+ begin
45
+ require "fog"
46
+ rescue LoadError => e
47
+ e.message << " (You may need to install the fog gem)"
48
+ raise e
49
+ end
50
+ end
51
+
52
+ base.instance_eval do
53
+ unless @options[:url].to_s.match(/\A:fog.*url\z/)
54
+ @options[:path] = @options[:path].gsub(/:url/, @options[:url]).gsub(/\A:rails_root\/public\/system\//, "")
55
+ @options[:url] = ":fog_public_url"
56
+ end
57
+ unless Paperclip::Interpolations.respond_to? :fog_public_url
58
+ Paperclip.interpolates(:fog_public_url) do |attachment, style|
59
+ attachment.public_url(style)
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX = /\A(?:[a-z]|\d(?!\d{0,2}(?:\.\d{1,3}){3}\z))(?:[a-z0-9]|\.(?![\.\-])|\-(?![\.])){1,61}[a-z0-9]\z/.freeze
66
+
67
+ def exists?(style = default_style)
68
+ if original_filename
69
+ !!directory.files.head(path(style))
70
+ else
71
+ false
72
+ end
73
+ end
74
+
75
+ def fog_credentials
76
+ @fog_credentials ||= parse_credentials(@options[:fog_credentials])
77
+ end
78
+
79
+ def fog_file
80
+ @fog_file ||= begin
81
+ value = @options[:fog_file]
82
+ if !value
83
+ {}
84
+ elsif value.respond_to?(:call)
85
+ value.call(self)
86
+ else
87
+ value
88
+ end
89
+ end
90
+ end
91
+
92
+ def fog_public(style = default_style)
93
+ if @options.key?(:fog_public)
94
+ value = @options[:fog_public]
95
+ if value.respond_to?(:key?) && value.key?(style)
96
+ value[style]
97
+ elsif value.respond_to?(:call)
98
+ value.call(self)
99
+ else
100
+ value
101
+ end
102
+ else
103
+ true
104
+ end
105
+ end
106
+
107
+ def flush_writes
108
+ @queued_for_write.each do |style, file|
109
+ log("saving #{path(style)}")
110
+ retried = false
111
+ begin
112
+ attributes = fog_file.merge(
113
+ body: file,
114
+ key: path(style),
115
+ public: fog_public(style),
116
+ content_type: file.content_type
117
+ )
118
+ attributes.merge!(@options[:fog_options]) if @options[:fog_options]
119
+ directory.files.create(attributes)
120
+ rescue Excon::Errors::NotFound
121
+ raise if retried
122
+
123
+ retried = true
124
+ directory.save
125
+ file.rewind
126
+ retry
127
+ ensure
128
+ file.rewind
129
+ end
130
+ end
131
+
132
+ after_flush_writes # allows attachment to clean up temp files
133
+
134
+ @queued_for_write = {}
135
+ end
136
+
137
+ def flush_deletes
138
+ @queued_for_delete.each do |path|
139
+ log("deleting #{path}")
140
+ directory.files.new(key: path).destroy
141
+ end
142
+ @queued_for_delete = []
143
+ end
144
+
145
+ def public_url(style = default_style)
146
+ if @options[:fog_host]
147
+ "#{dynamic_fog_host_for_style(style)}/#{path(style)}"
148
+ else
149
+ if fog_credentials[:provider] == "AWS"
150
+ "#{scheme}://#{host_name_for_directory}/#{path(style)}"
151
+ else
152
+ directory.files.new(key: path(style)).public_url
153
+ end
154
+ end
155
+ end
156
+
157
+ def expiring_url(time = (Time.now + 3600), style_name = default_style)
158
+ time = convert_time(time)
159
+ http_url_method = "get_#{scheme}_url"
160
+ if path(style_name) && directory.files.respond_to?(http_url_method)
161
+ expiring_url = directory.files.public_send(http_url_method, path(style_name), time)
162
+
163
+ if @options[:fog_host]
164
+ expiring_url.gsub!(/#{host_name_for_directory}/, dynamic_fog_host_for_style(style_name))
165
+ end
166
+ else
167
+ expiring_url = url(style_name)
168
+ end
169
+
170
+ expiring_url
171
+ end
172
+
173
+ def parse_credentials(creds)
174
+ creds = find_credentials(creds).stringify_keys
175
+ (creds[RailsEnvironment.get] || creds).symbolize_keys
176
+ end
177
+
178
+ def copy_to_local_file(style, local_dest_path)
179
+ log("copying #{path(style)} to local file #{local_dest_path}")
180
+ ::File.open(local_dest_path, "wb") do |local_file|
181
+ file = directory.files.get(path(style))
182
+ return false unless file
183
+
184
+ local_file.write(file.body)
185
+ end
186
+ rescue ::Fog::Errors::Error => e
187
+ warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
188
+ false
189
+ end
190
+
191
+ private
192
+
193
+ def convert_time(time)
194
+ time = Time.now + time if time.is_a?(Integer)
195
+ time
196
+ end
197
+
198
+ def dynamic_fog_host_for_style(style)
199
+ if @options[:fog_host].respond_to?(:call)
200
+ @options[:fog_host].call(self)
201
+ else
202
+ @options[:fog_host] =~ /%d/ ? @options[:fog_host] % (path(style).hash % 4) : @options[:fog_host]
203
+ end
204
+ end
205
+
206
+ def host_name_for_directory
207
+ if directory_name.to_s =~ Fog::AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX
208
+ "#{directory_name}.s3.amazonaws.com"
209
+ else
210
+ "s3.amazonaws.com/#{directory_name}"
211
+ end
212
+ end
213
+
214
+ def find_credentials(creds)
215
+ case creds
216
+ when File
217
+ YAML::safe_load(ERB.new(File.read(creds.path)).result)
218
+ when String, Pathname
219
+ YAML::safe_load(ERB.new(File.read(creds)).result)
220
+ when Hash
221
+ creds
222
+ else
223
+ if creds.respond_to?(:call)
224
+ creds.call(self)
225
+ else
226
+ raise ArgumentError, "Credentials are not a path, file, hash or proc."
227
+ end
228
+ end
229
+ end
230
+
231
+ def connection
232
+ @connection ||= ::Fog::Storage.new(fog_credentials)
233
+ end
234
+
235
+ def directory
236
+ @directory ||= connection.directories.new(key: directory_name)
237
+ end
238
+
239
+ def directory_name
240
+ if @options[:fog_directory].respond_to?(:call)
241
+ @options[:fog_directory].call(self)
242
+ else
243
+ @options[:fog_directory]
244
+ end
245
+ end
246
+
247
+ def scheme
248
+ @scheme ||= fog_credentials[:scheme] || "https"
249
+ end
250
+ end
251
+ end
252
+ end