May 11, 2015 by Daniel P. Clark

HABTM; You Don’t Need It

My point: “For any valid use of implementing a has_and_belongs_to_many there is an equally valid way to implement it without it.”

A few reasons: Rails had changed both the way it worked and removed it from reflections for a brief period (Rails version 4.1.0 and 4.1.1).  Also the preferred implementation is to use has_many :through.  See this former Rails Issue #14682 and the subsequent Pull Request #15210 on the details.  The reflection was added back in Rails 4.1.2, but this doesn’t seem to be desired for access as a public api (source).

But I’d like to go even further and say you can keep it simpler, get the job done, and be able to manage your data far more easily with just using the basics.

Let’s look at an example from from Kick-Off: First Rails 4 Application with HABTM Association.  He uses an example where a person HABTM communities and a community HABTM persons.

class Person < ActiveRecord::Base
  has_and_belongs_to_many :communities
end
 
class Community < ActiveRecord::Base
  has_and_belongs_to_many :persons
end

Now whereas this is true in a sense that they both have and belongs to many of the other, it leaves the relationship of how this is true as unknown.  For example, as person can belong to a community as a member, owner, founder, etc.  This is a perfect place to use a has_many :through and then define your members/founders as such.  Then when you select the community.founders it will give a results that makes sense by the context it is given.

Now you can even go a step further away from HABTM, if you wanted to, and just create a membership table.  Create a polymorphic membership table so you can define memberships for any other Rails object you’d like.

The advantages of polymorphic relations are countless.  If you are unfamiliar with polymorphic relations I’ve given a thorough post on it here: Rails Polymorphic Associations .  Polymorphic records can be assigned to any ActiveRecord model; even itself.  That’s right I’ve written an example model here: Coffee Model and a test to verify the relationship is valid here: Minitest Spec Example.  And everything passes all GREEN ^_^.  So polymorphic models are your best friend.

Since a person is always the one implied when a membership is involved here we’ll create a field person_id.  But other than that the polymorphic membership model will determine what the membership belongs to.

rails g model Membership person_id:integer:index membershipable:references{polymorphic}

Then update the models

class Person < ActiveRecord::Base
  has_many :memberships, dependent: :destroy
end
 
class Community < ActiveRecord::Base
  has_many :memberships, as: :membershipable, dependent: :destroy
end

class Membership < ActiveRecord::Base
  belongs_to :membershipable, polymorphic: true
  belongs_to :person
end

Then you can create memberships on your community rather simply.  First we’ll create a person and a community, then the membership with both.

Person.create
# => #<Person id: 1> 
Community.create
# => #<Community id: 1> 

Community.first.memberships.create(person_id: Person.first.id)
# => #<Membership id: 1, person_id: 1, membershipable_id: 1,
#                            membershipable_type: "Community"> 

Community.first.memberships
# => #<ActiveRecord::Associations::CollectionProxy [
#      #<Membership id: 1, person_id: 1, membershipable_id: 1,
#                            membershipable_type: "Community">
#     ]> 

Look ma!  No HABTMs!  In the last line I show looking up memberships for the community.  You can do the same thing for a person object to find the memberships a person belongs to e.g.) Person.first.memberships

Code is easier to maintain if you stick to what is simple and basic.  I’ve come to appreciate this over time as I’ve written a library that’s tied heavily into all this relationship-workings (PolyBelongsTo).  I hope I can save many people from headaches by doing things the hard way… even unknowingly.

Summary

I believe I’ve made my point on HABTM.  If you can see a counter-argument to what I’ve presented here I’d love to hear about it.  Often times we over-think things and overcomplicate them.  When the best thing to do is often to take a step back, simplify it, and keep it simple.

Please feel free to comment, share, subscribe to my RSS Feed, and follow me on twitter @6ftdan!

God Bless!
-Daniel P. Clark

Image by Len Matthews via the Creative Commons Attribution-NonCommercial-ShareAlike 2.0 Generic License.

#belongs to#easy#habtm#has many#has_many_and_belongs_to#rails#simplify

Comments

  1. Dev
    May 11, 2015 - 7:16 pm

    Fixing HABTM by using Polymorphic Relations? Thanks, no thanks.

    • Daniel P. Clark
      May 11, 2015 - 7:26 pm

      The HABTM example gives no clear definition on the relation. Polymorphic relations are as flexible as you could need. If nothing else it’s recommended to use has_many :through rather than has_and_belongs_to_many. Or you can make a nonpolymorphic table memberships with person_id and community_id … it’s just a regular database table.

      The biggest issue with HABTM is that you have to write a whole extra set of ways of handling relationships. It’s handled differently and requires additional code for its use case. While it would be much easier to use one uniform approach which permits a meta-programming friendly way of doing things.

      My main point is simply that you don’t need to use HABTM and it’s not as well treated in Rails as other ways of implementing things.

      What do you have against polymorphic relations?

  2. Nathan Ladd
    May 11, 2015 - 8:22 pm

    HABTM has nice syntax with fixtures:

    # persons.yml
    joe:
    communities: ruby, ios

    # communities.yml
    ruby:
    etc.

    HABTM is the right fit for pure many to many join tables. It results in the least amount of complexity and the cheapest maintenance long term.

    When you need more, it’s easy to switch to HM:T.

    • Daniel P. Clark
      May 12, 2015 - 12:14 am

      Thanks for your input! I did not know. I’d be very interested in learning more behind the statements you made of “least complexity” and “cheapest maintenance”. Are these based on not having a middle relational term? (such as membership) If so is that what makes it simpler/cheaper since there is “technically less” to deal with?

Leave a Reply

Your email address will not be published / Required fields are marked *