(See this project live at http://stackjobsmap.herokuapp.com/)

Have you ever looked back on your code and thought “Who wrote this! ….<git blame>…oh…it was me.” We are all constantly learning new things and frankly our past self is just not as smart as our current self (or so we think). So today I want to walk through a simple refactor I did on a Rails project I have worked on over the years. (Source code found on my GitHub)

Lets set the stage. This project is my first ever rails project (or MVC like application for that matter). I needed to build a geojson object and what better place to build than right inside a <script> tag in HTML. Have a look.

/* file:app/views/stack_jobs/index.html.erb */
/* Create the GeoJSON objects */
<script type="text/javascript">
  var geojson_jobs = {
      "type": "FeatureCollection",
      "features": [
          <% @stack_jobs.each do |job| %>
          <% scs = split_city_state job["location"] %>
          <% city_loc = find_coordinates(scs[0],scs[1]) %>
          <% if city_loc.nil? then city_loc = find_coordinates("not found", "us") end %> {
              "type": "Feature",
              "properties": {
                  "show_on_map": true,
                  "name": "<%= job[" title "] %>",
                  "link": "<%= job[" link "] %>",
                  "company": "<%= job[" author "][" name "] %>",
                  "city": "<%= job[" location "] %>",
                  "date": "<%= job[" pubDate "] %>",
                  <% categories = job[" category"].presence || ["none"] %>
                  <% categories.is_a?(Array) ? categories = categories : categories = [categories] %>
                  "category": <%= raw categories %>,
              },
              "geometry": {
                  "type": "Point",
                  "coordinates": [<%= city_loc.long %>, <%= city_loc.lat %>]
              }
          },
          <% end unless @stack_jobs.blank? %>
      ]
  };
</script>

Yikes! What is all that doing there and who wants to maintain that! Lets put it in a place that is more suited to logic and building strings…the Model!

I already had a Stack_Job model that was getting the data but that’s all it did. Lets put it to work. (git commit)

# file:app/models/stack_job.rb
def to_geojson
    hash = Hash.from_xml(@xml.to_s)
    items = hash['rss']['channel']['item']
    json = '{ "type": "FeatureCollection","features" : ['
    items.each do | item |
        json << '{"type": "Feature", "properties": {'
        json << '"show_on_map" : true,'
        json << '"name" : "' << item['title'] << '",'
        json << '"link" : "' << item['link'] << '",'
        json << '"company" : "' << item['author']['name'] << '",'
        json << '"city" : "' << item['location'] << '",'
        json << '"date" : "' << item['pubDate'] << '",'
        json << '},'
    end
    json = json[0...-1]
    json << ']}' #ignore the terrible building of json string in ruby...we will get to that later.
    @geojson = json
    puts @geojson
end

There now the model is doing what it’s suppose to and lets take a look and what the view looks like.

# Create the GeoJSON objects
<script type="text/javascript">
  var geojson_jobs = <%= raw stack_jobs.to_geojson %>
</script>

All of that view code is now just a single line. So much easier to read and maintain.

Now onto the problem of the json string building. Ruby has it’s own to_json method that we can use. We just need to update the string builder to use a ruby hash instead.

def to_geojson
    hash = Hash.from_xml(@xml.to_s)
    items = hash['rss']['channel']['item']
    json = Hash.new
    json["type"] = "FeatureCollection"
    features = Array.new
    items.each do | item |
        scs = City.split_city_state item["location"]
        city_loc = City.find_coordinates(scs[0],scs[1])
        if city_loc.nil? then city_loc = City.find_coordinates("not found", "us") end
        feature = { "type"           => "Feature",
                    "properties"     => {
                        "show_on_map"  => "true",
                        "link"         => item['link'],
                        "name"         => item['title'],
                        "company"      => item['author']['name'],
                        "city"         => item['location'],
                        "category"     => ['test'],
                        "date"         => item['pubDate']
                       },
                    "geometry"       => {
                        "type"         => "Point",
                        "coordinates"  => [ city_loc.long, city_loc.lat ]
                       }
                  }
        features.push( feature )
    end
    json["features"] = features
    @geojson = json.to_json
    @geojson
end

Look at that. This code is much easier to maintain (and read). Life gets easier when you use the tools that are given to you. If we ever want to make changes to how we display our data ( say use xml instead of json) we can create a new method and call that instead of to_geojson. Easy as that.

Thanks for reading!