Is your code rather imperative or declarative? What OOP patterns do you use? Do you extract abstractions?

In Ruby, we don’t have many ways to check for programs correctness and validate input params, besides explicitly asking whether they are nil or if they have an expected kind.

Tell-don’t-ask violation

Rails itself convinces us to validate tell-don’t-ask rule which states:

It is okay to use accessors to get the state of an object, as long as you don’t use the result to make decisions outside the object. Any decisions based entirely upon the state of one object should be made ‘inside’ the object itself.

That’s why we we check for .blank?, .empty? or .nil?. Sometimes we also use is_a? or kind_of? methods.

How is it done with the rails-way?

In this case, in particular, we use .save method to both create a client and check if it was created. Based on that, we make further decision what to render. Ugh…

How do you validate an input?

There are different ways to check incoming parameters as well as many ways to inform about errors. Error-driven flow, fail-fast approach, null-object pattern or monad-like error handling.

I described one of them in my article about Service Objects.

Many times though, we prefer to use a defensive approach instead. Here’s an example:

Do you see these trials and checks just to perform some final file write? If we remove these validations, the code will be much shorter, but it will crash with rather confusing messages. Before we actually do what we want (which is saving some content under a given path) we must protect ourselves and defence our program from possible failures. By making FileWriter class errorproof, we cannot focus on the core logic from the very beginning.

Time to refactor

How can we start? Simply. We need to extract MVP of our FileWriter. It will just take a path to a file and contents to fill it with.

Simple enough, right?

This is the real operation we want to perform. Ideally, input params are correct, so we shouldn’t care about validating them. We just let this method crash with nils and other exceptions when parameters are not valid. This may sound illogical, but only at the beginning.

The next step

We know the world is not ideal though, especially our programs. Thus, we need to extract and use one of these defensive checks. It can be either file existence or content emptiness check. Let’s do this with the former.

Keep in mind this class is just used to make desired validation. To write the actual file, we use injected FileWriter that we extracted at the very beginning.

The last step

Now, the same as previously, we just need to extract yet another validation.

Nothing new, as we almost reuse the existing code from the previous step.

Wrapping them all together

Finally, it’s time to use what we’ve created so far. But not this way presented at the very beginning, not by bloating the class with validations that have nothing to do with its core functionality. Instead, we should use decorators to do the validation:

We just needed to pipe the existing validations and provide the final object that performs our required action. Now, the client has the flexibility of composing a complex object from decorators that perform their specific tasks. The core object will do the saving while the decorators will validate parameters.

Subscribe to get the latest content immediately

https://tinyletter.com/KamilLelonek

East-oriented code

Jim Gay had an awesome talk about a clear path through Ruby with OO.

He calls it East-Oriented Code and introduces 4 basic rules how to write Ruby programs:

You may read them to see yet another approach how to model your flow.

Summary

As you can see we can come up with a more elegant approach combining OOP patterns, dependency injection and some decorators here. Smaller objects always mean higher maintainability. The core class will always remain small, no matter how many validations we may invent in the future. The more things we need to validate, the more validating decorators we will create. All of them will be small and cohesive. And we’ll be able to put them together in different variations.

Besides that, this approach makes our code much more reusable, as classes perform very few operations and don’t defend themselves by default. While being defensive is an important feature, we’ll use validating decorators.

You may be interested in active_nothing gem which simulates conditionals from Smalltalk and you can use them to branch flow of your checks.

References