Skip to content

Commit

Permalink
[Validator] Refacto some logic (#270)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mth0158 committed Oct 18, 2024
1 parent 856254f commit 6894f01
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 92 deletions.
24 changes: 9 additions & 15 deletions lib/active_storage_validations/aspect_ratio_validator.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# frozen_string_literal: true

require_relative 'concerns/active_storageable.rb'
require_relative 'concerns/errorable.rb'
require_relative 'concerns/metadatable.rb'
require_relative 'concerns/symbolizable.rb'
require_relative 'metadata.rb'

module ActiveStorageValidations
class AspectRatioValidator < ActiveModel::EachValidator # :nodoc
include OptionProcUnfolding
include ActiveStorageable
include Errorable
include Metadatable
include OptionProcUnfolding
include Symbolizable

AVAILABLE_CHECKS = %i[with].freeze
Expand All @@ -29,25 +32,16 @@ def check_validity!
end

def validate_each(record, attribute, _value)
return true unless record.send(attribute).attached?

changes = record.attachment_changes[attribute.to_s]
return true if changes.blank?
return if no_attachments?(record, attribute)

files = Array.wrap(changes.is_a?(ActiveStorage::Attached::Changes::CreateMany) ? changes.attachables : changes.attachable)

files.each do |file|
metadata = Metadata.new(file).metadata
next if is_valid?(record, attribute, file, metadata)
break
end
validate_changed_files_from_metadata(record, attribute)
end

private

def is_valid?(record, attribute, file, metadata)
def is_valid?(record, attribute, attachable, metadata)
flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
errors_options = initialize_error_options(options, file)
errors_options = initialize_error_options(options, attachable)

if metadata[:width].to_i <= 0 || metadata[:height].to_i <= 0
errors_options[:aspect_ratio] = flat_options[:with]
Expand Down
8 changes: 5 additions & 3 deletions lib/active_storage_validations/attached_validator.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# frozen_string_literal: true

require_relative 'concerns/active_storageable.rb'
require_relative 'concerns/errorable.rb'
require_relative 'concerns/symbolizable.rb'

module ActiveStorageValidations
class AttachedValidator < ActiveModel::EachValidator # :nodoc:
include ActiveStorageable
include Errorable
include Symbolizable

Expand All @@ -13,14 +15,14 @@ class AttachedValidator < ActiveModel::EachValidator # :nodoc:
def check_validity!
%i[allow_nil allow_blank].each do |not_authorized_option|
if options.include?(not_authorized_option)
raise ArgumentError, "You cannot pass the :#{not_authorized_option} option to the #{self.class.name.split('::').last.underscore}"
raise ArgumentError, "You cannot pass the :#{not_authorized_option} option to the #{self.class.to_sym} validator"
end
end
end

def validate_each(record, attribute, _value)
return if record.send(attribute).attached? &&
!Array.wrap(record.send(attribute)).all?(&:marked_for_destruction?)
return if attachments_present?(record, attribute) &&
will_have_attachments_after_save?(record, attribute)

errors_options = initialize_error_options(options)
add_error(record, attribute, ERROR_TYPES.first, **errors_options)
Expand Down
4 changes: 3 additions & 1 deletion lib/active_storage_validations/base_size_validator.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# frozen_string_literal: true

require_relative 'concerns/active_storageable.rb'
require_relative 'concerns/errorable.rb'
require_relative 'concerns/symbolizable.rb'

module ActiveStorageValidations
class BaseSizeValidator < ActiveModel::EachValidator # :nodoc:
include OptionProcUnfolding
include ActiveStorageable
include Errorable
include OptionProcUnfolding
include Symbolizable

delegate :number_to_human_size, to: ActiveSupport::NumberHelper
Expand Down
28 changes: 28 additions & 0 deletions lib/active_storage_validations/concerns/active_storageable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module ActiveStorageValidations
# ActiveStorageValidations::ActiveStorageable
#
# Validator helper methods to make our code more explicit.
module ActiveStorageable
extend ActiveSupport::Concern

private

# Retrieve either an ActiveStorage::Attached::One or an
# ActiveStorage::Attached::Many instance depending on attribute definition
def attached_files(record, attribute)
Array.wrap(record.send(attribute))
end

def attachments_present?(record, attribute)
record.send(attribute).attached?
end

def no_attachments?(record, attribute)
!attachments_present?(record, attribute)
end

def will_have_attachments_after_save?(record, attribute)
!Array.wrap(record.send(attribute)).all?(&:marked_for_destruction?)
end
end
end
31 changes: 31 additions & 0 deletions lib/active_storage_validations/concerns/metadatable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require_relative '../metadata'

module ActiveStorageValidations
# ActiveStorageValidations::Metadatable
#
# Validator methods for analyzing the attachment metadata.
module Metadatable
extend ActiveSupport::Concern

private

# Loop through the newly submitted attachables to validate them
def validate_changed_files_from_metadata(record, attribute)
attachables_from_changes(record, attribute).each do |attachable|
is_valid?(record, attribute, attachable, Metadata.new(attachable).metadata)
end
end

# Retrieve an array of newly submitted attachables which are file
# representations such as ActiveStorage::Blob, ActionDispatch::Http::UploadedFile,
# Rack::Test::UploadedFile, Hash, String, File or Pathname
def attachables_from_changes(record, attribute)
changes = record.attachment_changes[attribute.to_s]
return [] if changes.blank?

Array.wrap(
changes.is_a?(ActiveStorage::Attached::Changes::CreateMany) ? changes.attachables : changes.attachable
)
end
end
end
15 changes: 7 additions & 8 deletions lib/active_storage_validations/content_type_validator.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# frozen_string_literal: true

require_relative 'concerns/active_storageable.rb'
require_relative 'concerns/errorable.rb'
require_relative 'concerns/symbolizable.rb'
require_relative 'content_type_spoof_detector.rb'

module ActiveStorageValidations
class ContentTypeValidator < ActiveModel::EachValidator # :nodoc:
include ActiveStorageable
include OptionProcUnfolding
include Errorable
include Symbolizable
Expand All @@ -22,16 +24,13 @@ def check_validity!
end

def validate_each(record, attribute, _value)
return true unless record.send(attribute).attached?
return if no_attachments?(record, attribute)

types = authorized_types(record)
return true if types.empty?
return if types.empty?

files = Array.wrap(record.send(attribute))

files.each do |file|
next if is_valid?(record, attribute, file, types)
break
attached_files(record, attribute).each do |file|
is_valid?(record, attribute, file, types)
end
end

Expand Down Expand Up @@ -112,7 +111,7 @@ def ensure_content_types_validity
def invalid_content_type_message(content_type)
<<~ERROR_MESSAGE
You must pass valid content types to the validator
'#{content_type}' is not find in Marcel::EXTENSIONS mimes
'#{content_type}' is not found in Marcel::EXTENSIONS mimes
ERROR_MESSAGE
end

Expand Down
66 changes: 31 additions & 35 deletions lib/active_storage_validations/dimension_validator.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# frozen_string_literal: true

require_relative 'concerns/active_storageable.rb'
require_relative 'concerns/errorable.rb'
require_relative 'concerns/metadatable.rb'
require_relative 'concerns/symbolizable.rb'
require_relative 'metadata.rb'

module ActiveStorageValidations
class DimensionValidator < ActiveModel::EachValidator # :nodoc
include OptionProcUnfolding
include ActiveStorageable
include Errorable
include OptionProcUnfolding
include Metadatable
include Symbolizable

AVAILABLE_CHECKS = %i[width height min max].freeze
Expand All @@ -25,49 +28,20 @@ class DimensionValidator < ActiveModel::EachValidator # :nodoc
dimension_height_equal_to
].freeze

def process_options(record)
flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)

[:width, :height].each do |length|
if flat_options[length] and flat_options[length].is_a?(Hash)
if (range = flat_options[length][:in])
raise ArgumentError, ":in must be a Range" unless range.is_a?(Range)
flat_options[length][:min], flat_options[length][:max] = range.min, range.max
end
end
end
[:min, :max].each do |dim|
if (range = flat_options[dim])
raise ArgumentError, ":#{dim} must be a Range (width..height)" unless range.is_a?(Range)
flat_options[:width] = { dim => range.first }
flat_options[:height] = { dim => range.last }
end
end

flat_options
end

def check_validity!
unless AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
raise ArgumentError, 'You must pass either :width, :height, :min or :max to the validator'
end
end


def validate_each(record, attribute, _value)
return true unless record.send(attribute).attached?
return if no_attachments?(record, attribute)

changes = record.attachment_changes[attribute.to_s]
return true if changes.blank?

files = Array.wrap(changes.is_a?(ActiveStorage::Attached::Changes::CreateMany) ? changes.attachables : changes.attachable)
files.each do |file|
metadata = Metadata.new(file).metadata
next if is_valid?(record, attribute, file, metadata)
break
end
validate_changed_files_from_metadata(record, attribute)
end

private

def is_valid?(record, attribute, file, metadata)
flat_options = process_options(record)
errors_options = initialize_error_options(options, file)
Expand Down Expand Up @@ -146,5 +120,27 @@ def is_valid?(record, attribute, file, metadata)

true # valid file
end

def process_options(record)
flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)

[:width, :height].each do |length|
if flat_options[length] and flat_options[length].is_a?(Hash)
if (range = flat_options[length][:in])
raise ArgumentError, ":in must be a Range" unless range.is_a?(Range)
flat_options[length][:min], flat_options[length][:max] = range.min, range.max
end
end
end
[:min, :max].each do |dim|
if (range = flat_options[dim])
raise ArgumentError, ":#{dim} must be a Range (width..height)" unless range.is_a?(Range)
flat_options[:width] = { dim => range.first }
flat_options[:height] = { dim => range.last }
end
end

flat_options
end
end
end
8 changes: 5 additions & 3 deletions lib/active_storage_validations/limit_validator.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# frozen_string_literal: true

require_relative 'concerns/active_storageable.rb'
require_relative 'concerns/errorable.rb'
require_relative 'concerns/symbolizable.rb'

module ActiveStorageValidations
class LimitValidator < ActiveModel::EachValidator # :nodoc:
include ActiveStorageable
include OptionProcUnfolding
include Errorable
include Symbolizable
Expand All @@ -19,11 +21,11 @@ def check_validity!
ensure_arguments_validity
end

def validate_each(record, attribute, _)
files = Array.wrap(record.send(attribute)).reject { |file| file.blank? }.compact.uniq
def validate_each(record, attribute, _value)
files = attached_files(record, attribute).reject(&:blank?)
flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)

return true if files_count_valid?(files.count, flat_options)
return if files_count_valid?(files.count, flat_options)

errors_options = initialize_error_options(options)
errors_options[:min] = flat_options[:min]
Expand Down
25 changes: 13 additions & 12 deletions lib/active_storage_validations/processable_image_validator.rb
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
# frozen_string_literal: true

require_relative 'concerns/active_storageable.rb'
require_relative 'concerns/errorable.rb'
require_relative 'concerns/metadatable.rb'
require_relative 'concerns/symbolizable.rb'
require_relative 'metadata.rb'

module ActiveStorageValidations
class ProcessableImageValidator < ActiveModel::EachValidator # :nodoc
include OptionProcUnfolding
include ActiveStorageable
include Errorable
include Metadatable
include Symbolizable

ERROR_TYPES = %i[
image_not_processable
].freeze

def validate_each(record, attribute, _value)
return true unless record.send(attribute).attached?
return if no_attachments?(record, attribute)

changes = record.attachment_changes[attribute.to_s]
return true if changes.blank?
validate_changed_files_from_metadata(record, attribute)
end

private

files = Array.wrap(changes.is_a?(ActiveStorage::Attached::Changes::CreateMany) ? changes.attachables : changes.attachable)
def is_valid?(record, attribute, attachable, metadata)
return if !metadata.empty?

files.each do |file|
if !Metadata.new(file).valid?
errors_options = initialize_error_options(options, file)
add_error(record, attribute, ERROR_TYPES.first , **errors_options) unless Metadata.new(file).valid?
end
end
errors_options = initialize_error_options(options, attachable)
add_error(record, attribute, ERROR_TYPES.first , **errors_options)
end
end
end
Loading

0 comments on commit 6894f01

Please sign in to comment.