Dr Nic

CPK 0.6.2 – the “Santiago” release

Thanks to the fine work of Santiago on the forum, some sneaky defects have been found. The new release is now up on rubyforge and available via gem install composite_primary_keys.

Thanks to everyone for helping each other solve the “ActiveRecord hates Composite Primary Keys” legacy.

Bonus thought: Who wants to generate composite primary keys in their migrations?

Composite Primary Keys supports Associations

[Post summary: new release with support for ActiveRecord Associations]

This project started with an innocent idea as I walked along the street: you could easily write a find method for composite keys. “What’s the big deal about composite keys?”

I sat down and wrote the find method. But the crud operations didn’t work: create, update, destroy. So I rewrote them for composite key records. I released a gem on various mailing lists to see if the solution worked for other people. People were able to drop it into their legacy system and get it working instantly, and they were thrilled. Cool.

Support for CRUD is only half the job of adding Composite Primary Key support to ActiveRecords/Rails. The other half of ActiveRecord magic is its wonderful support for Associations: has_many, has_one, and belongs_to.

I wrote the association unit tests first and kept extending ActiveRecords until all the tests worked. [A much quicker edit-n-test method than using the console/irb].

And today I uploaded the latest release of Composite Primary Keys with support for Associations. Install it with gem install composite_primary_keys and include require 'composite_primary_keys' in your environment.rb (for Rails apps) or within your Ruby scripts. Visit http://compositekeys.rubyforge.org for other information.
Let me know if it works for you!

What is a composite key in Rails?

First, what is an ActiveRecord primary key currently? There are two versions of a primary key: an integer (12) and a string (’12′). That is, Person.find(1) and Person.find(’1′) return the same thing.

This is the theme of the composite key solution – any conceivable notion of 2+ numbers should work as well as any other. So, [12,2] should work, as should ’12,2′, as should ['12','2']. That is, Membership.find(12,2), Membership.find(’12,2′), Membership.find([12, 2]), Membership.find(’12′,’2′), etc, are treated the same.

Normally, if you want to find multiple objects by their primary key, you pass a list, so Person.find(1,2,3) and Person.find(’1′,’2′,’3′) give the same result.

For composite keys, there are many variations that you can use interchangably: Membership.find([12,1],[12,2],[12,3]) is the same as Membership.find(’12,1′,’12,2′,’12,3′) is the same as Membership.find(’12,1;12,2;12,3′).

The id of a normal object is the value of its primary key field. So Person.find(1).id == 1, and Person.find(’1′).id == 1.

To support interchangable use of the id value within your applications, the composite key result is a CompositeIds object, a subclass of Array where to_s method joins the keys by a comma. Want to pass the id of a composite object to your HTML and back to the server in URLs? You won’t need to do anything differently than you do now. The to_param method will return a string joined by commas – e.g. ’12,1′ – which will be happily accepted by your find method when you receive the value in your controller action methods.

The main code change that you will need to consider is that the primary_key method returns a list of column names, instead of a single column name. (Actually it returns a CompositeKeys instance which subclasses Array, and its to_s method joins the values by a comma).

If you find that Composite Primary Keys isn’t working in a sexily smooth way, let me know so the interface can be improved.