Counting ActiveRecord Associations
Q: When is an Array not an Array?
A: When it’s an ActiveRecord association proxy
When you have something like this:
class User < ActiveRecord::Base has_many :addresses end
the addresses property of a User instance feels a lot like an Array. You can iterate through it, you can count it, you can add to it, etc., but, it’s not actually an Array object: it’s an instance of ActiveRecord::Associations::AssociationProxy.
Most of the time, an AssociationProxy will do just what you expect an Array would do, but every now and then you get sandbagged by some unexpected behavior. For example, in Array, the size method is an alias for length; not so with an AssociationProxy.
I recently got bit by this subtlety until this excellent post by Xavier Shay set me straight. (For the impatient: bottom line is to use size most of the time. And don’t forget that count makes a round trip to the database every time).
So remember, kids: nice as those associations are, don’t let the smooth taste fool you.
One-Liner To See Your Last Git Commit
I just wrote a short Bash function that would call “git push” then print out my most recent commit (Why? Long story: mostly workflow related).
It took a bit of googling to get that second part to work, so I thought I would post it here:
git --no-pager log -1 --no-merges --author=darin --pretty=oneline
The --no-pager option turns off the automatic piping to less (I’m glad I found that – that might be useful in a number of contexts) and --no-merges means that I’m only seeing commits of actual code that I wrote, and not the merges.
You can also change the --pretty value to something more verbose if you need it. The options are described in the git-log docs (scroll down to “Pretty Formats”). You can use any of the pre-canned options (oneline, short, medium, full, fuller, email, raw) or roll your own with a format string.
Who knew that SCM could actually be fun?
Testing Templates In ActionMailer Functional Tests
I’ve been working on an application that takes user-submitted emails as input. After a bit of processing, it sends a reply back to the user, using one of several different templates depending what actually happened.
In my functional tests, I wanted to be able to confirm that the correct template was used in each of the different scenarios, but it turns out that there’s no equivalent to assert_template within the context of ActionMailer.
I tried googling for a solution, but most of the examples I saw just used a regex to confirm the actual copy in the body of the email. This seemed a little fragile to me, so I hacked my own solution: I added a custom X- header into the email that contained the template name, then checked for that header in the test. My mailer ended up looking roughly like this:
def action_succeeded(user) from 'support@myapp.com' recipients user.email headers 'X-Mail-Template' => 'action_succeeded' if RAILS_ENV == 'test' subject 'Your TPS Report Has Been Received!' body :user => user end
Note that this header is only added in the test environment. That’s a nicety – there’s probably no harm in having that go out in the real emails, but checking the RAILS_ENV is easy enough to do.
To keep the tests readable, I added this method to my test_helper:
def assert_mail_template(template, msg=ActionMailer::Base.deliveries.first)
assert_equal template, msg.header_string('X-Mailer-Template')
end
And my test method looks like this:
def test_action_succeeded do_some_stuff() assert_equal 1, ActionMailer::Base.deliveries.size assert_mail_template 'action_succeeded' end
It would be pretty easy to bake something like this directly into the Rails framework. Perhaps I will submit a patch in all of my copious spare time <snark/>.
Ruby’s URI.parse and Invalid Characters
The URI.parse method is damned handy and, to its credit, follows the RFC 1738 spec quite religiously. Unfortunately, web developers are not always as careful, and modern browsers are very forgiving. Net result: there are many URLs floating around in the wild that cause URI.parse to choke.
I ran into this recently while implementing an ETL process that parses web logs. For the most part, invalid characters were showing up in the query string, and not anywhere else, so I just broke it off and dealt with it separately. The result is not as clean as one would like, but it does work.
require 'uri'
require 'cgi'
def parse_uri(uri_string)
return nil unless uri_string && uri_string.size > 0
(base_uri, query) = uri_string.split('?', 2)
uri = URI.parse(base_uri)
return [uri, query]
end
# curly braces in query strings, seem to come up a lot
bad_uri = 'http://www.dummy.com/mangle?nasty=12345678{abcdef}'
uri = URI.parse(bad_uri)
# => URI::InvalidURIError: bad URI(is not URI?): http://www.dummy.com/mangle?nasty=12345678{abcdef}
(uri, query) = parse_uri(bad_uri)
#=> [#<URI::HTTP:0x38ca16 URL:http://www.dummy.com/mangle>, "nasty=12345678{abcdef}"]
CGI.parse(query)
# => {"nasty"=>["12345678{abcdef}"]}
Ruby Client for the Google Analytics API
I heart the Ruby community. Two days after Google announced their data export API a Ruby gem appeared on github.
The gem is called Gattica and is a breeze to use:
gs = Gattica.new('johndoe@google.com','password',123456)
results = gs.get({ :start_date => '2008-01-01',
:end_date => '2008-02-01',
:dimensions => 'browser',
:metrics => 'pageviews',
:sort => '-pageviews'})
Many thanks to Rob Cameron for making this available so soon!
TypeError with REXML pretty print
I got this error today when trying to pretty print an XML document using REXML:
/opt/local/lib/ruby/1.8/rexml/formatters/pretty.rb:133:in `[]': no implicit conversion from nil to integer (TypeError)
It appears to be an old bug in the 1.8 library – I’m not sure if it’s been fixed in 1.9 or not.
If you run into this, it’s an easy fix – just open up the pretty.rb file indicated in the error message and add a line to the wrap() method at the bottom of the file:
def wrap(string, width)
# Recursivly wrap string at width.
return string if string.length <= width
place = string.rindex(' ', width) # Position in string with last ' ' before cutoff
return string if place.nil? # <---------- ADD THIS LINE
return string[0,place] + "\n" + wrap(string[place+1..-1], width)
end
Converting a hash to a query string in Ruby
This might be part of a standard library somewhere, but I couldn’t find it after some quick Googling (at which point it became quicker to write the damn thing myself).
def hash_to_querystring(hash)
hash.keys.inject('') do |query_string, key|
query_string << '&' unless key == hash.keys.first
query_string << "#{URI.encode(key.to_s)}=#{URI.encode(hash[key])}"
end
end