DRY - Concerns and Services Objects

Rodrigo Souza
Rodrigo Souza August 14, 2024

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