jr-paperclip 7.3.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.
- checksums.yaml +7 -0
- data/.github/FUNDING.yml +3 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- data/.github/ISSUE_TEMPLATE/custom.md +10 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/workflows/reviewdog.yml +23 -0
- data/.github/workflows/test.yml +46 -0
- data/.gitignore +19 -0
- data/.qlty/.gitignore +7 -0
- data/.qlty/qlty.toml +89 -0
- data/.rubocop.yml +1060 -0
- data/Appraisals +29 -0
- data/CONTRIBUTING.md +85 -0
- data/Gemfile +17 -0
- data/LICENSE +25 -0
- data/NEWS +567 -0
- data/README.md +1083 -0
- data/RELEASING.md +17 -0
- data/Rakefile +52 -0
- data/bin/console +11 -0
- data/features/basic_integration.feature +85 -0
- data/features/migration.feature +29 -0
- data/features/rake_tasks.feature +62 -0
- data/features/step_definitions/attachment_steps.rb +121 -0
- data/features/step_definitions/html_steps.rb +15 -0
- data/features/step_definitions/rails_steps.rb +271 -0
- data/features/step_definitions/s3_steps.rb +16 -0
- data/features/step_definitions/web_steps.rb +106 -0
- data/features/support/env.rb +12 -0
- data/features/support/file_helpers.rb +34 -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 +39 -0
- data/features/support/selectors.rb +19 -0
- data/features/support/webmock_setup.rb +8 -0
- data/gemfiles/7.0.gemfile +20 -0
- data/gemfiles/7.1.gemfile +20 -0
- data/gemfiles/7.2.gemfile +20 -0
- data/gemfiles/8.0.gemfile +20 -0
- data/gemfiles/8.1.gemfile +20 -0
- data/lib/generators/paperclip/USAGE +8 -0
- data/lib/generators/paperclip/paperclip_generator.rb +36 -0
- data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
- data/lib/jr-paperclip.rb +1 -0
- data/lib/paperclip/attachment.rb +634 -0
- data/lib/paperclip/attachment_registry.rb +60 -0
- data/lib/paperclip/callbacks.rb +42 -0
- data/lib/paperclip/content_type_detector.rb +85 -0
- data/lib/paperclip/errors.rb +34 -0
- data/lib/paperclip/file_command_content_type_detector.rb +28 -0
- data/lib/paperclip/filename_cleaner.rb +15 -0
- data/lib/paperclip/geometry.rb +157 -0
- data/lib/paperclip/geometry_detector_factory.rb +45 -0
- data/lib/paperclip/geometry_parser_factory.rb +31 -0
- data/lib/paperclip/glue.rb +18 -0
- data/lib/paperclip/has_attached_file.rb +116 -0
- data/lib/paperclip/helpers.rb +60 -0
- data/lib/paperclip/interpolations/plural_cache.rb +18 -0
- data/lib/paperclip/interpolations.rb +205 -0
- data/lib/paperclip/io_adapters/abstract_adapter.rb +75 -0
- data/lib/paperclip/io_adapters/attachment_adapter.rb +56 -0
- data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
- data/lib/paperclip/io_adapters/empty_string_adapter.rb +19 -0
- data/lib/paperclip/io_adapters/file_adapter.rb +26 -0
- data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +16 -0
- data/lib/paperclip/io_adapters/identity_adapter.rb +17 -0
- data/lib/paperclip/io_adapters/nil_adapter.rb +37 -0
- data/lib/paperclip/io_adapters/registry.rb +36 -0
- data/lib/paperclip/io_adapters/stringio_adapter.rb +36 -0
- data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +44 -0
- data/lib/paperclip/io_adapters/uri_adapter.rb +78 -0
- data/lib/paperclip/locales/en.yml +18 -0
- data/lib/paperclip/locales/fr.yml +18 -0
- data/lib/paperclip/locales/gd.yml +20 -0
- data/lib/paperclip/logger.rb +21 -0
- data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
- data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +101 -0
- data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
- data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +97 -0
- data/lib/paperclip/matchers.rb +64 -0
- data/lib/paperclip/media_type_spoof_detector.rb +93 -0
- data/lib/paperclip/missing_attachment_styles.rb +84 -0
- data/lib/paperclip/processor.rb +56 -0
- data/lib/paperclip/processor_helpers.rb +52 -0
- data/lib/paperclip/rails_environment.rb +21 -0
- data/lib/paperclip/railtie.rb +31 -0
- data/lib/paperclip/schema.rb +104 -0
- data/lib/paperclip/storage/filesystem.rb +99 -0
- data/lib/paperclip/storage/fog.rb +262 -0
- data/lib/paperclip/storage/s3.rb +497 -0
- data/lib/paperclip/storage.rb +3 -0
- data/lib/paperclip/style.rb +106 -0
- data/lib/paperclip/tempfile.rb +42 -0
- data/lib/paperclip/tempfile_factory.rb +22 -0
- data/lib/paperclip/thumbnail.rb +131 -0
- data/lib/paperclip/url_generator.rb +83 -0
- data/lib/paperclip/validators/attachment_content_type_validator.rb +95 -0
- data/lib/paperclip/validators/attachment_file_name_validator.rb +82 -0
- data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +28 -0
- data/lib/paperclip/validators/attachment_presence_validator.rb +28 -0
- data/lib/paperclip/validators/attachment_size_validator.rb +126 -0
- data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +29 -0
- data/lib/paperclip/validators.rb +73 -0
- data/lib/paperclip/version.rb +3 -0
- data/lib/paperclip.rb +215 -0
- data/lib/tasks/paperclip.rake +140 -0
- data/paperclip.gemspec +51 -0
- data/shoulda_macros/paperclip.rb +134 -0
- data/spec/database.yml +4 -0
- data/spec/paperclip/attachment_definitions_spec.rb +13 -0
- data/spec/paperclip/attachment_processing_spec.rb +79 -0
- data/spec/paperclip/attachment_registry_spec.rb +158 -0
- data/spec/paperclip/attachment_spec.rb +1617 -0
- data/spec/paperclip/content_type_detector_spec.rb +58 -0
- data/spec/paperclip/file_command_content_type_detector_spec.rb +40 -0
- data/spec/paperclip/filename_cleaner_spec.rb +13 -0
- data/spec/paperclip/geometry_detector_spec.rb +47 -0
- data/spec/paperclip/geometry_parser_spec.rb +73 -0
- data/spec/paperclip/geometry_spec.rb +267 -0
- data/spec/paperclip/glue_spec.rb +63 -0
- data/spec/paperclip/has_attached_file_spec.rb +78 -0
- data/spec/paperclip/integration_spec.rb +702 -0
- data/spec/paperclip/interpolations_spec.rb +270 -0
- data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +160 -0
- data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +167 -0
- data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +88 -0
- data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
- data/spec/paperclip/io_adapters/file_adapter_spec.rb +134 -0
- data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +142 -0
- data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
- data/spec/paperclip/io_adapters/nil_adapter_spec.rb +25 -0
- data/spec/paperclip/io_adapters/registry_spec.rb +35 -0
- data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +64 -0
- data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +146 -0
- data/spec/paperclip/io_adapters/uri_adapter_spec.rb +231 -0
- data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
- data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +108 -0
- data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
- data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
- data/spec/paperclip/media_type_spoof_detector_spec.rb +126 -0
- data/spec/paperclip/meta_class_spec.rb +30 -0
- data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +88 -0
- data/spec/paperclip/paperclip_spec.rb +196 -0
- data/spec/paperclip/plural_cache_spec.rb +37 -0
- data/spec/paperclip/processor_helpers_spec.rb +57 -0
- data/spec/paperclip/processor_spec.rb +26 -0
- data/spec/paperclip/rails_environment_spec.rb +30 -0
- data/spec/paperclip/rake_spec.rb +103 -0
- data/spec/paperclip/schema_spec.rb +298 -0
- data/spec/paperclip/storage/filesystem_spec.rb +102 -0
- data/spec/paperclip/storage/fog_spec.rb +606 -0
- data/spec/paperclip/storage/s3_live_spec.rb +188 -0
- data/spec/paperclip/storage/s3_spec.rb +1974 -0
- data/spec/paperclip/style_spec.rb +251 -0
- data/spec/paperclip/tempfile_factory_spec.rb +33 -0
- data/spec/paperclip/tempfile_spec.rb +35 -0
- data/spec/paperclip/thumbnail_spec.rb +504 -0
- data/spec/paperclip/url_generator_spec.rb +231 -0
- data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +410 -0
- data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +249 -0
- data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
- data/spec/paperclip/validators/attachment_size_validator_spec.rb +325 -0
- data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +48 -0
- data/spec/paperclip/validators_spec.rb +179 -0
- data/spec/spec_helper.rb +52 -0
- data/spec/support/assertions.rb +84 -0
- data/spec/support/fake_model.rb +24 -0
- data/spec/support/fake_rails.rb +12 -0
- data/spec/support/fixtures/12k.png +0 -0
- data/spec/support/fixtures/50x50.png +0 -0
- data/spec/support/fixtures/5k.png +0 -0
- data/spec/support/fixtures/animated +0 -0
- data/spec/support/fixtures/animated.gif +0 -0
- data/spec/support/fixtures/animated.unknown +0 -0
- data/spec/support/fixtures/aws_s3.yml +13 -0
- data/spec/support/fixtures/bad.png +1 -0
- data/spec/support/fixtures/empty.html +1 -0
- data/spec/support/fixtures/empty.xlsx +0 -0
- data/spec/support/fixtures/fog.yml +8 -0
- data/spec/support/fixtures/rotated.jpg +0 -0
- data/spec/support/fixtures/s3.yml +8 -0
- data/spec/support/fixtures/sample.xlsm +0 -0
- data/spec/support/fixtures/spaced file.jpg +0 -0
- data/spec/support/fixtures/spaced file.png +0 -0
- data/spec/support/fixtures/text.txt +1 -0
- data/spec/support/fixtures/twopage.pdf +0 -0
- data/spec/support/fixtures/uppercase.PNG +0 -0
- data/spec/support/matchers/accept.rb +5 -0
- data/spec/support/matchers/exist.rb +5 -0
- data/spec/support/matchers/have_column.rb +23 -0
- data/spec/support/mock_attachment.rb +24 -0
- data/spec/support/mock_interpolator.rb +24 -0
- data/spec/support/mock_url_generator_builder.rb +26 -0
- data/spec/support/model_reconstruction.rb +72 -0
- data/spec/support/reporting.rb +11 -0
- data/spec/support/test_data.rb +13 -0
- data/spec/support/version_helper.rb +9 -0
- metadata +702 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
module Paperclip
|
|
2
|
+
# Handles thumbnailing images that are uploaded.
|
|
3
|
+
class Thumbnail < Processor
|
|
4
|
+
attr_accessor :current_geometry, :target_geometry, :format, :whiny, :convert_options,
|
|
5
|
+
:source_file_options, :animated, :auto_orient, :frame_index
|
|
6
|
+
|
|
7
|
+
# List of formats that we need to preserve animation
|
|
8
|
+
ANIMATED_FORMATS = %w(gif).freeze
|
|
9
|
+
MULTI_FRAME_FORMATS = %w(.mkv .avi .mp4 .mov .mpg .mpeg .gif).freeze
|
|
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
|
+
# +frame_index+ - the frame index of the source file to render as the thumbnail
|
|
29
|
+
def initialize(file, options = {}, attachment = nil)
|
|
30
|
+
super
|
|
31
|
+
|
|
32
|
+
geometry = options[:geometry].to_s
|
|
33
|
+
@crop = geometry[-1, 1] == "#"
|
|
34
|
+
@target_geometry = options.fetch(:string_geometry_parser, Geometry).parse(geometry)
|
|
35
|
+
@current_geometry = options.fetch(:file_geometry_parser, Geometry).from_file(@file)
|
|
36
|
+
@source_file_options = options[:source_file_options]
|
|
37
|
+
@convert_options = options[:convert_options]
|
|
38
|
+
@whiny = options.fetch(:whiny, true)
|
|
39
|
+
@format = options[:format]
|
|
40
|
+
@animated = options.fetch(:animated, true)
|
|
41
|
+
@auto_orient = options.fetch(:auto_orient, true)
|
|
42
|
+
@current_geometry.auto_orient if @auto_orient && @current_geometry.respond_to?(:auto_orient)
|
|
43
|
+
@source_file_options = @source_file_options.split(/\s+/) if @source_file_options.respond_to?(:split)
|
|
44
|
+
@convert_options = @convert_options.split(/\s+/) if @convert_options.respond_to?(:split)
|
|
45
|
+
|
|
46
|
+
@current_format = File.extname(@file.path)
|
|
47
|
+
@basename = File.basename(@file.path, @current_format)
|
|
48
|
+
@frame_index = multi_frame_format? ? options.fetch(:frame_index, 0) : 0
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns true if the +target_geometry+ is meant to crop.
|
|
52
|
+
def crop?
|
|
53
|
+
@crop
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Returns true if the image is meant to make use of additional convert options.
|
|
57
|
+
def convert_options?
|
|
58
|
+
!@convert_options.nil? && !@convert_options.empty?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Performs the conversion of the +file+ into a thumbnail. Returns the Tempfile
|
|
62
|
+
# that contains the new image.
|
|
63
|
+
def make
|
|
64
|
+
src = @file
|
|
65
|
+
filename = [@basename, @format ? ".#{@format}" : ""].join
|
|
66
|
+
dst = TempfileFactory.new.generate(filename)
|
|
67
|
+
|
|
68
|
+
begin
|
|
69
|
+
parameters = []
|
|
70
|
+
parameters << source_file_options
|
|
71
|
+
parameters << ":source"
|
|
72
|
+
parameters << transformation_command
|
|
73
|
+
parameters << convert_options
|
|
74
|
+
parameters << ":dest"
|
|
75
|
+
|
|
76
|
+
parameters = parameters.flatten.compact.join(" ").strip.squeeze(" ")
|
|
77
|
+
|
|
78
|
+
frame = animated? ? "" : "[#{@frame_index}]"
|
|
79
|
+
convert(
|
|
80
|
+
parameters,
|
|
81
|
+
source: "#{File.expand_path(src.path)}#{frame}",
|
|
82
|
+
dest: File.expand_path(dst.path)
|
|
83
|
+
)
|
|
84
|
+
rescue Terrapin::ExitStatusError => e
|
|
85
|
+
if @whiny
|
|
86
|
+
message = "There was an error processing the thumbnail for #{@basename}:\n" + e.message
|
|
87
|
+
raise Paperclip::Error, message
|
|
88
|
+
end
|
|
89
|
+
rescue Terrapin::CommandNotFoundError => e
|
|
90
|
+
raise Paperclip::Errors::CommandNotFoundError.new("Could not run the `convert` command. Please install ImageMagick.")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
dst
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Returns the command ImageMagick's +convert+ needs to transform the image
|
|
97
|
+
# into the thumbnail.
|
|
98
|
+
def transformation_command
|
|
99
|
+
scale, crop = @current_geometry.transformation_to(@target_geometry, crop?)
|
|
100
|
+
trans = []
|
|
101
|
+
trans << "-coalesce" if animated?
|
|
102
|
+
trans << "-auto-orient" if auto_orient
|
|
103
|
+
trans << "-resize" << %["#{scale}"] unless scale.nil? || scale.empty?
|
|
104
|
+
trans << "-crop" << %["#{crop}"] << "+repage" if crop
|
|
105
|
+
trans << '-layers "optimize"' if animated?
|
|
106
|
+
trans
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
protected
|
|
110
|
+
|
|
111
|
+
def multi_frame_format?
|
|
112
|
+
MULTI_FRAME_FORMATS.include? @current_format
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def animated?
|
|
116
|
+
@animated && (ANIMATED_FORMATS.include?(@format.to_s) || @format.blank?) && identified_as_animated?
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Return true if ImageMagick's +identify+ returns an animated format
|
|
120
|
+
def identified_as_animated?
|
|
121
|
+
if @identified_as_animated.nil?
|
|
122
|
+
@identified_as_animated = ANIMATED_FORMATS.include? identify("-format %m :file", file: "#{@file.path}[0]").to_s.downcase.strip
|
|
123
|
+
end
|
|
124
|
+
@identified_as_animated
|
|
125
|
+
rescue Terrapin::ExitStatusError => e
|
|
126
|
+
raise Paperclip::Error, "There was an error running `identify` for #{@basename}" if @whiny
|
|
127
|
+
rescue Terrapin::CommandNotFoundError => e
|
|
128
|
+
raise Paperclip::Errors::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.")
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require "uri"
|
|
2
|
+
require "active_support/core_ext/module/delegation"
|
|
3
|
+
|
|
4
|
+
module Paperclip
|
|
5
|
+
class UrlGenerator
|
|
6
|
+
class << self
|
|
7
|
+
def encoder
|
|
8
|
+
@encoder ||= URI::RFC2396_Parser.new
|
|
9
|
+
end
|
|
10
|
+
delegate :escape, :unescape, to: :encoder
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(attachment)
|
|
14
|
+
@attachment = attachment
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def for(style_name, options)
|
|
18
|
+
interpolated = attachment_options[:interpolator].interpolate(
|
|
19
|
+
most_appropriate_url, @attachment, style_name
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
escaped = escape_url_as_needed(interpolated, options)
|
|
23
|
+
timestamp_as_needed(escaped, options)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
attr_reader :attachment
|
|
29
|
+
delegate :options, to: :attachment, prefix: true
|
|
30
|
+
|
|
31
|
+
# This method is all over the place.
|
|
32
|
+
def default_url
|
|
33
|
+
if attachment_options[:default_url].respond_to?(:call)
|
|
34
|
+
attachment_options[:default_url].call(@attachment)
|
|
35
|
+
elsif attachment_options[:default_url].is_a?(Symbol)
|
|
36
|
+
@attachment.instance.send(attachment_options[:default_url])
|
|
37
|
+
else
|
|
38
|
+
attachment_options[:default_url]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def most_appropriate_url
|
|
43
|
+
if @attachment.original_filename.nil?
|
|
44
|
+
default_url
|
|
45
|
+
else
|
|
46
|
+
attachment_options[:url]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def timestamp_as_needed(url, options)
|
|
51
|
+
if options[:timestamp] && timestamp_possible?
|
|
52
|
+
delimiter_char = url.match(/\?.+=/) ? "&" : "?"
|
|
53
|
+
"#{url}#{delimiter_char}#{@attachment.updated_at}"
|
|
54
|
+
else
|
|
55
|
+
url
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def timestamp_possible?
|
|
60
|
+
@attachment.respond_to?(:updated_at) && @attachment.updated_at.present?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def escape_url_as_needed(url, options)
|
|
64
|
+
if options[:escape]
|
|
65
|
+
escape_url(url)
|
|
66
|
+
else
|
|
67
|
+
url
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def escape_url(url)
|
|
72
|
+
if url.respond_to?(:escape)
|
|
73
|
+
url.escape
|
|
74
|
+
else
|
|
75
|
+
self.class.escape(url).gsub(escape_regex) { |m| "%#{m.ord.to_s(16).upcase}" }
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def escape_regex
|
|
80
|
+
/[\?\(\)\[\]\+]/
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module Paperclip
|
|
2
|
+
module Validators
|
|
3
|
+
class AttachmentContentTypeValidator < ActiveModel::EachValidator
|
|
4
|
+
def initialize(options)
|
|
5
|
+
options[:allow_nil] = true unless options.key?(:allow_nil)
|
|
6
|
+
unless options.key?(:add_validation_errors_to)
|
|
7
|
+
options[:add_validation_errors_to] = Paperclip.options[:add_validation_errors_to]
|
|
8
|
+
end
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.helper_method_name
|
|
13
|
+
:validates_attachment_content_type
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def validate_each(record, attribute, value)
|
|
17
|
+
base_attribute = attribute.to_sym
|
|
18
|
+
attribute = "#{attribute}_content_type".to_sym
|
|
19
|
+
value = record.send :read_attribute_for_validation, attribute
|
|
20
|
+
|
|
21
|
+
return if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
|
22
|
+
|
|
23
|
+
validate_whitelist(record, attribute, value)
|
|
24
|
+
validate_blacklist(record, attribute, value)
|
|
25
|
+
|
|
26
|
+
if record.errors.include?(attribute) &&
|
|
27
|
+
[:both, :base].include?(options[:add_validation_errors_to])
|
|
28
|
+
|
|
29
|
+
record.errors[attribute].each do |error|
|
|
30
|
+
record.errors.add base_attribute, error
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
record.errors.delete(attribute) if options[:add_validation_errors_to] == :base
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def validate_whitelist(record, attribute, value)
|
|
38
|
+
if allowed_types.present? && allowed_types.none? { |type| type === value }
|
|
39
|
+
mark_invalid record, attribute, allowed_types
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def validate_blacklist(record, attribute, value)
|
|
44
|
+
if forbidden_types.present? && forbidden_types.any? { |type| type === value }
|
|
45
|
+
mark_invalid record, attribute, forbidden_types
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def mark_invalid(record, attribute, types)
|
|
50
|
+
record.errors.add attribute, :invalid, **options.merge(types: types.join(", "))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def allowed_types
|
|
54
|
+
[options[:content_type]].flatten.compact
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def forbidden_types
|
|
58
|
+
[options[:not]].flatten.compact
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def check_validity!
|
|
62
|
+
unless options.key?(:content_type) || options.key?(:not)
|
|
63
|
+
raise ArgumentError, "You must pass in either :content_type or :not to the validator"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
module HelperMethods
|
|
69
|
+
# Places ActiveModel validations on the content type of the file
|
|
70
|
+
# assigned. The possible options are:
|
|
71
|
+
# * +content_type+: Allowed content types. Can be a single content type
|
|
72
|
+
# or an array. Each type can be a String or a Regexp. It should be
|
|
73
|
+
# noted that Internet Explorer uploads files with content_types that you
|
|
74
|
+
# may not expect. For example, JPEG images are given image/pjpeg and
|
|
75
|
+
# PNGs are image/x-png, so keep that in mind when determining how you
|
|
76
|
+
# match. Allows all by default.
|
|
77
|
+
# * +not+: Forbidden content types.
|
|
78
|
+
# * +message+: The message to display when the uploaded file has an invalid
|
|
79
|
+
# content type.
|
|
80
|
+
# * +if+: A lambda or name of an instance method. Validation will only
|
|
81
|
+
# be run is this lambda or method returns true.
|
|
82
|
+
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
|
83
|
+
# NOTE: If you do not specify an [attachment]_content_type field on your
|
|
84
|
+
# model, content_type validation will work _ONLY upon assignment_ and
|
|
85
|
+
# re-validation after the instance has been reloaded will always succeed.
|
|
86
|
+
# You'll still need to have a virtual attribute (created by +attr_accessor+)
|
|
87
|
+
# name +[attachment]_content_type+ to be able to use this validator.
|
|
88
|
+
def validates_attachment_content_type(*attr_names)
|
|
89
|
+
options = _merge_attributes(attr_names)
|
|
90
|
+
validates_with AttachmentContentTypeValidator, options.dup
|
|
91
|
+
validate_before_processing AttachmentContentTypeValidator, options.dup
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module Paperclip
|
|
2
|
+
module Validators
|
|
3
|
+
class AttachmentFileNameValidator < ActiveModel::EachValidator
|
|
4
|
+
def initialize(options)
|
|
5
|
+
options[:allow_nil] = true unless options.key?(:allow_nil)
|
|
6
|
+
unless options.key?(:add_validation_errors_to)
|
|
7
|
+
options[:add_validation_errors_to] = Paperclip.options[:add_validation_errors_to]
|
|
8
|
+
end
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.helper_method_name
|
|
13
|
+
:validates_attachment_file_name
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def validate_each(record, attribute, value)
|
|
17
|
+
base_attribute = attribute.to_sym
|
|
18
|
+
attribute = "#{attribute}_file_name".to_sym
|
|
19
|
+
value = record.send :read_attribute_for_validation, attribute
|
|
20
|
+
|
|
21
|
+
return if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
|
22
|
+
|
|
23
|
+
validate_whitelist(record, attribute, value)
|
|
24
|
+
validate_blacklist(record, attribute, value)
|
|
25
|
+
|
|
26
|
+
if record.errors.include?(attribute) &&
|
|
27
|
+
[:both, :base].include?(options[:add_validation_errors_to])
|
|
28
|
+
|
|
29
|
+
record.errors[attribute].each do |error|
|
|
30
|
+
record.errors.add(base_attribute, error)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
record.errors.delete(attribute) if options[:add_validation_errors_to] == :base
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def validate_whitelist(record, attribute, value)
|
|
38
|
+
mark_invalid record, attribute, allowed if allowed.present? && allowed.none? { |type| type === value }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def validate_blacklist(record, attribute, value)
|
|
42
|
+
mark_invalid record, attribute, forbidden if forbidden.present? && forbidden.any? { |type| type === value }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def mark_invalid(record, attribute, patterns)
|
|
46
|
+
record.errors.add attribute, :invalid, **options.merge(names: patterns.join(", "))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def allowed
|
|
50
|
+
[options[:matches]].flatten.compact
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def forbidden
|
|
54
|
+
[options[:not]].flatten.compact
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def check_validity!
|
|
58
|
+
unless options.key?(:matches) || options.key?(:not)
|
|
59
|
+
raise ArgumentError, "You must pass in either :matches or :not to the validator"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
module HelperMethods
|
|
65
|
+
# Places ActiveModel validations on the name of the file
|
|
66
|
+
# assigned. The possible options are:
|
|
67
|
+
# * +matches+: Allowed filename patterns as Regexps. Can be a single one
|
|
68
|
+
# or an array.
|
|
69
|
+
# * +not+: Forbidden file name patterns, specified the same was as +matches+.
|
|
70
|
+
# * +message+: The message to display when the uploaded file has an invalid
|
|
71
|
+
# name.
|
|
72
|
+
# * +if+: A lambda or name of an instance method. Validation will only
|
|
73
|
+
# be run is this lambda or method returns true.
|
|
74
|
+
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
|
75
|
+
def validates_attachment_file_name(*attr_names)
|
|
76
|
+
options = _merge_attributes(attr_names)
|
|
77
|
+
validates_with AttachmentFileNameValidator, options.dup
|
|
78
|
+
validate_before_processing AttachmentFileNameValidator, options.dup
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require "active_model/validations/presence"
|
|
2
|
+
|
|
3
|
+
module Paperclip
|
|
4
|
+
module Validators
|
|
5
|
+
class AttachmentFileTypeIgnoranceValidator < ActiveModel::EachValidator
|
|
6
|
+
def validate_each(record, attribute, value)
|
|
7
|
+
# This doesn't do anything. It's just to mark that you don't care about
|
|
8
|
+
# the file_names or content_types of your incoming attachments.
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.helper_method_name
|
|
12
|
+
:do_not_validate_attachment_file_type
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module HelperMethods
|
|
17
|
+
# Places ActiveModel validations on the presence of a file.
|
|
18
|
+
# Options:
|
|
19
|
+
# * +if+: A lambda or name of an instance method. Validation will only
|
|
20
|
+
# be run if this lambda or method returns true.
|
|
21
|
+
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
|
22
|
+
def do_not_validate_attachment_file_type(*attr_names)
|
|
23
|
+
options = _merge_attributes(attr_names)
|
|
24
|
+
validates_with AttachmentFileTypeIgnoranceValidator, options.dup
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require "active_model/validations/presence"
|
|
2
|
+
|
|
3
|
+
module Paperclip
|
|
4
|
+
module Validators
|
|
5
|
+
class AttachmentPresenceValidator < ActiveModel::EachValidator
|
|
6
|
+
def validate_each(record, attribute, _value)
|
|
7
|
+
record.errors.add(attribute, :blank, **options) if record.send("#{attribute}_file_name").blank?
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.helper_method_name
|
|
11
|
+
:validates_attachment_presence
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module HelperMethods
|
|
16
|
+
# Places ActiveModel validations on the presence of a file.
|
|
17
|
+
# Options:
|
|
18
|
+
# * +if+: A lambda or name of an instance method. Validation will only
|
|
19
|
+
# be run if this lambda or method returns true.
|
|
20
|
+
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
|
21
|
+
def validates_attachment_presence(*attr_names)
|
|
22
|
+
options = _merge_attributes(attr_names)
|
|
23
|
+
validates_with AttachmentPresenceValidator, options.dup
|
|
24
|
+
validate_before_processing AttachmentPresenceValidator, options.dup
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
require "active_model/validations/numericality"
|
|
2
|
+
|
|
3
|
+
module Paperclip
|
|
4
|
+
module Validators
|
|
5
|
+
class AttachmentSizeValidator < ActiveModel::Validations::NumericalityValidator
|
|
6
|
+
AVAILABLE_CHECKS = [:less_than, :less_than_or_equal_to, :greater_than, :greater_than_or_equal_to].freeze
|
|
7
|
+
|
|
8
|
+
def initialize(options)
|
|
9
|
+
extract_options(options)
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.helper_method_name
|
|
14
|
+
:validates_attachment_size
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def validate_each(record, attr_name, value)
|
|
18
|
+
base_attr_name = attr_name
|
|
19
|
+
attr_name = "#{attr_name}_file_size".to_sym
|
|
20
|
+
|
|
21
|
+
error_attrs = []
|
|
22
|
+
case options[:add_validation_errors_to]
|
|
23
|
+
when :base
|
|
24
|
+
error_attrs << base_attr_name
|
|
25
|
+
when :attribute
|
|
26
|
+
error_attrs << attr_name
|
|
27
|
+
else
|
|
28
|
+
error_attrs << base_attr_name
|
|
29
|
+
error_attrs << attr_name
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
value = record.send(:read_attribute_for_validation, attr_name)
|
|
33
|
+
|
|
34
|
+
unless value.blank?
|
|
35
|
+
options.slice(*AVAILABLE_CHECKS).each do |option, option_value|
|
|
36
|
+
option_value = option_value.call(record) if option_value.is_a?(Proc)
|
|
37
|
+
option_value = extract_option_value(option, option_value)
|
|
38
|
+
operator = ActiveModel::VERSION::MAJOR >= 7 ? COMPARE_CHECKS[option] : CHECKS[option]
|
|
39
|
+
|
|
40
|
+
unless value.send(operator, option_value)
|
|
41
|
+
error_message_key = options[:in] ? :in_between : option
|
|
42
|
+
error_attrs.each do |error_attr_name|
|
|
43
|
+
record.errors.add(error_attr_name, error_message_key, **filtered_options(value).merge(
|
|
44
|
+
min: min_value_in_human_size(record),
|
|
45
|
+
max: max_value_in_human_size(record),
|
|
46
|
+
count: human_size(option_value)
|
|
47
|
+
))
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def check_validity!
|
|
55
|
+
unless (AVAILABLE_CHECKS + [:in]).any? { |argument| options.key?(argument) }
|
|
56
|
+
raise ArgumentError, "You must pass either :less_than, :greater_than, or :in to the validator"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def extract_options(options)
|
|
63
|
+
if range = options[:in]
|
|
64
|
+
if !options[:in].respond_to?(:call)
|
|
65
|
+
options[:less_than_or_equal_to] = range.max
|
|
66
|
+
options[:greater_than_or_equal_to] = range.min
|
|
67
|
+
else
|
|
68
|
+
options[:less_than_or_equal_to] = range
|
|
69
|
+
options[:greater_than_or_equal_to] = range
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
unless options.key?(:add_validation_errors_to)
|
|
74
|
+
options[:add_validation_errors_to] = Paperclip.options[:add_validation_errors_to]
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def extract_option_value(option, option_value)
|
|
79
|
+
if option_value.is_a?(Range)
|
|
80
|
+
if [:less_than, :less_than_or_equal_to].include?(option)
|
|
81
|
+
option_value.max
|
|
82
|
+
else
|
|
83
|
+
option_value.min
|
|
84
|
+
end
|
|
85
|
+
else
|
|
86
|
+
option_value
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def human_size(size)
|
|
91
|
+
ActiveSupport::NumberHelper.number_to_human_size(size)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def min_value_in_human_size(record)
|
|
95
|
+
value = options[:greater_than_or_equal_to] || options[:greater_than]
|
|
96
|
+
value = value.call(record) if value.respond_to?(:call)
|
|
97
|
+
value = value.min if value.respond_to?(:min)
|
|
98
|
+
human_size(value)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def max_value_in_human_size(record)
|
|
102
|
+
value = options[:less_than_or_equal_to] || options[:less_than]
|
|
103
|
+
value = value.call(record) if value.respond_to?(:call)
|
|
104
|
+
value = value.max if value.respond_to?(:max)
|
|
105
|
+
human_size(value)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
module HelperMethods
|
|
110
|
+
# Places ActiveModel validations on the size of the file assigned. The
|
|
111
|
+
# possible options are:
|
|
112
|
+
# * +in+: a Range of bytes (i.e. +1..1.megabyte+),
|
|
113
|
+
# * +less_than+: equivalent to :in => 0..options[:less_than]
|
|
114
|
+
# * +greater_than+: equivalent to :in => options[:greater_than]..Infinity
|
|
115
|
+
# * +message+: error message to display, use :min and :max as replacements
|
|
116
|
+
# * +if+: A lambda or name of an instance method. Validation will only
|
|
117
|
+
# be run if this lambda or method returns true.
|
|
118
|
+
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
|
119
|
+
def validates_attachment_size(*attr_names)
|
|
120
|
+
options = _merge_attributes(attr_names)
|
|
121
|
+
validates_with AttachmentSizeValidator, options.dup
|
|
122
|
+
validate_before_processing AttachmentSizeValidator, options.dup
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require "active_model/validations/presence"
|
|
2
|
+
|
|
3
|
+
module Paperclip
|
|
4
|
+
module Validators
|
|
5
|
+
class MediaTypeSpoofDetectionValidator < ActiveModel::EachValidator
|
|
6
|
+
def validate_each(record, attribute, value)
|
|
7
|
+
adapter = Paperclip.io_adapters.for(value)
|
|
8
|
+
if Paperclip::MediaTypeSpoofDetector.using(adapter, value.original_filename, value.content_type).spoofed?
|
|
9
|
+
record.errors.add(attribute, :spoofed_media_type)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
adapter.tempfile.close(true) if adapter.tempfile
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module HelperMethods
|
|
17
|
+
# Places ActiveModel validations on the presence of a file.
|
|
18
|
+
# Options:
|
|
19
|
+
# * +if+: A lambda or name of an instance method. Validation will only
|
|
20
|
+
# be run if this lambda or method returns true.
|
|
21
|
+
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
|
22
|
+
def validates_media_type_spoof_detection(*attr_names)
|
|
23
|
+
options = _merge_attributes(attr_names)
|
|
24
|
+
validates_with MediaTypeSpoofDetectionValidator, options.dup
|
|
25
|
+
validate_before_processing MediaTypeSpoofDetectionValidator, options.dup
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
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].freeze
|
|
21
|
+
|
|
22
|
+
module ClassMethods
|
|
23
|
+
# This method is a shortcut to the validator classes that are in
|
|
24
|
+
# "Attachment...Validator" format. It is almost the same as the
|
|
25
|
+
# +validates+ method that ships with Rails, but is customized for
|
|
26
|
+
# use 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.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}_validate", if: if_clause, unless: unless_clause) do |*_args|
|
|
68
|
+
validator_class.new(options.dup).validate(self)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|