Blog

Creating a Gem for Rendering Doge Serialized Object Notation

June 13, 2014    Rails

You may have seen the announcement regarding the creation of Doge Serialized Object Notation (DSON). In this post I bring some of that magic to Rails.

Since I first read about Doge Serialized Object Notation, for some reason I wanted to do this:

respond_to do |f|
  f.html
  f.dson { render dson: @apps }
end
so such "id" is 6. "published" is yes. "title" is "SaaS App With Rails", "description" 
is "In this series, I will show how to build a multi tenant SaaS (similar to Harvest 
or Basecamp) with Ruby on Rails and Postgresql."! "repository" is "saas-timetracker"?
 "created_at" is "2013-12-28 03:13:42 UTC"? "updated_at" is "2014-01-16 00:56:39 
UTC". "coming_soon" is no. "image_file_name" is "saas-series.png", "image_content_type"
 is "image/png". "image_file_size" is 72206? "image_updated_at" is "2014-01-16 00:56:39 
...

This post describes the creation of the very_dson gem, which makes this happen.

Start with the README

When beginning to develop a gem, a great way to get started is to write the README. Specifically, a "quick start" portion of it. This will give us a really clear goal to work towards. So here we go:

Add the gem to your Gemfile:

gem "very_dson"

Run bundle install.

In your controllers, you can now render DSON:

class DogeController < ApplicationController
  def index
    render dson: { "foo" => ["bar", "baz", "fizzbuzz"], such: true }
  end
end

This will output the following:

such "foo" is so "bar" also "baz" also "fizzbuzz" many? "such" is yes wow

Creating the plugin

Note: A number of the concepts demonstrated here are covered in much more detail in Crafting Rails 4 Applications by José Valim. I highly recommend you check out that book - I've learned quite a bit from it.

I'm going to use the Rails plugin generator to create the basic structure for the gem:

rails plugin new very_dson

If you've ever taken a dive into one of your favorite gems (if not, I suggest you do!), this directory structure probably looks quite familiar:

├── Gemfile
├── Gemfile.lock
├── MIT-LICENSE
├── README.rdoc
├── Rakefile
├── lib
│   ├── tasks
│   ├── very_dson
│   │   └── version.rb
│   └── very_dson.rb
├── test
│   ├── dummy    <-- this contains a complete Rails application for testing
│   ├── test_helper.rb
│   └── very_dson_test.rb
└── very_dson.gemspec

Now this is actually not very many files; if you're building your first gem I suggest you take a few minutes to open each file and check it out.

Now, the generator has created modules named VeryDson. I'd really like the capitalization to be different (VeryDSON), so I'm going to go ahead and change those module names appropriately.

Next up, we can delete the default README, and add our own.

Then, we'll need to add our information to the very_dson.gemspec file. Here's what mine looks like:

very_dson.gemspec
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$:.push File.expand_path("../lib", __FILE__)

# Maintain your gem's version:
require "very_dson/version"

# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
  s.name        = "very_dson"
  s.version     = VeryDSON::VERSION
  s.authors     = ["Ryan Boland"]
  s.email       = ["[email protected]"]
  s.homepage    = "https://github.com/bolandrm/very_dson"
  s.summary     = "very_dson-#{VeryDSON::VERSION}"
  s.description = "DSON renderer for Rails"
  s.license     = "MIT"

  s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.markdown"]
  s.test_files = Dir["test/**/*"]

  s.add_dependency "rails", "~> 4.0.0"

  s.add_development_dependency "sqlite3"
  s.add_development_dependency "pry"
end

You'll note that our dependencies are specified at the bottom of this file. Generally, if you want to add dependencies to your gem, you'll want to put them in here instead of the Gemfile.

It's worth noting that you should try to keep your gem's dependencies to a minimum. This is because anyone who includes your gem in their project will also need to take on all of your gem's dependencies.

The first two tests

The rails plugin generator includes a sanity test, just to check that everything is set up reasonably:

test/very_dson_test.rb
1
2
3
4
5
6
7
require 'test_helper'

class VeryDSONTest < ActiveSupport::TestCase
  test "truth" do
    assert_kind_of Module, VeryDSON
  end
end

This is using MiniTest, and can be run with rake test. This test should pass!

The next test, the first "real" one, will be an integration test that is based on the README example. We are going to use the Rails application that was generated in the tests/dummy directory to insure that our gem is working properly.

test/integration/render_doge_test.rb
1
2
3
4
5
6
7
8
9
10
11
12
require "test_helper"

class RenderDogeTest < ActionDispatch::IntegrationTest
  test "renders dson" do
    get "doge.dson"

    output = response.body

    assert_match /such "foo" is so "bar" (and|also) "baz" (and|also) "fizzbuzz" many(\.|!|,|\?) "such" is yes wow/,
                 output
  end
end

Note, because DSON is inherently random (arrays can be delimited by and or also, and object properties can be delimited by ., ,, !, or ?), I need to match the output to a regular expression.

Running the test results in this:

  1) Error:
RenderDogeTest#test_renders_dson:
ActionController::RoutingError: No route matches [GET] "/doge.dson"

We'll need to add the route to our dummy Rails application:

tests/dummy/config/routes.rb
1
2
3
Dummy::Application.routes.draw do
  get "doge", to: "doge#index"
end

And then we'll add our controller:

class DogeController < ApplicationController
  def index
    render dson: { "foo" => ["bar", "baz", "fizzbuzz"], such: true }
  end
end

Running the tests again, we get this:

  1) Error:
RenderDogeTest#test_renders_dson:
ActionView::MissingTemplate: Missing template doge/index, application/index

Now we'll actually need to add our custom DSON renderer.

Adding the renderer

Rails has a few methods for adding your own mime types and renderers. I'm going to show the code and then walk through it.

lib/very_dson/dson_renderer.rb
1
2
3
4
5
6
7
8
module VeryDSON
  Mime::Type.register_alias "text/html", :dson

  ActionController::Renderers.add :dson do |dson, options|
    self.content_type ||= Mime::DSON
    DSON.stringify(dson)
  end
end

The first line adds a mime type alias for DSON. Really, if we wanted, we could add a new mime type - something like application/dson. This isn't really great for our little demo though, because the browser won't recognize the type and will just attempt to download it. Thus, we're just going to use a type of text/html.

After that, we'll go ahead and add the renderer. The block receives two arguments, the first is the object passed to the render call, and the second is any accompanying options.

Our renderer is very simple. We'll set the mime type if it hasn't been set already, and then pass along the input to our DSON.stringify method (which we haven't created yet). This object/method will be responsible for converting the object passed to the render call into DSON.

Finally, we just need to require the renderer in our lib/very_dson.rb file:

lib/very_dson.rb
1
require "very_dson/dson_renderer"

After that, we get the following test error:

  1) Error:
RenderDogeTest#test_renders_dson:
NameError: uninitialized constant VeryDSON::DSON

The DSON serializer

Now we need to introduce the VeryDSON::DSON object, which will be responsible for taking Ruby objects and converting them to textual, DSON output.

lib/very_dson/dson.rb
1
2
3
4
5
6
7
module VeryDSON
  class DSON
    def self.stringify(value)
      # convert the input object to Doge serialized object notation
    end
  end
end

I'm not going to include the actual implementation here, because I did it quickly and it wasn't very good. If you're interested, you can check it out on Github. Anyway, that's not the point of this article.

Finally, we need to require this file as well:

lib/very_dson.rb
1
2
require "very_dson/dson"
require "very_dson/dson_renderer"

Now all of our tests are passing!

Run options: --seed 26256
# Running tests:
..

Finished tests in 0.123435s, 16.2029 tests/s, 24.3043 assertions/s.
2 tests, 3 assertions, 0 failures, 0 errors, 0 skips

I also added a bunch of tests to ensure that the DSON.stringify method works reasonably well.

Publishing the gem to RubyGems.org

Publishing your gem is surprisingly simple.

The first thing you'll want to do is make sure you've initialized a git repository. Since your gem is going to be open source anyways (you're publishing it to RubyGems), it's probably a good idea to push it up to Github as well.

After that, you'll want to head over to https://rubygems.org/ and create an account.

With your account credentials in hand, go ahead and run rake release. This will create the git release tag for you, and push your gem up to RubyGems. The first time you release a gem, you'll likely need to do an additional step for authentication.

If you've done everything correctly, it should look something like this:

very_dson (master) →  rake release

very_dson 0.0.3 built to pkg/very_dson-0.0.3.gem.
Tagged v0.0.3.
Pushed git commits and tags.
Pushed very_dson 0.0.3 to rubygems.org.

That's it!

Thanks for reading!

I hope that the process of creating a gem was a lot easier than you thought!

If you enjoyed this post, please use the form below to sign up for the newsletter. Thank you!