How to Work with Tables That Don't Have a Numeric PK?

I am a big advocate for using a "dumb" or non-intelligent primary key for transaction tables. But it doesn't make as much sense for many lookup/reference tables.

Personally speaking I, wholeheartedly, disagree. It always "makes sense" to use simple primary key to manage relationships in any object- relational framework. If you want a key to be used by the users of the system, give them a separate column in the database.

There are many techniques for providing both a key for the system, and a separate key for the users of the system. Many of these even base the user's key on the auto-generated key. Doing anything else you'll be fighting the Rails (or any other object-relational) system and that's never much fun.

Stop thinking like a DBA and start thinking like an OOP programmer and this begins to make a whole lot of sense.

CREATE TABLE states (    CODE VARCHAR(2), -- This would be the primary key    DESCRIP VARCHAR(50),    PRIMARY KEY (CODE) );

One other noteworthy thing to mention about this design is this: What happens when your client comes to you and asks to change a "CODE" value to something different? I know this should never happen, but in the real world it happens all the time. If you manage relationships using a system generated simple PK then this situation suddenly becomes a lot less painful.

Why store states in a table when a simple hash will do? Databases don’t need to be used for everything.

I believe the question was answered. If you decide to go against the convension of the framework then you need to step outside the framework and either code a solution yourself or find something that has already done so for you.

State Migration:

class CreateStates < ActiveRecord::Migration   def self.up     create_table :states, :id => false do |t| # Turns off the id field for primary key       t.column :code, :string, :limit => 2       t.column :name, :string     end   end

  def self.down     drop_table :states   end end

State Model:

class State < ActiveRecord::Base   self.primary_key = "code" end

Instantiate Wisconsin:

wi = State.new wi.id = "WI" wi.name = "Wisconsin" wi.save

Instantiation Note: You can only access wi.code after the object has been saved.

You can now call: State.find("WI") and it returns the proper state object.

AR Associations:

Company Migration:

class CreateCompanies < ActiveRecord::Migration   def self.up     create_table :companies do |t|       t.column :state_id, :string, :limit => 2 # 'state_id' is the Rails convention for fk       t.column :name, :string     end   end

  def self.down     drop_table :companies   end end

Company Model:

class Company < ActiveRecord::Base   belongs_to :state # Note: this is assuming you used "state_id" rails convention end

Coding:

bk = Company.new bk.name = "Burger King" bk.state = State.find("WI") bk.save

You can call:

bk.state.code #=> "WI" bk.state.name #=> "Wisconsin"

I have not tested other Rails functionality, but all of these examples will work for you. However, I agree with everyone else that the best practice is just to use the integer primary keys or to follow Brian's suggestion and create a non-ActiveRecord State model. Brian's suggestion would save you database hits for data that never changes and if put in the models folder will be auto-loaded for you. I would guess asking the DBMS to compare simple integers is more efficient than comparing strings. Or you can save database hits altogether by keeping the states list out of the database and just storing the state code in the table.

Goodluck,

Lee Hericks