<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Ruby On Rails on CodeGoalie</title><link>https://codegoalie.com/categories/ruby-on-rails/</link><description>Recent content in Ruby On Rails on CodeGoalie</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Fri, 12 Aug 2011 00:00:00 +0000</lastBuildDate><atom:link href="https://codegoalie.com/categories/ruby-on-rails/index.xml" rel="self" type="application/rss+xml"/><item><title>Ruby on Rails/ActiveSupport: To delegate or not to delegate</title><link>https://codegoalie.com/posts/2013-03-16-ruby-on-rails-slash-activesupport-to-delegate-or-not-to-delegate/</link><pubDate>Fri, 12 Aug 2011 00:00:00 +0000</pubDate><guid>https://codegoalie.com/posts/2013-03-16-ruby-on-rails-slash-activesupport-to-delegate-or-not-to-delegate/</guid><description>&lt;p>This is an account of my adventure with delegation from problem to apparent solution to bug in ActiveSupport to you&amp;rsquo;re doing it wrong!&lt;/p>
&lt;p>&lt;!-- raw HTML omitted -->TL;DR&lt;!-- raw HTML omitted -->&lt;/p>
&lt;p>Delegation is used when you want to forward a method call from on class/model to an associated class/model. The &lt;!-- raw HTML omitted -->Ruby on Rails Guides&lt;!-- raw HTML omitted --> use the example of a User model which has a Profile associated with it. This is a great example with a little subtlety which I overlooked. If you aren&amp;rsquo;t famaliar with delegation, go ahead and get yourself learned. I won&amp;rsquo;t wait for you though. This is text just come back when you&amp;rsquo;re up to speed.&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;pre>&lt;code># app/views/drivers/show.html.haml
.truck_number
-if @driver.truck
= @driver.truck.number
-else
Not Driving any Truck
&lt;/code>&lt;/pre>
&lt;p>NBD. Here&amp;rsquo;s our problem:&lt;/p>
&lt;h3 id="the-problem">The Problem&lt;/h3>
&lt;p>As when any good application which acts as a font-end for a database, we track history on all edits, etc. So, when we update the Driver in a Truck, we want to add history for the Truck and the Driver. If the Driver is already in another Truck we want to show, in his history, that he moved from Truck 1001 into Truck 2002. We&amp;rsquo;ll say we have a create_history method included into our Entity classes whose declaration looks something like:&lt;/p>
&lt;pre>&lt;code>def create_history(attribute, prev_value, new_value)
&lt;/code>&lt;/pre>
&lt;p>So, we want to call something like:&lt;/p>
&lt;pre>&lt;code>@driver.create_history('Truck Number', @driver.truck.number, @new_truck.number)
&lt;/code>&lt;/pre>
&lt;p>Jackpot! Works perfectly. We get the old Truck number (1001) from the @driver instance and the new Truck number (2002) directly from the @truck instance. We are geniuses.
Hold the phone though. What if that Driver didn&amp;rsquo;t have a previous truck&amp;hellip; Spoiler Alert: &lt;!-- raw HTML omitted -->NoMethodError: undefined method &amp;lsquo;number&amp;rsquo; for nil:NilClass&lt;!-- raw HTML omitted --> I guess we could check that a Truck exists each time we want to access any attributes for Driver.truck. But, really, what do we expect Driver.truck.number to be if the Driver is not in a Truck? Nil would work&amp;hellip;&lt;/p>
&lt;h3 id="delegate-for-science">Delegate: for science!&lt;/h3>
&lt;p>I&amp;rsquo;ll allow it. Let&amp;rsquo;s delegate and allow_nil:&lt;/p>
&lt;pre>&lt;code># app/models/driver.rb
class Driver &amp;lt; ActiveRecord::Model
delegate :number, :to =&amp;gt; :truck, :prefix =&amp;gt; true, :allow_nil =&amp;gt; true
# ...
end
&lt;/code>&lt;/pre>
&lt;p>Works! Now, we&amp;rsquo;ve got history showing: Driver had no Truck and moved
into Truck 2002. Geniusness times 2. Delegation to perferction.&lt;/p>
&lt;hr>
&lt;p>Sometime later&amp;hellip;&lt;/p>
&lt;p>Instead of the Truck number, maybe we want to store the id of the Truck
in the history. Perhaps we know the truck number may change over time and want to link up the correct Truck record. Seems easy:&lt;/p>
&lt;pre>&lt;code>delegate :id, to: :truck, prefix: true, allow_nil: true
&lt;/code>&lt;/pre>
&lt;p>But, when our Driver doesn&amp;rsquo;t have a &amp;lsquo;previous&amp;rsquo; truck:&lt;/p>
&lt;pre>&lt;code>RuntimeError - Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
&lt;/code>&lt;/pre>
&lt;p>WTF? I allowed nil. I know he doesn&amp;rsquo;t have a previous truck. I just want
my nil back and I&amp;rsquo;ll go about my business. Let&amp;rsquo;s get our hard hats on and
get digging into the source. I&amp;rsquo;ll save you some googling and just tell
you that the Delegation module is in ActiveSupport in the Core Extensions
under Module. Scroll, scroll, scroll. There! Line 136! They(we)(whomever)
are only rescuing from NoMethodErrors. Calling id on nil raises a
RuntimeError, which isn&amp;rsquo;t rescued and bubbles up to us. This is me
stomping my feet and wining. My knee jerk reaction was to just rescue
RuntimeErrors too:&lt;/p>
&lt;pre>&lt;code>rescue NoMethodError, RuntimeError
&lt;/code>&lt;/pre>
&lt;h3 id="were-gonna-be-famous">We&amp;rsquo;re gonna be Famous&lt;/h3>
&lt;p>It works! AND, how cool are we? We get to submit a bug fix to Rails.
Uber-cool. Uh-oh, uneasyness&amp;hellip;crap&amp;hellip;
It just doesn&amp;rsquo;t feel right to just catch any old RuntimeError and
possibly, silently fail to nil. However, the urge to try and submit a
simple &amp;lsquo;fix&amp;rsquo; to Rails was strong. Instead, I started doing more research
into delegate and Ruby Exception handling. Here&amp;rsquo;s what I&amp;rsquo;ve come up with:&lt;/p>
&lt;p>&lt;!-- raw HTML omitted -->&lt;!-- raw HTML omitted -->&lt;/p>
&lt;h3 id="the-moral-of-the-story">The Moral of the Story&lt;/h3>
&lt;p>You should delegate when you want to hide an architectural aspect and
expose the expected abstraction. In the Ruby on Rails Guides example,
we expect a user to have a name through @user.name even through name
actually resided in Profile. However, in our example, a Driver isn&amp;rsquo;t
expected to have a truck_id. A truck has an id, but a driver does not
have a truck_id.&lt;/p>
&lt;p>One of the classic &amp;lsquo;bad smells&amp;rsquo; in code for refactoring is using a
temporary variable in place a a query. Ruby and its optional
parantheses, blurs the lines between variable, attribute and method.
It&amp;rsquo;s easy to forget what you are doing and assume everything is an
attribute. Why shouldn&amp;rsquo;t a Driver have a truck_id? The same reason a
Truck doesn&amp;rsquo;t have a CDL. But, we can query the truck_id for the
kTruck associated with a Driver with a method.&lt;/p>
&lt;p>After all that, I should have just written a query method in Driver as
such:&lt;/p>
&lt;pre>&lt;code>def truck_id
self.truck.id if self.truck
end
&lt;/code>&lt;/pre>
&lt;p>Too easy&amp;hellip;&lt;/p>
&lt;p>&lt;strong>UPDATE 3/16/2013&lt;/strong>&lt;/p>
&lt;blockquote>
&lt;p>As I convert this blog to Octopress and reread
these posts, I&amp;rsquo;m see some imporvements. Instead of the code above, the
&lt;code>try&lt;/code> method is really what we want to use here:&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;pre>&lt;code> def truck_id
self.truck.try(:id)
end
&lt;/code>&lt;/pre>
&lt;/blockquote>
&lt;blockquote>
&lt;p>On &lt;code>Object&lt;/code>, &lt;code>try&lt;/code> will attempt to send the parameter as a message to
the invoking object. So, it works just like calling the method. However,
on &lt;code>nil&lt;/code>, &lt;code>try&lt;/code> simply returns &lt;code>nil&lt;/code>. It effectively exactly replaces
the first implementation nicely.&lt;/p>
&lt;/blockquote>
&lt;p>&amp;ndash; Chris&lt;/p></description></item></channel></rss>