From f6e8f18d89c7ad1aac3429941b5f71ab25b5e6c3 Mon Sep 17 00:00:00 2001 From: Kristina Lim Date: Fri, 21 Sep 2018 12:16:06 +0800 Subject: [PATCH] Add validator for integer arrays Example usage: validates :related_post_ids, integer_array: true --- app/validators/integer_array_validator.rb | 63 +++++++++++++++++++ config/locales/en.yml | 3 + .../integer_array_validator_spec.rb | 57 +++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 app/validators/integer_array_validator.rb create mode 100644 spec/validators/integer_array_validator_spec.rb diff --git a/app/validators/integer_array_validator.rb b/app/validators/integer_array_validator.rb new file mode 100644 index 0000000000..27042c972b --- /dev/null +++ b/app/validators/integer_array_validator.rb @@ -0,0 +1,63 @@ +# Validates an integer array +# +# This uses Integer() behind the scenes. +# +# === Example +# +# class Post +# include ActiveModel::Validations +# +# attr_accessor :related_post_ids +# validates :related_post_ids, integer_array: true +# end +# +# post = Post.new +# +# post.related_post_ids = nil +# post.valid? # => true +# +# post.related_post_ids = [] +# post.valid? # => true +# +# post.related_post_ids = 1 +# post.valid? # => false +# post.errors[:related_post_ids] # => ["must be an array"] +# +# post.related_post_ids = [1, 2, 3] +# post.valid? # => true +# +# post.related_post_ids = ["1", "2", "3"] +# post.valid? # => true +# +# post.related_post_ids = [1, "2", "Not Integer", 3] +# post.valid? # => false +# post.errors[:related_post_ids] # => ["must contain only valid integers"] +class IntegerArrayValidator < ActiveModel::EachValidator + NOT_ARRAY_ERROR = I18n.t("validators.integer_array_validator.not_array_error") + INVALID_ELEMENT_ERROR = I18n.t("validators.integer_array_validator.invalid_element_error") + + def validate_each(record, attribute, value) + return if value.nil? + + validate_attribute_is_array(record, attribute, value) + validate_attribute_elements_are_integer(record, attribute, value) + end + + protected + + def validate_attribute_is_array(record, attribute, value) + return if value.is_a?(Array) + + record.errors.add(attribute, NOT_ARRAY_ERROR) + end + + def validate_attribute_elements_are_integer(record, attribute, array) + return unless array.is_a?(Array) + + array.each do |element| + Integer(element) + end + rescue ArgumentError + record.errors.add(attribute, INVALID_ELEMENT_ERROR) + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index cea3d34d6b..f59b6d4bba 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -104,6 +104,9 @@ en: date_time_string_validator: not_string_error: "must be a string" invalid_format_error: "must be valid" + integer_array_validator: + not_array_error: "must be an array" + invalid_element_error: "must contain only valid integers" enterprise_mailer: confirmation_instructions: diff --git a/spec/validators/integer_array_validator_spec.rb b/spec/validators/integer_array_validator_spec.rb new file mode 100644 index 0000000000..156409f0d0 --- /dev/null +++ b/spec/validators/integer_array_validator_spec.rb @@ -0,0 +1,57 @@ +require "spec_helper" + +describe IntegerArrayValidator do + class TestModel + include ActiveModel::Validations + + attr_accessor :ids + + validates :ids, integer_array: true + end + + describe "internationalization" do + it "has translation for NOT_ARRAY_ERROR" do + expect(described_class::NOT_ARRAY_ERROR).not_to be_blank + end + + it "has translation for INVALID_ELEMENT_ERROR" do + expect(described_class::INVALID_ELEMENT_ERROR).not_to be_blank + end + end + + describe "validation" do + let(:instance) { TestModel.new } + + it "does not add error when nil" do + instance.ids = nil + expect(instance).to be_valid + end + + it "does not add error when blank array" do + instance.ids = [] + expect(instance).to be_valid + end + + it "adds error NOT_ARRAY_ERROR when neither nil nor an array" do + instance.ids = 1 + expect(instance).not_to be_valid + expect(instance.errors[:ids]).to include(described_class::NOT_ARRAY_ERROR) + end + + it "does not add error when array of integers" do + instance.ids = [1, 2, 3] + expect(instance).to be_valid + end + + it "does not add error when array of integers as String" do + instance.ids = ["1", "2", "3"] + expect(instance).to be_valid + end + + it "adds error INVALID_ELEMENT_ERROR when an element cannot be parsed as Integer" do + instance.ids = [1, "2", "Not Integer", 3] + expect(instance).not_to be_valid + expect(instance.errors[:ids]).to include(described_class::INVALID_ELEMENT_ERROR) + end + end +end