Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

203 validator custom message error causes test matcher error #206

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -19,6 +19,7 @@ Gem::Specification.new do |s|
s.files = Dir['{app,config,db,lib}/**/*', 'MIT-LICENSE', 'Rakefile', 'README.md']

%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
Loading