Robert Speicher

Ruby’s Extended Regular Expressions

I recently refactored the custom Markdown parsing for Gitlab, and I thought I’d explain my primary reason for doing so:

1
2
3
4
  # match     1    2 3               4     5            6
  text.gsub!(/(\W)?(@([\w\._]+)|[#!$](\d+)|([\h]{6,40}))(\W)?/) do |match|
    ...
  end

Rule of thumb: if you ever find yourself adding a comment above the pattern to number the match groups, the pattern is too complex for one line.

This is a perfect place to take advantage of Ruby’s x delimiter for patterns:

1
2
3
4
5
6
7
8
9
  REFERENCE_PATTERN = %r{
    (\W)?              # Prefix (1)
    (                  # Reference (2)
      @([\w\._]+)    | # User name (3)
      [#!$](\d+)     | # Issue/MR/Snippet ID (4)
      [\h]{6,40}       # Commit ID (2)
    )
    (\W)?              # Suffix (5)
  }x

So much easier to maintain and read.

Generating My Open Source Contributions Page

As I was working on my open source page, I got tired of copying and pasting the names of the pull requests and adding the link and so on. I wanted to generate them dynamically, so I whipped up a quick script to do just that.

I decided I wanted to have a YAML file which had project paths as keys, and pull request numbers as entries, which could later store the pull request titles. I ended up with a file like this:

contribs.yml
1
2
3
4
5
6
7
8
9
10
  ---
  brentd/gitploy:
    "1":
    "2":
  philc/vimium:
    "216":
  cucumber/aruba:
    "17":
    "19":
  # and so on...

Now I wanted to take that file, fetch the title for each pull request and write the changes back to the file. Really simple to do thanks to the awesome GitHub API and the Octokit gem:

contribs.rb Step 1 - Update YAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  require 'yaml'
  require 'octokit'

  YAML_FILE = 'contribs.yml'
  y = YAML.load(File.read(YAML_FILE))

  # I didn't enter them alphabetically, but I want them to be
  repos = y.keys.sort

  repos.each do |repo|
    y[repo].each_pair do |issue, title|
      # Only fetch the title if we need to
      if title.nil?
        y[repo][issue] = Octokit.pull(repo, issue).title
      end
    end
  end

  # Write back any changes
  File.open(YAML_FILE, 'w') do |f|
    f.puts y.to_yaml
  end

Now that we’ve got projects, pull request numbers and pull request titles, we can update the blog page:

contribs.rb Step 2 - Write Markdown
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  y = YAML.load(File.read(YAML_FILE))

  File.open('../_includes/open-source.md', 'w') do |f|
    y.sort_by { |v| v[0] }.each do |pair|
      f.puts "### [#{pair[0]}](https://github.com/#{pair[0]})"
      f.puts

      pair[1].each_pair do |issue, title|
        f.puts "* #{title} - [##{issue}](https://github.com/#{pair[0]}/pull/#{issue})"
      end

      f.puts
    end
  end

Converting 1r7.net Bookmarks to Pinboard

When Delicious originally shut down, Chris Heald whipped up a site called 1r7 to pick up its slack. I used it for a while, but recently discovered Pinboard and wanted to move over my bookmarks from 1r7. The bad news was that 1r7’s export feature was throwing a 500 error for me, but the good news was that between 1r7’s semantic markup and Pinboard’s API, it was crazy simple to write a quick script to transfer them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  #!/usr/bin/env ruby

  require 'cgi'
  require 'nokogiri'

  api_base = "https://user:pass@api.pinboard.in/v1/posts/add"

  # Pre-assumes that you've exported each HTML page of your 1r7 bookmarks to
  # "1r7p1.html", "1r7p2.html", "1r7p3.html", etc.
  1.upto(2) do |page|
    doc = Nokogiri::HTML(File.read("1r7p#{page}.html"))

    # Extracts bookmark data; note that '1r7' is added to each bookmark's tags
    # for easier filtering on Pinboard
    doc.css('#content li').each do |li|
      args = {
        url:         li.at_css('h1 a')['href'],
        description: li.at_css('h1 a').content,
        extended:    li.search('q').text.strip,
        tags:        (li.css('span.tags span.tag a').collect(&:content) << '1r7').join(' '),
        dt:          li.at_css('time')['datetime']
      }

      # Map our arguments to a query string
      q = "?" + args.map {|k,v| "#{k}=#{CGI::escape(v)}" }.join('&')

      `curl "#{api_base}#{q}"`
    end
  end

Padrino Rake Tasks With Whenever

Padrino as a framework is an interesting middle ground between Rails and Sinatra. However, it breaks from the norm in that it doesn’t have a traditional Rakefile, opting instead to pass everything through its padrino command (e.g., padrino rake console). Ordinarily this isn’t a problem, but it slightly conflicts with the excellent whenever gem for managing crontabs on a production server, since whenever’s default rake job type assumes there’s a traditional Rakefile.

Luckily whenever offers a simple solution by defining a custom job_type:

config/schedule.rb
1
2
3
4
5
job_type :padrino_rake, 'cd :path && padrino rake :task -e :environment'

every :hour do
  padrino_rake 'crawl'
end

Run after a deploy, my crontab will now look like this:

crontab
1
@hourly /bin/bash -l -c 'cd /path/to/my/project && padrino rake crawl -e production'

Complex MetaSearch Forms With Formtastic

meta_search lets you perform advanced queries on your ActiveRecord models, and Formtastic helps in building semantic forms quickly and easily.

Today I had need to build an advanced search form that would let the user define a search where an attribute was between two values. For example, finding users whose age is between 20 and 30.

MetaSearch defines the multiparameter_field form helper method, but getting that helper to work with Formtastic required some doing:

First, define the :between where as described in the README:

config/initializers/meta_search.rb
1
2
3
4
5
6
7
MetaSearch::Where.add :between, :btw,
  :predicate => :in,
  :types => [:integer, :float, :decimal, :date, :datetime, :timestamp, :time],
  :formatter => Proc.new {|param| Range.new(param.first, param.last)},
  :validator => Proc.new {|param|
  param.is_a?(Array) && !(param[0].blank? || param[1].blank?)
}

Next, we need to define a custom form builder class:

config/initializers/multi_param_form_builder.rb
1
2
3
4
5
6
7
8
9
10
11
12
class MultiParamFormBuilder < Formtastic::SemanticFormBuilder
  # Allows <tt>:as => :between</tt> which utilizes MetaSearch's
  # <tt>multiparameter_field</tt> helper
  def between_input(method, options = {})
    input_options = options.delete(:input_html) || {}
    label_options = options_for_label(options)
    label_options[:for] ||= input_options[:id]

    label(method, label_options) <<
      multiparameter_field(method, {:field_type => :text_field},{:field_type => :text_field}, input_options)
  end
end

And then tell Formtastic to use it:

config/initializers/formtastic.rb
1
Formtastic::SemanticFormHelper.builder = MultiParamFormBuilder

Now in a view, use Formtastic and MetaSearch as normal, just tell Formtastic to render the field as :between:

app/views/users/_search.html.haml
1
2
3
4
5
= semantic_form_for @search, :method => :post do |form|
  = form.inputs do
    = form.input :age_between, :as => :between
  = form.buttons do
    %button.button{:type => 'submit'} Search

And that’s it!

Alphabetizing a Block of Text in Vim

Today I had need to alphabetize a block of code in Vim, but on each line there was text which I didn’t want to be part of the sort. An example describes it best:

item 65110 #Heart of Ignacious
item 65111 #Scepter of Ice
item 65113 #Hydrolance Gloves
item 65114 #Feludius' Mantle
item 65115 #Glaciated Helm
item 65116 #Treads of Liquid Ice
item 65117 #Glittering Epidermis
item 65118 #Crushing Weight
item 65119 #Gravitational Pull
item 65120 #Arion's Crown
item 65121 #Terrastra's Legguards
item 65122 #Dispersing Belt

Currently the lines are sorted numerically. I needed the lines to be sorted alphabetically based on the content of the comments (the stuff after the #). Vim made this stupid-easy:

  1. Highlight the lines using visual line mode (V)
  2. Run :sort /.*#/

Which gives us:

item 65120 #Arion's Crown
item 65118 #Crushing Weight
item 65122 #Dispersing Belt
item 65114 #Feludius' Mantle
item 65115 #Glaciated Helm
item 65117 #Glittering Epidermis
item 65119 #Gravitational Pull
item 65110 #Heart of Ignacious
item 65113 #Hydrolance Gloves
item 65111 #Scepter of Ice
item 65121 #Terrastra's Legguards
item 65116 #Treads of Liquid Ice

That’s it. The argument to sort tells it to use everything after the pattern. Beautiful.

An RSpec2 / Shoulda Controller Gotcha

I’m in the process of upgrading a Rails 2.3.8 app to Rails 3, and part of that process involved upgrading to RSpec2. I made use of Shoulda, mostly in my controller tests, and I ran into a frustrating issue that hopefully I can prevent for other people with this post.

A simple example controller spec looked like this:

spec/controllers/achievements_controller_spec.rb
1
2
3
4
5
6
7
8
9
10
require 'spec_helper'

describe AchievementsController, "GET index" do
  before { get :index }

  it { should respond_with(:success) }
  it { should assign_to(:achievements).with_kind_of(Array) }
  it { should assign_to(:members).with_kind_of(Array) }
  it { should render_template(:index) }
end

Running this spec with RSpec2 generated this error:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Failures:
  1) AchievementsController GET index
     Failure/Error: it { should respond_with(:success) }
     undefined method `response_code' for nil:NilClass
     # gems/activesupport-3.0.0/lib/active_support/whiny_nil.rb:48:in `method_missing'
     # (path)shoulda/lib/shoulda/action_controller/matchers/respond_with_matcher.rb:57:in `response_code'
     # (path)shoulda/lib/shoulda/action_controller/matchers/respond_with_matcher.rb:48:in `correct_status_code?'
     # (path)shoulda/lib/shoulda/action_controller/matchers/respond_with_matcher.rb:30:in `matches?'
     # ./spec/controllers/achievements_controller_spec.rb:12
     # gems/activesupport-3.0.0/lib/active_support/dependencies.rb:239:in `inject'

  2) AchievementsController GET index
     Failure/Error: it { should assign_to(:achievements).with_kind_of(Array) }
     Expected action to assign a value for @achievements
     # ./spec/controllers/achievements_controller_spec.rb:13
     # gems/ree-1.8.7-2010.02@rails3/gems/activesupport-3.0.0/lib/active_support/dependencies.rb:239:in `inject'

  3) AchievementsController GET index
     Failure/Error: it { should assign_to(:members).with_kind_of(Array) }
     Expected action to assign a value for @members
     # ./spec/controllers/achievements_controller_spec.rb:14
     # gems/ree-1.8.7-2010.02@rails3/gems/activesupport-3.0.0/lib/active_support/dependencies.rb:239:in `inject'

The solution is ridiculously simple:

spec/controllers/achievements_controller_spec.rb
1
2
3
4
5
6
7
8
9
10
11
require 'spec_helper'

describe AchievementsController, "GET index" do
  before { get :index }
  subject { controller } # <- ADD THIS

  it { should respond_with(:success) }
  it { should assign_to(:achievements).with_kind_of(Array) }
  it { should assign_to(:members).with_kind_of(Array) }
  it { should render_template(:index) }
end

It makes perfect sense in retrospect, but it’s a little frustrating that in order to find this solution, I had to look through Shoulda’s commit log for a mention of rspec2, which noted a change to its Cucumber features, which included this subject line.