cult3

Using Form Objects in Ruby on Rails with Reform

Feb 10, 2016

Table of contents:

  1. What is the responsibility of the Form Object?
  2. Installing Reform
  3. Writing the tests
  4. Creating the Reform Form Object
  5. Custom behaviour on save
  6. Dealing with nested forms
  7. Conclusion

Form Objects have become a very well recognised pattern in the world of Ruby on Rails.

We have previously looked at creating our own Form Objects in using Active Model and Virtus (Using Form Objects in Ruby on Rails.

A lot of Rails developers like to write their own Form Object class that can be used as a base for all of the forms in the application.

But, with it being such a well recognised pattern, there are existing Open Source projects that look to handle this problem for us.

Open Source libraries tend to be better designed than hand-rolled solutions as they usually cover a lot of edge cases that you won’t think about at the start of your project.

Using an Open Source library will also save you a lot of headache if you are a newbie developer and you’re just trying to get the damn thing to work!

One good Open Source option is Reform, and so, in today’s tutorial we will look at creating Form Objects using Reform.

What is the responsibility of the Form Object?

As a quick recap we will look at the responsibility of the Form Object and why we need this extra layer during the request.

Firstly, A common convention of Ruby on Rails is to have the validation inside the model.

However, a typical web application will require certain specific validation rules under particular circumstances.

For example, when the user first signs up, you might want them to type their chosen password in twice for confirmation.

However, checking that a password has been typed correctly has nothing to do with the business rules of the User model and will only be required when the user first signs up to the application.

Secondly, you will often want to create multiple models at the same time during a request.

For example, when a user signs up, you might also need to create an Account model object too.

If you try and force one model to be responsible for creating another model, you will end up with a nested mess of code.

A Form Object sits in the middle of this problem to coordinate the validation of form specific data and the creation of the required models for the request.

This encapsulation provides a nice and easy way to capture the business logic and responsibility into one single object that can be used in the controller and the view.

So with that little explanation out of the way, lets to start creating Form Objects!

Installing Reform

As I mentioned in the introduction to this post, I’m going to be using Reform for this project.

So the first thing we need to do is to add it to the project:

gem 'reform'
gem 'reform-rails'

Next, run the following command from Terminal:

bundle install

We’re going to need a directory to store all of the forms for the application. Create a new forms directory under app and another one under test.

Writing the tests

So now that we have Reform added to the project we can start adding the first form.

However, before I actually create the form, first I will sketch out the requirements as tests. The first form I will be making will be for creating a new article.

Create a new file called article_form_test.rb under the test/forms directory:

require 'test_helper'

class ArticleFormTest < ActiveSupport::TestCase
  def setup
    @model = Article.new
    @form = ArticleForm.new(@model)
  end
end

The first thing I’m going to do is to create the ArticleForm in a setup method so I don’t have to repeat myself before each test.

Next I’m going to write a couple of validation-type tests for the properties of the form. If I were doing strict TDD I would go through the motions for each individual test, but to be honest, I think the ceremony of TDD is a bit of a waste when you are doing simple stuff like this.

test 'should require title' do
  @form.validate({})

  assert_includes(@form.errors[:title], "can't be blank")
end

test 'title should be unique' do
  @form.validate('title' => 'Hello World')

  assert_includes(@form.errors[:title], 'has already been taken')
end

test 'should require markdown' do
  @form.validate({})

  assert_includes(@form.errors[:markdown], "can't be blank")
end

test 'should require published_at' do
  @form.validate({})

  assert_includes(@form.errors[:published_at], "can't be blank")
end

test 'should require user' do
  @form.validate({})

  assert_includes(@form.errors[:user], "can't be blank")
end

If you run these tests you should see them all fail. With failing tests in place we can start to build the form.

Creating the Reform Form Object

The first thing we need to do is to add a new file called article_form.rb under the app/forms directory:

class ArticleForm < Reform::Form
end

As you can see from this example, your Form Objects should inherit from Reform::Form.

Next we need to define the properties of the form:

class ArticleForm < Reform::Form
  property :title
  property :markdown
  property :published_at
  property :user
end

The Form Object is responsible for validation and so we can define some validation rules:

validates :title, presence: true, unique: true
validates :markdown, presence: true
validates :published_at, presence: true
validates :user, presence: true

Reform recommends that you use it’s non-writing uniqueness validation. To do that we need to add the following line to the top of this file:

require 'reform/form/validation/unique_validator.rb'

Now if you run those tests from earlier again, you should see them all pass.

Custom behaviour on save

A couple of weeks ago I looked at converting Markdown to HTML (Rendering Markdown and HTML in Ruby). In this new version of Culttt I want to write my articles in Markdown and then have the application automatically convert them to HTML on save.

Reform makes it really easy to define your own custom save behaviour by simply implementing the save method on the form:

def save
  sync
end

The first thing we need to do is call the sync method from the parent class. This will automatically call the accessor methods on the model with each of the properties of the form.

Next we can define our custom behaviour, and then call save on the model.

First I want to automatically generate the slug of the article from the title:

model.slug = title.to_url

To convert a string to a slug, I’m using the Stringex gem that adds some useful extensions to Ruby’s String class.

Next I want to generate the HTML for the article from the given Markdown:

model.html = Render::HTML.new.render(markdown)

And finally I will call save! on the model:

model.save!

We can test that this is working correctly with the following test:

test 'should create the article' do
  @form.validate(
    title: 'My first blog post',
    markdown: '# My first blog post',
    published_at: Time.now.to_s,
    user: User.first
  )

  assert(@form.save)
end

Dealing with nested forms

The example we have looked at so far has been fairly simple. However, in the real world, things are never that simple.

A common requirement when dealing with forms is that multiple models are created at the same time. This can make things a lot more complicated.

Fortunately, Reform has been written in such a way that it makes dealing with nested models really easy.

For example, lets rewrite the test from above like this:

test 'should create the article' do
  @form.validate(
    title: 'My first blog post',
    markdown: '# My first blog post',
    published_at: Time.now.to_s,
    user: User.first,
    tags: [{ name: 'Code' }]
  )

  assert(@form.save)
  assert_equal(1, @model.tags.count)
end

In this version of the test I’ve added a collection of tags that should be added to the article. In my database, each Article is related to one or more Tag objects through a has has_many :through relationship.

So now we also need to deal with finding or creating each Tag on save and then associating them with the new Article.

The first thing we need to change is to add a collection to the Form Object:

collection :tags, populate_if_empty: :populate_tags! do
  property :name
  property :slug
end

Here I’m defining the tags collection and I’m telling Reform to call the populate_tags! method if the collection is empty.

Next we can define that method:

def populate_tags!(options)
  Tag.find_by(name: options[:fragment][:name]) or
    Tag.new(slug: options[:fragment][:name].to_url)
end

In the populate_tags! method we can attempt to find an existing tag with the given name and return it, otherwise we can create a new tag.

If you run those tests again, you should see them all pass!

Conclusion

Today’s tutorial has been a fairly simple example of dealing with the requirements of a Form Object.

I often find with this type of functionality, you roll your own solution because things are simple in the beginning. But over time, your hand-rolled solution starts to get more and more complicated as the requirements of additional forms drift from your early forms.

That is why I usually try to use an Open Source gem like Reform.

Dealing with the implementation details of how the Form Objects work in my application will have very little impact on the success of my application.

So instead of wasting time writing my own solution, I think my time is better spent worrying about the business logic of my application, and letting the wonderful world of Open Source worry about the edge cases I will no doubt encounter as my application continues to grow!

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.