tag:www.rhnh.net,2008:/compositionComposition - Xavier Shay's Blog2008-05-03T19:24:19ZEnkiXavier Shaynotreal@rhnh.nettag:www.rhnh.net,2008:Post/7782008-05-03T19:24:19Z2008-05-03T19:24:19ZContextual Composition With Delegation<p>I’ve had some models getting rather large recently. This makes them hard to comprehend and makes the source difficult to browse. A lot of the time, a big chunk of functionality is fairly context specific – it is only relevant to one particular part of my application (reporting, data integration, etc…). <a href="http://giantrobots.thoughtbot.com/2008/5/1/skinny-controllers-skinny-models">Thoughtbot presented one way to do this recently</a> by adding methods to the model that return another model with the extra goodness.</p>
<p>That’s not bad, but it still pollutes the class with methods that most users won’t care about. We can just decorate the class with extra methods at the time (context) that we need them. My first go at doing this used the <code>extend</code> method:</p><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt>5<tt>
</tt>6<tt>
</tt>7<tt>
</tt>8<tt>
</tt>9<tt>
</tt><strong>10</strong><tt>
</tt>11<tt>
</tt>12<tt>
</tt>13<tt>
</tt>14<tt>
</tt>15<tt>
</tt>16<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="r">class</span> <span class="cl">PurchaseOrder</span><tt>
</tt> attr_reader <span class="sy">:id</span><tt>
</tt><span class="r">end</span><tt>
</tt><tt>
</tt><span class="r">module</span> <span class="cl">Reports::PurchaseOrderMethods</span><tt>
</tt> <span class="r">def</span> <span class="fu">description</span><tt>
</tt> <span class="s"><span class="dl">"</span><span class="k">A Purchase Order</span><span class="dl">"</span></span><tt>
</tt> <span class="r">end</span><tt>
</tt><span class="r">end</span><tt>
</tt><tt>
</tt><span class="r">class</span> <span class="cl">ReportMakerWithExtend</span><tt>
</tt> <span class="r">def</span> <span class="pc">self</span>.report_for(po)<tt>
</tt> po.extend(<span class="co">Reports</span>::<span class="co">PurchaseOrderMethods</span>)<tt>
</tt> <span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>po.id<span class="idl">}</span></span><span class="k">: </span><span class="il"><span class="idl">#{</span>po.description<span class="idl">}</span></span><span class="dl">"</span></span><tt>
</tt> <span class="r">end</span><tt>
</tt><span class="r">end</span><tt>
</tt></pre></td>
</tr></table>
<p>This has a few edge case problems though. </p>
<ol>
<li>It can potentially override methods in our base class. Imagine if PurchaseOrder#description was defined as private, our module would override this defenition resulting in probably breakage.</li>
<li>It is inelegant to test – <code>extend</code> will override any existing stubs, so you need to stub it out. This is unintuitive and may have unintended consequences, for instance if the class is also using <code>extend</code> in a manner that doesn’t interfere with your stubs.</li>
</ol><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt>5<tt>
</tt>6<tt>
</tt>7<tt>
</tt>8<tt>
</tt>9<tt>
</tt><strong>10</strong><tt>
</tt>11<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="c"># Testing extended PurchaseOrder is inelegant</span><tt>
</tt>describe <span class="s"><span class="dl">'</span><span class="k">ReportMakerWithExtend#report_for</span><span class="dl">'</span></span> <span class="r">do</span><tt>
</tt> it <span class="s"><span class="dl">'</span><span class="k">returns a line containing both ID and description</span><span class="dl">'</span></span> <span class="r">do</span><tt>
</tt> po = stub(<tt>
</tt> <span class="sy">:id</span> => <span class="i">1</span><tt>
</tt> <span class="sy">:description</span> => <span class="s"><span class="dl">"</span><span class="k">hello</span><span class="dl">"</span></span>,<tt>
</tt> <span class="sy">:extend</span> => <span class="pc">nil</span> <span class="c"># :(</span><tt>
</tt> )<tt>
</tt> <span class="co">ReportMaker</span>.report_for(po).should == <span class="s"><span class="dl">"</span><span class="k">1: hello</span><span class="dl">"</span></span><tt>
</tt> <span class="r">end</span><tt>
</tt><span class="r">end</span><tt>
</tt></pre></td>
</tr></table>
<p>Ruby provides another method to achieve what we want in the form of <code>SimpleDelegator</code>. Basically, it passes on any methods not defined on itself to the object specified in the constructor. This way we can wrap another object without fear of interferring with its internals nor our stubs.</p><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt>2<tt>
</tt>3<tt>
</tt>4<tt>
</tt>5<tt>
</tt>6<tt>
</tt>7<tt>
</tt>8<tt>
</tt>9<tt>
</tt><strong>10</strong><tt>
</tt>11<tt>
</tt>12<tt>
</tt>13<tt>
</tt>14<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">require <span class="s"><span class="dl">'</span><span class="k">delegate</span><span class="dl">'</span></span><tt>
</tt><tt>
</tt><span class="r">class</span> <span class="cl">Reports::PurchaseOrder</span> < <span class="co">SimpleDelegator</span><tt>
</tt> <span class="r">def</span> <span class="fu">description</span><tt>
</tt> <span class="s"><span class="dl">"</span><span class="k">A Purchase Order</span><span class="dl">"</span></span><tt>
</tt> <span class="r">end</span><tt>
</tt><span class="r">end</span><tt>
</tt><tt>
</tt><span class="r">class</span> <span class="cl">ReportMaker</span><tt>
</tt> <span class="r">def</span> <span class="pc">self</span>.report_for(po)<tt>
</tt> po = <span class="co">Reports</span>::<span class="co">PurchaseOrder</span>.new(po)<tt>
</tt> <span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>po.id<span class="idl">}</span></span><span class="k">: </span><span class="il"><span class="idl">#{</span>po.description<span class="idl">}</span></span><span class="dl">"</span></span><tt>
</tt> <span class="r">end</span><tt>
</tt><span class="r">end</span><tt>
</tt></pre></td>
</tr></table>
<p>Much nicer. Of course, we would have specs for <code>Reports::PurchaseOrder</code> in addition to <code>PurchaseOrder</code> – this split allows us to keep our tests focussed and easy to read. Using delegation to split up your models allows you to separate code into areas where it is most relevant – helping keep both your models and your tests easy to read and maintain.</p>