[Rails] Associations


Yesterday I found out I had made a major boo-boo in setting up an ActiveRecord::Association.

Imagine a “Widget” model that is composited of serveral other fields. You might think to yourself.

“An Widget has a SKU ( housed in table ‘skus’ based on model SKU ), a widget has a color, etc.”

You then might model your code such that

    Widget < ActiveRecord::Base
      has_one :sku
      has_one :color

And accordingly

    SKU < ActiveRecord::Base
      belongs_to :widget

This is wrong.

You must think in terms of the most “atomic” element. What’s the relationship of a SKU? A SKU “has one” product to which it is applicable. You see, what you’ve done above is absolutely reverse of the way it should be.

Think this way:

    SKU < ActiveRecord::Base
      has_one :widget

And then just add the mirror entry, for completeness

    Widget < ActiveRecord::Base
      belongs_to :sku

You can see this explained in the Rails API:

class Employee < ActiveRecord::Base
    has_one :office

class Office < ActiveRecord::Base
   belongs_to :employee    # foreign key - employee_id

If you find yourself using the primary key of the composite entity to find something in the the constituent table or getting into weird sorts of problems with .create() or find yourself thinking that you may need to implement something like (although it is pretty cool):

    $ irb
    irb(main):001:0> class K
    irb(main):002:1> end
    => nil
    irb(main):003:0> k1 =
    => #<k:0xb7fc1788>
    irb(main):004:0> k2 = eval('K').new
    => #<k:0xb7fb8094>
    irb(main):005:0> k3 = Object.const_get('K').new
    => #<k:0xb7fb1474>


The real difficulty here is with how English speakers construe the transitivity of the phrase “has one”. Does this mean:

  • X is a superior containing entity i.e. “contains” (“The happy meal has one hamburger and one toy”)
  • X is uniquely assigned in a relationship (“The parolee has one parole officer”)

The word “relationship” in the second bullet is the use of “has_one” that Rails uses the phrase “has_one” to signify.