1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
2 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
5 <link rel="stylesheet" href="stylesheets/screen.css" type="text/css" media="screen" />
6 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
10 <script src="javascripts/rounded_corners_lite.inc.js" type="text/javascript"></script>
14 <script type="text/javascript">
15 window.onload = function() {
25 var versionBox = new curvyCorners(settings, document.getElementById("version"));
26 versionBox.applyCornersToAll();
33 <h1>Composite Primary Keys</h1>
34 <div id="version" class="clickable" onclick='document.location = "http://rubyforge.org/projects/compositekeys"; return false'>
36 <a href="http://rubyforge.org/projects/compositekeys" class="numbers">2.2.2</a>
38 <h1>→ Ruby on Rails</h1>
39 <h1>→ ActiveRecords</h1>
41 <p>Ruby on Rails does not support composite primary keys. This free software is an extension <br />
42 to the database layer of Rails – <a href="http://wiki.rubyonrails.com/rails/pages/ActiveRecord">ActiveRecords</a> – to support composite primary keys as transparently as possible.</p>
43 <p>Any Ruby script using ActiveRecords can use Composite Primary Keys with this library.</p>
45 <p><pre class="syntax"><span class="ident">sudo</span> <span class="ident">gem</span> <span class="ident">install</span> <span class="ident">composite_primary_keys</span></pre></p>
46 <p>Rails: Add the following to the bottom of your <code>environment.rb</code> file</p>
47 <p><pre class="syntax"><span class="ident">require</span> <span class="punct">'</span><span class="string">composite_primary_keys</span><span class="punct">'</span></pre></p>
48 <p>Ruby scripts: Add the following to the top of your script</p>
49 <p><pre class="syntax"><span class="ident">require</span> <span class="punct">'</span><span class="string">rubygems</span><span class="punct">'</span>
50 <span class="ident">require</span> <span class="punct">'</span><span class="string">composite_primary_keys</span><span class="punct">'</span></pre></p>
52 <p>A model with composite primary keys would look like…</p>
53 <p><pre class="syntax"><span class="keyword">class </span><span class="class">Membership</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
54 <span class="comment"># set_primary_keys *keys - turns on composite key functionality</span>
55 <span class="ident">set_primary_keys</span> <span class="symbol">:user_id</span><span class="punct">,</span> <span class="symbol">:group_id</span>
56 <span class="ident">belongs_to</span> <span class="symbol">:user</span>
57 <span class="ident">belongs_to</span> <span class="symbol">:group</span>
58 <span class="ident">has_many</span> <span class="symbol">:statuses</span><span class="punct">,</span> <span class="symbol">:class_name</span> <span class="punct">=></span> <span class="punct">'</span><span class="string">MembershipStatus</span><span class="punct">',</span> <span class="symbol">:foreign_key</span> <span class="punct">=></span> <span class="punct">[</span><span class="symbol">:user_id</span><span class="punct">,</span> <span class="symbol">:group_id</span><span class="punct">]</span>
59 <span class="keyword">end</span></pre></p>
60 <p>A model associated with a composite key model would be defined like…</p>
61 <p><pre class="syntax"><span class="keyword">class </span><span class="class">MembershipStatus</span> <span class="punct"><</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span>
62 <span class="ident">belongs_to</span> <span class="symbol">:membership</span><span class="punct">,</span> <span class="symbol">:foreign_key</span> <span class="punct">=></span> <span class="punct">[</span><span class="symbol">:user_id</span><span class="punct">,</span> <span class="symbol">:group_id</span><span class="punct">]</span>
63 <span class="keyword">end</span></pre></p>
64 <p>That is, associations can include composite keys too. Nice.</p>
65 <h2>Demonstration of usage</h2>
66 <p>Once you’ve created your models to specify composite primary keys (such as the Membership class) and associations (such as MembershipStatus#membership), you can uses them like any normal model with associations.</p>
67 <p>But first, lets check out our primary keys.</p>
68 <p><pre class="syntax"><span class="constant">MembershipStatus</span><span class="punct">.</span><span class="ident">primary_key</span> <span class="comment"># => "id" # normal single key</span>
69 <span class="constant">Membership</span><span class="punct">.</span><span class="ident">primary_key</span> <span class="comment"># => [:user_id, :group_id] # composite keys</span>
70 <span class="constant">Membership</span><span class="punct">.</span><span class="ident">primary_key</span><span class="punct">.</span><span class="ident">to_s</span> <span class="comment"># => "user_id,group_id"</span></pre></p>
71 <p>Now we want to be able to find instances using the same syntax we always use for ActiveRecords…</p>
72 <p><pre class="syntax"><span class="constant">MembershipStatus</span><span class="punct">.</span><span class="ident">find</span><span class="punct">(</span><span class="number">1</span><span class="punct">)</span> <span class="comment"># single id returns single instance</span>
73 <span class="punct">=></span> <span class="punct"><</span><span class="constant">MembershipStatus</span><span class="punct">:</span><span class="number">0x392a8c8</span> <span class="attribute">@attributes</span><span class="punct">={"</span><span class="string">id</span><span class="punct">"=>"</span><span class="string">1</span><span class="punct">",</span> <span class="punct">"</span><span class="string">status</span><span class="punct">"=>"</span><span class="string">Active</span><span class="punct">"}></span>
74 <span class="constant">Membership</span><span class="punct">.</span><span class="ident">find</span><span class="punct">(</span><span class="number">1</span><span class="punct">,</span><span class="number">1</span><span class="punct">)</span> <span class="comment"># composite ids returns single instance</span>
75 <span class="punct">=></span> <span class="punct"><</span><span class="constant">Membership</span><span class="punct">:</span><span class="number">0x39218b0</span> <span class="attribute">@attributes</span><span class="punct">={"</span><span class="string">user_id</span><span class="punct">"=>"</span><span class="string">1</span><span class="punct">",</span> <span class="punct">"</span><span class="string">group_id</span><span class="punct">"=>"</span><span class="string">1</span><span class="punct">"}></span></pre></p>
76 <p>Using <a href="http://www.rubyonrails.org">Ruby on Rails</a>? You’ll want to your url_for helpers<br />
77 to convert composite keys into strings and back again…</p>
78 <p><pre class="syntax"><span class="constant">Membership</span><span class="punct">.</span><span class="ident">find</span><span class="punct">(</span><span class="symbol">:first</span><span class="punct">).</span><span class="ident">to_param</span> <span class="comment"># => "1,1"</span></pre></p>
79 <p>And then use the string id within your controller to find the object again</p>
80 <p><pre class="syntax"><span class="ident">params</span><span class="punct">[</span><span class="symbol">:id</span><span class="punct">]</span> <span class="comment"># => '1,1'</span>
81 <span class="constant">Membership</span><span class="punct">.</span><span class="ident">find</span><span class="punct">(</span><span class="ident">params</span><span class="punct">[</span><span class="symbol">:id</span><span class="punct">])</span>
82 <span class="punct">=></span> <span class="punct"><</span><span class="constant">Membership</span><span class="punct">:</span><span class="number">0x3904288</span> <span class="attribute">@attributes</span><span class="punct">={"</span><span class="string">user_id</span><span class="punct">"=>"</span><span class="string">1</span><span class="punct">",</span> <span class="punct">"</span><span class="string">group_id</span><span class="punct">"=>"</span><span class="string">1</span><span class="punct">"}></span></pre></p>
83 <p>That is, an ActiveRecord supporting composite keys behaves transparently<br />
84 throughout your application. Just like a normal ActiveRecord.</p>
86 <h3>Pass a list of composite ids to the <code>#find</code> method</h3>
87 <p><pre class="syntax"><span class="constant">Membership</span><span class="punct">.</span><span class="ident">find</span> <span class="punct">[</span><span class="number">1</span><span class="punct">,</span><span class="number">1</span><span class="punct">],</span> <span class="punct">[</span><span class="number">2</span><span class="punct">,</span><span class="number">1</span><span class="punct">]</span>
88 <span class="punct">=></span> <span class="punct">[</span>
89 <span class="punct"><</span><span class="constant">Membership</span><span class="punct">:</span><span class="number">0x394ade8</span> <span class="attribute">@attributes</span><span class="punct">={"</span><span class="string">user_id</span><span class="punct">"=>"</span><span class="string">1</span><span class="punct">",</span> <span class="punct">"</span><span class="string">group_id</span><span class="punct">"=>"</span><span class="string">1</span><span class="punct">"}>,</span>
90 <span class="punct"><</span><span class="constant">Membership</span><span class="punct">:</span><span class="number">0x394ada0</span> <span class="attribute">@attributes</span><span class="punct">={"</span><span class="string">user_id</span><span class="punct">"=>"</span><span class="string">2</span><span class="punct">",</span> <span class="punct">"</span><span class="string">group_id</span><span class="punct">"=>"</span><span class="string">1</span><span class="punct">"}></span>
91 <span class="punct">]</span></pre></p>
92 <p>Perform <code>#count</code> operations</p>
93 <p><pre class="syntax"><span class="constant">MembershipStatus</span><span class="punct">.</span><span class="ident">find</span><span class="punct">(</span><span class="symbol">:first</span><span class="punct">).</span><span class="ident">memberships</span><span class="punct">.</span><span class="ident">count</span> <span class="comment"># => 1</span></pre></p>
94 <h3>Routes with Rails</h3>
95 <p>From Pete Sumskas:</p>
97 <p>I ran into one problem that I didn’t see mentioned on <a href="http://groups.google.com/group/compositekeys">this list</a> – <br />
98 and I didn’t see any information about what I should do to address it in the<br />
99 documentation (might have missed it).</p>
100 <p>The problem was that the urls being generated for a ‘show’ action (for<br />
101 example) had a syntax like:<br />
103 <pre>/controller/show/123000,Bu70</pre></p>
104 <p>for a two-field composite PK. The default routing would not match that,<br />
105 so after working out how to do the routing I added:<br />
107 <pre class="syntax"><span class="ident">map</span><span class="punct">.</span><span class="ident">connect</span> <span class="punct">'</span><span class="string">:controller/:action/:id</span><span class="punct">',</span> <span class="symbol">:id</span> <span class="punct">=></span> <span class="punct">/</span><span class="regex"><span class="escape">\w</span>+(,<span class="escape">\w</span>+)*</span><span class="punct">/</span></pre><br />
109 to my <code>route.rb</code> file.</p>
111 <p><a name="dbs"></a></p>
112 <h2>Which databases?</h2>
113 <p>A suite of unit tests have been run on the following databases supported by ActiveRecord:</p>
117 <th>Test Success</th>
118 <th>User feedback</th>
122 <td><span class=success><span class="caps">YES</span></span></td>
123 <td><span class=success><span class="caps">YES</span></span> (<a href="mailto:compositekeys@googlegroups.com?subject=Mysql+is+working">Yes!</a> or <a href="mailto:compositekeys@googlegroups.com?subject=Mysql+is+failing">No…</a>)</td>
127 <td><span class=success><span class="caps">YES</span></span></td>
128 <td><span class=success><span class="caps">YES</span></span> (<a href="mailto:compositekeys@googlegroups.com?subject=Sqlite3+is+working">Yes!</a> or <a href="mailto:compositekeys@googlegroups.com?subject=Sqlite3+is+failing">No…</a>)</td>
132 <td><span class=success><span class="caps">YES</span></span></td>
133 <td><span class=success><span class="caps">YES</span></span> (<a href="mailto:compositekeys@googlegroups.com?subject=Postgresql+is+working">Yes!</a> or <a href="mailto:compositekeys@googlegroups.com?subject=Postgresql+is+failing">No…</a>)</td>
137 <td><span class=success><span class="caps">YES</span></span></td>
138 <td><span class=success><span class="caps">YES</span></span> (<a href="mailto:compositekeys@googlegroups.com?subject=Oracle+is+working">Yes!</a> or <a href="mailto:compositekeys@googlegroups.com?subject=Oracle+is+failing">No…</a>)</td>
142 <td><span class=unknown>???</span> (<a href="mailto:compositekeys@googlegroups.com?subject=Help+with+SQLServer">I can help</a>)</td>
143 <td><span class=unknown>???</span> (<a href="mailto:compositekeys@googlegroups.com?subject=SQLServer+is+working">Yes!</a> or <a href="mailto:compositekeys@googlegroups.com?subject=SQLServer+is+failing">No…</a>)</td>
147 <td><span class=unknown>???</span> (<a href="mailto:compositekeys@googlegroups.com?subject=Help+with+DB2">I can help</a>)</td>
148 <td><span class=unknown>???</span> (<a href="mailto:compositekeys@googlegroups.com?subject=DB2+is+working">Yes!</a> or <a href="mailto:compositekeys@googlegroups.com?subject=DB2+is+failing">No…</a>)</td>
152 <td><span class=unknown>???</span> (<a href="mailto:compositekeys@googlegroups.com?subject=Help+with+Firebird">I can help</a>)</td>
153 <td><span class=unknown>???</span> (<a href="mailto:compositekeys@googlegroups.com?subject=Firebird+is+working">Yes!</a> or <a href="mailto:compositekeys@googlegroups.com?subject=Firebird+is+failing">No…</a>)</td>
157 <td><span class=unknown>???</span> (<a href="mailto:compositekeys@googlegroups.com?subject=Help+with+Sybase">I can help</a>)</td>
158 <td><span class=unknown>???</span> (<a href="mailto:compositekeys@googlegroups.com?subject=Sybase+is+working">Yes!</a> or <a href="mailto:compositekeys@googlegroups.com?subject=Sybase+is+failing">No…</a>)</td>
162 <td><span class=unknown>???</span> (<a href="mailto:compositekeys@googlegroups.com?subject=Help+with+Openbase">I can help</a>)</td>
163 <td><span class=unknown>???</span> (<a href="mailto:compositekeys@googlegroups.com?subject=Openbase+is+working">Yes!</a> or <a href="mailto:compositekeys@googlegroups.com?subject=Openbase+is+failing">No…</a>)</td>
167 <td><span class=unknown>???</span> (<a href="mailto:compositekeys@googlegroups.com?subject=Help+with+Frontbase">I can help</a>)</td>
168 <td><span class=unknown>???</span> (<a href="mailto:compositekeys@googlegroups.com?subject=Frontbase+is+working">Yes!</a> or <a href="mailto:compositekeys@googlegroups.com?subject=Frontbase+is+failing">No…</a>)</td>
171 <h2>Dr Nic’s Blog</h2>
172 <p><a href="http://www.drnicwilliams.com">http://www.drnicwilliams.com</a> – for future announcements and<br />
173 other stories and things.</p>
175 <p><a href="http://groups.google.com/group/compositekeys">http://groups.google.com/group/compositekeys</a></p>
176 <h2>How to submit patches</h2>
177 <p>Read the <a href="http://drnicwilliams.com/2007/06/01/8-steps-for-fixing-other-peoples-code/">8 steps for fixing other people’s code</a> and for section <a href="http://drnicwilliams.com/2007/06/01/8-steps-for-fixing-other-peoples-code/#8b-google-groups">8b: Submit patch to Google Groups</a>, use the Google Group above.</p>
178 <p>The source for this project is available via git. You can <a href="http://github.com/drnic/composite_primary_keys/tree/master">browse and/or fork the source</a>, or to clone the project locally:<br />
180 <pre>git clone git://github.com/drnic/composite_primary_keys.git</pre></p>
182 <p>This code is free to use under the terms of the <span class="caps">MIT</span> licence.</p>
184 <p>Comments are welcome. Send an email to <a href="mailto:drnicwilliams@gmail.com">Dr Nic Williams</a>.</p>
186 <a href="mailto:drnicwilliams@gmail.com">Dr Nic</a>, 21st January 2009<br>
187 Theme extended from <a href="http://rb2js.rubyforge.org/">Paul Battley</a>
191 <script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
193 <script type="text/javascript">
194 _uacct = "UA-567811-2";