What? You’re still generating migrations manually? I don’t anymore. I have tools which do it for me.

Hobofields is a set of extensions to ActiveRecord that gives us lots of goodies, including the ability to generate migrations automatically. The key to achieving this automatic property declaration is schema declaration in the model.

1 class Post < ActiveRecord::Base
2   fields do
3     title :string
4     published_at :datetime
5     timestamps
6   end
7 end

If you ever used DataMapper, the idea is similar. Where Hobofields shines though is in reading your model and inferring many, many conventions:

1 class Comment < ActiveRecord::Base
2   belongs_to :post
3 end

Running the hobo_migration generator results in:

 1 $ script/generate hobo_migration
 2 
 3 ---------- Up Migration ----------
 4 add_column :comments, :post_id, :integer
 5 
 6 add_index :comments, [:post_id]
 7 ----------------------------------
 8 
 9 ---------- Down Migration --------
10 remove_column :comments, :post_id
11 
12 remove_index :comments, :name => :index_comments_on_post_id rescue ActiveRecord::StatementInvalid
13 ----------------------------------

If you use acts_as_list, Hobofields also knows to add a position column (or whatever name is needed):

1 class Comment < ActiveRecord::Base
2   acts_as_list
3 end

Running the migration generator, you get:

 1 $ script/generate  hobo_migration
 2 
 3 ---------- Up Migration ----------
 4 add_column :comments, :position, :integer
 5 ----------------------------------
 6 
 7 ---------- Down Migration --------
 8 remove_column :comments, :position
 9 ----------------------------------
10 What now: [g]enerate migration, generate and [m]igrate now or [c]ancel? 

Another thing I love about Hobofields is rich type support. Need Textile?

1 class Post < ActiveRecord::Base
2   fields do
3     body :textile
4   end
5 end

 1 content = <<EOTEXTILE
 2 This is some Textile content
 3 
 4 h1. Automatically formatted
 5 
 6 To your:
 7 
 8 * specifications,
 9 * using standard tools
10 EOTEXTILE
11 
12 Post.new(:body => content).body.to_html
13 #=> "<p>This is some Textile content</p>\n<h1>Automatically formatted</h1>\n<p>To your:</p>\n<ul><li>specifications,</li>\n<li>using standard tools</li>\n</ul>\n"

Or maybe you need an enumeration?

1 class Comment < ActiveRecord::Base
2   fields do
3     status enum_string(:spam, :ham), :default => :spam, :required => true
4   end
5 end

 1 $ script/generate hobo_migration
 2 
 3 ---------- Up Migration ----------
 4 add_column :comments, :status, :string, :default => "ham", :required => true
 5 ----------------------------------
 6 
 7 ---------- Down Migration --------
 8 remove_column :comments, :status
 9 ----------------------------------

1 >>Comment.new.status
2 #=> "ham"
3 >> Comment.create!(:status => "foo")
4 #=> ActiveRecord::RecordInvalid: Validation failed: Status must be one of spam, ham

Admittedly, this last example could be better handled with Hobo’s lifecycle (state machine) implementation, but still, it’s nice that the validation already exists. Using :null => false, :required => true creates the appropriate validation as well:

1 class Comment < ActiveRecord::Base
2   fields do
3     author :string, :required
4     email  :string, :required
5     status enum_string(:spam, :ham), :required => true, :default => "ham"
6     timestamps
7   end
8 end

And Hobofields is smart enough to know when no changes are required:

1 $ script/generate hobo_migration
2 Database and models match -- nothing to change

But with this model definition, we now have new validation automatically:

1 >> Comment.create!
2 #=> ActiveRecord::RecordInvalid: Validation failed: Author can't be blank, Email can't be blank

Oh wait, just realized that the email address should be an email address:

1 class Comment < ActiveRecord::Base
2   fields do
3     email :email_address, :required
4   end
5 end

1 >> Comment.create!(:email => "a")
2 #=> ActiveRecord::RecordInvalid: Validation failed: Author can't be blank, Email is invalid

I really enjoy Hobofields for all the goodies it brings to the table. I haven’t even started on all the automatic scopes goodies, the lifecycle methods (which I haven’t used yet), or scoped associations.

Until we meet again tomorrow, I suggest you read Hobofields Reference, as well as Hobofields rich types to get the full goods.

Recently, a friend asked me to help him rewrite the Access database they use to track donations into something better. I offered to rewrite as a web app (surprise!), and chose to use Hobo.

I’m not a designer, and it shows. My apps are horrible, and have bad choice of color. Red on blue? Check. Green on orange? Check. Wide margins and misaligned text base? Check. Name any design mistakes, and I can make it happen.

I had seen Hobo used by another developer, and I was impressed with the ActiveRecord extensions. There was also the fact that Hobo has a pleasing design that could serve as an initial draft, until external help can be acquired.

Hobo isn’t really different from Rails: hobo name_of_new_app. Done.

Things I liked immediately: Declarative declaration of fields and validations, automatic generation of migrations, permissions built-in. You know, stuff that covers the 80%

The thing I didn’t like: DRYML. Don’t get me wrong: DRYML is a very nice tool, and I didn’t have any problems understanding the implicit context. All I had was the age-old problem: learning a new API. After 2-3 weeks (very part time), I was more comfortable with the different tags, and how to use them.

I really enjoy writing this:

1 <table-plus fields="this, city.name, toll_free_number, main_number"/>

and receiving this in return:

Click for larger view

Right now, I’ve had a very positive experience with Hobo. Having good documentation helps a lot, and Hobo hasn’t disappointed: http://cookbook.hobocentral.net/

Over the next few days, I’ll discuss Hobo in more details. Hope you follow along!

At Bloom, we’re looking into using JRuby instead of MRI. To support the two implementations, some gems are different. Case in point: RMagick and the JSON gems:

config/environment.rb
 1 # More config.gem declarations elided
 2 
 3 if defined?(JRUBY_VERSION) then
 4   config.gem "activerecord-jdbc-adapter", :version => "0.9.2",  :lib => "jdbc_adapter"
 5   config.gem "json-jruby",  :version => "1.2.0",  :lib => "json"
 6   config.gem "rmagick4j",   :version => "0.3.6",  :lib => "RMagick"
 7 else
 8   config.gem "json",        :version => "1.2.0"
 9   config.gem "rmagick",     :version => "2.12.2", :lib => "RMagick"
10 end

I created the following in Gemfile:

Gemfile
 1 source :gemcutter
 2 
 3 gem "rails", "= 2.3.4"
 4 gem "pg", "~> 0.8.0" if !defined?(JRUBY_VERSION)
 5 
 6 if defined?(JRUBY_VERSION) then
 7   gem "activerecord-jdbc-adapter", ">= 0.9.2",  :lib => "jdbc_adapter"
 8   gem "json-jruby", ">= 1.2.0"
 9   gem "rmagick4j", ">= 0.3.6"
10 else
11   gem "json", ">= 1.2.0"
12   gem "rmagick", ">= 2.12.2"
13 end
14 
15 # Ruby's 1.9 stdlib replaced CSV with the implementation of 1.8's FasterCSV, but
16 # the upgrade path is problematic: we have to change how we're loading the library.
17 gem "fastercsv" if RUBY_VERSION =~ /^1\.8/

Notice I target 3 versions of Ruby: MRI 1.8, MRI 1.9 and JRuby. If I bundle lock from one Ruby implementation, then the other ones won’t be able to use the locked bundle:

 1 [jruby-1.4.0]$ bundle unlock && bundle lock
 2 The bundle is now unlocked. The dependencies may be changed.
 3 The bundle is now locked. Use `bundle show` to list the gems in the environment.
 4 
 5 [jruby-1.4.0]$ rvm use 1.8.7
 6 Now using ruby 1.8.7 p248
 7 
 8 [ruby-1.8.7-p248]$ bundle check
 9 Could not find gem 'rmagick4j (= 0.3.7, runtime)' in any of the sources.

I’m using Bundler in the context of a Rails 2.3.4 application, but we’ll eventually move to Rails 3. Anybody has any experience using Bundler in this way?

At yesterday’s Montreal.rb I presented Nestor, an autotest-like framework. This is it’s official release announcement.

Nestor is different in that it uses an explicit state machine, namely Aaron Pfeifer‘s StateMachine. Nestor also uses Martin Aumont’s Watchr to listen to filesystem events. But the biggest difference is that the default Rails + Test::Unit is a forking test server. Nestor will load the framework classes—ActiveRecord, ActionController, ActionView, plugins and gems—only once. This saves a lot of time on the aggregate versus running rake everytime.

Nestor's state diagram with events denoting success or failure of a run, and states such as green, running_all or run_focused_pending.
Click for larger version

This release of Nestor is 0.2 quality: it’s not ready for large projects. It only supports Rails + Test::Unit, probably doesn’t run on 1.9 or JRuby, but it’s a solid foundation for going further. In the coming days, I will blog on the internals of Nestor and how StateMachine allowed me to add plugins with very little effort.

Installation

1 $ gem install nestor
2 $ cd railsapp
3 $ # edit config/environments/test.rb to set cache_classes to false
4 $ nestor

I already have a plugin that enables Growl notifications. Install and use:

1 $ gem install nestor_growl
2 $ cd railsapp
3 $ nestor start --require nestor/growl

The —require option is where plugins are loaded. This is an Array of files Nestor will require on startup.

Notes

You must set cache_classes to false in test mode for now. This is a limitation of how Rails boots. With cache_classes set to true, Rails will load the controllers and models when it boots. Since this happens before forking, the code under test would never get reloaded. Did I say it was 0.2 quality?

Piston 2.0.8 is here. This is a minor bugfix release:

  • piston status with no path would not check any status. Thanks to Michael Grosser for the heads up;
  • The ActiveSupport gem deprecated require “activesupport” in favor of “active_support”. Thanks for Michael for reporting this as well;
  • Scott Johnson reported and fixed a problem where a git mv would fail because a parent directory was missing.

Thanks to all contributors!

Installing

1 $ gem install piston

After a few years as a consultant, I received an offer from Bosko Milekic from Bloom Digital Platforms Inc. Starting on October 5th, I will be a permanent Bloom employee. Marc-André and Gary are moving outside the enterprise, while I’m moving inside.

At Bloom, I will be responsible for the AdGear API, enabling applications to easily connect to their platform for ad serving and management. We even have an open source Ruby client available on GitHub as ad_gear_client.

This will be a welcome change of pace for me.

See you at the next Montreal.rb, at my new office!

I just merged Rick Olson’s (technoweenie) rest-client multipart_streaming branch with RestClient 1.0.4. You can see the results at francois/rest-client. I sent a pull request to Adam, so if he pulls it in, everyone will benefit from this code.

I have a work-in-progress branch for using Digest authentication with ActiveResource.

So, how did I do it? It wasn’t too hard actually. When I spiked, I changed ActiveResource::Connection#request to handle authentication itself. I ended up with a big mess: a new rescue clause, 10 lines of code to calculate the digest and so on. But I knew it would work. So, I git checkout . and started with tests, as it should.

The way ActiveResource is built, if a username / password is sent in, ActiveResource will send those automatically in an Authorization header, using the Basic authentication method. I need a way to turn this off. Thus grew #use_basic_authentication= and #use_digest_authentication=.

Next up, actually being able to calculate the Digest. A quick search turned up code by Eric Hodel in the form of a Ruby module: An implementation of HTTP Digest Authentication in Ruby

After a bit of cleanup and rewriting, I have a branch of ActiveResource that’s ready to be commented on. Please see francois/ar_digest and leave comments there.

An example of using Digest would be:

 1 require "logger"
 2 require "activeresource"
 3 require "pp"
 4 
 5 ActiveResource::Base.logger = Logger.new(STDERR)
 6 
 7 ActiveResource::Base.site     = "http://adgear.local/api"
 8 ActiveResource::Base.user     = "francois"
 9 ActiveResource::Base.password = "my-funny-new-password-which-you've-never-seen-before"
10 ActiveResource::Base.timeout  = 30
11 
12 # Don't attempt Basic authentication, but be sure to use Digest
13 ActiveResource::Base.use_basic_authentication  = false
14 ActiveResource::Base.use_digest_authentication = true
15 
16 class Site < ActiveResource::Base
17 end
18 
19 pp sites = Site.find(:all)

This work was sponsored by Bloom Digital Platforms, as part of my work on their AdGear API.

I just pulled in a couple of patches from outside contributors. These are all minor bug fixes, but are important:

  • Chris Gibson: When forcing the repository type, Piston would break because it called #downcase on a Symbol. 1bcc16bf8
  • Terry Heath: Subversion‘s —non-interactive would prevent OS X’s keychain from kicking in. 93d9a957
  • Florian Aßmann: In certain cases, the revision would be a String, and other times it would be an Integer. Normalize before comparing. 40c0bc4e

All users are advised to upgrade:

1 $ sudo gem install piston

I just found Your Web Service Might Not Be RESTful If… by Paul Sadauskas. This article is a very clear and concise explanation of how to do real RESTful services.

His arguments are:

1. Present a single top-level resource;
2. All resources should be accessible by followling links from the top-level resource;
3. Don’t put API tokens in the URL or headers (use representations instead).

A very enjoyable read, and something we should all be following.