I am building Pondo as an experiment on how to build a functional, modern web application without necessarily using a large front-end framework (e.g. Angular, Ember, React). The idea is to mostly develop applications as one would within the usual semantics of the normal Request-Response cycle. If you’ve used Pjax, or Turbolinks then you’re familiar with this.

As the name might imply, Nitrolinks was inspired by Turbolinks. In fact, some of the code on the Rails-side is shamelessly stolen from Turbolinks and the events fired are named similarly. However, there are two major differences between them. For one, Turbolinks right now only supports GET requests (it will support POST in the future). The other is while Turbolinks replaces the code in the <body> element in its entirety, Nitrolinks uses set-dom to only update the elements that actually changed. This is similar to how the virtual dom works in React.

Pondo is meant to be an experiment and that is why Nitrolinks lives inside its codebase. However, it does make testing and refactoring Nitrolinks harder. The plan is to move the Nitrolinks code to a gem so I can test it separately.

The Plan

  1. Move Nitrolinks out into its own gem
  2. Create tests within Nitrolinks that mirror tests in Pondo
  3. Remove Nitrolinks-specific tests out of Pondo
  4. Rewrite Nitrolinks in ES6

Today’s task will only cover #1.

Creating a Gem

I haven’t created a ruby gem before so this is going to be a learning experience. I am using this tutorial from bundler and this guide on creating an asset gem to help me.

We need to make sure that Pondo still works even when we remove Nitrolinks. Luckily, Pondo has good test coverage. Nitrolinks itself has some tests that were written for it specifically, but we’ll have to leave them on the Pondo codebase for now to make things simpler.

First, we’ll create the gem using bundler:

$ bundle gem nitrolinks-rails

Bundler makes creating gems really easy. Running the previous command creates the files and directories we need to get started. However, we need to clean it up a bit. We don’t need the bin and spec directories right now and some other files. Here’s what it looks now:

├── Gemfile
├── Rakefile
├── README.md
├── lib
│   └── nitrolinks
│       ├── rails
│       │   └── version.rb
│       └── rails.rb
└── nitrolinks-rails.gemspec

Rails Engine

Since we’re using this with Rails, we’ll turn this gem into a Rails Engine. To do that, let’s create an Engine class under nitrolinks.

# lib/nitrolinks/rails/engine.rb

module Nitrolinks
  module Rails
    class Engine < ::Rails::Engine
      isolate_namespace Nitrolinks
    end
  end
end

And we can require this on the nitrolinks module code itself.

# lib/nitrolinks.rb

require "nitrolinks/version"
require "nitrolinks/engine"

module Nitrolinks
end

The next part is just moving the nitrolinks javascript code from Pondo to Nitrolinks itself. We’re going to match the directory structure here. We’re also going to need the set-dom library so we’ll place it at the vendor directory. Here’s what the directory looks now:

├── Gemfile
├── Rakefile
├── README.md
├── app
│   └── assets
│       └── javascripts
│           └── nitrolinks.coffee
├── lib
│   └── nitrolinks
│       ├── rails
│       │   ├── engine.rb
│       │   └── version.rb
│       └── rails.rb
├── nitrolinks-rails.gemspec
└── vendor
    └── assets
        └── javascripts
            └── set-dom.js

On Pondo, let’s add nitrolinks-rails as a dependency. We’ll point it to our local copy for now for testing.

# Gemfile
gem "nitrolinks-rails", path: "../nitrolinks-rails"

After that, we’ll have to remove Pondo’s copy of nitrolinks code. At this point, we need to make sure that everything works as expected. Luckily, we still have our nitrolinks tests on Pondo so this really helps.

# Pondo directory
$ bundle exec cucumber

# TEST RUN OUTPUT #

29 scenarios (29 passed)
148 steps (148 passed)
0m35.374s

Great!

A Temporary Duplication

Between Pondo app itself and Nitrolinks, they share a common js script I wrote called pu (for “pondo utilities”, :/ ). Some of them may need to be moved to nitrolinks. Let’s tally all the calls we make to pu on the nitrolinks side:

  • pu.getContentOfElement
  • pu.isCurrentPageReloaded
  • pu.handleLinkClicks
  • pu.handleFormSubmits
  • pu.merge
  • pu.uuid

That’s a lot! We’ll need to have them on nitrolinks. To simplify things, we’ll copy all of the pu script for now. We’ll manage this duplication of code later.

Rails Engine Part 2

Now that we know that the Nitrolinks scripts are working, it’s time to move the controller-related Nitrolinks code on Pondo to Nitrolinks itself. That way we we’re left with just Pondo code on Pondo.

# lib/nitrolinks/rails/engine.rb

module Nitrolinks
  module Rails

    class Engine < ::Rails::Engine
      isolate_namespace Nitrolinks

      config.nitrolinks = ActiveSupport::OrderedOptions.new
      config.nitrolinks.auto_include = true

      initializer :turbolinks do |app|
        ActiveSupport.on_load(:action_controller) do
          if app.config.nitrolinks.auto_include
            include Controller
          end
        end
      end
    end

  end
end

# lib/nitrolinks/rails/controller.rb

module Nitrolinks
  module Rails

    module Controller
      extend ActiveSupport::Concern

      included do
        before_action :set_nitrolinks_location_header_from_session if respond_to?(:before_action)
      end

      def nitrolinks_request?
        request.headers.key? "nitrolinks-referrer"
      end

      def redirect_to(url = {}, options = {})
        super.tap do
          if nitrolinks_request?
            store_nitrolinks_location_in_session(location)
          end
        end
      end

      protected

      def nitrolinks_location(location)
        response.headers["Nitrolinks-Location"] = location
      end

      private

      def store_nitrolinks_location_in_session(location)
        session[:nitrolinks_location] = location if session
      end

      def set_nitrolinks_location_header_from_session
        if session && session[:nitrolinks_location]
          nitrolinks_location(session.delete(:nitrolinks_location))
        end
      end
    end

  end
end

Okay. Is everything still working? Let’s run some tests:

# Pondo directory
$ bundle exec cucumber

# TEST RUN OUTPUT #

29 scenarios (29 passed)
148 steps (148 passed)
0m29.948s

Awesome!

Publishing the Gem

Now it’s time to publish our gem to rubygems. First we’ll update our README to talk about what Nitrolinks is and warn people because it’s not yet ready for production.

<!-- README.md -->
# Nitrolinks

Nitrolinks is PJAX-like library for making website navigation fast and more like
modern web apps. Nitrolinks is inspired by and borrows heavily from
[Turbolinks](https://github.com/turbolinks/turbolinks) implementation on the
Rails-side.

## Warning! Not Production-Ready
This is an experiment and not yet heavily tested. Please don't use in critical
applications.

Then we commit everything. And add it to our remote repository, which in this case is https://github.com/asartalo/nitrolinks-rails.git

And lastly, run a helpful rake task from Bundler:

$ rake release
Tagged v0.1.0.
Pushed git commits and tags.
Pushed nitrolinks-rails 0.1.0 to rubygems.org.

And we’re done! Now it’s just a matter of testing it out again…

# Pondo directory
$ bundle exec cucumber

# TEST RUN OUTPUT #

29 scenarios (29 passed)
148 steps (148 passed)
0m28.503s

Green! This ends our first task for refactoring Nitrolinks. The next part is figuring out how to add tests to our gem. That will be a post for another day.