4 min read
Next in trending

Rails Form Objects

When I started learning Rails, one the first rules of thumb I’ve stumbled upon was “fat model, skinny controller” rule.

Rails Form Objects


When I started learning Rails, one the first rules of thumb I’ve stumbled upon was “fat model, skinny controller” rule. Being enthusiastic for new things as I am, I immediately took it for face value. Which obviously led to bloated models and spaghetti callbacks.

Time passed and I was happy with the things they were until I’ve seen Gary Bernhardt talking about Service Objects in his Destroy All Software screencast. At the same time I’ve heard Ruby Rogues podcast where the crew of the podcast was talking about Form Objects (I think it was episode 104).

I’ve bit the bullet and tried both and here are my thoughts on them. I’ll start off with Service Objects: the technique is based on capturing services (or I prefer to call them processes — chains of events logically coupled together) in a plain ruby object. I won’t talk about advantages of using Service Object Pattern and will cut down to the point: the bad thing about it is that it encapsulates only single process. If you want to capture another process you have to create a new Object. In case you have multiple controllers with each controller having few different actions you will end up with huge amount of Service Objects all doing roughly the same thing.

Form Objects to the Rescue

Form Object is very similar to the Service Object; the only difference is the philosophy beneath it. Form Object mimics the HTML form being submitted to the server. It is yet another layer placed between your view and controller taking some responsibilities from both (and from model as well). Here is the list of responsibilities I give to Form Objects in my projects:

  1. validation of data,
  2. notifications (both via e-mail and in application),
  3. audit logging (it gives me bad dreams since there is no clean way to do it via models),
  4. handling all the CUD (as in CRUD) actions on the object,
  5. creating nested records (for which you would probably use nested attributes).

As you may have guessed, I mimic almost every model in my project with a Form Object. This way I can keep both my controllers and models free of repetitive logic that simply doesn’t belong there.

Less Words, More Code

There are many ways to implement the Form Object pattern. Here is the way I do it.

First of all lets start with creating the Form Object:

# lib/form_object.rb
class FormObject
include ActiveModel::Model
  def initialize(args={})
args.each do |k,v|
instance_variable_set(“@#{k}”, v) unless v.nil?
end
end
end

In the project I am currently working on I’ve few more methods in the object and few concerns included; I’ve even defined CUD (as in CRUD) methods in the main Form Object (since they are reused in every other form).

Now we can move on to creating a Form Object which will inherit from the Form Object:

# app/forms/todo_form.rb
class TodoForm < FormObject
attr_accessor :todo, :comment
  validates :todo, presence: true
  def self.create_new_todo
if valid?
# Put all logic related to creation of new Todo
# in here — log actions, dispatch e-mails etc
create_todo
end
end
  private
def create_todo
# You can even encapsulate the acutual creation of
# the object into it's separate private method; if
# it's needed of course
Todo.new(...)
end
end

Another thing that we need to do before we are finished — to change controller a little bit:

# app/controllers/todo_controller.rb
class TodoController < ApplicationController
def create
TodoForm.create_new_todo(todo_params)
end
  private
def todo_params
params.require(:todo).permit(:todo, :comment)
end
end

Now there is last small concern left and that is usage of Form Object in your views; I have created a small helper which renders form for Form Object. With just a little patience and Rails API you will build your own in no time.

Final thoughts

In my current project I’ve ended up using both techniques mentioned earlier — for one-time actions such as user registration or password reset I prefer using Service Objects. For actions that are being repeated through entire model I prefer Form Objects.

I think using either pattern is a great way to clean up and organise your code. After using both of them for a while I came to conclusion that Form Objects are better suited for large projects where Service Objects are ideal for small ones (in terms of processes and object behind the code) or for actions that are rare.



Shameless self-promotion

If you are looking for a place to start learning Ember.js you can try the book I’ve wrote — Ambitious Ember Applications which is a comprehensive Ember.js tutorial. During the course of the book you will build a simple application to help you understand concepts behind Ember.js. After finishing this book you should have enough knowledge to start building complex Ember.js applications.