Skip to content

Commit

Permalink
Merge pull request #206 from Mth0158/203-validator-custom-message-err…
Browse files Browse the repository at this point in the history
…or-causes-test-matcher-error

203 validator custom message error causes test matcher error
  • Loading branch information
igorkasyanchuk authored Nov 10, 2023
2 parents 77f797b + c82a8f9 commit bd26c11
Show file tree
Hide file tree
Showing 46 changed files with 1,677 additions and 487 deletions.
7 changes: 6 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
active_storage_validations (1.1.0)
active_storage_validations (1.1.1)
activejob (>= 5.2.0)
activemodel (>= 5.2.0)
activestorage (>= 5.2.0)
Expand Down Expand Up @@ -46,6 +46,7 @@ GEM
zeitwerk (~> 2.3)
ast (2.4.1)
builder (3.2.4)
byebug (11.1.3)
coderay (1.1.3)
combustion (1.3.1)
activesupport (>= 3.0.0)
Expand All @@ -72,6 +73,8 @@ GEM
mini_magick (4.11.0)
mini_portile2 (2.5.1)
minitest (5.14.3)
minitest-focus (1.4.0)
minitest (>= 4, < 6)
nokogiri (1.11.3)
mini_portile2 (~> 2.5.0)
racc (~> 1.4)
Expand Down Expand Up @@ -134,10 +137,12 @@ PLATFORMS

DEPENDENCIES
active_storage_validations!
byebug
combustion (~> 1.3)
globalid
marcel
mini_magick (>= 4.9.5)
minitest-focus (~> 1.4)
pry
rubocop
ruby-vips (>= 2.1.0)
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,11 @@ BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle exec rake test
BUNDLE_GEMFILE=gemfiles/rails_next.gemfile bundle exec rake test
```

Tips:
- To focus a specific test, use the `focus` class method provided by [minitest-focus](https://github.com/minitest/minitest-focus)
- To focus a specific file, use the TEST option provided by minitest, e.g. to only run size_validator_test.rb file you will execute the following command: `bundle exec rake test TEST=test/validators/size_validator_test.rb``
## Known issues
- There is an issue in Rails which it possible to get if you have added a validation and generating for example an image preview of attachments. It can be fixed with this:
Expand Down
1 change: 1 addition & 0 deletions active_storage_validations.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Gem::Specification.new do |s|
s.metadata['rubygems_mfa_required'] = 'true'

%w[activejob activemodel activestorage activesupport].each { |rails_subcomponent| s.add_dependency rails_subcomponent, '>= 5.2.0' }
s.add_development_dependency 'minitest-focus', "~> 1.4"
s.add_development_dependency 'combustion', "~> 1.3"
s.add_development_dependency 'mini_magick', ">= 4.9.5"
s.add_development_dependency 'ruby-vips', ">= 2.1.0"
Expand Down
7 changes: 7 additions & 0 deletions gemfiles/rails_6_0.gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,15 @@ GEM
mini_magick (4.11.0)
mini_portile2 (2.8.0)
minitest (5.16.3)
minitest-focus (1.4.0)
minitest (>= 4, < 6)
nokogiri (1.13.8)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
nokogiri (1.13.8-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.13.8-x86_64-linux)
racc (~> 1.4)
parallel (1.20.1)
parser (3.0.0.0)
ast (~> 2.4.1)
Expand Down Expand Up @@ -141,6 +147,7 @@ DEPENDENCIES
globalid
marcel
mini_magick (>= 4.9.5)
minitest-focus (~> 1.4)
pry
rubocop
ruby-vips (>= 2.1.0)
Expand Down
7 changes: 7 additions & 0 deletions gemfiles/rails_6_1.gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,15 @@ GEM
mini_magick (4.11.0)
mini_portile2 (2.8.0)
minitest (5.16.3)
minitest-focus (1.4.0)
minitest (>= 4, < 6)
nokogiri (1.13.8)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
nokogiri (1.13.8-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.13.8-x86_64-linux)
racc (~> 1.4)
parallel (1.20.1)
parser (3.0.0.0)
ast (~> 2.4.1)
Expand Down Expand Up @@ -142,6 +148,7 @@ DEPENDENCIES
globalid
marcel
mini_magick (>= 4.9.5)
minitest-focus (~> 1.4)
pry
rubocop
ruby-vips (>= 2.1.0)
Expand Down
3 changes: 3 additions & 0 deletions gemfiles/rails_7_0.gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ GEM
mini_mime (1.1.2)
mini_portile2 (2.8.0)
minitest (5.16.3)
minitest-focus (1.4.0)
minitest (>= 4, < 6)
nokogiri (1.13.8)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
Expand Down Expand Up @@ -137,6 +139,7 @@ DEPENDENCIES
globalid
marcel
mini_magick (>= 4.9.5)
minitest-focus (~> 1.4)
pry
rubocop
ruby-vips (>= 2.1.0)
Expand Down
3 changes: 3 additions & 0 deletions gemfiles/rails_next.gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ GEM
mini_mime (1.1.2)
mini_portile2 (2.8.2)
minitest (5.15.0)
minitest-focus (1.4.0)
minitest (>= 4, < 6)
net-imap (0.2.3)
digest
net-protocol
Expand Down Expand Up @@ -215,6 +217,7 @@ DEPENDENCIES
globalid
marcel
mini_magick (>= 4.9.5)
minitest-focus (~> 1.4)
pry
rails!
rubocop
Expand Down
2 changes: 2 additions & 0 deletions lib/active_storage_validations/aspect_ratio_validator.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# frozen_string_literal: true

require_relative 'concerns/symbolizable.rb'
require_relative 'metadata.rb'

module ActiveStorageValidations
class AspectRatioValidator < ActiveModel::EachValidator # :nodoc
include OptionProcUnfolding
include ErrorHandler
include Symbolizable

AVAILABLE_CHECKS = %i[with].freeze
PRECISION = 3
Expand Down
7 changes: 6 additions & 1 deletion lib/active_storage_validations/attached_validator.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
# frozen_string_literal: true

require_relative 'concerns/symbolizable.rb'

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

ERROR_TYPES = %i[blank].freeze

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

errors_options = initialize_error_options(options)

add_error(record, attribute, :blank, **errors_options)
add_error(record, attribute, ERROR_TYPES.first, **errors_options)
end
end
end
10 changes: 10 additions & 0 deletions lib/active_storage_validations/concerns/symbolizable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module Symbolizable
extend ActiveSupport::Concern

class_methods do
def to_sym
validator_class = self.name.split("::").last
validator_class.sub(/Validator/, '').underscore.to_sym
end
end
end
8 changes: 6 additions & 2 deletions lib/active_storage_validations/content_type_validator.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# frozen_string_literal: true

require_relative 'concerns/symbolizable.rb'

module ActiveStorageValidations
class ContentTypeValidator < ActiveModel::EachValidator # :nodoc:
include OptionProcUnfolding
include ErrorHandler
include Symbolizable

AVAILABLE_CHECKS = %i[with in].freeze

ERROR_TYPES = %i[content_type_invalid].freeze

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

Expand All @@ -22,7 +26,7 @@ def validate_each(record, attribute, _value)
next if is_valid?(file, types)

errors_options[:content_type] = content_type(file)
add_error(record, attribute, :content_type_invalid, **errors_options)
add_error(record, attribute, ERROR_TYPES.first, **errors_options)
break
end
end
Expand Down
15 changes: 15 additions & 0 deletions lib/active_storage_validations/dimension_validator.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
# frozen_string_literal: true

require_relative 'concerns/symbolizable.rb'
require_relative 'metadata.rb'

module ActiveStorageValidations
class DimensionValidator < ActiveModel::EachValidator # :nodoc
include OptionProcUnfolding
include ErrorHandler
include Symbolizable

AVAILABLE_CHECKS = %i[width height min max].freeze
ERROR_TYPES = %i[
image_metadata_missing
dimension_min_inclusion
dimension_max_inclusion
dimension_width_inclusion
dimension_height_inclusion
dimension_width_greater_than_or_equal_to
dimension_height_greater_than_or_equal_to
dimension_width_less_than_or_equal_to
dimension_height_less_than_or_equal_to
dimension_width_equal_to
dimension_height_equal_to
].freeze

def process_options(record)
flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
Expand Down
15 changes: 9 additions & 6 deletions lib/active_storage_validations/error_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ module ErrorHandler

def initialize_error_options(options)
{
message: (options[:message] if options[:message].present?)
}
validator_type: self.class.to_sym,
custom_message: (options[:message] if options[:message].present?)
}.compact
end

def add_error(record, attribute, default_message, **errors_options)
message = errors_options[:message].presence || default_message
return if record.errors.added?(attribute, message)
def add_error(record, attribute, error_type, **errors_options)
type = errors_options[:custom_message].presence || error_type
return if record.errors.added?(attribute, type)

record.errors.add(attribute, message, **errors_options)
# You can read https://api.rubyonrails.org/classes/ActiveModel/Errors.html#method-i-add
# to better understand how Rails model errors work
record.errors.add(attribute, type, **errors_options)
end

end
Expand Down
3 changes: 3 additions & 0 deletions lib/active_storage_validations/limit_validator.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# frozen_string_literal: true

require_relative 'concerns/symbolizable.rb'

module ActiveStorageValidations
class LimitValidator < ActiveModel::EachValidator # :nodoc:
include OptionProcUnfolding
include ErrorHandler
include Symbolizable

AVAILABLE_CHECKS = %i[max min].freeze

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# frozen_string_literal: true

require_relative 'concerns/validatable.rb'

module ActiveStorageValidations
module Matchers
def validate_attached_of(name)
AttachedValidatorMatcher.new(name)
end

class AttachedValidatorMatcher
include Validatable

def initialize(attribute_name)
@attribute_name = attribute_name
@custom_message = nil
Expand All @@ -23,7 +27,10 @@ def with_message(message)

def matches?(subject)
@subject = subject.is_a?(Class) ? subject.new : subject
responds_to_methods && valid_when_attached && invalid_when_not_attached
responds_to_methods &&
is_valid_when_file_attached? &&
is_invalid_when_file_not_attached? &&
validate_custom_message?
end

def failure_message
Expand All @@ -42,27 +49,44 @@ def responds_to_methods
@subject.public_send(@attribute_name).respond_to?(:detach)
end

def valid_when_attached
@subject.public_send(@attribute_name).attach(attachable) unless @subject.public_send(@attribute_name).attached?
@subject.validate
@subject.errors.details[@attribute_name].exclude?(error: error_message)
def is_valid_when_file_attached?
attach_dummy_file unless file_attached?
validate
is_valid?
end

def invalid_when_not_attached
@subject.public_send(@attribute_name).detach
# Unset the direct relation since `detach` on an unpersisted record does not set `attached?` to false.
@subject.public_send("#{@attribute_name}=", nil)
def is_invalid_when_file_not_attached?
detach_file if file_attached?
validate
!is_valid?
end

def validate_custom_message?
return true unless @custom_message

@subject.validate
@subject.errors.details[@attribute_name].include?(error: error_message)
detach_file if file_attached?
validate
has_an_error_message_which_is_custom_message?
end

def error_message
@custom_message || :blank
def attach_dummy_file
dummy_file = {
io: Tempfile.new('.'),
filename: 'dummy.txt',
content_type: 'text/plain'
}

@subject.public_send(@attribute_name).attach(dummy_file)
end

def file_attached?
@subject.public_send(@attribute_name).attached?
end

def attachable
{ io: Tempfile.new('.'), filename: 'dummy.txt', content_type: 'text/plain' }
def detach_file
@subject.public_send(@attribute_name).detach
# Unset the direct relation since `detach` on an unpersisted record does not set `attached?` to false.
@subject.public_send("#{@attribute_name}=", nil)
end
end
end
Expand Down
Loading

0 comments on commit bd26c11

Please sign in to comment.