How would you validate each object inside an array of object with Rails?
I am building a user profile form in our Rails app. Inside user model, we have basic string attributes but some jsonb fields as well. JSONb fields default to []
because we want to store an array of objects inside that attribute. Here is an example of simplified user model attributes:
name: string
email: string
education: jsonb, default: []
Education is an array of objects such as:
[{
school: 'Harvard university',
degree: 'Computer Science',
from_date: 'Tue, 11 Jul 2017 16:22:12 +0200`,
to_date: 'Tue, 11 Jul 2017 16:22:12 +0200'
},{
school: 'High school',
degree: 'Whatever',
from_date: 'Tue, 11 Jul 2017 16:22:12 +0200`,
to_date: 'Tue, 11 Jul 2017 16:22:12 +0200'
}]
User should be able to click Add school button, to add more fields via jquery. That jquery part is not important for this question - maybe just an explanation why we used an Array of objects.
How would you validate each item in education array, so I can mark the text field containing validtion error with red color? I got adviced that using FormObject pattern might help here. I have also tried writing custom validator that inherits from ActiveModel::Validator
class, but the main problem still lies in fact, that I am dealing with an array, not actual object..
Thanks for any constructive help.
You could treat education records as first-class citizens in your Rails model layer by introducing a non-database backed ActiveModel
model class for them:
class Education
include ActiveModel::Model
attr_accessor :school, :degree, :from_date, :to_date
validates :school, presence: true
validates :degree, presence: true
def initialize(**attrs)
attrs.each do |attr, value|
send("#{attr}=", value)
end
end
def attributes
[:school, :degree, :from_date, :to_date].inject({}) do |hash, attr|
hash[attr] = send(attr)
hash
end
end
class ArraySerializer
class << self
def load(arr)
arr.map do |item|
Education.new(item)
end
end
def dump(arr)
arr.map(&:attributes)
end
end
end
end
Then you can transparently serialize and deserialize the education
array in your User
model:
class User
# ...
serialize :education, Education::ArraySerializer
# ...
end
This solution should allow you to validate individual attributes of Education
objects with built-in Rails validators, embed them in a nested form, and so on.
Important: I wrote the code above without testing it, so it might need a few modifications.
Something like this perhaps? (probably you'll want the hash to be passed with keys as strings and not symbols and then verified for item[string] not item[symbol]
class MyModel < ActiveRecord::Base
include ActiveModel::Validations
validates :education, evalidation: true
class EvalidationValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors[attribute] << "must be a valid array & json" unless (check_json_array(value) rescue nil)
end
end
def check_json_array(value)
return false unless value.is_a?(Array)
value.each do |item|
return false unless item.is_a?(Hash)
return false if item[:school].blank? || item[:degree].blank? || !valid_date_time(item[:from_date]) || !valid_date_time(item[:to_date])
end
true
end
def valid_date_time(date)
#this needs work as it will pass for instance "Tue" as true, since DateTime will parse even single day initials to the current closest date
(DateTime.parse(date) rescue nil)
end
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With