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!

In Part I, I showed how to configure Rails to use DataMapper instead of ActiveRecord. Well, after some thought, I realized we couldn’t really test anything. Here’s the proof:


1 class ArticleTest < ActiveSupport::TestCase
2 def test_database_is_empty
3 assert Article.all.empty?
4 end
5 end

Running the tests results in a failure:


1 $ rake test:units
2 (in /Users/francois/Documents/work/dm_on_rails)
3 /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -Ilib:test "/Library/Ruby/Gems/1.8/gems/rake-0.8.2/lib/rake/rake_test_loader.rb" "test/unit/article_test.rb"
4 Loaded suite /Library/Ruby/Gems/1.8/gems/rake-0.8.2/lib/rake/rake_test_loader
5 Started
6 .F
7 Finished in 0.049976 seconds.
8
9 1) Failure:
10 test_database_is_empty(ArticleTest)
11 [./test/unit/article_test.rb:10:in `test_db_empty_on_start’
12 /Library/Ruby/Gems/1.8/gems/activesupport-2.1.0/lib/active_support/testing/setup_and_teardown.rb:67:in `__send__’
13 /Library/Ruby/Gems/1.8/gems/activesupport-2.1.0/lib/active_support/testing/setup_and_teardown.rb:67:in `run’]:
14 <false> is not true.
15
16 2 tests, 2 assertions, 1 failures, 0 errors
17 rake aborted!
18 Command failed with status (1): [/System/Library/Frameworks/Ruby.framework/…]
19
20 (See full trace by running task with —trace)

Time to open the wonderful world of transactions! At the time of this writing, DataMapper is at 0.9.6. There is a comment in lib/dm-core/transaction.rb saying TODO: move to dm-more/dm-transactions. If the code below doesn’t work, add a new dependency on dm-transaction.

So, let’s begin by adding new setup / teardown hooks to Test::Unit::TestCase:

test/test_helper.rb

1 class Test::Unit::TestCase
2 setup do
3 __transaction</span> = <span class="co">DataMapper</span>::<span class="co">Transaction</span>.new(<span class="co">DataMapper</span>.repository(<span class="sy">:default</span>)) <span class="no"> 4</span> <span class="iv">transaction.begin
5
6 # FIXME: Should I really be calling #push_transaction like that, or is there a better way?
7 DataMapper.repository(:default).adapter.push_transaction(@
transaction)
8 end
9
10 teardown do
11 if __transaction</span> <span class="no">12</span> <span class="co">DataMapper</span>.repository(<span class="sy">:default</span>).adapter.pop_transaction <span class="no">13</span> <span class="iv">transaction.rollback
14 @
transaction = nil
15 end
16 end
17 end

With that code in place, we can run the tests again and see the results of our hard work:


1 $ rake test:units
2 (in /Users/francois/Documents/work/dm_on_rails)
3 /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -Ilib:test "/Library/Ruby/Gems/1.8/gems/rake-0.8.2/lib/rake/rake_test_loader.rb" "test/unit/article_test.rb"
4 Loaded suite /Library/Ruby/Gems/1.8/gems/rake-0.8.2/lib/rake/rake_test_loader
5 Started
6 ..
7 Finished in 0.009485 seconds.
8
9 2 tests, 2 assertions, 0 failures, 0 errors

UPDATE 2008/10/17: See Part 2 for transation support, including the ability to run multiple tests (which the code below fails to do because of missing transaction support).

I was bored this evening and wanted to do something at least semi interesting. I settled on integrating DataMapper with Rails. Seemed like a nice enough thing to do. Searching for datamapper on rails on Google gave me a link to DataMapper 0.9 avec Rails (Google translation). Nicolas’ article was very interesting, but it’s focus is just on the basics. I decided to tackle migrations and testing.

Unit testing DataMapper models from within Rails

Let’s start by generating a blank Rails application:


1 $ rails dm_on_rails

Then, we’ll remove as much dependency on ActiveRecord as we can. From config/environment.rb, uncomment the config.frameworks line and edit it to the following:

config/environment.rb

1 # Skip frameworks you’re not going to use. To use Rails without a database
2 # you must remove the Active Record framework.
3 config.frameworks -= [ :active_record ]

Rails 2.1 installs some new defaults in config/initializers/new_rails_defaults.rb. Remove the ones that reference ActiveRecord.

Then we need to load the DataMapper gem. Do it by editing config/environment.rb and loading gems:

config/environment.rb

1 # I use SQLite3 here, but if you need/want MySQL, replace sqlite3 with mysql
2 config.gem "do_sqlite3", :version => "0.9.6"
3 config.gem "dm-core", :version => "0.9.6"

Make sure your gems are up to date by running rake gems:install. I had problems with older versions of dm-core polluting my system. You might want to remove those if you get dependency issues.

Then, we need to configure config/database.yml. Completely replace the data in there with this:

config/database.yml

1 development: &defaults
2 :adapter: sqlite3
3 :database: db/development.sqlite3
4
5 test:
6 <<: defaults
7 :database: db/test.sqlite3
8
9 production:
10 <<:
defaults
11 :database: db/production.sqlite3

Then, we need to load this YAML file to configure DataMapper. Create config/initializers/datamapper.rb:

config/initializers/datamapper.rb

1 require "dm-core"
2 hash = YAML.load(File.new(Rails.root + "/config/database.yml"))
3 DataMapper.setup(:default, hash[Rails.env])

Next, let’s generate a model to play with.


1 $ script/generate model article title:string body:text
2 exists app/models/
3 exists test/unit/
4 exists test/fixtures/
5 create app/models/article.rb
6 create test/unit/article_test.rb
7 create test/fixtures/articles.yml
8 uninitialized constant Rails::Generator::GeneratedAttribute::ActiveRecord

Seems we can’t use script/generate model. But the the important files have been created: the fixtures, test and model.

Running rake test:recent gives us another error:


1 $ rake test:recent
2 (in /Users/francois/Documents/work/dm_on_rails)
3 /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -Ilib:test "/Library/Ruby/Gems/1.8/gems/rake-0.8.2/lib/rake/rake_test_loader.rb" "test/unit/article_test.rb"
4 /Library/Ruby/Gems/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:414:in `to_constant_name’: Anonymous modules have no name to be referenced by (ArgumentError)
5 from /Library/Ruby/Gems/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:226:in `qualified_name_for’
6 from /Library/Ruby/Gems/1.8/gems/activesupport-2.1.0/lib/active_support/dependencies.rb:491:in `const_missing’
7 from /Library/Ruby/Gems/1.8/gems/activerecord-2.1.0/lib/active_record/fixtures.rb:869:in `require_fixture_classes’
8 from /Library/Ruby/Gems/1.8/gems/activerecord-2.1.0/lib/active_record/fixtures.rb:867:in `each’
9 from /Library/Ruby/Gems/1.8/gems/activerecord-2.1.0/lib/active_record/fixtures.rb:867:in `require_fixture_classes’
10 from /Library/Ruby/Gems/1.8/gems/activerecord-2.1.0/lib/active_record/fixtures.rb:850:in `fixtures’
11 from ./test/test_helper.rb:35
12 from ./test/unit/article_test.rb:1:in `require’
13 from ./test/unit/article_test.rb:1
14 from /Library/Ruby/Gems/1.8/gems/rake-0.8.2/lib/rake/rake_test_loader.rb:5:in `load’
15 from /Library/Ruby/Gems/1.8/gems/rake-0.8.2/lib/rake/rake_test_loader.rb:5
16 from /Library/Ruby/Gems/1.8/gems/rake-0.8.2/lib/rake/rake_test_loader.rb:5:in `each’
17 from /Library/Ruby/Gems/1.8/gems/rake-0.8.2/lib/rake/rake_test_loader.rb:5
18 rake aborted!
19 Command failed with status (1): [/System/Library/Frameworks/Ruby.framework/…]
20
21 (See full trace by running task with —trace)

This is caused by the default fixtures :all line in test/test_helper.rb. In fact, all the fixtures code is highly dependent on ActiveRecord. So, we won’t use Rails fixtures with DataMapper. Comment out the fixtures :all line, as well as the lines that specify transactional fixtures options.

Next up, the database isn’t created. Ooops! Let’s use a nice DataMapper feature, auto migrations. Append to the end of test/test_helper.rb:

test/test_helper.rb

1 Dir[File.join(Rails.root, "app", "models", "*")].each {|f| require f}
2 DataMapper.auto_migrate!

We begin by loading all models, because at the end of test/test_helper.rb, no model files have been loaded yet. So we have to define the models in memory before DataMapper can auto migrate them.

Edit test/unit/article_test.rb and put the following:

test/unit/article_test.rb

1 class ArticleTest < ActiveSupport::TestCase
2 def test_create
3 article = Article.create("First Post", :body => "This is my first-ever post", :published_at => Time.now.utc)
4 assert !article.new_record?
5 end
6 end

And put real code in app/models/article.rb:

app/models/article.rb

1 class Article
2 include DataMapper::Resource
3
4 property :id, Integer, :serial => true
5 property :title, String
6 property :body, Text
7 property :published_at, DateTime
8 end

And with that, we get a fully functional DataMapper integration!


1 $ rake test:units
2 (in /Users/francois/Documents/work/dm_on_rails)
3 /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -Ilib:test "/Library/Ruby/Gems/1.8/gems/rake-0.8.2/lib/rake/rake_test_loader.rb" "test/unit/article_test.rb"
4 Loaded suite /Library/Ruby/Gems/1.8/gems/rake-0.8.2/lib/rake/rake_test_loader
5 Started
6 .
7 Finished in 0.005368 seconds.
8
9 1 tests, 0 assertions, 0 failures, 0 errors

Running DataMapper migrations from Rails

What about migrations? Well, now we need to enter the world of dm-migrations. Back in config/environment.rb, depend on a new gem:

config/environment.rb

1 config.gem "dm-migrations", :version => "0.9.6"

Then, create the migration file itself (remember! no script/generate migration for you):

db/migrate/articles.rb

1 migration 1, :create_articles do
2 up do
3 create_table :articles do
4 column :id, Integer, :serial => true, :nullable? => false
5 column :title, String
6 column :body, "TEXT"
7 end
8 end
9
10 down do
11 drop_table :articles
12 end
13 end

Finally, we want a Rake task which will run the migrations. Since the db:migrate namespace is already used, we’ll create a dm:migrate namespace instead. Create lib/tasks/migrations.rake:

lib/tasks/migrations.rake

1 namespace :dm do
2 task :migrate => :environment do
3 gem "dm-migrations", "=0.9.6"
4 require "migration_runner"
5 Dir[File.join(Rails.root, "db", "migrate", "*")].each {|f| require f}
6 migrate_up!
7 end
8 end

With all that support in place, it’s time to run the migrations:


1 $ rake dm:migrate
2 (in /Users/francois/Documents/work/dm_on_rails)
3 == Performing Up Migration #1: create_articles
4 CREATE TABLE "articles" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "title" VARCHAR, "body" TEXT)
5 -> 0.0005s
6 -> 0.0010s

If you wish to see the code in action, you may checkout the GitHub repository dm_on_rails. Each commit corresponds to a step in this article.

Pradipta's Rolodex Logo

Late last night, I had the honor of being mailed a Ruby on Rails offer. See 416 Random People with RoR on their resume + Reply All = Reverse Flash Mob for the full details.

Pretty amusing, really.

Does anyone know why ActiveRecord does not merge joins when using scopes ?

Scopes are wonderful, thanks for has_finder and named scopes, but joins aren’t supported. This code:


1 City.nearest(latitude, longitude).find_tagged_with(tags)

raises an ActiveRecord::StatementInvalid complaining that a specified column could not be found. The column can’t be found because the join was dropped on the floor, instead of being merged.

The change me and Marc-André are proposing to make is this:


1 $ git diff 36236235039b3b0cab22a24ce7b45a8ec071cb5e 45e7c94be02a586f70908694545e8484e4a85382 vendor
2 diff -git a/vendor/rails/activerecord/lib/active_record/base.rb b/vendor/rails/activerecord/lib/active_record/base.rb
3 index 261d854..427e3ef 100755
4 a/vendor/rails/activerecord/lib/active_record/base.rb
5 + b/vendor/rails/activerecord/lib/active_record/base.rb
6 @</span> -1673,6 +1673,8 <span class="chg">@ module ActiveRecord #:nodoc:
7 hash[method][key] = [params[key], hash[method][key]].collect{ |sql| "( %s )" % sanitize_sql(sql) }.join(" AND "
8 elsif key == :include && merge
9 hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
10 elsif key == :joins && merge
11 hash[method][key] = [params[key], hash[method][key]].join(" ")
12 else
13 hash[method][key] = hash[method][key] || params[key]
14 end

This is based on Rails 2.0.2, but Rails Edge has the same problem. So, any ideas ?

I needed to send E-Mail as part of my work on XLsuite, and I had problems with HTML E-Mail. I know, I know, it’s preferable to have plain text and all, but user requirements being what they are…

So, anyway, long story short, it didn’t work. I just can’t use the multiple views with content type embedded in them, as the HTML’s text is written from the user interface. So, I turned to the trusted Google, and found an older article on court3nay website How to send multipart/alternative e-mail with inline attachments. The essence of the trick is to use a multipart/related part, and embed the HTML content inside this part. Enjoy !

Rails deprecations are a nice thing. They allow us to know what transition path to use, instead of being left in the dark. With deprecated functionality being removed from Rails, it’s important to know these things.

If you have a decent test coverage, you might want to add this to your config/environments/test.rb:

config/environments/test.rb

1 class DeprecatedFunctionality < SyntaxError; end
2 ActiveSupport::Deprecation.behavior = Proc.new {|message, callstack|
3 raise DeprecatedFunctionality, message
4 }

This will raise an exception instead of simply reporting the warning to the console. If you have CruiseControl.rb installed, the messages will show up as errors there too. This is a quick and easy way to force yourself to actually use the new functionality.

Newshutch, an online feed reader, is closing. I have used their service for quite a while now and, sadly, they are closing. As the authors say in their We’re pulling the plug on Newshutch:

The main problem we’ve been facing is that none of us were passionate about Newshutch anymore.

Ah, passion… The key to everything. On projects I am passionate about, I will work 10/15 hours a day, and on others, I will procrastinate until the end of time.

No words yet on whether or not the source code will be open sourced, sold or anything else.

UPDATE 2007-10-23: Changed repository URLs.

Me and my wife have a funny relationship with money: it never stays in our hands. I would guess the majority of people have the same problem. Back in the days, I started by making ourselves a budget using OpenOffice.org spreadsheet. That was fine, until I realized my wife was always changing the numbers. She used it to record a budget of sorts, but when she actually paid the utilities, she’d change the numbers.

At about the same time, I read about Big Visible Charts. I took a piece of 2 ft × 4 ft of paper, and started at the top:

Month of November 2007

That worked OK, until we were both tired of doing all the calculations by hand… The computer is the perfect tool for the job. So now, I’m back to square one, but this time, I am armed with a lot more knowledge. I want a solution that will:

  • record budgets (planned income and expenditures);
  • record actuals (actual income and expenditures);
  • report planned vs actual values, to see where we’re over-budget (it’s the restaurants!).

After learning how to make a simple todo application on Seaside, I think this application is just a little bit meatier that it’s not going to be too hard to do. At the same time, I will use this opportunity to contrast both Seaside and Rails, to help the community at large to see the differences between both of the frameworks.

Both applications are released under the MIT License.

I have setup two repositories:

You can already grab the code from the Monticello repository: I am done coding the models on Seaside. I checked in the skeleton Rails application, and will add a couple of pieces shortly. Expect to see this series pretty regularly in the coming weeks.

Ain’t Release Early, Release Often wonderful ? I just received a patch from Evgeniy Pirogov that makes the failing test from the AutoScope plugin work.

Evgeniy’s patch is available here, but you should simply svn or piston update to get the latest version, since I eagerly accepted Evgeniy’s patch.

Thank you, Evgeniy for your work.

Well, in relation to my Useful #with_scope technique post, here’s a plugin that implements that idea. This code is used on a production system. It works perfectly for my needs at the moment.

AutoScope

Automatically create scoped access methods on your ActiveRecord models.

Examples

Declare your scopes within your ActiveRecord::Base subclasses.


1 class Contact < ActiveRecord::Base
2 auto_scope \
3 :old => {:find => {:conditions => ["born_on < ?", 30.years.ago]}},
4 :young => {:find => {:conditions => ["born_on > ?", 1.year.ago]}}
5 end
6
7 class Testimonial < ActiveRecord::Base
8 auto_scope \
9 :approved => {
10 :find => {:conditions => ["approved_at < ?", proc {Time.now}]},
11 :create => {:approved_at => proc {Time.now}}},
12 :unapproved => {
13 :find => {:conditions => "approved_at IS NULL"},
14 :create => {:approved_at => nil}}
15 end

These declarations give you access to the following scoped methods:


1 Testimonial.approved.count
2 Testimonial.unapproved.create!(params[:testimonial])
3 young_contacts</span> = <span class="co">Contact</span>.young <span class="no">4</span> <span class="iv">contacts = Contact.old.find(:all, :conditions => ["name LIKE ?", params[:name]])

The plugin’s home page is: http://xlsuite.org/plugins/auto_scope
The plugin’s Subversion repository is: http://svn.xlsuite.org/plugins/auto_scope

Search

Your Host

A picture of me

I am François Beausoleil, a Ruby on Rails and Scala developer. During the day, I work on Seevibes, a platform to measure social interactions related to TV shows. At night, I am interested many things. Read my biography.

Top Tags

Books I read and recommend

Links

Projects I work on

Projects I worked on