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.
As a user I can edit my profile
The system should validate the presence of name, country and preferred language.
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.
I send an email each week, trying to share knowledge and fixes to common problems and struggles for ruby on rails developers, like How to fetch the latest-N-of-each record or How to test that an specific mail was sent or a Capybara cheatsheet. You can see more examples on Most recent posts or All post by topic.