Here today...Still here today: Using Gon with Javascript and Rails

As much as we all love Ruby (and Rails!), I'm hard pressed to think of an application that uses it exclusively. One of the most common languages to incorporate into a Rails app is Javascript.

Javascript determines the behavior of webpages. Many would consider it to be the lingua franca of the web and whether you like it or not, it's here to stay. Many Rubyists use Javascript on the front end of an application to do things like enhance the user's experience with Ajax, appear/disappear certain elements of a page based on information about the user.

For example, you may have a blogging app that allows users to comment on blog posts. User's can add comments and see them dynamically appear on the page as they submit them. This would require sending an AJAX post request to the controller-side of your application. The controller would respond by creating the comment and a Javascript function would be responsible for magically appending to the page.

But, what if we need to give our JS functions access to more complicated data? In a recent project, I used Javascript's amazing D3 library to visualize data about repair violations in New York City Public Housing. D3 allows you to build functions that operate on data sets and visualize them in super cool and beautiful ways. You can learn more about D3 visualizations in my post on the D3 sunburst diagram (ooooooh--it's as pretty as it sounds).

How can a Javascript function gain access to data that I've stored in my database? In a Rails application, when I dip into my database for a dataset, I am creating Ruby objects. How can I feed, for example, a hash of Ruby objects, into a D3 Javascript function?

Enter the Gon gem!

What is Gon?

Gon is a gem that allows you to send data to your Javascript files. This means you can avoid having to call long, complication JS functions in views and using ERB to parse Ruby within these functions.

Why use Gon?

With Gon, you can store Ruby objects in a special kind of variable that can then be accessed by any of the Javascript files in your Rails asset pipeline.

To continue with our example from earlier--the funciton that builds a D3 sunburst diagram is expecting to operate on a hierarchical data structure. This means it expects an collection of parent and children objects. In the context of this application, we are creating a diagram that shows NYCHA repair issues by category (such as fire hazard, plumbing, infestation) and subcategory (blocked fire escape, broken smoke detector, etc). Each violation, after being pulled down from the NYC Open Data database (learn more in my post on working with this API), is stored in a Postgresql database and each record is tagged with a category and subcategory. Knowing this, it's easy to write a query that will find us all of the violations we want to render in our diagram.

Building our data structure

In our Violation class, we build a method that finds us the records we need and maps them into the hierarchical, or nested, data structure that the sunburst diagram function is expecting:

def self.get_sunburst_data(boro_id)

   Violation.where("boro_id = ?", boro_id).group_by { |v| v.keyword }.map do |keyword, violations|
        {
          name: keyword,
          children: violations.group_by{ |v| v.subcat }.map do |subcat, violations|
            {
              name: subcat,
              count: violations.count
            }
          end
        }
      end

  end

  def self.get_bar_chart_data(boro_id)
     Violation.where("boro_id = ?", boro_id).group_by { |v| v.keyword }.map do |keyword, violations|
        {keyword: keyword, value: violations.count}
      end

  end
  
This builds us an array of hashes. Each hash has a key, `children`, that points to another array of hashes. Thus we have our categories and subcategories nested in the appropriate manner. Our resulting collection looks like this:
[{:name=>"Insect/rodent infestation",
    :children=>
       [{:name=>"mice", :count=>30},
        {:name=>"insect", :count=>14},
        {:name=>"rat", :count=>6},
        {:name=>nil, :count=>2}]},
 {:name=>"Window",
    :children=>
       [{:name=>"broken window guards", :count=>27},
       {:name=>nil, :count=>49},
       {:name=>"broken glass", :count=>2}]},
 {:name=>"Misc.", 
    :children=>
       [{:name=>nil, :count=>262}]},
 {:name=>"Plaster/Paint", 
    :children=>
       [{:name=>nil, :count=>313}]},
 {:name=>"Fire Hazard", 
    :children=>
       [{:name=>"smoke detector", :count=>51}]},
 {:name=>"Broken Lock", 
    :children=>
       [{:name=>nil, :count=>43}]},
 {:name=>"CO Detector", 
    :children=>
       [{:name=>nil, :count=>46}]},
 {:name=>"Bathroom", 
    :children=>
       [{:name=>nil, :count=>68}]},
 {:name=>"Water Leak", 
    :children=>
       [{:name=>nil, :count=>49}]},
 {:name=>"Water/Plumbing", 
    :children=>
       [{:name=>nil, :count=>15}]},
 {:name=>"Mold", 
    :children=>
       [{:name=>nil, :count=>22}]},
 {:name=>"Electrical", 
    :children=>
       [{:name=>nil, :count=>1}]}]

But this is a collection of Ruby objects. Here is where Gon comes in to help us carry these Ruby objects into our Javascript file.

How does Gon work?

In the corresponding action of our Violations controller, we call on our get_sunburst_data method to build the above collection. We set the result of this method call equal to gon.sunburst_data.

And that's it! Now, inside our sunburst.js file, we can call our createSunburst function with an arguments of gon.sunburst_data, and the above data is available to for our D3 function to operate on.

Okay, I lied. That's not quite it. There are two more things you need to do to allow your application to use the Gon gem.

Setting up Gon

  1. Add gem 'gon' to your gem file and then run bundle update.

  2. Add the following line to your application.html.erb file (in the layouts subdirectory of your views directory). <%= include_gon %>

And that's (really) it! You're ready to pass around Ruby objects like a boss. Happy coding.

subscribe and never miss a post!

Blog Logo

Sophie DeBenedetto

comments powered by Disqus
comments powered by Disqus