Lately I have been working with Abi in a way to separate the core of an application in small modules using rails just as a platform that uses those small modules/programs to build a real web application.

These modules are programs that mostly work for data transformation (although sometimes also to interact with external services).

I will use an example to explain a little more about the things that these small programs should do.

Example

Use case specification

As a user I can edit my profile

  • I click the link “Profile”
  • I modify my name to “Benito”
  • I select my country as “Mexico”
  • I select my preferred language as “Español”
  • I click the submit button
  • I see my name as Benito
  • I see my preferred language as “Español” and the page is in Spanish
  • I see my country as México (with accent)

The system should validate the presence of name, country and preferred language.

Proposed solution

To solve this problem we are going to build a Profiles module that will be responsible for implementing the application logic of this use case.

The first part of the solution needs an edit action in the controller and a form. So we are going to write a method Profiles.profile_for that will return the data that is needed in the form.

class ProfilesController < ApplicationController
  def edit
    @profile = Profiles.profile_for(current_user)
  end
end
<%= simple_form_for @profile, as: :profile, url: profile_url do |f| %>
  <%= f.input :preferred_language, collection: @profile.language_options %>
  <%= f.input :name %>
  <%= f.input :country %>
  <%= f.button :submit %>
<% end %>

A first specification for Profiles.profile_for could be:

modules Profiles
  describe 'profile for user' do
    it 'has the current prefered language'
    it 'has the current name'
    it 'has the current country in the current language'
    it 'has the language options'
  end
end

We need to show the country in the current language, so we are going to need something like:

class ProfilesController < ApplicationController
  def edit
    @profile = profile_for(current_user)
  end

  private

  def profile_for(user)
    Profiles.profile_for(user, locale: I18n.locale)
  end
end

After that we can continue with the update action that will update the information in the profile, validates the updated profile and then store the new information in the database. We are going to create a method Profiles.update_profile(profile, params) that will return a new profile with the new information that was given.

Our profile object should respond to valid? and errors. This errors method should return something compatible with the form builder. The profile should be able to return a data structure with the information to update the user record, we will call this method profile.to_record_attributes.

So now we can have an update action as:

class ProfilesController < ApplicationController
  def update
    @profile = Profiles.update_profile(profile_for(current_user), profile_params)

    if @profile.valid?
      current_user.update_attributes(@profile.to_record_attributes)
      redirect_to edit_profile_path
    else
      render :new
    end
  end
end

Now the specification for the profile needs an update.

modules Profiles
  describe 'profile for user' do
    it 'has the current prefered language'
    it 'has the current name'
    it 'has the current country in the current language'
    it 'has the language options'
    it 'is not valid without a name'
    it 'is not valid without a country'
    it 'is not valid without a prefered language'
    it 'can be transformed to record attributes'
  end

  describe 'update profile' do
    it 'updates the name'
    it 'updates the preferred language'
    it 'updates the country'
  end
end

How hard would be to implement this module?

As you can see, we are avoiding to work with ActiveRecord inside this module. This is because is simpler to just focus on data transformation, and because we trust that ActiveRecord will do its job updating the record attributes.

Extract this kind of modules will help you to make your Rails app simpler. And also will be easier to maintain because your test will run fast (really fast… maybe 600 tests in less than a second) and you will be able to make changes without fear of breaking something.