paperclip_jk 5.0.0.beta2

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 (190) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +21 -0
  3. data/.hound.yml +1066 -0
  4. data/.rubocop.yml +1 -0
  5. data/.travis.yml +26 -0
  6. data/Appraisals +27 -0
  7. data/CONTRIBUTING.md +86 -0
  8. data/Gemfile +15 -0
  9. data/LICENSE +24 -0
  10. data/NEWS +424 -0
  11. data/README.md +999 -0
  12. data/RELEASING.md +17 -0
  13. data/Rakefile +44 -0
  14. data/UPGRADING +17 -0
  15. data/features/basic_integration.feature +81 -0
  16. data/features/migration.feature +70 -0
  17. data/features/rake_tasks.feature +62 -0
  18. data/features/step_definitions/attachment_steps.rb +110 -0
  19. data/features/step_definitions/html_steps.rb +15 -0
  20. data/features/step_definitions/rails_steps.rb +230 -0
  21. data/features/step_definitions/s3_steps.rb +14 -0
  22. data/features/step_definitions/web_steps.rb +107 -0
  23. data/features/support/env.rb +11 -0
  24. data/features/support/fakeweb.rb +13 -0
  25. data/features/support/file_helpers.rb +34 -0
  26. data/features/support/fixtures/boot_config.txt +15 -0
  27. data/features/support/fixtures/gemfile.txt +5 -0
  28. data/features/support/fixtures/preinitializer.txt +20 -0
  29. data/features/support/paths.rb +28 -0
  30. data/features/support/rails.rb +63 -0
  31. data/features/support/selectors.rb +19 -0
  32. data/gemfiles/4.2.awsv2.0.gemfile +17 -0
  33. data/gemfiles/4.2.awsv2.1.gemfile +17 -0
  34. data/gemfiles/4.2.awsv2.gemfile +20 -0
  35. data/gemfiles/5.0.awsv2.0.gemfile +17 -0
  36. data/gemfiles/5.0.awsv2.1.gemfile +17 -0
  37. data/gemfiles/5.0.awsv2.gemfile +25 -0
  38. data/lib/generators/paperclip/USAGE +8 -0
  39. data/lib/generators/paperclip/paperclip_generator.rb +30 -0
  40. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
  41. data/lib/paperclip.rb +211 -0
  42. data/lib/paperclip/attachment.rb +610 -0
  43. data/lib/paperclip/attachment_registry.rb +60 -0
  44. data/lib/paperclip/callbacks.rb +42 -0
  45. data/lib/paperclip/content_type_detector.rb +80 -0
  46. data/lib/paperclip/errors.rb +34 -0
  47. data/lib/paperclip/file_command_content_type_detector.rb +30 -0
  48. data/lib/paperclip/filename_cleaner.rb +16 -0
  49. data/lib/paperclip/geometry.rb +158 -0
  50. data/lib/paperclip/geometry_detector_factory.rb +48 -0
  51. data/lib/paperclip/geometry_parser_factory.rb +31 -0
  52. data/lib/paperclip/glue.rb +17 -0
  53. data/lib/paperclip/has_attached_file.rb +115 -0
  54. data/lib/paperclip/helpers.rb +60 -0
  55. data/lib/paperclip/interpolations.rb +197 -0
  56. data/lib/paperclip/interpolations/plural_cache.rb +18 -0
  57. data/lib/paperclip/io_adapters/abstract_adapter.rb +47 -0
  58. data/lib/paperclip/io_adapters/attachment_adapter.rb +36 -0
  59. data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
  60. data/lib/paperclip/io_adapters/empty_string_adapter.rb +18 -0
  61. data/lib/paperclip/io_adapters/file_adapter.rb +22 -0
  62. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +15 -0
  63. data/lib/paperclip/io_adapters/identity_adapter.rb +12 -0
  64. data/lib/paperclip/io_adapters/nil_adapter.rb +34 -0
  65. data/lib/paperclip/io_adapters/registry.rb +32 -0
  66. data/lib/paperclip/io_adapters/stringio_adapter.rb +33 -0
  67. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +42 -0
  68. data/lib/paperclip/io_adapters/uri_adapter.rb +44 -0
  69. data/lib/paperclip/locales/en.yml +18 -0
  70. data/lib/paperclip/logger.rb +21 -0
  71. data/lib/paperclip/matchers.rb +64 -0
  72. data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
  73. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +100 -0
  74. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
  75. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +96 -0
  76. data/lib/paperclip/media_type_spoof_detector.rb +89 -0
  77. data/lib/paperclip/missing_attachment_styles.rb +79 -0
  78. data/lib/paperclip/processor.rb +48 -0
  79. data/lib/paperclip/processor_helpers.rb +50 -0
  80. data/lib/paperclip/rails_environment.rb +25 -0
  81. data/lib/paperclip/railtie.rb +31 -0
  82. data/lib/paperclip/schema.rb +77 -0
  83. data/lib/paperclip/storage.rb +4 -0
  84. data/lib/paperclip/storage/database.rb +140 -0
  85. data/lib/paperclip/storage/filesystem.rb +90 -0
  86. data/lib/paperclip/storage/fog.rb +244 -0
  87. data/lib/paperclip/storage/s3.rb +442 -0
  88. data/lib/paperclip/style.rb +109 -0
  89. data/lib/paperclip/tempfile.rb +43 -0
  90. data/lib/paperclip/tempfile_factory.rb +23 -0
  91. data/lib/paperclip/thumbnail.rb +121 -0
  92. data/lib/paperclip/url_generator.rb +72 -0
  93. data/lib/paperclip/validators.rb +74 -0
  94. data/lib/paperclip/validators/attachment_content_type_validator.rb +88 -0
  95. data/lib/paperclip/validators/attachment_file_name_validator.rb +80 -0
  96. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +29 -0
  97. data/lib/paperclip/validators/attachment_presence_validator.rb +30 -0
  98. data/lib/paperclip/validators/attachment_size_validator.rb +109 -0
  99. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +27 -0
  100. data/lib/paperclip/version.rb +3 -0
  101. data/lib/tasks/paperclip.rake +127 -0
  102. data/paperclip.gemspec +54 -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 +80 -0
  107. data/spec/paperclip/attachment_registry_spec.rb +158 -0
  108. data/spec/paperclip/attachment_spec.rb +1517 -0
  109. data/spec/paperclip/content_type_detector_spec.rb +48 -0
  110. data/spec/paperclip/file_command_content_type_detector_spec.rb +26 -0
  111. data/spec/paperclip/filename_cleaner_spec.rb +14 -0
  112. data/spec/paperclip/geometry_detector_spec.rb +39 -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 +44 -0
  116. data/spec/paperclip/has_attached_file_spec.rb +158 -0
  117. data/spec/paperclip/integration_spec.rb +668 -0
  118. data/spec/paperclip/interpolations_spec.rb +262 -0
  119. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +78 -0
  120. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +139 -0
  121. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +83 -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 +113 -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 +102 -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 +109 -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 +70 -0
  136. data/spec/paperclip/meta_class_spec.rb +30 -0
  137. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +84 -0
  138. data/spec/paperclip/paperclip_spec.rb +192 -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 +33 -0
  143. data/spec/paperclip/rake_spec.rb +103 -0
  144. data/spec/paperclip/schema_spec.rb +248 -0
  145. data/spec/paperclip/storage/filesystem_spec.rb +79 -0
  146. data/spec/paperclip/storage/fog_spec.rb +545 -0
  147. data/spec/paperclip/storage/s3_live_spec.rb +186 -0
  148. data/spec/paperclip/storage/s3_spec.rb +1583 -0
  149. data/spec/paperclip/style_spec.rb +255 -0
  150. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  151. data/spec/paperclip/thumbnail_spec.rb +500 -0
  152. data/spec/paperclip/url_generator_spec.rb +211 -0
  153. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
  154. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +160 -0
  155. data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
  156. data/spec/paperclip/validators/attachment_size_validator_spec.rb +235 -0
  157. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +52 -0
  158. data/spec/paperclip/validators_spec.rb +164 -0
  159. data/spec/spec_helper.rb +45 -0
  160. data/spec/support/assertions.rb +78 -0
  161. data/spec/support/fake_model.rb +25 -0
  162. data/spec/support/fake_rails.rb +12 -0
  163. data/spec/support/fixtures/12k.png +0 -0
  164. data/spec/support/fixtures/50x50.png +0 -0
  165. data/spec/support/fixtures/5k.png +0 -0
  166. data/spec/support/fixtures/animated +0 -0
  167. data/spec/support/fixtures/animated.gif +0 -0
  168. data/spec/support/fixtures/animated.unknown +0 -0
  169. data/spec/support/fixtures/bad.png +1 -0
  170. data/spec/support/fixtures/empty.html +1 -0
  171. data/spec/support/fixtures/empty.xlsx +0 -0
  172. data/spec/support/fixtures/fog.yml +8 -0
  173. data/spec/support/fixtures/rotated.jpg +0 -0
  174. data/spec/support/fixtures/s3.yml +8 -0
  175. data/spec/support/fixtures/spaced file.jpg +0 -0
  176. data/spec/support/fixtures/spaced file.png +0 -0
  177. data/spec/support/fixtures/text.txt +1 -0
  178. data/spec/support/fixtures/twopage.pdf +0 -0
  179. data/spec/support/fixtures/uppercase.PNG +0 -0
  180. data/spec/support/matchers/accept.rb +5 -0
  181. data/spec/support/matchers/exist.rb +5 -0
  182. data/spec/support/matchers/have_column.rb +23 -0
  183. data/spec/support/mock_attachment.rb +22 -0
  184. data/spec/support/mock_interpolator.rb +24 -0
  185. data/spec/support/mock_url_generator_builder.rb +27 -0
  186. data/spec/support/model_reconstruction.rb +68 -0
  187. data/spec/support/reporting.rb +11 -0
  188. data/spec/support/test_data.rb +13 -0
  189. data/spec/support/version_helper.rb +9 -0
  190. metadata +713 -0
@@ -0,0 +1,43 @@
1
+ module Paperclip
2
+ # Overriding some implementation of Tempfile
3
+ class Tempfile < ::Tempfile
4
+ # Due to how ImageMagick handles its image format conversion and how
5
+ # Tempfile handles its naming scheme, it is necessary to override how
6
+ # Tempfile makes # its names so as to allow for file extensions. Idea
7
+ # taken from the comments on this blog post:
8
+ # http://marsorange.com/archives/of-mogrify-ruby-tempfile-dynamic-class-definitions
9
+ #
10
+ # This is Ruby 1.9.3's implementation.
11
+ def make_tmpname(prefix_suffix, n)
12
+ if RUBY_PLATFORM =~ /java/
13
+ case prefix_suffix
14
+ when String
15
+ prefix, suffix = prefix_suffix, ''
16
+ when Array
17
+ prefix, suffix = *prefix_suffix
18
+ else
19
+ raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
20
+ end
21
+
22
+ t = Time.now.strftime("%y%m%d")
23
+ path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}-#{n}#{suffix}"
24
+ else
25
+ super
26
+ end
27
+ end
28
+ end
29
+
30
+ module TempfileEncoding
31
+ # This overrides Tempfile#binmode to make sure that the extenal encoding
32
+ # for binary mode is ASCII-8BIT. This behavior is what's in CRuby, but not
33
+ # in JRuby
34
+ def binmode
35
+ set_encoding('ASCII-8BIT')
36
+ super
37
+ end
38
+ end
39
+ end
40
+
41
+ if RUBY_PLATFORM =~ /java/
42
+ ::Tempfile.send :include, Paperclip::TempfileEncoding
43
+ end
@@ -0,0 +1,23 @@
1
+ module Paperclip
2
+ class TempfileFactory
3
+
4
+ def generate(name = random_name)
5
+ @name = name
6
+ file = Tempfile.new([basename, extension])
7
+ file.binmode
8
+ file
9
+ end
10
+
11
+ def extension
12
+ File.extname(@name)
13
+ end
14
+
15
+ def basename
16
+ Digest::MD5.hexdigest(File.basename(@name, extension))
17
+ end
18
+
19
+ def random_name
20
+ SecureRandom.uuid
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,121 @@
1
+ module Paperclip
2
+ # Handles thumbnailing images that are uploaded.
3
+ class Thumbnail < Processor
4
+
5
+ attr_accessor :current_geometry, :target_geometry, :format, :whiny, :convert_options,
6
+ :source_file_options, :animated, :auto_orient
7
+
8
+ # List of formats that we need to preserve animation
9
+ ANIMATED_FORMATS = %w(gif)
10
+
11
+ # Creates a Thumbnail object set to work on the +file+ given. It
12
+ # will attempt to transform the image into one defined by +target_geometry+
13
+ # which is a "WxH"-style string. +format+ will be inferred from the +file+
14
+ # unless specified. Thumbnail creation will raise no errors unless
15
+ # +whiny+ is true (which it is, by default. If +convert_options+ is
16
+ # set, the options will be appended to the convert command upon image conversion
17
+ #
18
+ # Options include:
19
+ #
20
+ # +geometry+ - the desired width and height of the thumbnail (required)
21
+ # +file_geometry_parser+ - an object with a method named +from_file+ that takes an image file and produces its geometry and a +transformation_to+. Defaults to Paperclip::Geometry
22
+ # +string_geometry_parser+ - an object with a method named +parse+ that takes a string and produces an object with +width+, +height+, and +to_s+ accessors. Defaults to Paperclip::Geometry
23
+ # +source_file_options+ - flags passed to the +convert+ command that influence how the source file is read
24
+ # +convert_options+ - flags passed to the +convert+ command that influence how the image is processed
25
+ # +whiny+ - whether to raise an error when processing fails. Defaults to true
26
+ # +format+ - the desired filename extension
27
+ # +animated+ - whether to merge all the layers in the image. Defaults to true
28
+ def initialize(file, options = {}, attachment = nil)
29
+ super
30
+
31
+ geometry = options[:geometry].to_s
32
+ @crop = geometry[-1,1] == '#'
33
+ @target_geometry = options.fetch(:string_geometry_parser, Geometry).parse(geometry)
34
+ @current_geometry = options.fetch(:file_geometry_parser, Geometry).from_file(@file)
35
+ @source_file_options = options[:source_file_options]
36
+ @convert_options = options[:convert_options]
37
+ @whiny = options.fetch(:whiny, true)
38
+ @format = options[:format]
39
+ @animated = options.fetch(:animated, true)
40
+ @auto_orient = options.fetch(:auto_orient, true)
41
+ if @auto_orient && @current_geometry.respond_to?(:auto_orient)
42
+ @current_geometry.auto_orient
43
+ end
44
+
45
+ @source_file_options = @source_file_options.split(/\s+/) if @source_file_options.respond_to?(:split)
46
+ @convert_options = @convert_options.split(/\s+/) if @convert_options.respond_to?(:split)
47
+
48
+ @current_format = File.extname(@file.path)
49
+ @basename = File.basename(@file.path, @current_format)
50
+ end
51
+
52
+ # Returns true if the +target_geometry+ is meant to crop.
53
+ def crop?
54
+ @crop
55
+ end
56
+
57
+ # Returns true if the image is meant to make use of additional convert options.
58
+ def convert_options?
59
+ !@convert_options.nil? && !@convert_options.empty?
60
+ end
61
+
62
+ # Performs the conversion of the +file+ into a thumbnail. Returns the Tempfile
63
+ # that contains the new image.
64
+ def make
65
+ src = @file
66
+ filename = [@basename, @format ? ".#{@format}" : ""].join
67
+ dst = TempfileFactory.new.generate(filename)
68
+
69
+ begin
70
+ parameters = []
71
+ parameters << source_file_options
72
+ parameters << ":source"
73
+ parameters << transformation_command
74
+ parameters << convert_options
75
+ parameters << ":dest"
76
+
77
+ parameters = parameters.flatten.compact.join(" ").strip.squeeze(" ")
78
+
79
+ success = convert(parameters, :source => "#{File.expand_path(src.path)}#{'[0]' unless animated?}", :dest => File.expand_path(dst.path))
80
+ rescue Cocaine::ExitStatusError => e
81
+ raise Paperclip::Error, "There was an error processing the thumbnail for #{@basename}" if @whiny
82
+ rescue Cocaine::CommandNotFoundError => e
83
+ raise Paperclip::Errors::CommandNotFoundError.new("Could not run the `convert` command. Please install ImageMagick.")
84
+ end
85
+
86
+ dst
87
+ end
88
+
89
+ # Returns the command ImageMagick's +convert+ needs to transform the image
90
+ # into the thumbnail.
91
+ def transformation_command
92
+ scale, crop = @current_geometry.transformation_to(@target_geometry, crop?)
93
+ trans = []
94
+ trans << "-coalesce" if animated?
95
+ trans << "-auto-orient" if auto_orient
96
+ trans << "-resize" << %["#{scale}"] unless scale.nil? || scale.empty?
97
+ trans << "-crop" << %["#{crop}"] << "+repage" if crop
98
+ trans << '-layers "optimize"' if animated?
99
+ trans
100
+ end
101
+
102
+ protected
103
+
104
+ # Return true if the format is animated
105
+ def animated?
106
+ @animated && (ANIMATED_FORMATS.include?(@format.to_s) || @format.blank?) && identified_as_animated?
107
+ end
108
+
109
+ # Return true if ImageMagick's +identify+ returns an animated format
110
+ def identified_as_animated?
111
+ if @identified_as_animated.nil?
112
+ @identified_as_animated = ANIMATED_FORMATS.include? identify("-format %m :file", :file => "#{@file.path}[0]").to_s.downcase.strip
113
+ end
114
+ @identified_as_animated
115
+ rescue Cocaine::ExitStatusError => e
116
+ raise Paperclip::Error, "There was an error running `identify` for #{@basename}" if @whiny
117
+ rescue Cocaine::CommandNotFoundError => e
118
+ raise Paperclip::Errors::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.")
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,72 @@
1
+ require 'uri'
2
+
3
+ module Paperclip
4
+ class UrlGenerator
5
+ def initialize(attachment, attachment_options)
6
+ @attachment = attachment
7
+ @attachment_options = attachment_options
8
+ end
9
+
10
+ def for(style_name, options)
11
+ timestamp_as_needed(
12
+ escape_url_as_needed(
13
+ @attachment_options[:interpolator].interpolate(most_appropriate_url, @attachment, style_name),
14
+ options
15
+ ), options)
16
+ end
17
+
18
+ private
19
+
20
+ # This method is all over the place.
21
+ def default_url
22
+ if @attachment_options[:default_url].respond_to?(:call)
23
+ @attachment_options[:default_url].call(@attachment)
24
+ elsif @attachment_options[:default_url].is_a?(Symbol)
25
+ @attachment.instance.send(@attachment_options[:default_url])
26
+ else
27
+ @attachment_options[:default_url]
28
+ end
29
+ end
30
+
31
+ def most_appropriate_url
32
+ if @attachment.original_filename.nil?
33
+ default_url
34
+ else
35
+ @attachment_options[:url]
36
+ end
37
+ end
38
+
39
+ def timestamp_as_needed(url, options)
40
+ if options[:timestamp] && timestamp_possible?
41
+ delimiter_char = url.match(/\?.+=/) ? '&' : '?'
42
+ "#{url}#{delimiter_char}#{@attachment.updated_at.to_s}"
43
+ else
44
+ url
45
+ end
46
+ end
47
+
48
+ def timestamp_possible?
49
+ @attachment.respond_to?(:updated_at) && @attachment.updated_at.present?
50
+ end
51
+
52
+ def escape_url_as_needed(url, options)
53
+ if options[:escape]
54
+ escape_url(url)
55
+ else
56
+ url
57
+ end
58
+ end
59
+
60
+ def escape_url(url)
61
+ if url.respond_to?(:escape)
62
+ url.escape
63
+ else
64
+ URI.escape(url).gsub(escape_regex){|m| "%#{m.ord.to_s(16).upcase}" }
65
+ end
66
+ end
67
+
68
+ def escape_regex
69
+ /[\?\(\)\[\]\+]/
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,74 @@
1
+ require 'active_model'
2
+ require 'active_support/concern'
3
+ require 'active_support/core_ext/array/wrap'
4
+ require 'paperclip/validators/attachment_content_type_validator'
5
+ require 'paperclip/validators/attachment_file_name_validator'
6
+ require 'paperclip/validators/attachment_presence_validator'
7
+ require 'paperclip/validators/attachment_size_validator'
8
+ require 'paperclip/validators/media_type_spoof_detection_validator'
9
+ require 'paperclip/validators/attachment_file_type_ignorance_validator'
10
+
11
+ module Paperclip
12
+ module Validators
13
+ extend ActiveSupport::Concern
14
+
15
+ included do
16
+ extend HelperMethods
17
+ include HelperMethods
18
+ end
19
+
20
+ ::Paperclip::REQUIRED_VALIDATORS = [AttachmentFileNameValidator, AttachmentContentTypeValidator, AttachmentFileTypeIgnoranceValidator]
21
+
22
+ module ClassMethods
23
+ # This method is a shortcut to validator classes that is in
24
+ # "Attachment...Validator" format. It is almost the same thing as the
25
+ # +validates+ method that shipped with Rails, but this is customized to
26
+ # be using with attachment validators. This is helpful when you're using
27
+ # multiple attachment validators on a single attachment.
28
+ #
29
+ # Example of using the validator:
30
+ #
31
+ # validates_attachment :avatar, :presence => true,
32
+ # :content_type => { :content_type => "image/jpg" },
33
+ # :size => { :in => 0..10.kilobytes }
34
+ #
35
+ def validates_attachment(*attributes)
36
+ options = attributes.extract_options!.dup
37
+
38
+ Paperclip::Validators.constants.each do |constant|
39
+ if constant.to_s =~ /\AAttachment(.+)Validator\Z/
40
+ validator_kind = $1.underscore.to_sym
41
+
42
+ if options.has_key?(validator_kind)
43
+ validator_options = options.delete(validator_kind)
44
+ validator_options = {} if validator_options == true
45
+ conditional_options = options.slice(:if, :unless)
46
+ Array.wrap(validator_options).each do |local_options|
47
+ method_name = Paperclip::Validators.const_get(constant.to_s).helper_method_name
48
+ send(method_name, attributes, local_options.merge(conditional_options))
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ def validate_before_processing(validator_class, options)
56
+ options = options.dup
57
+ attributes = options.delete(:attributes)
58
+ attributes.each do |attribute|
59
+ options[:attributes] = [attribute]
60
+ create_validating_before_filter(attribute, validator_class, options)
61
+ end
62
+ end
63
+
64
+ def create_validating_before_filter(attribute, validator_class, options)
65
+ if_clause = options.delete(:if)
66
+ unless_clause = options.delete(:unless)
67
+ send(:"before_#{attribute}_post_process", :if => if_clause, :unless => unless_clause) do |*args|
68
+ validator_class.new(options.dup).validate(self)
69
+ end
70
+ end
71
+
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,88 @@
1
+ module Paperclip
2
+ module Validators
3
+ class AttachmentContentTypeValidator < ActiveModel::EachValidator
4
+ def initialize(options)
5
+ options[:allow_nil] = true unless options.has_key?(:allow_nil)
6
+ super
7
+ end
8
+
9
+ def self.helper_method_name
10
+ :validates_attachment_content_type
11
+ end
12
+
13
+ def validate_each(record, attribute, value)
14
+ base_attribute = attribute.to_sym
15
+ attribute = "#{attribute}_content_type".to_sym
16
+ value = record.send :read_attribute_for_validation, attribute
17
+
18
+ return if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
19
+
20
+ validate_whitelist(record, attribute, value)
21
+ validate_blacklist(record, attribute, value)
22
+
23
+ if record.errors.include? attribute
24
+ record.errors[attribute].each do |error|
25
+ record.errors.add base_attribute, error
26
+ end
27
+ end
28
+ end
29
+
30
+ def validate_whitelist(record, attribute, value)
31
+ if allowed_types.present? && allowed_types.none? { |type| type === value }
32
+ mark_invalid record, attribute, allowed_types
33
+ end
34
+ end
35
+
36
+ def validate_blacklist(record, attribute, value)
37
+ if forbidden_types.present? && forbidden_types.any? { |type| type === value }
38
+ mark_invalid record, attribute, forbidden_types
39
+ end
40
+ end
41
+
42
+ def mark_invalid(record, attribute, types)
43
+ record.errors.add attribute, :invalid, options.merge(:types => types.join(', '))
44
+ end
45
+
46
+ def allowed_types
47
+ [options[:content_type]].flatten.compact
48
+ end
49
+
50
+ def forbidden_types
51
+ [options[:not]].flatten.compact
52
+ end
53
+
54
+ def check_validity!
55
+ unless options.has_key?(:content_type) || options.has_key?(:not)
56
+ raise ArgumentError, "You must pass in either :content_type or :not to the validator"
57
+ end
58
+ end
59
+ end
60
+
61
+ module HelperMethods
62
+ # Places ActiveModel validations on the content type of the file
63
+ # assigned. The possible options are:
64
+ # * +content_type+: Allowed content types. Can be a single content type
65
+ # or an array. Each type can be a String or a Regexp. It should be
66
+ # noted that Internet Explorer uploads files with content_types that you
67
+ # may not expect. For example, JPEG images are given image/pjpeg and
68
+ # PNGs are image/x-png, so keep that in mind when determining how you
69
+ # match. Allows all by default.
70
+ # * +not+: Forbidden content types.
71
+ # * +message+: The message to display when the uploaded file has an invalid
72
+ # content type.
73
+ # * +if+: A lambda or name of an instance method. Validation will only
74
+ # be run is this lambda or method returns true.
75
+ # * +unless+: Same as +if+ but validates if lambda or method returns false.
76
+ # NOTE: If you do not specify an [attachment]_content_type field on your
77
+ # model, content_type validation will work _ONLY upon assignment_ and
78
+ # re-validation after the instance has been reloaded will always succeed.
79
+ # You'll still need to have a virtual attribute (created by +attr_accessor+)
80
+ # name +[attachment]_content_type+ to be able to use this validator.
81
+ def validates_attachment_content_type(*attr_names)
82
+ options = _merge_attributes(attr_names)
83
+ validates_with AttachmentContentTypeValidator, options.dup
84
+ validate_before_processing AttachmentContentTypeValidator, options.dup
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,80 @@
1
+ module Paperclip
2
+ module Validators
3
+ class AttachmentFileNameValidator < ActiveModel::EachValidator
4
+ def initialize(options)
5
+ options[:allow_nil] = true unless options.has_key?(:allow_nil)
6
+ super
7
+ end
8
+
9
+ def self.helper_method_name
10
+ :validates_attachment_file_name
11
+ end
12
+
13
+ def validate_each(record, attribute, value)
14
+ base_attribute = attribute.to_sym
15
+ attribute = "#{attribute}_file_name".to_sym
16
+ value = record.send :read_attribute_for_validation, attribute
17
+
18
+ return if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
19
+
20
+ validate_whitelist(record, attribute, value)
21
+ validate_blacklist(record, attribute, value)
22
+
23
+ if record.errors.include? attribute
24
+ record.errors[attribute].each do |error|
25
+ record.errors.add base_attribute, error
26
+ end
27
+ end
28
+ end
29
+
30
+ def validate_whitelist(record, attribute, value)
31
+ if allowed.present? && allowed.none? { |type| type === value }
32
+ mark_invalid record, attribute, allowed
33
+ end
34
+ end
35
+
36
+ def validate_blacklist(record, attribute, value)
37
+ if forbidden.present? && forbidden.any? { |type| type === value }
38
+ mark_invalid record, attribute, forbidden
39
+ end
40
+ end
41
+
42
+ def mark_invalid(record, attribute, patterns)
43
+ record.errors.add attribute, :invalid, options.merge(:names => patterns.join(', '))
44
+ end
45
+
46
+ def allowed
47
+ [options[:matches]].flatten.compact
48
+ end
49
+
50
+ def forbidden
51
+ [options[:not]].flatten.compact
52
+ end
53
+
54
+ def check_validity!
55
+ unless options.has_key?(:matches) || options.has_key?(:not)
56
+ raise ArgumentError, "You must pass in either :matches or :not to the validator"
57
+ end
58
+ end
59
+ end
60
+
61
+ module HelperMethods
62
+ # Places ActiveModel validations on the name of the file
63
+ # assigned. The possible options are:
64
+ # * +matches+: Allowed filename patterns as Regexps. Can be a single one
65
+ # or an array.
66
+ # * +not+: Forbidden file name patterns, specified the same was as +matches+.
67
+ # * +message+: The message to display when the uploaded file has an invalid
68
+ # name.
69
+ # * +if+: A lambda or name of an instance method. Validation will only
70
+ # be run is this lambda or method returns true.
71
+ # * +unless+: Same as +if+ but validates if lambda or method returns false.
72
+ def validates_attachment_file_name(*attr_names)
73
+ options = _merge_attributes(attr_names)
74
+ validates_with AttachmentFileNameValidator, options.dup
75
+ validate_before_processing AttachmentFileNameValidator, options.dup
76
+ end
77
+ end
78
+ end
79
+ end
80
+