cult3

Getting started with Trailblazer Cells

Jun 08, 2016

Table of contents:

  1. The state of Rails View development
  2. What is the benefit of using Cells?
  3. Creating a registration form Cell
  4. Integrating Form Cells with Rails
  5. Creating the Cell class
  6. Adding the template
  7. Testing the Cell
  8. Conclusion

The very first component of Trailblazer was Cells. I briefly mentioned Cells in my review of Trailblazer, but so far in this exploration of Trailblazer we yet to explore them.

Cells encapsulate parts of your UI into components. A Cell is basically a View Model that allows you to define logic for your view using good Object-oriented programming techniques.

This makes it really easy to bundle your User Interface into widgets that can then be composed and tested in isolation.

This is in stark contrast to the Rails way of having an ocean of global helper functions and messy views.

In today’s tutorial we will take our first look at getting started with Trailblazer Cells.

The state of Rails View development

If you have only ever worked on small to medium sized Rails applications, you might be wondering why Cells is such a good idea. Isn’t it an unnecessary level of indirection?

The answer to that question is yes. Yes it is an unnecessary level of indirection for small, or simple view templates.

However, if you crack open just about any large or “legacy” Rails project, you will often find an ApplicationHelper with hundreds of helper methods.

The Rails way of working with views is to have helper functions that are sprinkled through your views.

But it is almost inevitable that these helper functions get out of control. One day you will wake up and find that what was once just a handle of helpful methods is hundreds of random methods that are distributed seemingly at random throughout your views.

What is the benefit of using Cells?

So if the out-of-the-box Rails development tools are not enough, what makes Trailblazer Cells so good?

A Cell encapsulates a part of your UI into a component. This allows you to move the logic from the View into the Cell, and keep the View itself clean and easy to work with.

Cells also reintroduce the Object-oriented methodologies that have been sucked out of Rails View layer. Inheritance suddenly becomes really easy, and we can often remove messy if statements all together.

Finally, Cells are really easy to test. Instead of booting up the entire stack to test the contents of a particular UI component, you can instantiate the Cell in isolation. This gives you a lot of confidence in your View layer, as it saves you from writing brittle tests that are hard to maintain.

There are actually more benefits to using Cells, but for simplicity we will concentrate on these characteristics to begin with!

Creating a registration form Cell

Last week I created the Operation classes for creating a new user. This is your typical registration process that requires the user to enter an email, username, and password.

As a follow on from last week, in today’s tutorial we will look at a very simple Cell example that encapsulates the registration form.

We won’t be covering every bit of functionality of Trailblazer’s Cells in today’s tutorial as there is a lot to cover. But hopefully it will give you a good sense of the philosophy behind the architecture style.

Integrating Form Cells with Rails

By default, Cells are completely decoupled from Rails. You could use Cells in pretty much any type of Ruby application, I’m just using Rails in this example because it’s what I’m most familiar with.

However, Rails has certain expectations and so we annoyingly have to implement extra stuff in our code to make it play nicely.

The first thing I’m going to do is to create a Form Cell class that can be extended by any of my Form Cells in this application:

module Culttt
  module Cells
    class Form < Trailblazer::Cell
      include ActionView::Helpers::FormHelper

      def dom_class(record, prefix = nil)
        ActionView::RecordIdentifier.dom_class(record, prefix)
      end

      def dom_id(record, prefix = nil)
        ActionView::RecordIdentifier.dom_id(record, prefix)
      end

      def form
        @model.contract
      end
    end
  end
end

This implements the dom_class and dom_id methods that Rails will be expecting, as well as includes ActionView::Helpers::FormHelper.

I’ve also provided a helper method for the @model.contract.

If you remember back to What are Trailblazer Contracts?, the Contract is actually just an instance of Reform. We’ll be using this later in the view template, and so I think making it available as form makes more sense.

Creating the Cell class

Now that we have the base Form class setup, we can create the Cell class for the registration form.

Create a new file under your user concept directory called cell.rb:

module User::Create
  class Cell < Culttt::Cells::Form
    def show
      render
    end
  end
end

As you can see, I’m extending from Culttt::Cells::Form, which in turn is extending from Trailblazer::Cell.

This is a really simple example as so we only need to implement the show method.

As we will see in the coming weeks, the Cell class provides a really nice way to implementing View based logic.

Adding the template

Now that we have the Cell class, we also need to create the actual template that will hold the HTML. In my case I’m using Slim, but you can use whatever templating engine you prefer.

Create a new directory under your user concept directory called cell/view and then create a new file called cell.slim:

- if form.errors.any? ul - form.errors.full_messages.each do |msg| li = msg =
form_for form, html: {class: "registration-form"}, url: join_url do |f| div =
f.label :email, class: "qa-email-label" = f.text_field :email, class:
"qa-email-input" div = f.label :username, class: "qa-username-label" =
f.text_field :username, class: "qa-username-input" div = f.label :password,
class: "qa-password-label" = f.password_field :password, class:
"qa-password-input" div = f.submit "Register", class: "qa-submit"

Hopefully you will recognise this as a fairly typical example of a form in Rails. There are only a couple of things to note.

Firstly, the form_for form is using the form helper method that we defined in the base Form class from earlier.

Secondly, the join_url is simply the Rails helper method for the /join URL.

And finally, I’ve included qa- classes on the inputs to help with testing. This idea came from this excellent article More Transparent UI Code with Namespaces.

Testing the Cell

There are a couple of different types of tests you can write for Cells. In this pretty simple example, I’m just going to assert that the correct form elements have been included.

Obviously these tests will pass right now, but as you can probably imagine, views tend to change a lot, and so ensuring that form elements haven’t accidently been “deleted” is actually a valuable regression test to have.

First I’m going to add Capybara to my project. This will allow me to write nice HTML and CSS assertions:

group :test do
  gem 'faker'
  gem 'factory_girl_rails'
  gem 'minitest-rails-capybara'
end

Next, I will include Capybara in the Cell::TestCase test class:

Cell::TestCase.class_eval do
  include ::Capybara::DSL
  include ::Capybara::Assertions
end

And finally I can write my tests:

require 'test_helper'

module User::Create::CellTest
  class CellTest < Cell::TestCase
    controller JoinController

    test 'has correct markup' do
      html =
        concept(
          'user/create/cell',
          User::Create::Operation::Default.present({})
        ).()

      html.must_have_selector('form.registration-form')
      html.must_have_selector('label.qa-email-label')
      html.must_have_selector('input.qa-email-input')
      html.must_have_selector('label.qa-username-label')
      html.must_have_selector('input.qa-username-input')
      html.must_have_selector('label.qa-password-label')
      html.must_have_selector('input.qa-password-input')
      html.must_have_selector('input.qa-submit')
    end
  end
end

There is a couple of interesting things to point out in this test.

First we need to set the controller to JoinController because Rails will require it.

Secondly we (ironically) use the concept helper method to generate the HTML from the cell.

Thirdly, when passing the Operation, I use the present method. I don’t want to run the Operation before it has been passed to the Cell.

Finally, as you can see, in this test I’m simply asserting that the correct qa- classes are in the generated HTML.

Conclusion

One of my favourite aspects of Trailblazer is definitely Cells. I really hate having messy logic in my views and so I’m definitely a big fan of View Models. I find it inevitable that you will need this type of abstraction in larger projects.

I also really love how easy it is to test Cells. I can simply generate the HTML and make the assertions I require. Previously I probably would of just not bothered to test the contents of a view because it would be too much hassle.

In an upcoming tutorial I’m going to show you how I used Cells to clean up what would of been a really messy view situation.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.