Wednesday, September 25, 2013

Dirt-simple validation and coercion in Ruby

Dirt-simple validation and coercion in Ruby

How many times have you been bitten by Ruby’s dynamic typing? Personally, this happens to me all the time–which might explain why the last few posts here have been Haskell posts ;)

It’s not Ruby’s fault though, many times this flexibility is exactly what we want to get something up and running quickly. Get it done. Ship it!. Right? Right. Well…maybe.

Perhaps we can find some kind of happy medium? Are there any lightweight strategies we can use to ensure that the values in our program are what we think they are?

Picture this: you are working with some sort of service. You need to deal with values accessible in your domain object that might or might not be nil. Further, when these variable are not nil, they will be coming over the wire as strings.

How can we develop a nice way to deal with this?

Coercive behavior

Let’s take a crack at this with wish driven development.

Say we have a method named coerce. coerce will take a value, and if it is nil, it will leave the value alone. If the value is non-nil it will return the value untouched.

@foo = nil
@bar = "34"

coerce(@foo) # => nil
coerce(@bar) # => "34"

Hey wait, that’s not helpful at all! That’s just the identity function, passing back whatever we give it. Well, sure, but you gotta start somewhere. How can we make it better?

Instead of just returning the non-nil value, we’d like to convert the parameter using some logic that we specify. This is a perfect place to use a block:

coerce(@foo) {|foo| foo.to_i} # => nil
coerce(@bar) {|bar| bar.to_i} # => 34

Cool, but it’s a little bit verbose. Let’s use the old symbol #to_proc trick to cut it down a bit:

coerce(@foo, &:to_i)  # => nil
coerce(@bar, &:to_i)  # => 34

And, if we don’t pass a block, it will keep the old behavior and just return the value as a string.

coerce(@bar) # => "34"

Nice. But what happens when we don’t want to silently ignore a nil value? Depending on the use case, ignoring bad data like this could be a recipe for disaster! The answer here is obvious, we can just make a version of our method that will raise an error on nil.

coerce!(@foo) # => raise CoercisionError, 'Expected a non-nil value'
coerce!(@bar, &:to_i) => 34

Implementation

Alright, so how do we implement this logic. We want to have this method available in any class or module we right, so let’s create it as a Module that we can include in our domain classes.

module Coercion
  class ::CoercionError < StandardError; end

  def coerce(value, &transform)
    transform = transform || identity_transform

    if value.nil?
      nil
    else
      transform.(value)
    end
  end

  def coerce!(value, &transform)
    raise CoercionError, 'Expected non-nil value' if value.nil?
    coerce(value, &transform)
  end

  private

  def identity_transform
    @identify_transform ||= lambda { |value| value }
  end
end

Super simple, but effective. We could easily add to this framework to raise errors if the transform doesn’t have a value it can do something meaningful with, etc.

The point is though, that we can use a method like coerce to do lightweight validation at the beginning of a method, and then not have to worry about it anymore. Instead the code can focus on doing the one thing the method is supposed to be doing.

The method is doing just one thing, right?

What do you think? How do you handle ensuring that the values you’re passed are the type you expect? Do you do some sort of lightweight validation like this? Use a gem to do it? Don’t worry about it and let Ruby throw an error?

Let us know in the comments below!

P.S. Interested in working with us? We’re currently looking for a senior level Ruby developer. Check out our listing and get in touch.

No comments:

Post a Comment