Active Shipping
April 21st, 2008
Active Shipping is now available under an open source license. It's on GitHub, here. Active Shipping is an extension for Active Merchant which I wrote to let Shopify users provide carrier-calculated shipping rates to their customers. You can easily include Active Shipping in your Rails project as a plugin (or in any other ruby project, for that matter) and start doing things like this:
include ActiveMerchant::Shipping
usps = USPS.new(:login => '123JAMES4567')
origin = Location.new(:country => 'US', :zip => '90210')
destination = Location.new(:country => 'CA', :postal_code => 'K2P 0K3')
grams = 100
centimetres = [20,10,10]
packages = [Package.new(grams, centimetres)]
response = usps.find_rates(origin, destination, packages)
rates = response.rates.sort_by(&:price)
cheapest = rates.map {|r| [ r.service_name,
r.price.to_f / 100, # rates returned in cents
r.currency]}.first
# => ["USPS First-Class Mail International", 1.62, "USD"]
It's under active development and more details for contributing to the project can be found in the README.
Rails migrations in local git branches: not so atrocious now
February 13th, 2008
One of the really annoying things about working on a Rails project with other developers is the inevitable conflicts with migration numbering. We recently switched to Git for version control on Shopify, and we all really love the ease of branching and merging to our hearts’ content. Here is a snippet of my workflow today:
- Edit in my local “shipping” branch, adding some functionality to Shopify’s shipping system.
- Commit a simple migration which adds a column that’s necessary for the aforementioned functionality.
- Switch to my master branch and pull from our central repository to get the latest stuff.
- Notice that Cody has added some crazy migration with an SQL query that I don’t really want to understand.
Now, at this point, my usual required course of action would be to switch back to my shipping branch, migrate down, rename my migration (thankfully there was just one this time), then git-rebase onto the updated master branch and migrate back up again. This happens fairly often, and it makes the world a darker place.
So I wrote this script. Save it as .git/hooks/pre-rebase in your git repository and make it executable. Now whenever you rebase one of your local development branches it will detect potential migration conflicts with the target of the rebase and do most of the dirty work for you: it migrates down, renames your branch’s migrations so they’ll be at the end of the line, generates a new commit for you with the new renaming, then lets the rebase proceed.
It prompts before it does the renaming or committing, so if you really, really want to have “23_foo.rb” and “23_bar.rb” after your rebase, then all the power to you. The prompts also give you a chance to ctrl-C out of the whole thing if you realize that the migrations will need more tweaking.
#!/usr/bin/env ruby
# Save to .git/hooks/pre-rebase in your rails project and chmod +x
#
# Checks for migration numbering conflicts and sorts them out automagically.
# Intended for use in multi-dev environments where you have a local branch for
# your own development that you rebase every so often onto an updated (i.e.
# freshly pulled) master branch. Conflicts will still come up, but this covers
# 90%+ of cases.
root_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", ".."))
orig_dir = `pwd`.chomp
`cd #{root_dir}`
# uses parameters passed by git-rebase
upstream = ARGV[0]
subject = ARGV[1] || "HEAD"
migration_changes = `git diff #{subject} #{upstream} --summary | grep db/migrate`
migration_ary= migration_changes.split(/\n/)
# not really deletions in terms of the rebase; just named so by the diff output
# really these "deletions" are just the current branch's migrations that don't exist
# in the target of the rebase
deletions = migration_ary.grep(/^\s*delete/)
other_changes = migration_ary - deletions
# filenames of any migrations affected in one way or another by the rebase
migration_files = migration_ary.map {|line| line.split.last }
version_pattern = /^db\/migrate\/(\d+)/
branch_migration_files = deletions.map {|line| line.split.last }.sort
deletion_versions = branch_migration_files.map {|file| file.match(version_pattern)[1].to_i }.sort
other_changes_versions = other_changes.map {|line| line.split.last.match(version_pattern)[1].to_i }.sort
# detect duplicate versions by looking at intersection of arrays
conflicts = deletion_versions & other_changes_versions
conflict_files = migration_files.select {|f| conflicts.include? f.match(version_pattern)[1].to_i }
if conflicts.empty?
puts "\nNo migration conflicts detected."
else
puts "\n*** Migration conflicts detected"
puts "\nLocal branch migrations not present in rebase target:"
puts(deletions.map {|line| line.gsub(/^.*(db\/migrate)/, ' \1')}.join("\n") + "\n")
puts "\nMigrations in rebase target to be added or modified:"
puts(other_changes.map {|line| line.gsub(/^.*(db\/migrate)/, ' \1')}.join("\n") + "\n")
print "\nFix by migrating down and renaming branch migrations? (Y/n) "
response = STDIN.readline.chomp.strip
if response == '' or response =~ /^[Yy]/
# migrate down
puts (cmd = "rake db:migrate VERSION=#{(migration_files.map {|file| file.match(version_pattern)[1].to_i }).min - 1}")
puts `#{cmd}`
last_version = other_changes_versions.max
# move each migration unique to the current branch to the end of the migration list
branch_migration_files.each do |file|
last_version += 1
`git mv #{file} #{new_name}`
puts "\nmoved #{file}"
puts "to #{new_name}"
end
print "Generate a new commit? (Y/n) "
response = STDIN.readline.chomp.strip
if response == '' or response =~ /^[Yy]/
puts(cmd = "git commit -a -m \"Renamed migrations\"")
puts `#{cmd}`
end
puts "\n*** Be sure to run rake db:migrate after rebase has completed.\n"
else
puts "Okay, not doing anything then."
end
end
`cd #{orig_dir}`
exit 0
Modified rss_reader Radiant extension
November 8th, 2007
Part of my work at jaded Pixel a few months ago was development and writing for a new “brochure” site at www.shopify.info. The current site, like the old one, is built with the fantastic Radiant CMS and uses a modified version of the rss_reader extension from BJ Clark, Loren Johnson, and Alessandro Preite Martinez.
We are using it to grab feeds that we publish in other places and re-display them to fit with the design of the site. For example our screenshots page is generated from our blog’s Shop of the Moment feed, and our list of supported methods for accepting payment is fed directly by Shopify itself.
Here is a modified version of the extension, available for your enjoyment and scrutiny:
rss_reader-0.2a-jadedpixelmod.tgz
Just unpack with tar xvzf rss_reader-0.2a-jadedpixelmod.tgz in your Radiant app’s root directory and it’ll put the necessary stuff in lib and vendor.
The modifications that I made add a few new features and, thanks to Tobi’s help, improved the robustness of the feed fetching code quite a bit. So for example, now you can order the feed however you want using a syntax similar to SQL’s ORDER BY:
<ul>
<r:feed:items
url="http://feeds.boingboing.net/boingboing/iBag"
order="creator ASC">
<li><r:feed:link /></li>
</r:feed:items>
</ul>
You can also do headers to mark off sections:
<ul>
<r:feed:items
url="http://feeds.boingboing.net/boingboing/iBag"
order="creator ASC">
<r:feed:header for="creator">
<h2><r:feed:creator /></h2>
</r:feed:header>
<li><r:feed:link /></li>
</r:feed:items>
</ul>
This would show the code within the <r:feed:header /> tag if and only if the “creator” attribute of the item is different from the previous item in the list. Thus, it would only make reasonable output if the feed were ordered by creator with the above method. Another more obvious grouping for headers would be the item’s “date” attribute. Yes, the date of a feed item has hours and minutes and seconds, but I made it so that a new header only appears on new days of the month.
You can sort items and group headers by date, title, content, creator, or link (i.e. the URL of the item). There are a few other options for the feed tags that I haven’t mentioned here but which are documented within the code.
A browser history for the command line
May 25th, 2007
I'm not a shell expert by any means. I'm certainly comfortable enough on the command line and can pipe I/O fairly effectively; however, I know very little about bash scripting, and irb and svn really cover the vast majority of my Terminal usage aside from the ubiquitous ls and cd.
Even humble cd has features which I had not initially fathomed: it was a wonderful moment when I recently saw Tobi use cd - to flip back and forth from the last directory he had visited. I had been vaguely yearning for a way to go back directories for a while, but never got around to figuring it out. This cd - thing wasn't really what I was looking for, though, because what I wanted was the equivalent of the back and forward buttons in a web browser and cd - doesn't have a memory of anything more than the last directory you were in, via the environment variable OLDPWD.
So I did some googling and found the necessary components to come up with something that is pretty darn satisfying.
How it works
I added three short functions in ~/.bash_profile:
function cd {
pushd "$@" > /dev/null
}
Instead of its regular behaviour, cd is now mapped to a nifty command called pushd. This command takes a directory path as an argument and changes to that directory, just like cd does, but it also stores the new path in a stack stored in an environment variable.
It outputs the contents of that stack whenever you use pushd, but I don't want to see it every time I change directory which is why I've redirected standard output to oblivion. STDERR is left untouched so if I type a nonexistent directory then I will still find out about it. The "$@" provides pushd with all the arguments I pass to cd separated by spaces, so everything stays the way I typed it; if I were to use "$1" here, then it would choke on directory names with spaces in them.
Now that we are building onto our stack of visited directories every time we cd anywhere, we can use this as a browser history for the shell:
function bd {
if [ -z $1 ]; then
n=1
else
n=$1
fi
pushd +$n > /dev/null
}
function fd {
if [ -z $1 ]; then
n=0
else
n=$[$1-1]
fi
pushd -$n > /dev/null
}
bd is for "back a directory" and fd is for "forward a directory". We are still just using pushd to manipulate the stack, but now we are using a different kind of argument for it. pushd +n will rotate the stack backwards n steps and change to that directory. We give n a default value of 1 when no arguments are passed to bd, and do pushd +n.
For fd, we rotate the other way. The default for n is now 0 and we decrement the argument by 1 otherwise because pushd -0 refers to the bottom element of the stack. If we go forward before going backward, it doesn't care and will just rotate back to the first pushed directory (which is probably ~) and continue rotating from there, which is fine by me.
How it plays
So now I can go back and forward with bd and fd on their own, or do bd 8 to go back wherever I was eight directories ago and then maybe a little fd 3 to revise my search.
Each shell gets its own history which just accumulates and accumulates. If I feel like keeping a Terminal window open for a few years then I can do dirs -c to clear the stack. The way I have this set up, cd without any arguments will swap back and forth between the current and last directory as cd - would normally do, because that's how pushd behaves when you type it by itself. Normally cd by itself would be the equivalent of cd ~, but I never used cd like that in the first place so I'm not missing anything.
There's obviously room to modify this formula and use this wonderful directory stack in different ways. Here is a straightforward reference for the directory stack commands in bash. There's also a few tilde expansions that reference the directory stack which some people might find useful.
