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.