views:

812

answers:

2

Suppose the following data schema:

Usage
======
client_id
resource
type
amount

Billing
======
client_id
usage_resource
usage_type
rate

In this example, suppose I have multiple resources, each of which can be used in many ways. For example, one resource is a widget. Widgets can be fooed and they can be bared. Gizmos can also be fooed and bared. These usage types are billed at different rates, possibly even different rates for different clients. Each occurence of a usage (of a resource) is recorded in the Usage table. Each billing rate (for client, resource, and type combination) is stored in the billing table.

(By the way, if this data schema is not the right way to approach this problem, please make suggestions.)

Is it possible, using Ruby on Rails and ActiveRecord, to create a has_many relationship from Billings to Usages so that I can get a list of usage instances for a given billing rate? Is there a syntax of the has_many, :through that I don't know?

Once again, I may be approaching this problem from the wrong angle, so if you can think of a better way, please speak up!

+4  A: 

There is apparently an project at sourceforge to extend Rails' ActiveRecord with support for Composite Primary Keys. I haven't used this extension, but it might help you. It's also a gem at rubyforge.

Plain Ruby on Rails, as of version 2.0, does not support compound primary keys (cf. HowToUseLegacySchemas). Every table must have a single-column, auto-increment key named "id".

The explanation I have seen is: "You only need compound primary keys if you want to use a legacy database." This is of course a ridiculously ignorant view of data modeling.

The solution I see would be:

  • Usage.client_id -> Client.id
  • Usage.type_id -> Usagetype.id
  • Usage.resource_id -> Resource.id
  • Billing.usage_id -> Usage.id
  • Billing.client_id -> Client.id
  • Billing.type_id -> Usagetype.id
  • Billing.resource_id -> Resource.id

The apparently redundant foreign keys in Billing attempt to enforce partial referential integrity. But it doesn't quite get there -- it does not prevent you from creating rows in Billing that reference a row in Usage with the wrong client/resource/usagetype combination, not matching those in the referencing row in the Billing table.

edit: @Yarik: yes, you're right. It makes more sense for Usage to reference Billing.

  • Usage.billing_id -> Billing.id

Hmm. I made an ER diagram but I'm having trouble inserting it as an image.

Here: http://www.freeimagehosting.net/image.php?ae4cd6c3ba.png

Bill Karwin
Wait a second... Shouldn't it be the other way around - a primary key in Billing and a foreign key in Usage??
Yarik
Good answer, good background. The RoR view of databases seems to me problematic. But the dbms seems to have exacted its revenge by burdening RoR with a reputation as a slow, unscalable database hog.
le dorfier
+1  A: 

You can create any constraint you want with the 'execute' command in a migration.

You would probably want to add some error handling to .save to deal with cases when the constraint throws an error.

You can't use AR's built-in method generators to generate the usages off the billing, but you can still have the method:

class Billing
  def usages
    Usage.find(:all, :conditions => ["x = ? and y = ?", self.x, self.y])
  end
end
Michael Sofaer