<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/css" href="/stylesheets/rss.css"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
  <channel>
    <title>RealityForge.org: Many-to-many rails goodness with the through directive</title>
    <link>/articles/2005/12/05/many-to-many-rails-goodness</link>
    <language>en-us</language>
    <ttl>40</ttl>
    <description>A little short for a storm trooper</description>
    <item>
      <title>Many-to-many rails goodness with the through directive</title>
      <description>&lt;p&gt;The has_many :through option will rock your socks off! (Or at least make things a little easier) Read on to understand why!&lt;/p&gt;


	&lt;p&gt;Rails has the ability to declare many-to-many relationships between ActiveRecord objects using the &lt;a href="http://api.rubyonrails.com/classes/ActiveRecord/Associations/ClassMethods.html#M000467"&gt;has_and_belongs_to_many&lt;/a&gt; macro. &lt;span class="caps"&gt;HABTM&lt;/span&gt; relationships as they are affectionately known, require a third join table that contains the keys of the two domain objects.&lt;/p&gt;


	&lt;p&gt;Consider the example where a Student can be enrolled in 1 or more Subjects and each Subject can have 1 or more Students. This would be modelled by the following domain classes.&lt;/p&gt;


&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_ruby "&gt;&lt;span class="keyword"&gt;class &lt;/span&gt;&lt;span class="class"&gt;Student&lt;/span&gt; &lt;span class="punct"&gt;&amp;lt;&lt;/span&gt; &lt;span class="constant"&gt;ActiveRecord&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Base&lt;/span&gt;
  &lt;span class="ident"&gt;has_and_belongs_to_many&lt;/span&gt; &lt;span class="symbol"&gt;:subjects&lt;/span&gt;
  &lt;span class="punct"&gt;...&lt;/span&gt;
&lt;span class="keyword"&gt;end&lt;/span&gt;

&lt;span class="keyword"&gt;class &lt;/span&gt;&lt;span class="class"&gt;Subject&lt;/span&gt; &lt;span class="punct"&gt;&amp;lt;&lt;/span&gt; &lt;span class="constant"&gt;ActiveRecord&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Base&lt;/span&gt;
  &lt;span class="ident"&gt;has_and_belongs_to_many&lt;/span&gt; &lt;span class="symbol"&gt;:students&lt;/span&gt;
  &lt;span class="punct"&gt;...&lt;/span&gt;
&lt;span class="keyword"&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

	&lt;p&gt;With the sql &lt;span class="caps"&gt;DDL&lt;/span&gt; looking something like;&lt;/p&gt;


&lt;pre&gt;
&lt;code&gt;
CREATE TABLE students (
  id INT NOT NULL AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  ...
  PRIMARY KEY (id) 
) ENGINE = InnoDB;

CREATE TABLE subjects (
  id INT NOT NULL AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  ...
  PRIMARY KEY (id) 
) ENGINE = InnoDB;

CREATE TABLE students_subjects (
  student_id INT NOT NULL,
  subjects_id INT NOT NULL,
  FOREIGN KEY (student_id) REFERENCES students(id),
  FOREIGN KEY (subject_id) REFERENCES subjects(id)
) ENGINE = InnoDB;
&lt;/code&gt;
&lt;/pre&gt;

At some point you may want to add in some information 
about the &lt;span class="caps"&gt;HABTM&lt;/span&gt; relationship such as the year that the 
student enrolled in the subject. This can be done using 
&lt;code&gt;push_with_attributes&lt;/code&gt; and adding an extra 
&amp;#8216;year&amp;#8217; column to the &lt;span class="caps"&gt;SQL DDL&lt;/span&gt;.

&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_ruby "&gt;&lt;span class="keyword"&gt;class &lt;/span&gt;&lt;span class="class"&gt;Subject&lt;/span&gt; &lt;span class="punct"&gt;&amp;lt;&lt;/span&gt; &lt;span class="constant"&gt;ActiveRecord&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Base&lt;/span&gt;
  &lt;span class="ident"&gt;has_and_belongs_to_many&lt;/span&gt; &lt;span class="symbol"&gt;:students&lt;/span&gt;
  &lt;span class="punct"&gt;...&lt;/span&gt;
  &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;enrol&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;student&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span class="ident"&gt;students&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;push_with_attributes&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;student&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="symbol"&gt;:year&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="constant"&gt;Time&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;now&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;year&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
  &lt;span class="keyword"&gt;end&lt;/span&gt;
&lt;span class="keyword"&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

	&lt;p&gt;Join tables with attributes tend to become ugly fast and it 
is rare that a few days don&amp;#8217;t go by on the rails mailing list without 
someone asking for features that imply they are using join tables as 
a crutch for a missing domain object. Enrolment would be a good choice 
for the example above.&lt;/p&gt;


	&lt;p&gt;However if we introduce an Enrolment object, the naive approach of 
accessing the students of the subject (or vice versa) is extremely 
inefficient as you will first hit the enrolments table before loading 
from the students table (or conversly the subjects table). A more 
efficient way using hand crafted &lt;span class="caps"&gt;SQL&lt;/span&gt; would be the following&lt;/p&gt;


&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_ruby "&gt;&lt;span class="keyword"&gt;class &lt;/span&gt;&lt;span class="class"&gt;Subject&lt;/span&gt; &lt;span class="punct"&gt;&amp;lt;&lt;/span&gt; &lt;span class="constant"&gt;ActiveRecord&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Base&lt;/span&gt;
  &lt;span class="ident"&gt;has_many&lt;/span&gt; &lt;span class="symbol"&gt;:enrolments&lt;/span&gt;
  &lt;span class="punct"&gt;...&lt;/span&gt;
  &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;students&lt;/span&gt;
    &lt;span class="ident"&gt;find_by_sql&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;
      &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;SELECT students.* &lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt; &lt;span class="punct"&gt;+&lt;/span&gt;
      &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;FROM subjects, enrolments, students &lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt; &lt;span class="punct"&gt;+&lt;/span&gt;
 &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;WHERE enrolments.student_id = students.id AND + &lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;
      &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;      enrolments.subject_id = &lt;span class="expr"&gt;#{id}&lt;/span&gt;&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;
    &lt;span class="punct"&gt;)&lt;/span&gt;
  &lt;span class="keyword"&gt;end&lt;/span&gt;
&lt;span class="keyword"&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

	&lt;p&gt;This pattern is likely to be duplicated across many domain objects. 
Luckily David Heinemeier Hansson &lt;a href="http://article.gmane.org/gmane.comp.lang.ruby.rails/32742/"&gt;mentioned&lt;/a&gt; 
that a &lt;code&gt;:through&lt;/code&gt; option will be supported on the the &lt;code&gt;has_many&lt;/code&gt; macro that allows you to replace the above code with;&lt;/p&gt;


&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_ruby "&gt;&lt;span class="keyword"&gt;class &lt;/span&gt;&lt;span class="class"&gt;Subject&lt;/span&gt; &lt;span class="punct"&gt;&amp;lt;&lt;/span&gt; &lt;span class="constant"&gt;ActiveRecord&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Base&lt;/span&gt;
  &lt;span class="ident"&gt;has_many&lt;/span&gt; &lt;span class="symbol"&gt;:enrolments&lt;/span&gt;
  &lt;span class="ident"&gt;has_many&lt;/span&gt; &lt;span class="symbol"&gt;:students&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="symbol"&gt;:through&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="symbol"&gt;:enrolments&lt;/span&gt;
  &lt;span class="punct"&gt;...&lt;/span&gt;
&lt;span class="keyword"&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

	&lt;p&gt;Rails just keeps getting better and better!&lt;/p&gt;</description>
      <pubDate>Mon, 05 Dec 2005 17:15:00 +1100</pubDate>
      <guid isPermaLink="false">urn:uuid:b40c08b62d55865ff1be3a929d34f369</guid>
      <author>Peter Donald</author>
      <link>http://www.realityforge.org/articles/2005/12/05/many-to-many-rails-goodness</link>
      <category>Rails</category>
      <trackback:ping>http://www.realityforge.org/articles/trackback/10</trackback:ping>
    </item>
  </channel>
</rss>
