validates_type
10 May 2015In keeping with my apparent obsession with types, I created another helpful library: validates_type. I wanted a nice, lightweight way to assert that the types of my attributes are exactly what I need them to be once I save them to the database.
No one is particularly fond of littering their code with try
s and is_a?
s. To remedy this, I make my models assert confidence that the data in my database is what I expect. I’m not talking about type coercion, type casting or any other munging of types that might happen on a typical read/write from your handy dandy ORM of choice. No no, this is the real deal, the bees knees, the elbows of a gazelle: actual type validation.
Data quality is important to me, and should be important to you too. This library is an attempt at injecting some structure into your typical Rails/Sinatra app in order to keep your data clean and your system happy.
Alright so when would this kind of thing be necessary? Let’s see an example of how automagic type coercion can bite us.
Let’s say you’re using Rails, and let’s pretend for a moment that for some reason, you have some code that looks like this:
A very important class:
class ImportantResource < ActiveRecord::Base
# Attribute named :settings with what we want to be an array
# of important things encoded and stored in either
# a nice postres json column or a text column
store_accessor :settings, :array_of_things
end
And a matching very important controller:
class ImportantResourceController < ApplicationController
def create
resource = ImportantResource.new
# let's set the array_of_things to a parameter that is passed in
# via a form or something
resource.array_of_things = params[:array_of_things]
# great, lets save that resource
resource.save!
# cool everything worked, this could not have gone better
end
end
Oh no, george! What about if something other than array is passed to that parameter??
Well let’s see what might happen. If params[:array_of_things] = 'a random string'
, any other method or object that needs to interact with ImportantResource
would have a bad time unless they explictly validated array_of_things
is an array. We don’t have any ORM coercion to fall back on here since this is a store_accessor
.
Wouldn’t it be nicer to have ImportantResource
validate itself so no other object needs to care about array_of_things
’s array-ness?
This sounds awesome to me, and I agree. Let’s check it out and how you can harness the power for yourself. What if instead of letting ImportantResource
behave in any which way, we locked that thing down? It might look something like this:
class ImportantResource < ActiveRecord::Base
# Attribute named :settings with what we want to be an array
# of important things encoded and stored in either
# a nice postres json column or a text column
store_accessor :settings, :array_of_things
validates_type :array_of_things, :array
end
Now if we try to save any other value to array_of_things
that isn’t an array, we receive validation errors added to important_resource.errors
in the same way other ActiveRecord/ActiveModel classes behave! This will keep us honest with what we set our data to.
So why use this pattern at all? Some of my data might be the wrong type but who cares?
I think you should care, I sure as heck do. For instance, what if the software we’re writing is an API that people need to use? Wouldn’t you rather them be confident that your documentation (that you painstakingly wrote for hours) is correct?
For example, lets say you build a medical API: SuperImportantMedicalAPI
. And in a super important response about a patient, a boolean value is returned for patient_is_allergic_to_nuts
.
This totally legitimate example shows how imperative it is that SuperImportantMedicalAPI
’s types be locked down. If people are depending on your awesome API to save them from nut allergies, don’t let a string like 't'
be your downfall. The people need true
and can handle true
, give them what they want.
Great? Great! Here’s some FAQs:
Q: Do the validations behave exactly the same as ActiveModel::Validations
?
A: Exactly the same.
Q: So does that mean I can chain other validations on?
A: Heck yes, check it out:
# custom messaging for errors
validates_type :array_of_things,
:array,
message: 'This needs to be an array!!!'
# chaining on other validation like numericality
validates :other_thing,
type: { type: :integer,
numericality: { greater_than: 1 } }
# making sure object is a string and included in a specific list
validates :yet_another_attribute,
type: { type: :string,
inclusion: { in: ['foo', 'bar'] } }
Q: Can I use this with ActiveRecord
or ActiveModel
?
A: Y to the es! Yes! Isn’t that cool? Any ruby code that uses either ActiveModel
or ActiveRecord
can use all the power of this gem on day
Q: Does this cost at least $1,000 every time I need to use it?
A: Close, you can use this library right now for the low low price of $0. I tried to make it lower but just couldn’t do it.
Q: Where can I read more?