active_storage_validations 1.1.4 → 1.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.
- checksums.yaml +4 -4
- data/README.md +56 -18
- data/config/locales/da.yml +33 -0
- data/config/locales/de.yml +5 -0
- data/config/locales/en.yml +5 -0
- data/config/locales/es.yml +5 -0
- data/config/locales/fr.yml +5 -0
- data/config/locales/it.yml +5 -0
- data/config/locales/ja.yml +5 -0
- data/config/locales/nl.yml +5 -0
- data/config/locales/pl.yml +5 -0
- data/config/locales/pt-BR.yml +5 -0
- data/config/locales/ru.yml +5 -0
- data/config/locales/sv.yml +10 -1
- data/config/locales/tr.yml +5 -0
- data/config/locales/uk.yml +5 -0
- data/config/locales/vi.yml +5 -0
- data/config/locales/zh-CN.yml +5 -0
- data/lib/active_storage_validations/attached_validator.rb +3 -3
- data/lib/active_storage_validations/base_size_validator.rb +66 -0
- data/lib/active_storage_validations/concerns/errorable.rb +2 -2
- data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +6 -15
- data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +5 -13
- data/lib/active_storage_validations/matchers/base_size_validator_matcher.rb +134 -0
- data/lib/active_storage_validations/matchers/concerns/attachable.rb +48 -0
- data/lib/active_storage_validations/matchers/concerns/contextable.rb +20 -8
- data/lib/active_storage_validations/matchers/concerns/validatable.rb +9 -3
- data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +5 -2
- data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +6 -15
- data/lib/active_storage_validations/matchers/processable_image_validator_matcher.rb +78 -0
- data/lib/active_storage_validations/matchers/size_validator_matcher.rb +4 -139
- data/lib/active_storage_validations/matchers/total_size_validator_matcher.rb +40 -0
- data/lib/active_storage_validations/matchers.rb +2 -0
- data/lib/active_storage_validations/metadata.rb +20 -2
- data/lib/active_storage_validations/size_validator.rb +4 -49
- data/lib/active_storage_validations/total_size_validator.rb +49 -0
- data/lib/active_storage_validations/version.rb +1 -1
- data/lib/active_storage_validations.rb +2 -1
- metadata +31 -38
| @@ -1,6 +1,7 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require_relative 'concerns/active_storageable.rb'
         | 
| 4 | 
            +
            require_relative 'concerns/attachable.rb'
         | 
| 4 5 | 
             
            require_relative 'concerns/contextable.rb'
         | 
| 5 6 | 
             
            require_relative 'concerns/messageable.rb'
         | 
| 6 7 | 
             
            require_relative 'concerns/rspecable.rb'
         | 
| @@ -8,12 +9,13 @@ require_relative 'concerns/validatable.rb' | |
| 8 9 |  | 
| 9 10 | 
             
            module ActiveStorageValidations
         | 
| 10 11 | 
             
              module Matchers
         | 
| 11 | 
            -
                def validate_attached_of( | 
| 12 | 
            -
                  AttachedValidatorMatcher.new( | 
| 12 | 
            +
                def validate_attached_of(attribute_name)
         | 
| 13 | 
            +
                  AttachedValidatorMatcher.new(attribute_name)
         | 
| 13 14 | 
             
                end
         | 
| 14 15 |  | 
| 15 16 | 
             
                class AttachedValidatorMatcher
         | 
| 16 17 | 
             
                  include ActiveStorageable
         | 
| 18 | 
            +
                  include Attachable
         | 
| 17 19 | 
             
                  include Contextable
         | 
| 18 20 | 
             
                  include Messageable
         | 
| 19 21 | 
             
                  include Rspecable
         | 
| @@ -47,7 +49,7 @@ module ActiveStorageValidations | |
| 47 49 | 
             
                  private
         | 
| 48 50 |  | 
| 49 51 | 
             
                  def is_valid_when_file_attached?
         | 
| 50 | 
            -
                     | 
| 52 | 
            +
                    attach_file unless file_attached?
         | 
| 51 53 | 
             
                    validate
         | 
| 52 54 | 
             
                    is_valid?
         | 
| 53 55 | 
             
                  end
         | 
| @@ -66,16 +68,6 @@ module ActiveStorageValidations | |
| 66 68 | 
             
                    has_an_error_message_which_is_custom_message?
         | 
| 67 69 | 
             
                  end
         | 
| 68 70 |  | 
| 69 | 
            -
                  def attach_dummy_file
         | 
| 70 | 
            -
                    dummy_file = {
         | 
| 71 | 
            -
                      io: Tempfile.new('.'),
         | 
| 72 | 
            -
                      filename: 'dummy.txt',
         | 
| 73 | 
            -
                      content_type: 'text/plain'
         | 
| 74 | 
            -
                    }
         | 
| 75 | 
            -
             | 
| 76 | 
            -
                    @subject.public_send(@attribute_name).attach(dummy_file)
         | 
| 77 | 
            -
                  end
         | 
| 78 | 
            -
             | 
| 79 71 | 
             
                  def file_attached?
         | 
| 80 72 | 
             
                    @subject.public_send(@attribute_name).attached?
         | 
| 81 73 | 
             
                  end
         | 
| @@ -0,0 +1,134 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # Big thank you to the paperclip validation matchers:
         | 
| 4 | 
            +
            # https://github.com/thoughtbot/paperclip/blob/v6.1.0/lib/paperclip/matchers/validate_attachment_size_matcher.rb
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            require_relative 'concerns/active_storageable.rb'
         | 
| 7 | 
            +
            require_relative 'concerns/allow_blankable.rb'
         | 
| 8 | 
            +
            require_relative 'concerns/attachable.rb'
         | 
| 9 | 
            +
            require_relative 'concerns/contextable.rb'
         | 
| 10 | 
            +
            require_relative 'concerns/messageable.rb'
         | 
| 11 | 
            +
            require_relative 'concerns/rspecable.rb'
         | 
| 12 | 
            +
            require_relative 'concerns/validatable.rb'
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            module ActiveStorageValidations
         | 
| 15 | 
            +
              module Matchers
         | 
| 16 | 
            +
                class BaseSizeValidatorMatcher
         | 
| 17 | 
            +
                  # BaseSizeValidatorMatcher is an abstract class and shouldn't be instantiated directly.
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  include ActiveStorageable
         | 
| 20 | 
            +
                  include AllowBlankable
         | 
| 21 | 
            +
                  include Attachable
         | 
| 22 | 
            +
                  include Contextable
         | 
| 23 | 
            +
                  include Messageable
         | 
| 24 | 
            +
                  include Rspecable
         | 
| 25 | 
            +
                  include Validatable
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def initialize(attribute_name)
         | 
| 28 | 
            +
                    initialize_allow_blankable
         | 
| 29 | 
            +
                    initialize_contextable
         | 
| 30 | 
            +
                    initialize_messageable
         | 
| 31 | 
            +
                    initialize_rspecable
         | 
| 32 | 
            +
                    @attribute_name = attribute_name
         | 
| 33 | 
            +
                    @min = @max = nil
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  def less_than(size)
         | 
| 37 | 
            +
                    @max = size - 1.byte
         | 
| 38 | 
            +
                    self
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def less_than_or_equal_to(size)
         | 
| 42 | 
            +
                    @max = size
         | 
| 43 | 
            +
                    self
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  def greater_than(size)
         | 
| 47 | 
            +
                    @min = size + 1.byte
         | 
| 48 | 
            +
                    self
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  def greater_than_or_equal_to(size)
         | 
| 52 | 
            +
                    @min = size
         | 
| 53 | 
            +
                    self
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  def between(range)
         | 
| 57 | 
            +
                    @min, @max = range.first, range.last
         | 
| 58 | 
            +
                    self
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  def matches?(subject)
         | 
| 62 | 
            +
                    @subject = subject.is_a?(Class) ? subject.new : subject
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                    is_a_valid_active_storage_attribute? &&
         | 
| 65 | 
            +
                      is_context_valid? &&
         | 
| 66 | 
            +
                      is_allowing_blank? &&
         | 
| 67 | 
            +
                      is_custom_message_valid? &&
         | 
| 68 | 
            +
                      not_lower_than_min? &&
         | 
| 69 | 
            +
                      higher_than_min? &&
         | 
| 70 | 
            +
                      lower_than_max? &&
         | 
| 71 | 
            +
                      not_higher_than_max?
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  protected
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  def build_failure_message(message)
         | 
| 77 | 
            +
                    return unless @failure_message_artefacts.present?
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                    message << "  but there seem to have issues with the matcher methods you used, since:"
         | 
| 80 | 
            +
                    @failure_message_artefacts.each do |error_case|
         | 
| 81 | 
            +
                      message << "  validation failed when provided with a #{error_case[:size]} bytes test file"
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
                    message << "  whereas it should have passed"
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                  def not_lower_than_min?
         | 
| 87 | 
            +
                    @min.nil? || !passes_validation_with_size(@min - 1)
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  def higher_than_min?
         | 
| 91 | 
            +
                    @min.nil? || passes_validation_with_size(@min + 1)
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                  def lower_than_max?
         | 
| 95 | 
            +
                    @max.nil? || @max == Float::INFINITY || passes_validation_with_size(@max - 1)
         | 
| 96 | 
            +
                  end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                  def not_higher_than_max?
         | 
| 99 | 
            +
                    @max.nil? || @max == Float::INFINITY || !passes_validation_with_size(@max + 1)
         | 
| 100 | 
            +
                  end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  def passes_validation_with_size(size)
         | 
| 103 | 
            +
                    mock_size_for(io, size) do
         | 
| 104 | 
            +
                      attach_file
         | 
| 105 | 
            +
                      validate
         | 
| 106 | 
            +
                      detach_file
         | 
| 107 | 
            +
                      is_valid? || add_failure_message_artefact(size)
         | 
| 108 | 
            +
                    end
         | 
| 109 | 
            +
                  end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  def add_failure_message_artefact(size)
         | 
| 112 | 
            +
                    @failure_message_artefacts << { size: size }
         | 
| 113 | 
            +
                    false
         | 
| 114 | 
            +
                  end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                  def is_custom_message_valid?
         | 
| 117 | 
            +
                    return true unless @custom_message
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                    mock_size_for(io, -1.kilobytes) do
         | 
| 120 | 
            +
                      attach_file
         | 
| 121 | 
            +
                      validate
         | 
| 122 | 
            +
                      detach_file
         | 
| 123 | 
            +
                      has_an_error_message_which_is_custom_message?
         | 
| 124 | 
            +
                    end
         | 
| 125 | 
            +
                  end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                  def mock_size_for(io, size)
         | 
| 128 | 
            +
                    Matchers.stub_method(io, :size, size) do
         | 
| 129 | 
            +
                      yield
         | 
| 130 | 
            +
                    end
         | 
| 131 | 
            +
                  end
         | 
| 132 | 
            +
                end
         | 
| 133 | 
            +
              end
         | 
| 134 | 
            +
            end
         | 
| @@ -0,0 +1,48 @@ | |
| 1 | 
            +
            module ActiveStorageValidations
         | 
| 2 | 
            +
              module Matchers
         | 
| 3 | 
            +
                module Attachable
         | 
| 4 | 
            +
                  private
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  def attach_file(file = dummy_file)
         | 
| 7 | 
            +
                    @subject.public_send(@attribute_name).attach(file)
         | 
| 8 | 
            +
                    @subject.public_send(@attribute_name)
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def dummy_file
         | 
| 12 | 
            +
                    {
         | 
| 13 | 
            +
                      io: io,
         | 
| 14 | 
            +
                      filename: 'test.png',
         | 
| 15 | 
            +
                      content_type: 'image/png'
         | 
| 16 | 
            +
                    }
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def processable_image
         | 
| 20 | 
            +
                    {
         | 
| 21 | 
            +
                      io: File.open(Rails.root.join('public', 'image_1920x1080.png')),
         | 
| 22 | 
            +
                      filename: 'image_1920x1080_file.png',
         | 
| 23 | 
            +
                      content_type: 'image/png'
         | 
| 24 | 
            +
                    }
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def not_processable_image
         | 
| 28 | 
            +
                    {
         | 
| 29 | 
            +
                      io: Tempfile.new('.'),
         | 
| 30 | 
            +
                      filename: 'processable.txt',
         | 
| 31 | 
            +
                      content_type: 'text/plain'
         | 
| 32 | 
            +
                    }
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def io
         | 
| 36 | 
            +
                    @io ||= Tempfile.new('Hello world!')
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def detach_file
         | 
| 40 | 
            +
                    @subject.attachment_changes.delete(@attribute_name.to_s)
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  def file_attached?
         | 
| 44 | 
            +
                    @subject.public_send(@attribute_name).attached?
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
            end
         | 
| @@ -17,19 +17,31 @@ module ActiveStorageValidations | |
| 17 17 | 
             
                  private
         | 
| 18 18 |  | 
| 19 19 | 
             
                  def is_context_valid?
         | 
| 20 | 
            -
                    return true if !@context &&  | 
| 20 | 
            +
                    return true if !@context && attribute_validators.none? { |validator| validator.options[:on] }
         | 
| 21 21 |  | 
| 22 | 
            -
                    raise ArgumentError, "This validator matcher needs the #on option to work since its validator has one" if !@context
         | 
| 22 | 
            +
                    raise ArgumentError, "This validator matcher needs the #on option to work since its validator has one" if !@context && attribute_validators.all? { |validator| validator.options[:on] }
         | 
| 23 23 | 
             
                    raise ArgumentError, "This validator matcher option only allows a symbol or an array" if !(@context.is_a?(Symbol) || @context.is_a?(Array))
         | 
| 24 24 |  | 
| 25 | 
            -
                    if @context.is_a?(Array) | 
| 26 | 
            -
                      @context. | 
| 27 | 
            -
                    elsif @context.is_a?(Symbol) | 
| 28 | 
            -
                      @context  | 
| 29 | 
            -
                    else
         | 
| 30 | 
            -
                      false
         | 
| 25 | 
            +
                    if @context.is_a?(Array)
         | 
| 26 | 
            +
                      (validator_contexts & @context.map(&:to_s)) == validator_contexts || raise_context_not_listed_error
         | 
| 27 | 
            +
                    elsif @context.is_a?(Symbol)
         | 
| 28 | 
            +
                      validator_contexts.include?(@context.to_s) || raise_context_not_listed_error
         | 
| 31 29 | 
             
                    end
         | 
| 32 30 | 
             
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  def validator_contexts
         | 
| 33 | 
            +
                    attribute_validators.map do |validator|
         | 
| 34 | 
            +
                      case validator.options[:on]
         | 
| 35 | 
            +
                      when Array then validator.options[:on].map { |context| context.to_s }
         | 
| 36 | 
            +
                      when NilClass then nil
         | 
| 37 | 
            +
                      else validator.options[:on].to_s
         | 
| 38 | 
            +
                      end
         | 
| 39 | 
            +
                    end.flatten.compact
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  def raise_context_not_listed_error
         | 
| 43 | 
            +
                    raise ArgumentError, "One of the provided contexts to the #on method is not found in any of the listed contexts for this attribute"
         | 
| 44 | 
            +
                  end
         | 
| 33 45 | 
             
                end
         | 
| 34 46 | 
             
              end
         | 
| 35 47 | 
             
            end
         | 
| @@ -26,7 +26,7 @@ module ActiveStorageValidations | |
| 26 26 | 
             
                  def available_errors
         | 
| 27 27 | 
             
                    [
         | 
| 28 28 | 
             
                      *validator_class::ERROR_TYPES,
         | 
| 29 | 
            -
                      * | 
| 29 | 
            +
                      *errors_from_custom_messages
         | 
| 30 30 | 
             
                    ].compact
         | 
| 31 31 | 
             
                  end
         | 
| 32 32 |  | 
| @@ -40,8 +40,14 @@ module ActiveStorageValidations | |
| 40 40 | 
             
                    end
         | 
| 41 41 | 
             
                  end
         | 
| 42 42 |  | 
| 43 | 
            -
                  def  | 
| 44 | 
            -
                     | 
| 43 | 
            +
                  def attribute_validators
         | 
| 44 | 
            +
                    @subject.class.validators_on(@attribute_name).select do |validator|
         | 
| 45 | 
            +
                      validator.class == validator_class
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  def errors_from_custom_messages
         | 
| 50 | 
            +
                    attribute_validators.map { |validator| validator.options[:message] }
         | 
| 45 51 | 
             
                  end
         | 
| 46 52 | 
             
                end
         | 
| 47 53 | 
             
              end
         | 
| @@ -5,6 +5,7 @@ | |
| 5 5 |  | 
| 6 6 | 
             
            require_relative 'concerns/active_storageable.rb'
         | 
| 7 7 | 
             
            require_relative 'concerns/allow_blankable.rb'
         | 
| 8 | 
            +
            require_relative 'concerns/attachable.rb'
         | 
| 8 9 | 
             
            require_relative 'concerns/contextable.rb'
         | 
| 9 10 | 
             
            require_relative 'concerns/messageable.rb'
         | 
| 10 11 | 
             
            require_relative 'concerns/rspecable.rb'
         | 
| @@ -12,13 +13,14 @@ require_relative 'concerns/validatable.rb' | |
| 12 13 |  | 
| 13 14 | 
             
            module ActiveStorageValidations
         | 
| 14 15 | 
             
              module Matchers
         | 
| 15 | 
            -
                def validate_content_type_of( | 
| 16 | 
            -
                  ContentTypeValidatorMatcher.new( | 
| 16 | 
            +
                def validate_content_type_of(attribute_name)
         | 
| 17 | 
            +
                  ContentTypeValidatorMatcher.new(attribute_name)
         | 
| 17 18 | 
             
                end
         | 
| 18 19 |  | 
| 19 20 | 
             
                class ContentTypeValidatorMatcher
         | 
| 20 21 | 
             
                  include ActiveStorageable
         | 
| 21 22 | 
             
                  include AllowBlankable
         | 
| 23 | 
            +
                  include Attachable
         | 
| 22 24 | 
             
                  include Contextable
         | 
| 23 25 | 
             
                  include Messageable
         | 
| 24 26 | 
             
                  include Rspecable
         | 
| @@ -99,6 +101,7 @@ module ActiveStorageValidations | |
| 99 101 | 
             
                  def type_allowed?(type)
         | 
| 100 102 | 
             
                    attach_file_of_type(type)
         | 
| 101 103 | 
             
                    validate
         | 
| 104 | 
            +
                    detach_file
         | 
| 102 105 | 
             
                    is_valid?
         | 
| 103 106 | 
             
                  end
         | 
| 104 107 |  | 
| @@ -2,6 +2,7 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require_relative 'concerns/active_storageable.rb'
         | 
| 4 4 | 
             
            require_relative 'concerns/allow_blankable.rb'
         | 
| 5 | 
            +
            require_relative 'concerns/attachable'
         | 
| 5 6 | 
             
            require_relative 'concerns/contextable.rb'
         | 
| 6 7 | 
             
            require_relative 'concerns/messageable.rb'
         | 
| 7 8 | 
             
            require_relative 'concerns/rspecable.rb'
         | 
| @@ -9,13 +10,14 @@ require_relative 'concerns/validatable.rb' | |
| 9 10 |  | 
| 10 11 | 
             
            module ActiveStorageValidations
         | 
| 11 12 | 
             
              module Matchers
         | 
| 12 | 
            -
                def validate_dimensions_of( | 
| 13 | 
            -
                  DimensionValidatorMatcher.new( | 
| 13 | 
            +
                def validate_dimensions_of(attribute_name)
         | 
| 14 | 
            +
                  DimensionValidatorMatcher.new(attribute_name)
         | 
| 14 15 | 
             
                end
         | 
| 15 16 |  | 
| 16 17 | 
             
                class DimensionValidatorMatcher
         | 
| 17 18 | 
             
                  include ActiveStorageable
         | 
| 18 19 | 
             
                  include AllowBlankable
         | 
| 20 | 
            +
                  include Attachable
         | 
| 19 21 | 
             
                  include Contextable
         | 
| 20 22 | 
             
                  include Messageable
         | 
| 21 23 | 
             
                  include Rspecable
         | 
| @@ -162,6 +164,7 @@ module ActiveStorageValidations | |
| 162 164 | 
             
                  def passes_validation_with_dimensions(width, height)
         | 
| 163 165 | 
             
                    mock_dimensions_for(attach_file, width, height) do
         | 
| 164 166 | 
             
                      validate
         | 
| 167 | 
            +
                      detach_file
         | 
| 165 168 | 
             
                      is_valid? || add_failure_message_artefact(width, height)
         | 
| 166 169 | 
             
                    end
         | 
| 167 170 | 
             
                  end
         | 
| @@ -176,6 +179,7 @@ module ActiveStorageValidations | |
| 176 179 |  | 
| 177 180 | 
             
                    mock_dimensions_for(attach_file, -1, -1) do
         | 
| 178 181 | 
             
                      validate
         | 
| 182 | 
            +
                      detach_file
         | 
| 179 183 | 
             
                      has_an_error_message_which_is_custom_message?
         | 
| 180 184 | 
             
                    end
         | 
| 181 185 | 
             
                  end
         | 
| @@ -185,19 +189,6 @@ module ActiveStorageValidations | |
| 185 189 | 
             
                      yield
         | 
| 186 190 | 
             
                    end
         | 
| 187 191 | 
             
                  end
         | 
| 188 | 
            -
             | 
| 189 | 
            -
                  def attach_file
         | 
| 190 | 
            -
                    @subject.public_send(@attribute_name).attach(dummy_file)
         | 
| 191 | 
            -
                    @subject.public_send(@attribute_name)
         | 
| 192 | 
            -
                  end
         | 
| 193 | 
            -
             | 
| 194 | 
            -
                  def dummy_file
         | 
| 195 | 
            -
                    {
         | 
| 196 | 
            -
                      io: Tempfile.new('Hello world!'),
         | 
| 197 | 
            -
                      filename: 'test.png',
         | 
| 198 | 
            -
                      content_type: 'image/png'
         | 
| 199 | 
            -
                    }
         | 
| 200 | 
            -
                  end
         | 
| 201 192 | 
             
                end
         | 
| 202 193 | 
             
              end
         | 
| 203 194 | 
             
            end
         | 
| @@ -0,0 +1,78 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'concerns/active_storageable.rb'
         | 
| 4 | 
            +
            require_relative 'concerns/allow_blankable.rb'
         | 
| 5 | 
            +
            require_relative 'concerns/attachable.rb'
         | 
| 6 | 
            +
            require_relative 'concerns/contextable.rb'
         | 
| 7 | 
            +
            require_relative 'concerns/messageable.rb'
         | 
| 8 | 
            +
            require_relative 'concerns/rspecable.rb'
         | 
| 9 | 
            +
            require_relative 'concerns/validatable.rb'
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            module ActiveStorageValidations
         | 
| 12 | 
            +
              module Matchers
         | 
| 13 | 
            +
                def validate_processable_image_of(name)
         | 
| 14 | 
            +
                  ProcessableImageValidatorMatcher.new(name)
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                class ProcessableImageValidatorMatcher
         | 
| 18 | 
            +
                  include ActiveStorageable
         | 
| 19 | 
            +
                  include AllowBlankable
         | 
| 20 | 
            +
                  include Attachable
         | 
| 21 | 
            +
                  include Contextable
         | 
| 22 | 
            +
                  include Messageable
         | 
| 23 | 
            +
                  include Rspecable
         | 
| 24 | 
            +
                  include Validatable
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def initialize(attribute_name)
         | 
| 27 | 
            +
                    initialize_allow_blankable
         | 
| 28 | 
            +
                    initialize_contextable
         | 
| 29 | 
            +
                    initialize_messageable
         | 
| 30 | 
            +
                    initialize_rspecable
         | 
| 31 | 
            +
                    @attribute_name = attribute_name
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def description
         | 
| 35 | 
            +
                    "validate that :#{@attribute_name} is a processable image"
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def failure_message
         | 
| 39 | 
            +
                    "is expected to validate the processable image of :#{@attribute_name}"
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  def matches?(subject)
         | 
| 43 | 
            +
                    @subject = subject.is_a?(Class) ? subject.new : subject
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    is_a_valid_active_storage_attribute? &&
         | 
| 46 | 
            +
                    is_context_valid? &&
         | 
| 47 | 
            +
                    is_custom_message_valid? &&
         | 
| 48 | 
            +
                    is_valid_when_image_processable? &&
         | 
| 49 | 
            +
                    is_invalid_when_image_not_processable?
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  private
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  def is_valid_when_image_processable?
         | 
| 55 | 
            +
                    attach_file(processable_image)
         | 
| 56 | 
            +
                    validate
         | 
| 57 | 
            +
                    detach_file
         | 
| 58 | 
            +
                    is_valid?
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  def is_invalid_when_image_not_processable?
         | 
| 62 | 
            +
                    attach_file(not_processable_image)
         | 
| 63 | 
            +
                    validate
         | 
| 64 | 
            +
                    detach_file
         | 
| 65 | 
            +
                    !is_valid?
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  def is_custom_message_valid?
         | 
| 69 | 
            +
                    return true unless @custom_message
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                    attach_file(not_processable_image)
         | 
| 72 | 
            +
                    validate
         | 
| 73 | 
            +
                    detach_file
         | 
| 74 | 
            +
                    has_an_error_message_which_is_custom_message?
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
              end
         | 
| 78 | 
            +
            end
         | 
| @@ -1,38 +1,14 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 4 | 
            -
            # https://github.com/thoughtbot/paperclip/blob/v6.1.0/lib/paperclip/matchers/validate_attachment_size_matcher.rb
         | 
| 5 | 
            -
             | 
| 6 | 
            -
            require_relative 'concerns/active_storageable.rb'
         | 
| 7 | 
            -
            require_relative 'concerns/allow_blankable.rb'
         | 
| 8 | 
            -
            require_relative 'concerns/contextable.rb'
         | 
| 9 | 
            -
            require_relative 'concerns/messageable.rb'
         | 
| 10 | 
            -
            require_relative 'concerns/rspecable.rb'
         | 
| 11 | 
            -
            require_relative 'concerns/validatable.rb'
         | 
| 3 | 
            +
            require_relative 'base_size_validator_matcher'
         | 
| 12 4 |  | 
| 13 5 | 
             
            module ActiveStorageValidations
         | 
| 14 6 | 
             
              module Matchers
         | 
| 15 | 
            -
                def validate_size_of( | 
| 16 | 
            -
                  SizeValidatorMatcher.new( | 
| 7 | 
            +
                def validate_size_of(attribute_name)
         | 
| 8 | 
            +
                  SizeValidatorMatcher.new(attribute_name)
         | 
| 17 9 | 
             
                end
         | 
| 18 10 |  | 
| 19 | 
            -
                class SizeValidatorMatcher
         | 
| 20 | 
            -
                  include ActiveStorageable
         | 
| 21 | 
            -
                  include AllowBlankable
         | 
| 22 | 
            -
                  include Contextable
         | 
| 23 | 
            -
                  include Messageable
         | 
| 24 | 
            -
                  include Rspecable
         | 
| 25 | 
            -
                  include Validatable
         | 
| 26 | 
            -
             | 
| 27 | 
            -
                  def initialize(attribute_name)
         | 
| 28 | 
            -
                    initialize_allow_blankable
         | 
| 29 | 
            -
                    initialize_contextable
         | 
| 30 | 
            -
                    initialize_messageable
         | 
| 31 | 
            -
                    initialize_rspecable
         | 
| 32 | 
            -
                    @attribute_name = attribute_name
         | 
| 33 | 
            -
                    @min = @max = nil
         | 
| 34 | 
            -
                  end
         | 
| 35 | 
            -
             | 
| 11 | 
            +
                class SizeValidatorMatcher < BaseSizeValidatorMatcher
         | 
| 36 12 | 
             
                  def description
         | 
| 37 13 | 
             
                    "validate file size of :#{@attribute_name}"
         | 
| 38 14 | 
             
                  end
         | 
| @@ -42,117 +18,6 @@ module ActiveStorageValidations | |
| 42 18 | 
             
                    build_failure_message(message)
         | 
| 43 19 | 
             
                    message.join("\n")
         | 
| 44 20 | 
             
                  end
         | 
| 45 | 
            -
             | 
| 46 | 
            -
                  def less_than(size)
         | 
| 47 | 
            -
                    @max = size - 1.byte
         | 
| 48 | 
            -
                    self
         | 
| 49 | 
            -
                  end
         | 
| 50 | 
            -
             | 
| 51 | 
            -
                  def less_than_or_equal_to(size)
         | 
| 52 | 
            -
                    @max = size
         | 
| 53 | 
            -
                    self
         | 
| 54 | 
            -
                  end
         | 
| 55 | 
            -
             | 
| 56 | 
            -
                  def greater_than(size)
         | 
| 57 | 
            -
                    @min = size + 1.byte
         | 
| 58 | 
            -
                    self
         | 
| 59 | 
            -
                  end
         | 
| 60 | 
            -
             | 
| 61 | 
            -
                  def greater_than_or_equal_to(size)
         | 
| 62 | 
            -
                    @min = size
         | 
| 63 | 
            -
                    self
         | 
| 64 | 
            -
                  end
         | 
| 65 | 
            -
             | 
| 66 | 
            -
                  def between(range)
         | 
| 67 | 
            -
                    @min, @max = range.first, range.last
         | 
| 68 | 
            -
                    self
         | 
| 69 | 
            -
                  end
         | 
| 70 | 
            -
             | 
| 71 | 
            -
                  def matches?(subject)
         | 
| 72 | 
            -
                    @subject = subject.is_a?(Class) ? subject.new : subject
         | 
| 73 | 
            -
             | 
| 74 | 
            -
                    is_a_valid_active_storage_attribute? &&
         | 
| 75 | 
            -
                      is_context_valid? &&
         | 
| 76 | 
            -
                      is_allowing_blank? &&
         | 
| 77 | 
            -
                      is_custom_message_valid? &&
         | 
| 78 | 
            -
                      not_lower_than_min? &&
         | 
| 79 | 
            -
                      higher_than_min? &&
         | 
| 80 | 
            -
                      lower_than_max? &&
         | 
| 81 | 
            -
                      not_higher_than_max?
         | 
| 82 | 
            -
                  end
         | 
| 83 | 
            -
             | 
| 84 | 
            -
                  protected
         | 
| 85 | 
            -
             | 
| 86 | 
            -
                  def build_failure_message(message)
         | 
| 87 | 
            -
                    return unless @failure_message_artefacts.present?
         | 
| 88 | 
            -
             | 
| 89 | 
            -
                    message << "  but there seem to have issues with the matcher methods you used, since:"
         | 
| 90 | 
            -
                    @failure_message_artefacts.each do |error_case|
         | 
| 91 | 
            -
                      message << "  validation failed when provided with a #{error_case[:size]} bytes test file"
         | 
| 92 | 
            -
                    end
         | 
| 93 | 
            -
                    message << "  whereas it should have passed"
         | 
| 94 | 
            -
                  end
         | 
| 95 | 
            -
             | 
| 96 | 
            -
                  def not_lower_than_min?
         | 
| 97 | 
            -
                    @min.nil? || !passes_validation_with_size(@min - 1)
         | 
| 98 | 
            -
                  end
         | 
| 99 | 
            -
             | 
| 100 | 
            -
                  def higher_than_min?
         | 
| 101 | 
            -
                    @min.nil? || passes_validation_with_size(@min + 1)
         | 
| 102 | 
            -
                  end
         | 
| 103 | 
            -
             | 
| 104 | 
            -
                  def lower_than_max?
         | 
| 105 | 
            -
                    @max.nil? || @max == Float::INFINITY || passes_validation_with_size(@max - 1)
         | 
| 106 | 
            -
                  end
         | 
| 107 | 
            -
             | 
| 108 | 
            -
                  def not_higher_than_max?
         | 
| 109 | 
            -
                    @max.nil? || @max == Float::INFINITY || !passes_validation_with_size(@max + 1)
         | 
| 110 | 
            -
                  end
         | 
| 111 | 
            -
             | 
| 112 | 
            -
                  def passes_validation_with_size(size)
         | 
| 113 | 
            -
                    mock_size_for(io, size) do
         | 
| 114 | 
            -
                      attach_file
         | 
| 115 | 
            -
                      validate
         | 
| 116 | 
            -
                      is_valid? || add_failure_message_artefact(size)
         | 
| 117 | 
            -
                    end
         | 
| 118 | 
            -
                  end
         | 
| 119 | 
            -
             | 
| 120 | 
            -
                  def add_failure_message_artefact(size)
         | 
| 121 | 
            -
                    @failure_message_artefacts << { size: size }
         | 
| 122 | 
            -
                    false
         | 
| 123 | 
            -
                  end
         | 
| 124 | 
            -
             | 
| 125 | 
            -
                  def is_custom_message_valid?
         | 
| 126 | 
            -
                    return true unless @custom_message
         | 
| 127 | 
            -
             | 
| 128 | 
            -
                    mock_size_for(io, -1.kilobytes) do
         | 
| 129 | 
            -
                      attach_file
         | 
| 130 | 
            -
                      validate
         | 
| 131 | 
            -
                      has_an_error_message_which_is_custom_message?
         | 
| 132 | 
            -
                    end
         | 
| 133 | 
            -
                  end
         | 
| 134 | 
            -
             | 
| 135 | 
            -
                  def mock_size_for(io, size)
         | 
| 136 | 
            -
                    Matchers.stub_method(io, :size, size) do
         | 
| 137 | 
            -
                      yield
         | 
| 138 | 
            -
                    end
         | 
| 139 | 
            -
                  end
         | 
| 140 | 
            -
             | 
| 141 | 
            -
                  def attach_file
         | 
| 142 | 
            -
                    @subject.public_send(@attribute_name).attach(dummy_file)
         | 
| 143 | 
            -
                  end
         | 
| 144 | 
            -
             | 
| 145 | 
            -
                  def dummy_file
         | 
| 146 | 
            -
                    {
         | 
| 147 | 
            -
                      io: io,
         | 
| 148 | 
            -
                      filename: 'test.png',
         | 
| 149 | 
            -
                      content_type: 'image/png'
         | 
| 150 | 
            -
                    }
         | 
| 151 | 
            -
                  end
         | 
| 152 | 
            -
             | 
| 153 | 
            -
                  def io
         | 
| 154 | 
            -
                    @io ||= Tempfile.new('Hello world!')
         | 
| 155 | 
            -
                  end
         | 
| 156 21 | 
             
                end
         | 
| 157 22 | 
             
              end
         | 
| 158 23 | 
             
            end
         | 
| @@ -0,0 +1,40 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'base_size_validator_matcher'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module ActiveStorageValidations
         | 
| 6 | 
            +
              module Matchers
         | 
| 7 | 
            +
                def validate_total_size_of(attribute_name)
         | 
| 8 | 
            +
                  TotalSizeValidatorMatcher.new(attribute_name)
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                class TotalSizeValidatorMatcher < BaseSizeValidatorMatcher
         | 
| 12 | 
            +
                  def description
         | 
| 13 | 
            +
                    "validate total file size of :#{@attribute_name}"
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def failure_message
         | 
| 17 | 
            +
                    message = ["is expected to validate total file size of :#{@attribute_name}"]
         | 
| 18 | 
            +
                    build_failure_message(message)
         | 
| 19 | 
            +
                    message.join("\n")
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  protected
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def attach_file
         | 
| 25 | 
            +
                    # We attach blobs instead of io for has_many_attached relation
         | 
| 26 | 
            +
                    @subject.public_send(@attribute_name).attach([dummy_blob])
         | 
| 27 | 
            +
                    @subject.public_send(@attribute_name)
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def dummy_blob
         | 
| 31 | 
            +
                    ActiveStorage::Blob.create_and_upload!(
         | 
| 32 | 
            +
                      io: io,
         | 
| 33 | 
            +
                      filename: 'test.png',
         | 
| 34 | 
            +
                      content_type: 'image/png',
         | 
| 35 | 
            +
                      service_name: 'test'
         | 
| 36 | 
            +
                    )
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
            end
         | 
| @@ -2,9 +2,11 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require 'active_storage_validations/matchers/aspect_ratio_validator_matcher'
         | 
| 4 4 | 
             
            require 'active_storage_validations/matchers/attached_validator_matcher'
         | 
| 5 | 
            +
            require 'active_storage_validations/matchers/processable_image_validator_matcher'
         | 
| 5 6 | 
             
            require 'active_storage_validations/matchers/content_type_validator_matcher'
         | 
| 6 7 | 
             
            require 'active_storage_validations/matchers/dimension_validator_matcher'
         | 
| 7 8 | 
             
            require 'active_storage_validations/matchers/size_validator_matcher'
         | 
| 9 | 
            +
            require 'active_storage_validations/matchers/total_size_validator_matcher'
         | 
| 8 10 |  | 
| 9 11 | 
             
            module ActiveStorageValidations
         | 
| 10 12 | 
             
              module Matchers
         |