Strongly Emergent

What comes from combining humans, computers, and narrative

First Date With Ruby

Last night I took a notion into my head and wound up spending a solid few hours with Ruby. I’m happy with how that went! There’s some first-time-with-a-new-language friction, but nothing out of the ordinary. Here’s what I came up with, and afterwards, why I chose that and what I think it shows that I accomplished that.

require 'cgi'
require 'json'
require 'net/http'
require 'uri'

module Jekyll class MusicLink < Liquid::Tag

def initialize(tag_name, contents, tokens)
  super
  @contents = contents
end

def render(context)
  @affiliateCode = &#39;secret&#39; # Fill in yours!
  page = context.environments.first[&#39;page&#39;]
  if page[&#39;music-artist&#39;] &amp;&amp; page[&#39;music-track&#39;]
    music_url, music_string = getMusic(page[&#39;music-artist&#39;], page[&#39;music-track&#39;])
    return %(&lt;span class=&#39;music-box&#39;&gt;Music: &lt;a class=&#39;music-link&#39; href=&quot;#{music_url}&quot;&gt;#{music_string}&lt;/a&gt;&lt;/span&gt;)
  else
    return %()
  end
end

def makeItunesTarget(artist, track)
  iTunesURL = URI(&quot;https://itunes.apple.com/search&quot;)
  iTunesParams = {
    :country =&gt; &quot;us&quot;, :media =&gt; &quot;music&quot;,
    :limit =&gt; &quot;5&quot;, :entity =&gt; &quot;musicTrack&quot;,
    :term =&gt; artist + &quot; &quot; + track,
  }
  iTunesURL.query = URI.encode_www_form(iTunesParams)
  return iTunesURL
end

def getFromItunes(iTunesURL)
  http = Net::HTTP.new(iTunesURL.host, iTunesURL.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER

  request = Net::HTTP::Get.new(iTunesURL.request_uri)
  response = http.request(request)
  if response.code == &quot;200&quot;
    jsonResponse = JSON.load(response.body)
    if jsonResponse[&#39;resultCount&#39;] == 0
      jsonResponse = false
    end
  else
    jsonResponse = false
  end
  return jsonResponse
end

def makeAnchorFromItunesData(iTunesJSON)
  unless iTunesJSON
    return iTunesJSON, iTunesJSON
  end

  primaryResult = iTunesJSON[&#39;results&#39;].first
  if @affiliateCode
    urlRegex = /(https:\/\/itunes\.apple\.com\/[^?]+\?[^&amp;]+).+/
    urlReplacement = &#39;\1\2&amp;partnerId=30&amp;siteID=&#39; + @affiliateCode
    affiliatedTrackUrl= primaryResult[&#39;trackViewUrl&#39;].sub(urlRegex, urlReplacement)
    primaryResult[&#39;trackViewUrl&#39;] = affiliatedTrackUrl
  end

  anchorURL = primaryResult[&#39;trackViewUrl&#39;]
  anchorString = CGI.escapeHTML(&quot;%s - %s&quot; % [primaryResult[&#39;artistName&#39;], primaryResult[&#39;trackName&#39;]])
  return anchorURL, anchorString
end

def getMusic(artist_name, track_name)
  music_url, music_string = makeAnchorFromItunesData(getFromItunes(makeItunesTarget(artist_name, track_name)))
  return music_url, music_string
end

end end

Liquid::Template.register_tag('music', Jekyll::MusicLink)

This creates a new Liquid tag, {% music %}, which can be inserted in page templates. I added it to my footer.html after the byline, timestamp, and categories. The tag checks whether the post’s YAML front-matter has data for a musician and a track name. If the post has that data, the plugin attempts to create a link to the iTunes Store for the given track. With makeItunesTarget() it puts together a URL that is a query to the iTunes Store Search API, with getFromItunes() it loads the query URL and hands off the response to the standard library’s JSON parser, and with makeAnchorFromItunesData() it takes the first search result and generates text to use for an <a> tag and a URL to use for the tag’s href attribute (if you have an affiliate code for the iTunes store, it’ll be inserted). Finally, there’s a convenience function, getMusic(), that just composes the previous three.

Part of why this worked well is that it’s another project with limited scope: I had a specific objective in mind, so I was able to keep moving gradually towards it. However, that limited scope was a way of making progress towards the broad goal of “learn Ruby” and also took on the medium-scope goal of “learn the iTunes Store Search API.” As a practical matter, learning to work with other people’s APIs, whether they’re libraries, services, or daemons, is an important skill for a working programmer; toy projects that include cultivating that skill are good uses of my time. Learning new languages is also a career-long thing: for all the talk of Lisp being “the hundred-year language,” no-one now working as a programmer will be programming in just one language for the rest of their days. There are shell scripts and libraries and wrappers: there is a fragmented world that despite the friction of fragmentation, would not actually be better-served by a language monoculture. In addition, there are plenty of exciting things out there whose roots are in Ruby, so I was enthusiastic about picking up a smattering of Ruby.

I’m definitely fond of Ruby so far. Part of this is because I’m getting to the point where I’m seeing parallels with other languages and able to make good guesses about how a new language will behave. I was able to guess from reading source “oh okay, Ruby is one of the languages where the return value of a function, if not explicit, is the value of the last statement evaluated in its body,” was pleasantly surprised that it has the same tuple-packing return-multiple-values feature as Python, and noticed “oh hey neat, there’s a Scheme-like function!() naming convention for functions that mutate their parameters.” So that’s all good stuff.

Part of choosing Ruby, too, is that I’m currently blogulating via Octopress, which is built on Ruby. Most of why I chose it is that Wordpress is awful (on the axes I care about), but now that I’ve chosen it, I want to have a grasp of how it works. That means learning Ruby and tinkering—which I’m looking forward to.

As a supplemental note, if this stuff sounds to you like a good attitude for a programmer to have, you should hire me.