At Iora we are always discussing the best ways to architect our apps. We tend to do a number of things to isolate concerns and reduce complexity. Sometimes we start out creating a gem we will need, other times we extract code into libraries as complexity grows. When we do this we often have an eye on what parts of our app can one day be stand alone services. This allows us to keep our ActiveRecord Models light on domain logic.
We plan on writing a few posts about isolation of concerns and reducing complexity, and I wanted to start small. This first post is a simple example of how you can extract some logic from a model into a module that is reusable.
User Model
In our User model we use a unique identifier (GUID) for each third party service we communicate with. When we create a user we want to generate their GUIDs for each service. Each guid is stored on the user table in a separate column. For this example we have three GUIDs guid_a, guid_b, and guid_c.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Now to get this to pass we can have our User model create the GUIDs in a before_create callback
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
So this is a pretty straight forward example. And it is not a lot of logic on the model. That said it does not mean that it should not be extracted. I had two motivations for extracting this into a module
- The User model was getting large with a lot of different concerns.
- There was also a Patient model that would need GUIDs in the future.
Refactoring to a module
Before you begin a refactor you need to be confident in you tests. So that once you move some code your test should still pass.
Extracting the GUID logic out into a module I get:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
In order for the module to be reusable we cannot reference User.guid_keys directly (line 11). So I make use of the model’s class method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
At this point we have refactored the logic. Our specs should still pass because we have not changed any behavior. Once we confirm this we should then refactor our specs.
Refactoring the specs
When we reuse the module in other models we do not want to rewrite the spec each time. So let’s move the existing spec to a shared example and call it from the user spec.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Now to use that in the models spec we do the following
1 2 3 4 5 6 7 | |
it_behaves_like is how you call RSpec’s shared examples. You can learn more about shared examples here.
Now that we have nicely extracted this we can very easily apply it to other models. Our Patient model needs to interact with a number of third party services also. And again we want to use a unique id for each service.
Let’s start with the spec
1 2 3 4 5 6 7 | |
That was easy. The only difference being the guid keys. Now let’s get it to pass.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
This was a simple example of how you can extract a concern into a module and make it reusable. Patrick will soon be posting about how he extracted more complex code using DCI type roles and contexts.
Code from this example can be viewed here. You can follow the step by step process of how I extracted this logic to a module by looking at each commit.