Using Datamapper 0.9

So, DataMapper 0.3 was behaving weirdly for me, and I thought I'd try upgrading to 0.9 to see how things are there. Overall I'm quite liking it, but there's a few catches:

  • It's incompatible with Vlad for deployment, haven't looked into it but something in DM is making the 'repository' value unsettable, which Vlad uses to determine the checkout path.

  • Legacy connections beware, there doesn't seem to be a current alternative for settablename at the moment.

  • :memory: is no longer a good name for your test database when using sqlite. Use a fully-qualified connection string like sqlite://:memory: instead.

  • DataMapper::Persistence has been renamed DataMapper::Resource. Include it in models.

  • Validations are an add-on now, include DataMapper::Validate in your model (or even re-open DM:Resource and include it there).

  • Properties now take a class instead of a symbol (you can guess at all the main ones), and require the id to be specified like so: property :id, Fixnum, :serial => true

  • Associations are renamed, hasone is now onetoone, hasmany is oneto_many or manyto_many, etc. Haven't delved in deep yet though, so I'm not sure how to define who gets the foreign key.

  • New migration code is just now getting in to dm-core, and auto_migrate! is a thing of the past. Sucks for those of us using sqlite in-memory test databases that need a fresh migration every time.

My installation Rakefile follows, just stuff it in an empty directory and it'll do everything from there. Much thanks to Atmos for a good starting point and some setup help.

desc "Fetch and Install DM and Merb"
task :install_all do 
  config = CONFIG['dm']
  fetch config[:user], config[:repos]
  install config[:install]
  config = CONFIG['merb']
  fetch config[:user], config[:repos]
  install config[:install]
end

desc "Uninstall DM and Merb"
task :uninstall_all do
  uninstall CONFIG['dm'][:gems]  
  uninstall CONFIG['merb'][:gems]  
end

desc "Download latest sources for :project from git"
task :fetch, :project do |task, args|
  config = CONFIG[args[:project]]
  fetch config[:user], config[:repos]
end

desc "Install :project from git"
task :install, :project do |task, args|
  config = CONFIG[args[:project]]
  install config[:install]
end

desc "Uninstall :project"
task :uninstall, :project do |task, args|
  config = CONFIG[args[:project]]
  uninstall config[:gems]
end

def fetch(user, repos)
  base = File.expand_path(".")
  Dir.chdir base do
    repos.each do |repo|
      repo_dir = "#{base}/#{repo}"
      unless File.directory?(repo_dir)
        %x{git clone git://github.com/#{user}/#{repo}.git }
      end
      Dir.chdir(repo_dir) { %x{git pull} }
    end
  end
end

def install(modules)
  base = File.expand_path(".")
  modules.each do |lib|
    Dir.chdir("#{base}/#{lib}") do
      cmd = "sudo rake install 2>/dev/null |" +
            " grep -v '^(in' |" +
            " grep -v '^[0-9] gem' |" +
            " grep -v '^[IUc. ]'"
      puts %x{#{cmd}}
    end
  end
end

def uninstall(gems)
  gems.each do |name|
    puts %x{yes | sudo gem uninstall #{name} -aI}
  end
end

CONFIG = {
  'dm' => {
    :user => 'sam',
    :repos => %w(
      do
      dm-core
      dm-more),
    :install => %w(
      do/data_objects
      do/do_sqlite3
      do/do_mysql
      do/do_postgres
      dm-core
      dm-more/merb_datamapper
      dm-more/dm-migrations
      dm-more/dm-serializer
      dm-more/dm-validations
    ),
    :gems => %w(
      data_objects
      do_sqlite3
      do_mysql
      do_postgres
      dm-core
      merb_datamapper
      dm-migrations
      dm-serializer
      dm-validations
    )
  },
  'merb' => {
    :user => 'wycats',
    :repos => %w(
      merb-core
      merb-more
      merb-plugins
      merb-plugins/merb_param_protection),
    :install => %w(
      merb-core
      merb-more
      merb-plugins
    ),
    :gems => %w(
      merb
      merb-action-args
      merb-assets
      merb-builder
      merb-cache
      merb-core
      merb-gen
      merb-haml
      merb-mailer
      merb-more
      merb-parts
      merb_activerecord
      merb_datamapper
      merb_helpers
      merb_param_protection
      merb_rspec
      merb_sequel
      merb_stories
      merb_test_unit
    )
  }
}

Snippet: col

#!/usr/bin/env ruby
col = ARGV.pop.to_i-1
while line = gets
  puts line.chomp.split(/\s+/)[col]
end

For when you just want a list of filenames from version control, hg st | grep '?' | col 2

And because I can never remember the standard unix tool that does the same thing, and awk is awkward.

Now with comments

I've got comments done up now, and I'm mostly happy with them. They should show up on all the articles added this year.

For spam management purposes, I'm approving comments, but once you've got one approved comment it'll let your email address through immediately in the future.

Fish at Last!

Somehow, the last update of MacPorts has let me install Fish cleanly, rather than obtusely blowing up as it has in the past.

I highly recommend trying it out: port selfupdate followed by port install fish should do the trick.

If you want to use it as your default shell, you'll need to sudo echo /opt/local/bin/fish >> /etc/shells so chsh will treat it as an approved shell.

Automatic Feeds with a few Quirks

So, in the interest of keeping this blog as simple as possible (for me), I'm providing feed information on the front page as hAtom marked-up data.

This is then passed through Subtlety to provide a regular Atom feed, which Feedburner is pointed at. Then I include a tag in the HTML head, and away we go.

However, I just noticed an odd little quirk (in google reader at least), in that if I edit a post that's still on the front page, even though the timestamps and description shouldn't have changed, the post body changing caused a duplicate entry in my reader. Go figure.

Hopefully, I can bump the Holmes article off the front page before I add more entries to it.

Spec Timing

As an update to a previous article,

if $specs_timed.nil? && ENV.has_key?('SLOW')
  $specs_timed = true
  $timings = []

  Spec::Example::ExampleGroup.prepend_before do
    @start = Time.now
  end
  Spec::Example::ExampleGroup.append_after do
    elapsed = Time.now - @start
    if elapsed > ENV['SLOW'].to_f
      $timings << [elapsed, "#{self.class.description} #{description}"]
    end
  end

  at_exit do
    puts "\nSlow Specs:"
    $timings.sort{|a,b| a.first <=> b.first}.each do |time, name|
      puts " %7.4f #{name}" % time
    end
    puts "  None!" if $timings.empty?
  end
end

Then, simply run

rake SLOW=0.1

Two gotchas if you're using Rails though: instead of hooking S::E::ExampleGroup, you'll need to hook Spec::Rails::Example::RailsExampleGroup. Second, if you have any spec failures the timings don't seem to get output, since spec/rails aborts execution after failing.