199 lines
18 KiB
HTML
199 lines
18 KiB
HTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
|
<head>
|
|
<link rel="stylesheet" href="stylesheets/screen.css" type="text/css" media="screen" />
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
<title>
|
|
Composite Primary Keys
|
|
</title>
|
|
<script src="javascripts/rounded_corners_lite.inc.js" type="text/javascript"></script>
|
|
<style>
|
|
|
|
</style>
|
|
<script type="text/javascript">
|
|
window.onload = function() {
|
|
settings = {
|
|
tl: { radius: 10 },
|
|
tr: { radius: 10 },
|
|
bl: { radius: 10 },
|
|
br: { radius: 10 },
|
|
antiAlias: true,
|
|
autoPad: true,
|
|
validTags: ["div"]
|
|
}
|
|
var versionBox = new curvyCorners(settings, document.getElementById("version"));
|
|
versionBox.applyCornersToAll();
|
|
}
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<div id="main">
|
|
|
|
<h1>Composite Primary Keys</h1>
|
|
<div id="version" class="clickable" onclick='document.location = "http://rubyforge.org/projects/compositekeys"; return false'>
|
|
Get Version
|
|
<a href="http://rubyforge.org/projects/compositekeys" class="numbers">2.2.2</a>
|
|
</div>
|
|
<h1>→ Ruby on Rails</h1>
|
|
<h1>→ ActiveRecords</h1>
|
|
<h2>What</h2>
|
|
<p>Ruby on Rails does not support composite primary keys. This free software is an extension <br />
|
|
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>
|
|
<p>Any Ruby script using ActiveRecords can use Composite Primary Keys with this library.</p>
|
|
<h2>Installing</h2>
|
|
<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>
|
|
<p>Rails: Add the following to the bottom of your <code>environment.rb</code> file</p>
|
|
<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>
|
|
<p>Ruby scripts: Add the following to the top of your script</p>
|
|
<p><pre class="syntax"><span class="ident">require</span> <span class="punct">'</span><span class="string">rubygems</span><span class="punct">'</span>
|
|
<span class="ident">require</span> <span class="punct">'</span><span class="string">composite_primary_keys</span><span class="punct">'</span></pre></p>
|
|
<h2>The basics</h2>
|
|
<p>A model with composite primary keys would look like…</p>
|
|
<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>
|
|
<span class="comment"># set_primary_keys *keys - turns on composite key functionality</span>
|
|
<span class="ident">set_primary_keys</span> <span class="symbol">:user_id</span><span class="punct">,</span> <span class="symbol">:group_id</span>
|
|
<span class="ident">belongs_to</span> <span class="symbol">:user</span>
|
|
<span class="ident">belongs_to</span> <span class="symbol">:group</span>
|
|
<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>
|
|
<span class="keyword">end</span></pre></p>
|
|
<p>A model associated with a composite key model would be defined like…</p>
|
|
<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>
|
|
<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>
|
|
<span class="keyword">end</span></pre></p>
|
|
<p>That is, associations can include composite keys too. Nice.</p>
|
|
<h2>Demonstration of usage</h2>
|
|
<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>
|
|
<p>But first, lets check out our primary keys.</p>
|
|
<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>
|
|
<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>
|
|
<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>
|
|
<p>Now we want to be able to find instances using the same syntax we always use for ActiveRecords…</p>
|
|
<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>
|
|
<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>
|
|
<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>
|
|
<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>
|
|
<p>Using <a href="http://www.rubyonrails.org">Ruby on Rails</a>? You’ll want to your url_for helpers<br />
|
|
to convert composite keys into strings and back again…</p>
|
|
<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>
|
|
<p>And then use the string id within your controller to find the object again</p>
|
|
<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>
|
|
<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>
|
|
<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>
|
|
<p>That is, an ActiveRecord supporting composite keys behaves transparently<br />
|
|
throughout your application. Just like a normal ActiveRecord.</p>
|
|
<h2>Other tricks</h2>
|
|
<h3>Pass a list of composite ids to the <code>#find</code> method</h3>
|
|
<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>
|
|
<span class="punct">=></span> <span class="punct">[</span>
|
|
<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>
|
|
<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>
|
|
<span class="punct">]</span></pre></p>
|
|
<p>Perform <code>#count</code> operations</p>
|
|
<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>
|
|
<h3>Routes with Rails</h3>
|
|
<p>From Pete Sumskas:</p>
|
|
<blockquote>
|
|
<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 />
|
|
and I didn’t see any information about what I should do to address it in the<br />
|
|
documentation (might have missed it).</p>
|
|
<p>The problem was that the urls being generated for a ‘show’ action (for<br />
|
|
example) had a syntax like:<br />
|
|
<br />
|
|
<pre>/controller/show/123000,Bu70</pre></p>
|
|
<p>for a two-field composite PK. The default routing would not match that,<br />
|
|
so after working out how to do the routing I added:<br />
|
|
<br />
|
|
<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 />
|
|
<br />
|
|
to my <code>route.rb</code> file.</p>
|
|
</blockquote>
|
|
<p><a name="dbs"></a></p>
|
|
<h2>Which databases?</h2>
|
|
<p>A suite of unit tests have been run on the following databases supported by ActiveRecord:</p>
|
|
<table>
|
|
<tr>
|
|
<th>Database</th>
|
|
<th>Test Success</th>
|
|
<th>User feedback</th>
|
|
</tr>
|
|
<tr>
|
|
<td>mysql </td>
|
|
<td><span class=success><span class="caps">YES</span></span></td>
|
|
<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>
|
|
</tr>
|
|
<tr>
|
|
<td>sqlite3 </td>
|
|
<td><span class=success><span class="caps">YES</span></span></td>
|
|
<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>
|
|
</tr>
|
|
<tr>
|
|
<td>postgresql</td>
|
|
<td><span class=success><span class="caps">YES</span></span></td>
|
|
<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>
|
|
</tr>
|
|
<tr>
|
|
<td>oracle </td>
|
|
<td><span class=success><span class="caps">YES</span></span></td>
|
|
<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>
|
|
</tr>
|
|
<tr>
|
|
<td>sqlserver </td>
|
|
<td><span class=unknown>???</span> (<a href="mailto:compositekeys@googlegroups.com?subject=Help+with+SQLServer">I can help</a>)</td>
|
|
<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>
|
|
</tr>
|
|
<tr>
|
|
<td>db2 </td>
|
|
<td><span class=unknown>???</span> (<a href="mailto:compositekeys@googlegroups.com?subject=Help+with+DB2">I can help</a>)</td>
|
|
<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>
|
|
</tr>
|
|
<tr>
|
|
<td>firebird </td>
|
|
<td><span class=unknown>???</span> (<a href="mailto:compositekeys@googlegroups.com?subject=Help+with+Firebird">I can help</a>)</td>
|
|
<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>
|
|
</tr>
|
|
<tr>
|
|
<td>sybase </td>
|
|
<td><span class=unknown>???</span> (<a href="mailto:compositekeys@googlegroups.com?subject=Help+with+Sybase">I can help</a>)</td>
|
|
<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>
|
|
</tr>
|
|
<tr>
|
|
<td>openbase </td>
|
|
<td><span class=unknown>???</span> (<a href="mailto:compositekeys@googlegroups.com?subject=Help+with+Openbase">I can help</a>)</td>
|
|
<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>
|
|
</tr>
|
|
<tr>
|
|
<td>frontbase </td>
|
|
<td><span class=unknown>???</span> (<a href="mailto:compositekeys@googlegroups.com?subject=Help+with+Frontbase">I can help</a>)</td>
|
|
<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>
|
|
</tr>
|
|
</table>
|
|
<h2>Dr Nic’s Blog</h2>
|
|
<p><a href="http://www.drnicwilliams.com">http://www.drnicwilliams.com</a> – for future announcements and<br />
|
|
other stories and things.</p>
|
|
<h2>Forum</h2>
|
|
<p><a href="http://groups.google.com/group/compositekeys">http://groups.google.com/group/compositekeys</a></p>
|
|
<h2>How to submit patches</h2>
|
|
<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>
|
|
<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 />
|
|
<br />
|
|
<pre>git clone git://github.com/drnic/composite_primary_keys.git</pre></p>
|
|
<h2>Licence</h2>
|
|
<p>This code is free to use under the terms of the <span class="caps">MIT</span> licence.</p>
|
|
<h2>Contact</h2>
|
|
<p>Comments are welcome. Send an email to <a href="mailto:drnicwilliams@gmail.com">Dr Nic Williams</a>.</p>
|
|
<p class="coda">
|
|
<a href="mailto:drnicwilliams@gmail.com">Dr Nic</a>, 21st January 2009<br>
|
|
Theme extended from <a href="http://rb2js.rubyforge.org/">Paul Battley</a>
|
|
</p>
|
|
</div>
|
|
|
|
<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
|
|
</script>
|
|
<script type="text/javascript">
|
|
_uacct = "UA-567811-2";
|
|
urchinTracker();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|