Refactoring an ActiveRecord callback

Inspired by a few articles and pesentations (1 2 3 4), I decided it was time to cleanup some of the logic in my Post model related to a particular ActiveRecord callback. The fact that I needed some comments to explain what it is doing should be a red flag.

before_validation :update_published_at_if_necessary

def published?
  self.is_published == true
end

def unpublished?
  ! published?
end

protected
  # Ensure that published_at is set accordingly.
  # 
  #  * Unpublished posts should not have this set
  #  * Published posts should have it set to the current time
  def update_published_at_if_necessary
    if new_record? && published? && published_at.nil?
      self.published_at = Time.now
    end
    if ! published? && !published_at.nil?
      self.published_at = nil
    end
  end

Fortunately, I have tests in place that excercise this logic, so as long as my tests are passing, the refactorings must work (hopefully!).

My first impression is that the callback is doing too much work. Let's split it up into two pieces.

before_validation :set_published_at_to_now_if_necessary
before_validation :unset_published_at_if_necessary

protected
  def set_published_at_to_now_if_necessary
    if new_record? && published? && published_at.nil?
      self.published_at = Time.now
    end
  end

  def unset_published_at_if_necessary
    if ! published? && !published_at.nil?
      self.published_at = nil
    end
  end

That's somewhat better. At least the methods are more focused.

Hmm, I probably don't need to check the existing published_at value when something isn't published. I can also make use of unpublished?

def unset_published_at_if_necessary
  if unpublished?
    self.published_at = nil
  end
end

I would do something similar for set_published_at_if_necessary, but I don't want to override the published_at if it was explicitly set. Maybe I post something in the future, or past. I go a little crazy sometimes with that.

I could probably simplify the conditional logic in set_published_at_if_necessary by making a new method.

protected
  def set_published_at_to_now_if_necessary
    if new_published_post_without_published_at?
      self.published_at = Time.now
    end
  end

  def new_published_post_without_published_at?
    new_record? && published? && published_at.nil?
  end

Having if_necessary into the method names are kind of bugging me. before_filter supports :if and :unless options, so we should use those, and remove the conditionals from the callback methods.

before_validation :set_published_at_to_now, :if => :new_published_post_without_published_at?
before_validation :unset_published_at, :if => :unpublished?

protected
  def set_published_at_to_now
    self.published_at = Time.now
  end

  def unset_published_at
    self.published_at = nil
  end

Looking good. Looking pretty, pretty good.

Let's see the finished product:

before_validation :set_published_at_to_now, :if => :new_published_post_without_published_at?
before_validation :unset_published_at, :if => :unpublished?

def published?
  self.is_published == true
end

def unpublished?
  ! published?
end

protected
  def set_published_at_to_now
    self.published_at = Time.now
  end

  def unset_published_at
    self.published_at = nil
  end

  def new_published_post_without_published_at?
    new_record? && published? && published_at.nil?
  end

I'm pretty happy with this. Reads really well. Some of the methods have kind of long names, but I can deal with that.

The only part I don't really like is published? and unpublished?, but that's for another day.

July 03, 2008 at 22:13 Permalink Edit Destroy

A walk through of test-driven development with shoulda

I'm not sure if you've heard, but shoulda makes test-driven development really simple.

Last week's boston.rb focused on testing, where we split up into two groups. One group worked on adding a test suite to an app that had no tests, and my group was using test-driven development for adding new features to the boston.rb website.

I figured I'd do recap how we used TDD that night.

shoulda-generators, a quick plug

One thing I'm going to omit from the walkthrough is that a chunk of time was spent converting the scaffold-generated test from test-unit to shoulda, and from erb to haml. While it was a worthwhile detour for some present, it was a little annoying. If only there were shoulda and haml generators, I say!

Struck awake by inspiration at 5am the next day, I took fate into my own hands...

BEHOLD!

It is available as a plugin for now, but I'll try to gemify it sometime.

While we didn't have the luxury of these generators at the time, I'm going to use them now for the sake of this writeup.

The new feature: Places

Before we start, let's just make sure the tests work... alright, good.

We want to add functionality for 'places.' A Place is somewhere in the area that's somehow connected to Ruby: a business using it, a venue for a meeting, an incubator, things like that.

A Place probably has:

  • A name
  • An address
  • A description

Let's generate the scaffold:

$ script/generate shoulda_scaffold place name:string address:string description:text
      exists  app/models/
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/places
      exists  app/views/layouts/
      exists  test/functional/
      exists  test/unit/
      create  public/stylesheets/blueprint
      create  app/views/places/index.html.haml
      create  app/views/places/show.html.haml
      create  app/views/places/new.html.haml
      create  app/views/places/edit.html.haml
      create  app/views/places/_form.html.haml
      create  app/views/layouts/places.html.haml
      create  public/stylesheets/blueprint/print.css
      create  public/stylesheets/blueprint/screen.css
      create  public/stylesheets/blueprint/ie.css
      create  app/controllers/places_controller.rb
      create  test/functional/places_controller_test.rb
      create  app/helpers/places_helper.rb
       route  map.resources :places
  dependency  shoulda_model
      exists    app/models/
      exists    test/unit/
      create    test/factories/
      create    app/models/place.rb
      create    test/unit/place_test.rb
      create    test/factories/place.rb
      create    db/migrate
      create    db/migrate/20080620011343_create_places.rb

Let's try the tests again.

$ rake test
(in /Users/nichoj/Projects/bostonrb)
You have 1 pending migrations:
  20080620011343 CreatePlaces
Run "rake db:migrate" to update your database then try again.

Whoops, need to migrate first.

$ rake db:migrate
(in /Users/nichoj/Projects/bostonrb)
== 20080620011343 CreatePlaces: migrating =====================================
-- create_table(:places)
   -> 0.0171s
==
$ rake test
(in /Users/nichoj/Projects/bostonrb)
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -Ilib:test "/Library/Ruby/Gems/1.8/gems/rake-0.8.1/lib/rake/rake_test_loader.rb" "test/unit/place_test.rb" 
Loaded suite /Library/Ruby/Gems/1.8/gems/rake-0.8.1/lib/rake/rake_test_loader
Started
...
Finished in 0.147509 seconds.

3 tests, 3 assertions, 0 failures, 0 errors
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -Ilib:test "/Library/Ruby/Gems/1.8/gems/rake-0.8.1/lib/rake/rake_test_loader.rb" "test/functional/places_controller_test.rb" 
Loaded suite /Library/Ruby/Gems/1.8/gems/rake-0.8.1/lib/rake/rake_test_loader
Started
...........
Finished in 0.293275 seconds.

11 tests, 14 assertions, 0 failures, 0 errors
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -Ilib:test "/Library/Ruby/Gems/1.8/gems/rake-0.8.1/lib/rake/rake_test_loader.rb"

Good so far.

Now what do we want to do with the place? Those attributes should be required. Open the test, and see what we have so far:

class PlaceTest < ActiveSupport::TestCase
  should_have_db_column :name
  should_have_db_column :address
  should_have_db_column :description
end

Oh, cool. The generated test already has a few shoulds in there. Now we want to add should_require_attributes.

class PlaceTest < ActiveSupport::TestCase
  should_have_db_column :name
  should_have_db_column :address
  should_have_db_column :description
  should_require_attributes :name, :address, :description
end

And let's see how this does...

$ ruby test/unit/place_test.rb
Loaded suite test/unit/place_test
Started
...FFF
Finished in 0.154072 seconds.

  1) Failure:
test: Place should require address to be set. (PlaceTest)
    [/Users/nichoj/Projects/bostonrb/vendor/plugins/shoulda/lib/shoulda/active_record_helpers.rb:41:in `__bind_1213925922_190232'
     /Users/nichoj/Projects/bostonrb/vendor/plugins/shoulda/lib/shoulda/gem/shoulda.rb:191:in `call'
     /Users/nichoj/Projects/bostonrb/vendor/plugins/shoulda/lib/shoulda/gem/shoulda.rb:191:in `test: Place should require address to be set. '
     /Library/Ruby/Gems/1.8/gems/activesupport-2.1.0/lib/active_support/testing/setup_and_teardown.rb:67:in `__send__'
     /Library/Ruby/Gems/1.8/gems/activesupport-2.1.0/lib/active_support/testing/setup_and_teardown.rb:67:in `run']:
Place does not require address.
<false> is not true.

  2) Failure:
test: Place should require description to be set. (PlaceTest)
    [/Users/nichoj/Projects/bostonrb/vendor/plugins/shoulda/lib/shoulda/active_record_helpers.rb:41:in `__bind_1213925922_194386'
     /Users/nichoj/Projects/bostonrb/vendor/plugins/shoulda/lib/shoulda/gem/shoulda.rb:191:in `call'
     /Users/nichoj/Projects/bostonrb/vendor/plugins/shoulda/lib/shoulda/gem/shoulda.rb:191:in `test: Place should require description to be set. '
     /Library/Ruby/Gems/1.8/gems/activesupport-2.1.0/lib/active_support/testing/setup_and_teardown.rb:67:in `__send__'
     /Library/Ruby/Gems/1.8/gems/activesupport-2.1.0/lib/active_support/testing/setup_and_teardown.rb:67:in `run']:
Place does not require description.
<false> is not true.

  3) Failure:
test: Place should require name to be set. (PlaceTest)
    [/Users/nichoj/Projects/bostonrb/vendor/plugins/shoulda/lib/shoulda/active_record_helpers.rb:41:in `__bind_1213925922_196154'
     /Users/nichoj/Projects/bostonrb/vendor/plugins/shoulda/lib/shoulda/gem/shoulda.rb:191:in `call'
     /Users/nichoj/Projects/bostonrb/vendor/plugins/shoulda/lib/shoulda/gem/shoulda.rb:191:in `test: Place should require name to be set. '
     /Library/Ruby/Gems/1.8/gems/activesupport-2.1.0/lib/active_support/testing/setup_and_teardown.rb:67:in `__send__'
     /Library/Ruby/Gems/1.8/gems/activesupport-2.1.0/lib/active_support/testing/setup_and_teardown.rb:67:in `run']:
Place does not require name.
<false> is not true.

6 tests, 6 assertions, 3 failures, 0 errors

Ok, this is to be expected. We didn't actually validate_presence_of the fields. Let's do that now:

class Place < ActiveRecord::Base
  validates_presence_of :name
  validates_presence_of :address
  validates_presence_of :description
end

If we try it again...

$ ruby test/unit/place_test.rb
Loaded suite test/unit/place_test
Started
......
Finished in 0.153653 seconds.

6 tests, 12 assertions, 0 failures, 0 errors

Test-driven, wooo!!! I think this is good enough to commit.

Relating places and events

We already have an Event model, so I figure a Place should be able to have events, and event would have could be at a particular place. Shall we test it?

class PlaceTest < ActiveSupport::TestCase
  # omitted...
  should_have_many :events
end

We get a failure though:

  1) Failure:
test: Place should have many events. (PlaceTest)
    [/Users/nichoj/Projects/bostonrb/vendor/plugins/shoulda/lib/shoulda/active_record_helpers.rb:378:in `__bind_1213931312_854475'
     /Users/nichoj/Projects/bostonrb/vendor/plugins/shoulda/lib/shoulda/gem/shoulda.rb:191:in `call'
     /Users/nichoj/Projects/bostonrb/vendor/plugins/shoulda/lib/shoulda/gem/shoulda.rb:191:in `test: Place should have many events. '
     /Library/Ruby/Gems/1.8/gems/activesupport-2.1.0/lib/active_support/testing/setup_and_teardown.rb:67:in `__send__'
     /Library/Ruby/Gems/1.8/gems/activesupport-2.1.0/lib/active_support/testing/setup_and_teardown.rb:67:in `run']:
Place does not have any relationship to events.
<nil> is not true.

Now we add the has_many to Place. But we still have a failure!

 1) Failure:
test: Place should have many events. (PlaceTest)
    [/Users/nichoj/Projects/bostonrb/vendor/plugins/shoulda/lib/shoulda/active_record_helpers.rb:405:in `__bind_1213931441_769681'
     /Users/nichoj/Projects/bostonrb/vendor/plugins/shoulda/lib/shoulda/gem/shoulda.rb:191:in `call'
     /Users/nichoj/Projects/bostonrb/vendor/plugins/shoulda/lib/shoulda/gem/shoulda.rb:191:in `test: Place should have many events. '
     /Library/Ruby/Gems/1.8/gems/activesupport-2.1.0/lib/active_support/testing/setup_and_teardown.rb:67:in `__send__'
     /Library/Ruby/Gems/1.8/gems/activesupport-2.1.0/lib/active_support/testing/setup_and_teardown.rb:67:in `run']:
Event does not have a place_id foreign key.
<false> is not true.

Oh, probably should migrate something.

$ script/generate migration place_event_relationship
      exists  db/migrate
      create  db/migrate/20080620031448_place_event_relationship.rb

And we make it look like:

class PlaceEventRelationship < ActiveRecord::Migration
  def self.up
    add_column :events, :place_id, :integer
  end

  def self.down
    remove_column :events, :place_id
  end
end

Do a rake db:migrate db:test:prepare, and we're ready to run it again. Wait for it... wait for it...

$ ruby test/unit/place_test.rb 
Loaded suite test/unit/place_test
Started
.......
Finished in 0.159728 seconds.

7 tests, 15 assertions, 0 failures, 0 errors

Let's add a test for the other side of the relationship:

class EventTest < ActiveSupport::TestCase
  # ... snip ...
  should_belong_to :place
end

Failures, as expected.

 1) Failure:
test: Event should belong_to place. (EventTest)
    [/Users/nichoj/Projects/bostonrb/vendor/plugins/shoulda/lib/shoulda/active_record_helpers.rb:476:in `__bind_1213933933_768823'
     /Users/nichoj/Projects/bostonrb/vendor/plugins/shoulda/lib/shoulda/gem/shoulda.rb:191:in `call'
     /Users/nichoj/Projects/bostonrb/vendor/plugins/shoulda/lib/shoulda/gem/shoulda.rb:191:in `test: Event should belong_to place. '
     /Library/Ruby/Gems/1.8/gems/activesupport-2.1.0/lib/active_support/testing/setup_and_teardown.rb:67:in `__send__'
     /Library/Ruby/Gems/1.8/gems/activesupport-2.1.0/lib/active_support/testing/setup_and_teardown.rb:67:in `run']:
Event does not have any relationship to place.
<nil> is not true.

And now we actually implement it...

class Event < ActiveRecord::Base
  belongs_to :place
end

And guess what? They pass. We win at things that are winnable, like TDD.

Summary

These have been pretty trivial examples, but it demonstrates how to roll with TDD.

  • Write a test
  • See it fail
  • Write code
  • See the test pass
  • Repeat

It also demonstrates how shoulda lets tersely test things that can be written tersely with Rails.

June 23, 2008 at 02:56 Permalink Edit Destroy

Getting back to my vim roots with MacVim

Back when I got my MacBook last fall, I chose to go with TextMate for Ruby and Rails development, despite being a long vim user.

And it was good. I think it was deserving of the hype it usually receives from Ruby developers on Macs. Except for the paying for software thing... I still feel dirty about that.

Recently a friend ditched TextMate cold turkey in favor of vim, after seeing the cool and useful stuff coworkers could do with it. After seeing it myself, let's just say I'm going back to my roots.

If you're on a Mac, you just have to use MacVim (not to be confused with this MacVim). It's basically like gvim, except for Mac OS. Duh.

There are a lot really small, nice integration points and features I've been noticing.

  • Mac-like text navigation
    • option-left/right for navigating words
    • apple-left/right for start/end of line
    • apple-up/down for start/end of document
  • Shortcuts using the apple key
    • apple-s for saving
    • apple-n for new item
    • apple-a for select all. ggVG always felt annoying for doing this
  • Recent documents
  • Tabbing (apple-t creates a one, apple-w closes it)
  • Command line tool, mvim (similar to the mate command). It seems to aaccept the same options you'd expect of vim and gvim.

Similar to gvim using ~/.gvimrc, MacVim uses ~/.macvimrc. Personally, I've taken to putting everything in ~/.vimrc, and just creating a symlink.

I'm still working on replicating most of the functionality I came to find useful for Ruby development in TextMate, but I will keep you posted.

I'm also vaguely considering doing some screencasts with vim tips/tricks. One thing I'd want though, is a good way to show what keys are being pressed. I remember back in OS9 days, there was a utility for doing this, but I haven't found a modern equivilant.

June 17, 2008 at 13:03 Permalink Edit Destroy

Boston Ruby Group Meeting June 2008 Summary

Yours truly gave a presentation last night titled 'git as a subversion replacement'. I put the slides up on SlideShare for anyone interested.

Joe Ferris also gave a demo/talk about factory_girl, a tool for using factories in your tests. See the original post announcing factory_girl over at GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS.

There was a backchannel going during the meeting. Here are some notes and links gathered it from and overheard elsewhere:

June 11, 2008 at 19:25 Permalink Edit Destroy

Embracing your paranoia with acts_as_paranoid

According to their RDoc, "Overrides some basic methods for the current model so that calling destroy sets a 'deleted_at' field to the current timestamp. ActiveRecord is required."

There doesn't seem to be really good up-to-date documentation on how to actually use it though. Let's take a stab at it.

Installing

First thing is to install it. I typically use piston to manage them. We need the at least version 1.9.x for its git support.

myapp $ cd vendor/plugins
myapp/vendor/gems $  piston import git://github.com/technoweenie/acts_as_paranoid.git
INFO main: Guessing the repository type
INFO main: Guessing the working copy type
INFO main: Checking out the repository
INFO main: Copying from Piston::Revision(git://github.com/technoweenie/acts_as_paranoid.git@1e9144e483524b4f246a1768462eadb22f634f19)
INFO main: Checked out "git://github.com/technoweenie/acts_as_paranoid.git" 1e9144e to "acts_as_paranoid"

Configuring

We now can define a model class to be paranoid:

class Event < ActiveRecord::Base  
  acts_as_paranoid
end

Internally, acts_as_paranoid uses the deleted_at column to track if something is deleted or not. So, we need to make a migration for it:

myapp $ script/generate migration MakeEventParanoid
  exists  db/migrate
  create  db/migrate/012_make_event_paranoid.rb

It should look something like:

class MakeEventParanoid < ActiveRecord::Migration
  def self.up
    add_column :events, :deleted_at, :datetime
  end

  def self.down
    remove_column :events, :deleted_at
  end
end

You can now migrate:

myapp $ rake db:migrate
== 12 AddDeletedAt: migrating =================================================
-- add_column("events", :deleted_at, :datetime)
   -> 0.3596s
== 12 AddDeletedAt: migrated (0.3596s) ========================================

Using

It might be easiest to demonstrate with this snippet from script/console:

>> event = Event.find(:first)
=> #<Event id: 91133883, date: "2008-04-28 00:00:00", description: "The Ezra event tonight has been cancelled.The Ezra ...", created_at: "2008-04-16 13:30:20", updated_at: "2008-05-30 23:43:22", location: "", lng: nil, lat: nil, title: "Meeting", deleted_at: nil>
>> event.destroy!
=> #<Event id: 91133883, date: "2008-04-28 00:00:00", description: "The Ezra event tonight has been cancelled.The Ezra ...", created_at: "2008-04-16 13:30:20", updated_at: "2008-05-30 23:43:22", location: "", lng: nil, lat: nil, title: "Meeting", deleted_at: nil>
>> Event.find(:first)
=> #<Event id: 420662881, date: "2008-04-15 00:00:00", description: "Worked on creating the app for this site, [bostonrb...", created_at: "2008-04-16 13:30:20", updated_at: "2008-05-29 01:19:41", location: nil, lng: nil, lat: nil, title: "Hackfest", deleted_at: nil>
>> Event.find_with_deleted(:first)
=> #<Event id: 420662881, date: "2008-04-15 00:00:00", description: "Worked on creating the app for this site, [bostonrb...", created_at: "2008-04-16 13:30:20", updated_at: "2008-05-29 01:19:41", location: nil, lng: nil, lat: nil, title: "Hackfest", deleted_at: nil>
>> event.deleted_at = nil
=> nil
>> event.save!
=> true
>> Event.find(:first)
=> #<Event id: 420662881, date: "2008-04-15 00:00:00", description: "Worked on creating the app for this site, [bostonrb...", created_at: "2008-04-16 13:30:20", updated_at: "2008-05-29 01:19:41", location: nil, lng: nil, lat: nil, title: "Hackfest", deleted_at: nil>

A synopsis:

  • Find the first event
  • Delete it
  • Find the first event again, and see that it's something else
  • Find with deleted records, to see our original event
  • Unset deleted_at to 'undelete' the orignal event.

Update: Fixed the script/console section. Was using the wrong variable that I used earlier in the IRB session, but wasn't displayed.

May 31, 2008 at 13:27 Permalink Edit Destroy

Playing with heroku

If you've been following things in the Ruby sphere, you might have heard of heroku. In my mind, it provides two major things:

  • Web UI for Rails development
  • Turnkey Rails hosting and deployment

I attended a workshop earlier today giving a shallow dive through Ruby and Rails, where we used heroku as our testbed.

Initial setup

To start, you'll obviously need an account :) It is currently in invite-only beta, but I might be able to swing an invite or two, so drop me a note if you're interested.

Once you have an account setup, you should install their gem.

urmachine $ sudo gem install heroku
Updating metadata for 3 gems from http://gems.rubyforge.org/
...
complete
Successfully installed heroku-0.2
1 gem installed
Installing ri documentation for heroku-0.2...
Installing RDoc documentation for heroku-0.2...

This gives you access to the basic stuff for managing your heroku apps.

Creating an app

urmachine $ heroku create urapp
Enter your Heroku credentials.
Email: joshua.nichols@gmail.com
Password:
Uploading ssh public key
Created http://urapp.heroku.com/

This does a few things:

  • The first time you use any heroku commands, you will be prompted for your heroku email and password. It will also upload your public ssh key (which you need to create ahead of time)
  • Creates a default app, as if you had done rails urapp locally
  • Sticks this in a remote git repository
  • Starts up the app, and makes it accessible at http://urapp.heroku.com

Developing the app

At this point, you have two options for going forward with development.

  • Use the heroku web interface. Not for me.
  • Pull down a copy of your repository, work remotely and periodically push changes. Yes yes yes yes yes.

The heroku command line tool acts as a front to

urmachine $ heroku clone urapp
Initialized empty Git repository in /Users/nichoj/Projects/urapp/.git/
The authenticity of host 'heroku.com (67.202.2.198)' can't be established.
RSA key fingerprint is 8b:48:5e:67:0e:c9:16:47:32:f2:87:0c:1f:c8:60:ad.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'heroku.com,67.202.2.198' (RSA) to the list of known hosts.
remote: Generating pack...
remote: Done counting 56 objects.
remote: Deltifying 56 objects...
remote:  100% (56/56) done
Indexing 56 objects...
remote: Total 56 (delta 14), reused 0 (delta 0)
 100% (56/56) done
Resolving 14 deltas...
 100% (14/14) done
(in /Users/nichoj/Projects/urapp)

You can now enter the urapp directory and hack away.

Deploying

You'd think this be a long section but it isn't.

urmachine $ git commit -a -m "behold my awesome changes."

This would just commit to your local repository though. Now push it.

urmachine $ git push
updating 'refs/heads/master'
  from 6ee014dfd32c5496b0cc4c3c7301e1c47b9f1b2f
  to   ed9a9e46ebeb90f1d288fc31063439938b9f7e00
 Also local refs/remotes/origin/master
Generating pack...
Done counting 44 objects.
Deltifying 44 objects...
 100% (44/44) done
Writing 44 objects...
 100% (44/44) done
Total 44 (delta 7), reused 0 (delta 0)
refs/heads/master: 6ee014dfd32c5496b0cc4c3c7301e1c47b9f1b2f -> ed9a9e46ebeb90f1d288fc31063439938b9f7e00
HEAD is now at ed9a9e4... behold my changes.
NOTICE:  CREATE TABLE will create implicit sequence "posts_id_seq" for serial column "posts.id"
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "posts_pkey" for table "posts"
NOTICE:  CREATE TABLE will create implicit sequence "comments_id_seq" for serial column "comments.id"
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "comments_pkey" for table "comments"
(in /mnt/home/userapps/18228)
== 1 CreatePosts: migrating ===================================================
-- create_table(:posts)
   -> 0.0361s
== 1 CreatePosts: migrated (0.0362s) ==========================================

== 2 CreateComments: migrating ================================================
-- create_table(:comments)
   -> 0.0367s
== 2 CreateComments: migrated (0.0368s) =======================================

Mongrel not running, can't restart

WHOA.

Not only did it push the commit, it seems to have some post-commit magic to migrate your database and restart mongrel.

Impressions

Overall, this seems pretty slick. Lots of cool stuff going on.

  • Web UI for Rails development if you want it
  • git access for when you want to do local development
  • Effortless deployment
  • Really useful for trying out proof-of-concept apps
  • Free

The only concern I have is pricing. It's free now, but I can only imagine it'll be a pay service at one point or another. I'm told development will be free, but there will be some pricing scheme for making stuff publicly accessible.

May 25, 2008 at 03:59 Permalink Edit Destroy

Managing svn:ignore with impunity

Managing svn:ignore has always annoyed me. After using git for the past few months, it bugs me even more now.

I think I've come up with a simple way of coping. That, or I totally stole it from someone else on the internet, without realizing it. It's hard to keep track of these kinds of things.

The trick

Here's what you do:

  • Create a .svnignore in the top of your project.
  • Make a list of files/wildcards you care to ignore
  • Save it

Now, from the top of your project, you can do:

$ svn propset svn:ignore -F .svnignore  .

If one were to wordify this command, it's be something like "Set the svn:ignore property from the contents of the .svnignore onto the current directory." It can really be any file, but I kinda like this convention.

It is also likely you will want to keep .svnignore under source control.

Now automate it

It's usually best to automate when possible, since even this simplied way of managing ignored files might get tedious. Or maybe someone coming onto the project doesn't have our level svn-fu.

We already have a lib/tasks/svn.rake that has a few tricks in it, so I figured on expanding it. Here's the original:

namespace :svn do
  desc "Adds all files with an svn status flag of '?'"
  task(:add) { system %q(svn status | awk '/\\?/ {print $2}' | xargs svn add) }

  desc "Deletes all files with an svn status flag of '!'"
  task(:delete) { system %q(svn status | awk '/\\!/ {print $2}' | xargs svn delete) }

  desc "Writes the log file to doc/svn_log.txt"
  task(:log) do
    File.delete("#{RAILS_ROOT}/doc/svn_log.txt") if File::exists?("#{RAILS_ROOT}/doc/svn_log.txt")
    File.new("#{RAILS_ROOT}/doc/svn_log.txt", "w+")
    system("svn log >> doc/svn_log.txt")
  end
end
I added a new task that looks like:
desc 'Updates svn:ignore from .svnignore'
task(:update_svn_ignore) do
  system %q(svn propset svn:ignore -F .svnignore .)
end
Now we can tweak `.svnignore` to our hearts' content, and then just run:
$ rake svn:update_svn_ignore
(in /Users/nichoj/Projects/boston_rb)
property 'svn:ignore' set on '.'

Awesome.

May 23, 2008 at 05:09 Permalink Edit Destroy

Notes from BarCampBoston3

This past weekend I attended BarCampBoston3, my first unconference ever. Overall, I'd saw it was an amazing experience.

I went to BarCampBoston3!

If you're not familiar with BarCamp, and the whole unconference thing, here are some quick links that might give you a better idea:

I gave 2 talk sessions and 2 discussion sessions over the course of the weekend. I hope to post the presentation/notes from the talks as I recover and find time this week:

  • git as a replacement for subversion... and more!
  • Boston Ruby birds of a feather
  • Testing, quality control, and other ways to preserve your sanity
  • Who is the final cylon model?

Just a few quick notes and thoughts, as a presenter and attendee

For presenters

  • Get to your room early, so you don't fumble for 5 minutes setting things up
  • Don't forget appropriate dongles if you happen to be cursed by Apple's mini-DVI
  • 30-minute sessions go real quick
  • If running your laptop off battery, set to optimal performance to avoid display turning off
  • Turn off notifications, like growl

For attendees

  • Don't use your first attendance to get to know how these work. Jump on it! Give a talk.
  • You probably have something interesting to give a talk on. You just don't realize it.
  • Don't be shy. If all else fails, you could get this shirt
  • Bring business cards
May 19, 2008 at 17:54 Permalink Edit Destroy

Boston.rb Hackfest 5/07 Post Mortem

By now, I think it's a running joke that we'd work on the recommendable plugin we started a month or so ago.

Instead, we worked on the Factory stuff. This article captures most of the ideas of using factories for testing and reasons for using them instead of fixtures.

The crew tonight:

Overview

As the article mentions, someone did implement a plugin which captures most of these ideas.

There were a few reasons (and some more which I don't remember) why we didn't like this plugin.

  • Plugin, not a gem, so unusable outside of rails
  • Pollutes the namespace of Object
  • No tests

So, the journey for better Factories begins. The basic ideas/goals we settled on is this:

  • Define your factories in test/test_helper.rb
  • Be able to define a factory for a class with default options
  • Be able to define alternate factories for a class, for example, to create an admin user versus a normal user
  • Flexibility for defining default attributes, ie be able to call other methods
  • Choice of making new instance, persisting a new instance, stubbing an instance, or getting valid attributes
  • Flexible API for actually calling the factories

The API

So we started out with a basic API that Dan Croak had outlined:

Factory.define do |factory|
  factory.add :project do |project|
    project.name {"factoryfu"}
    project.homepage_url {"http://factoryfu.rubyforge.org"}
  end

  factory.add :event, :meeting do |event|
    event.event_type {"Meeting"}
    event.date {Time.now}
    event.description {"an sick meeting"}
  end

  factory.add :event, :hackfest do |event|
    event.event_type {"Hackfest"}
    event.date {Time.now}
    event.description {"a cool hackfest"}
  end
end

As for usage of the factories, we had 3 possibilities:

Factory.create :project, :name => 'ambitious_sphinx'
Factory.create_project :name => 'ambitious_sphinx'
create_project :name => 'ambitious_sphinx'

I'm not going to say much about these, aside from the first being the simplest to implement, the second being the most explicit about what you are doing, and the last is the most concise.

Because it seemed like the simplest, we started implementing the first. I believe that the other two syntaxes can actually be emulated using it.

To simulate the second, as we register factories, we can create the appropriate methods, ie create_event, new_event, valid_event_attributes.

To simulate the third, we can build on the second, and probably do some magic to delegate from TestCase to our Factory.

Implementing it

For simplicity's sake, we started with our work from last week's hackfest on bostonrb.org.

We added the snippet for defining factories into our test_helper.rb, made a new model unit test which used the factory methods, and started a Factory class.

We basically ran the test, watched it blow up horribly, and added some code, until eventually the test passed.

You can see the work we ended up in the hackfest repository under 'boston_rb'

Of particular interest are:

So, I'm just go over a few of the more interesting bits of the implementation:

Factory.define

This is the entrance into defining our factories.

The implementation isn't very interesting. We just call the block on Factory. We were originally making new instances of Factory, and passing that to the block, but we then realized that this made it a little trickier to have factory methods be class-level methods.

Factory.add

This is what you use to define how you the factory behaves.

It takes a block which defines how to create new attributes. If you look at the snippet:

event.event_type {"Hackfest"}
event.date {Time.now}

Notice that event.event_type and event.date are actually taking blocks. This allows us to do Time.now, and have this not actually be invoked until object creation.

Factory.add actually passes your block a magical Hash which captures this attribute/attribute creation data. We'll talk about this has in a little bit.

After we get back from the block, we store in @@model_templates, keyed by the model and kind of the factory.

The Factory methods

We provide three factory methods:

  • valid_attributes: a Hash of valid attributes for the object
  • new: a new unpersisted instance, using valid_attributes
  • create: a persisted instance, using new

You invoke them like:

Factory.valid_attributes :project
Factory.new :project, :name => 'factoryfu', :homepage_url => 'http://somewhere.com'
Factory.create :event, :hackfest, :date => 3.days.ago, :description => 'your looking at it'

The way we parse the arguments is a little tricky. Initially, we only supported the first 2 lines. This meant our method signature looked like:

def self.new(model, attributes = {})

When we went to support the 3rd line, we thought to go like:

def self.new(model, kind = 'default', attributes = {})

By doing so, you break the 2nd line, because kind would get set to our attributes.

activesupport provides a solution: Array#extract_options. Basically, it looks at the Array, and pops the last element off the end if it's a Hash. We use it like:

def self.valid_attributes(model, *args)
  kind, attributes = parse_args(*args)
  # ...
end

protected

def self.parse_args(*args)
  attributes = args.extract_options!
  kind = args.first || 'default'
  [kind, attributes]
end

BlockfulHash

We made a subclass of Hash which lets you define keys like:

event.date {Time.now}
event.description {"This is going to be SICK!"}

Such that it's equivalent to:

event[:date] = {Time.now}
event[:description] = {"This is going to be SICK!"}

Here's the implementation:

class BlockfulHash < Hash
  def method_missing(method, *args)    
    # When Proc.new is given no arguments,
    # it returns a Proc for the block passed to the current method
    # Proc.new... yeah, it's officially a hackfest.
    self[method.to_sym] = Proc.new
  end
end

This BlockfulHash is what gets passed to the block given to Factory.add.

What's next

So, we have something workable here:

  • Uses the syntax we laid out for defining factory
  • Supports the first syntax for using factories (Factory.create :event)

As far as what is left to do:

  • Extract it out of the boston_rb code base
  • More tests
  • Gemify
  • Support Factory.create_event syntax
  • ...
  • PROFIT!!!

Addendum

Some links and other ideas tossed around over the course of the night:

May 07, 2008 at 19:52 Permalink Edit Destroy

Managing RubyGems on Gentoo

For Ruby on Gentoo, it has been asked how one should go about managing RubyGems. You have a few options:

  • Use Gentoo packages exclusively
  • Use gem exclusively
  • Mix and match and pray

Mix and match

Offhand, mixing and matching seems like a bad idea.

Doing so, you might install a gem using portage, but then you could use gem update to update it. Then you update it with portage...

Overall, seems like it could be problematic in the long run.

Gentoo packages exclusively

Being a Gentoo packager, my first take is that using packages is the way to go.

Some pluses:

  • One stop for installing/updating packages, ruby or otherwise
  • For gems with native extensions, can have dependencies on the things the native extensions use

Of course, there are a number of disadvantages.

  • There are tons of gems out there
  • Some update frequently
  • Lot of packages to be keeping up with stable keywords
  • Despite gem ebuilds being pretty straightforward, there is a bit of grunt work involved on to verify dependencies are accurate
  • Lots of gems are unpackaged, because they are usually packaged as requested or as developers need them
  • Despite being able to install multiple gems at once, packages usually aren't slotted that way

Gems exclusively

If you're a Ruby developer, the most natural way would be using RubyGem directly. Some nice things include:

  • Most direct way to have the latest and greatest gems
  • Can have as many versions installed as you want
  • Zero maintenance for Gentoo packagers (sweet!)

Of course, not without their drawbacks:

  • If a gem uses native extensions, it can be tricky to determine what the it's dependencies are
  • Problematic when other (non-ruby) packages need to depend on gems

My current practices

So where does that bring us?

Here are the practices I've taken to using for my Ruby and Rails development and deployment needs.

In development

On my development machine (4 MacBook running Leopard with Gentoo/Prefix), I've using RubyGems directly.

If I'm working on a Rails app, and it needs any gems, I've taken to vendoring everything. This type of thing will be supported out of the box for Rails 2.1.

Of course, this wouldn't work so well for things with native extensions, like hpricot.

In production

I avoid installing any gems in production as much as possible. If an app needs something, it should be vendored, like I just described.

For gems needed by my apps that have native extensions, or are otherwise needed in production (like rake and the mysql gem), again, I'm using RubyGem directly.

May 05, 2008 at 02:57 Permalink Edit Destroy

Boston.rb Hackfest 4/29 Post Mortem

So, we were just kidding about working on the recommendable plugin. We actually worked on the bostonrb.org website some more.

The crew tonight was:

The significant things we worked on were:

  • Projects have descriptions now
  • Events now have a location
  • Reorganized events page
    • left side has all upcoming events
    • center has next event with google map
    • right side has nothing now, but we're thinking to put a calendar type view

Some articles, resources, that came up while hacking away:

The fruits of our labor have been pushed to http://bostonrb.org, and the source can be found at https://svn.thoughtbot.com/hackfest/boston_rb.

April 30, 2008 at 04:04 Permalink Edit Destroy

piston and git for the win

Piston has had support for git (both importing from, and into) for a little over a month.

Before that, I felt like developing a rails app in git was a bit painful. Considering most plugins are kept in subversion, you're only real option is using svn export to install plugins, and check that into git. There was braid, but I really didn't have any luck with it.

But, like I said, it's all good now. It hasn't been officially released (ie as gems), but you can build and install it yourself easily enough.

$ git clone git://github.com/francois/piston.git
$ sudo gem install -y main open4 log4r
$ cd piston
$ sudo rake install_gem

Pretty straight forward to use.

$ cd vendor/plugins
$ piston import git://github.com/technoweenie/restful-authentication.git
INFO main: Guessing the repository type
INFO main: Guessing the working copy type
INFO main: Checking out the repository
INFO main: Copying from Piston::Revision(git://github.com/technoweenie/restful-authentication.git@42083ffa31e0a9792472780854ddd81bcc9b2f61)
INFO main: Checked out "git://github.com/technoweenie/restful-authentication.git" 42083ff to "restful-authentication"

And since it's since it's being developed in a git repository, and on GitHub, it's extremely easy to start contributing.

I mean, if I can contribute something, I'm sure you can pull it off too :)

April 23, 2008 at 01:55 Permalink Edit Destroy

Boston.rb Hackfest 4/15 Post Mortem

Last Tuesday, we had a small, but dedicated showing at the Hackfest:

Going in, we didn't really have a something in mind to work.

We thought to pick up the app we started in merb for organizing katas. But when it came down to it, we figured it'd be a lot quicker if we just did it rails.

We actually wanted something a little more general, kind of a one stop shop for Boston Ruby stuff. Here's what we wanted:

  • Keep track of events (meetings, hackfests, and katas)
  • Projects that have come out of above-mentioned meetings
  • Recent commits from these projects

Tools we used:

You can see the code we ended up with in the hackfest repository.

Or, you can see it in action.

For simplicity's sake, we didn't initially bother with users/permissions/etc, so be gentle with it.

April 22, 2008 at 03:17 Permalink Edit Destroy

How to raise the bar to contribute to your open source project

I've been doing quite a bit of Ruby development lately, not going to lie.

There have been several times where I'm working with something, and need a better understanding of how it works. Or maybe I think I have a bug. Or maybe I want to add some sort of improvement.

But, it's not always so easy to do so. So, if you want to raise the barrier to contribute to your project, here are some practices for you:

  • Do...
    • Make a single blog post the authoritative home page
    • Distribute tarballs that can't reproduce your artifacts (ie gems)
    • Use RubyForge for things like ticket tracking
  • Don't...
    • Don't make it obvious where to find the source under version control.
    • Don't make it obvious where to file bugs
      • Most projects that use their RDoc as their web page
    • Write RDoc, or otherwise comment your code
    • Tag releases

Certainly, this some of this list doesn't just apply can apply to many other projects, not just Ruby ones.

April 20, 2008 at 19:59 Permalink Edit Destroy

Dead easy nginx configuration on Gentoo

Considering the hype about nginx in the Rails community, I figured it was about time to try it out.

Being the slacker that I am, I didn't really want to have to learn how to use it. Fortunately, Ezra gives us a good starting point for a configuration.

Then Chris Wanstrath comes along and creates nginxconfiggenerator to make it even easier, by providing a generator around this configuration.

One kink though. Running on Gentoo and using the latest nginx package. Default template has paths not suitable for this. Bummer.

Being the clever fellow he is, Chris's generator does provide a way around this, by letting you specify your own template to use.

I took a little time to tweak his original template for Gentoo consumption. Behold!

Here's how you'd use it, assuming you've gone through the steps already mentioned in the post.

$ wget http://p.caboo.se/182778.txt -O gentoo_nginx.conf.erb
$ generate_nginx_config config.yml nginx.conf \
      --template gentoo_nginx.conf.erb

You should be all set now.

One last note, you need to set fair: false for your sites. The fair directive doesn't seem to work with the current version we have on Gentoo. I'm not positive, but I think it's related to this post by Ezra

April 18, 2008 at 15:54 Permalink Edit Destroy

GitHub: Requesting your changes be pulled from a fork

After forking the recently gitified Shoulda, I started poking around. First thing I noticed is tests were failing because some directories were missing. Presumably, this is because git ignores empty directories.

Let's use this opportunity to using git and GitHub to contribute a fix for this, shall we?

Here's a sky high view of the process:

  • Create a fix branch
  • Make changes
  • Push fix branch to GitHub
  • Browse to branch in browser
  • Submit a 'pull request' to the maintainer for review

After the maintainer gets the request, they are free to do as they see fit. Accept it, tweak it, kick it, and so on.

Fixing it

First thing's first. We should create a branch off of master to isolate the fix:

git checkout -b add_missing_dirs

Now the tricky part: actually fixing it. When everything is ready, I just commit it:

git commit

As always, it's good to include meaningful commit messages. Keep in mind that if accepted, this message will become eternally bound to the repository.

With that out of the way, we actually need to push it to our 'origin' remote. This would be GitHub.

git push origin  add_missing_dirs

Requesting the pull

We can now go to our repository on GitHub. We want to browse to the add_missing_dirs branch. Mouse over 'all branches' to get there.

This gets us to the branch. Next, we indicate our desire to submit a pull request.

This prompts us for some details. I only had one commit, so I used its log as the message. I added tsaleh aka Tammer Saleh as the recipient, since he's the maintainer of Shoulda.

After you submit, you'll get a notice that it was sent successfully, and you'll be returned to your branch. The recipients now get a notice about this pull request, and can take appropriate action from there.

Done and done

Congratulations! You are now at the mercy of the maintainer.

April 15, 2008 at 08:20 Permalink Edit Destroy

GitHub: Forking a project

If you hadn't heard, GitHub went officially live last week.

As I've posted before, git is pretty awesome. GitHub makes it even better.

In honor of the launch, here's a quick rundown of forking a repository to start hacking away.

First off, you need to find a victim. In this case, I'm going to go after thoughtbot's Shoulda.

First navigate to the repository, and click 'fork'.

Give it minute while the hardcore forking action happens. Now you have your own fork of the repository.

Here you can see your clone/push URL. So we should go ahead and clone it:

git clone git@github.com:technicalpickles/shoulda.git

So we have a checkout now. As is, it's kind of detached from the official repository. If we're going to be actively working on it, we should add the official repository as a remote.

cd shoulda
git remote add official git://github.com/thoughtbot/shoulda.git

From now on, we should be able easily pull in the changes:

git pull official master

So now what? Get hacking of course. But how do send our changes back? Well, this is a tricky question. There are several ways, but it mostly depend on those receiving the changes back. As people get more comfortable with git, I think we'll start to see some best practices emerge here.

April 14, 2008 at 15:25 Permalink Edit Destroy

BDD with Shoulda talk from MountainWest RubyConf

I've been using Shoulda on a few projects recently, and have done a few posts on it as well.

Last week, I came across this talk by Tammer Saleh by way of GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS .

Tammer is the author of Shoulda, and hearing him talk about it definitely gave me some insight into Shoulda and how to use it.

April 13, 2008 at 19:31 Permalink Edit Destroy

Meet Merb: Ezra's MountainWest RubyConf Talk

Hear a lot about merb straight from Ezra:

I don't know about you, but this got me pretty pumped about merb.

April 11, 2008 at 13:53 Permalink Edit Destroy

Tell Hoe what tests to run

I've been recently playing around with a Ruby frontend to the del.ico.us API. I decided to use Hoe to get things rolling with a proper project layout.

It's pretty straightforward to use initially:

$ sow deliciousr

This generates everything you need to start.

$ cd deliciousr
$ rake -T
rake announce         # Create news email file and post to rubyforge.
rake audit            # Run ZenTest against the package.
rake check_manifest   # Verify the manifest.
rake clean            # Clean up all the extras.
rake clobber_docs     # Remove rdoc products
rake clobber_package  # Remove package products
rake config_hoe       # Create a fresh ~/.hoerc file.
rake debug_gem        # Show information about the gem.
rake default          # Run the default tasks.
rake docs             # Build the docs HTML Files
rake email            # Generate email announcement file.
rake gem              # Build the gem file deliciousr-1.0.0.gem
rake generate_key     # Generate a key for signing your gems.
rake install_gem      # Install the package as a gem.
rake multi            # Run the test suite using multiruby.
rake package          # Build all the packages
rake post_blog        # Post announcement to blog.
rake post_news        # Post announcement to rubyforge.
rake publish_docs     # Publish RDoc to RubyForge.
rake redocs           # Force a rebuild of the RDOC files
rake release          # Package and upload the release to rubyforge.
rake repackage        # Force a rebuild of the package files
rake ridocs           # Generate ri locally for testing.
rake test             # Run the test suite.
rake test_deps        # Show which test files fail when run alone.

You get a lot of stuff for free here. So I went about happily coding, and of course, testing. Initially, I was just running the tests by invoking them directly, or using TextMate's Ruby bundle. But, I figured it'd be best to be running it by using the free rake task.

$ rake test
(in /Users/nichoj/Projects/deliciousr)
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -w -Ilib:ext:bin:test -e 'require "test/unit"; require "test/test_helper.rb"' 
./test/../lib/deliciousr/v1/api_call.rb:64: warning: method redefined; discarding old http
Loaded suite .
Started

Finished in 0.000223 seconds.

0 tests, 0 assertions, 0 failures, 0 errors

Uh, why aren't my tests running???

The short of it is that hoe looks for a certain filename pattern, specifically, it likes tests to be like:

test/**/test_*.rb

My tests? They might be named more like:

test/**/*_test.rb

So, ideally I would rename my tests, but fortunately there is a quick fix. In your Rakefile, you need to add a line to the Hoe configuration.

Hoe.new('deliciousr', Deliciousr::VERSION) do |p|
  # snip
  p.test_globs = 'test/**/*_test.rb'
  # snip
end

It was only by digging into the source that I found it.

Remember kiddies, the source shall set you free.

April 10, 2008 at 08:54 Permalink Edit Destroy

Shoulda macros allows you to embrace your inner slacker

So I think I've discovered Shoulda's secret sauce, the reason why you should use it.

Macros.

It's nice and all to have a decent syntax for doing nested contexts, and declaring tests. But the real power comes from having macros that define tests for you.

Here's a fully shoulda-ified model test (from an example on the Shoulda website):

class PostTest < Test::Unit::TestCase
  load_all_fixtures

  should_belong_to :user
  should_have_many :tags, :through => :taggings

  should_require_unique_attributes :title
  should_require_attributes :body, :message => /wtf/
  should_require_attributes :title
  should_only_allow_numeric_values_for :user_id
end

Imagine how much more code you'd have if you were to do this by hand.

It's not magic though. It's pretty easy to write your own. To start with, you can just define them in your test_helper.rb.

Here's the jist of what you do:

  • Define a class method, usually like should_something_something, or maybe something_should_something.
  • In this method, declare a should block. You'd probably want to name this dynamically based on the parameters given to your method.
  • Assert stuff.
  • ...
  • PROFIT!

As an example, here's something I wrote recently:

class Test::Unit::TestCase
  #snip
  def self.should_build_request_path(path)
    should "build request path of #{path}" do
      assert {path == @api_call.build_request_path}
    end
  end
  # snip
end

Then in your tests, you can use it like:

class GetTagsCallTest < Test::Unit::TestCase
  # snip
  should_build_request_path '/v1/tags/get'
  # snip
end

It's pretty straightforward stuff.

If you start to get a lot of these in your test_helper.rb, you can always move them into module, include/extend them. If they are general enough, who knows, maybe release it as a gem.

April 09, 2008 at 10:29 Permalink Edit Destroy

Follow up: Haml no longer be blowing my spot

I just barely posted about Haml blowin my spot. Thanks to the kindness of a reader, Haml is no longer blowin my spot.

The problem

Imagine we have the following snippet:

%div{ :id => "post_#{ post.id }" }
  %h2{:class => 'post_title'}= post.title
  .post_content= post.cached_content

This post.cached_content contains some <pre> tags. As a result, the contents get kind of skewed.

The fix

So, it seems you can do something like:

%div{ :id => "post_#{ post.id }" }
  %h2{:class => 'post_title'}= post.title
  .post_content~ post.cached_content

The key here is that we use ~ instead of = to output stuff. Apparently this is an alias for Haml::Helpers.find_and_preserve. I actually didn't see any documentation about this alias though.

Looking to the future

Nathan Weizenbaum also pointed out that "Incidentally, as of Haml 1.9 pre and textarea will automatically eschew indentation-management." Sick!

April 08, 2008 at 11:20 Permalink Edit Destroy

Haml be blowing my spot

I've heard of the awesome that is Haml before. I've had friends rave about it. But it was only recently that I broke down to actually use it.

So I went forth and migrated my blog's rhtml templates to haml. Hell, I even migrated my resume from straight-up html to Haml.

It's good. Really good. But, I have some regrets.

One of the features of haml is to automatically pretty-print your markup. But, can you think of any elements that would probably be better left un-pretty-printified?

  • <pre>
  • <textarea>

So, if you're wondering why my code snippets are all skewed, you have your answer.

I haven't found a good work around yet. An obvious answer would be using filters, but this doesn't quite work since I have markup stored in my model. There seems to be a :dirty option for Haml which doesn't format things, but I haven't quite found out how to flip that switch.

So the search continues.

April 07, 2008 at 16:49 Permalink Edit Destroy

Shoulda and assert{2.0} make swell bedfellows

I have been generally using RSpec for my testing needs, but I've recently been persuaded to use Shoulda on a few projects.

For most things, they seem about on par.

One thing I missed in particular was the 'should' syntax of RSpec, like:

x.should == 42

As opposed to using test-unit's:

assertEqual 42, x

I haven't been able to entirely quantify why I prefer the former syntax, but I think it has to do with more succinctly expressing the assertion.

While I haven't found quite to replace that style syntax, I do have something that fits my craving: assert {2.0}. This gives us something like:

assert {x == 42}

I think I'm actually digging this over the other two styles, because:

  • If you can come up with an expression, you can assert it.
  • Nice output when the assertion fails.
April 06, 2008 at 18:29 Permalink Edit Destroy

Hacking on Wikipedia: links for 2008-03-20

I was recently talking to a friend about writing to screen-scraping tool for wikipedia.

My first two thoughts:

  1. Scraping can be problematic at best
    • Dealing with nasty, nasty markup
    • Prone to change
  2. There has to be a better way
    • Preferably someone else has already done the legwork

After some digging, I uncovered the following bits:

The new API is very new, so your mileage may vary.

Update:

Rob Cakebread pointed me at a couple more resources:

March 20, 2008 at 11:43 Permalink Edit Destroy

Meet Merb: Action methods taking arguments

Have you ever written a controller action like:

class PostsController < ApplicationController
  def create
    @post = Post.new(params[:post])
    @post.save
  end
end

Notice how we yank something out of the request parameters? Wouldn't it be nice if you could just define that as an argument to the action method?

Well, merb-action-args does exactly that.

class Posts < Application
  def create(post)
    @post = Post.new(post)
    @post.save