DRY - Concerns and Services Objects
When you start reading about best practices of development you certainly will see the acronym “DRY”. In the development world, it means "Don’t Repeat Yourself". This principle helps us to improve our projects by avoiding - as the name of the principle - repeating the same logic multiple times across the project. Today I would like to present you two Ruby on Rails hand tools that will help to write more maintainable, clean, and efficient code. We're talking about Concerns and Services.
What Concerns and Services Objects are?
In a simple way, concerns are modules that extract sharable code, making it easy to reuse on models, controllers, or any other application part. Services Objects are specialized classes created to accomplish one task that doesn't fit into controllers or models. When I think about concerns, I like to imagine them as drawers. There we can store related items. Service objects are like tools on a superhero tool belt.
Let's code!
First, let's have an example of how concerns work:
Imagine we have two models: Person and Company. Usually, these two models share common information like the address.
Person < ApplicationRecord
def full_street_address
"#{address_line_one} #{address_line_two} #{city} #{state} #{zip_postcode} #{country}"
end
end
Company < ApplicationRecord
def full_street_address
"#{address_line_one} #{address_line_two} #{city} #{state} #{zip_postcode} #{country}"
end
end
There we go. Let's create a concern to share this full_street_address with Person and Company models:
module Addressable
extend ActiveSupport::Concern
def full_street_address
"#{address_line_one} #{address_line_two} #{city} #{state} #{zip_postcode} #{country}"
end
end
Now, we can refactor our classes to something like this:
Person < ApplicationRecord
include Addressable
end
Company < ApplicationRecord
include Addressable
end
You see? Now the full_street_address is shared with the two models and can be accessed on each instance of the Person and Company classes.
Now, let's see an example of how to implement a Service Object to handle a User authentication feature.
class AuthenticationService
def initialize(user_info)
@user_info = user_info
end
def call
# Very complex authentication logic here
end
end
You see? As the authentication logic is housed by the Service Object, you avoid cluttering your controllers or models. This approach will help you even to test the authentication feature regarding the isolation of the logic. But it's a subject for the next posts.
Conclusion
Concerns and Service Objects are awesome tools to DRY our code. However, balancing the use of concerns is important because it can be a little confusing, principally for new developers. Another good attention point is to keep your concerns always straightforward, handling simple logic.
That's it for today.
Do you have implemented concerns and service objects before? Do you have other examples you want to share with us? We would love to hear from you!
Ps. if you have any questions
Ask here