tag:www.rhnh.net,2008:/activerecordActiverecord - Xavier Shay's Blog2010-08-17T16:41:42ZEnkiXavier Shaynotreal@rhnh.nettag:www.rhnh.net,2008:Post/8282010-08-17T16:41:42Z2010-08-17T16:41:42ZUpdating Class Table Inheritance Tables<p>My last post covered <a href="http://rhnh.net/2010/08/15/class-table-inheritance-and-eager-loading">querying class table inheritance</a> tables; this one presents a method for updating them. Having set up our ActiveRecord models using composition, we can use a standard rails method <code>accepts_nested_attributes_for</code> to allow easy one-form updating of the relationship.</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>17<tt>
</tt>18<tt>
</tt>19<tt>
</tt><strong>20</strong><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">Item</span> < <span class="co">ActiveRecord</span>::<span class="co">Base</span><tt>
</tt> validates_numericality_of <span class="sy">:quantity</span><tt>
</tt><tt>
</tt> <span class="co">SUBCLASSES</span> = [<span class="sy">:dvd</span>, <span class="sy">:car</span>]<tt>
</tt> <span class="co">SUBCLASSES</span>.each <span class="r">do</span> |class_name|<tt>
</tt> has_one class_name<tt>
</tt> <span class="r">end</span><tt>
</tt><tt>
</tt> accepts_nested_attributes_for *<span class="co">SUBCLASSES</span><tt>
</tt><span class="r">end</span><tt>
</tt><tt>
</tt><span class="iv">@item</span> = <span class="co">Dvd</span>.create!(<tt>
</tt> <span class="sy">:title</span> => <span class="s"><span class="dl">'</span><span class="k">The Matix</span><span class="dl">'</span></span>,<tt>
</tt> <span class="sy">:item</span> => <span class="co">Item</span>.create!(<span class="sy">:quantity</span> => <span class="i">1</span>))<tt>
</tt><tt>
</tt><span class="iv">@item</span>.update_attributes(<tt>
</tt> <span class="sy">:quantity</span> => <span class="i">2</span>,<tt>
</tt> <span class="sy">:dvd_attributes</span> => {<tt>
</tt> <span class="sy">:id</span> => <span class="iv">@item</span>.dvd.id,<tt>
</tt> <span class="sy">:title</span> => <span class="s"><span class="dl">'</span><span class="k">The Matrix</span><span class="dl">'</span></span>})<tt>
</tt></pre></td>
</tr></table>
<p>This issues the following <span class="caps">SQL</span> to the database:</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></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">UPDATE "items" SET "quantity" = 10 WHERE ("items"."id" = 12)<tt>
</tt>UPDATE "dvds" SET "title" = 'The Matrix' WHERE ("dvds"."id" = 12)<tt>
</tt></pre></td>
</tr></table>
<p>Note that depending on your application, you may need some extra locking to ensure this method is concurrent, for example if you allow items to change type. Be sure to read the <a href="http://apidock.com/rails/ActiveRecord/NestedAttributes/ClassMethods/accepts_nested_attributes_for">accepts_nested_attributes_for documentation</a> for the full <span class="caps">API</span>.</p>
<p><em>I talk about this sort of thing in my “Your Database Is Your Friend” training sessions. They are happening throughout the US and UK in the coming months. One is likely coming to a city near you. Head on over to <a href="http://www.dbisyourfriend.com">www.dbisyourfriend.com</a> for more information and free screencasts <img src="http://www.dbisyourfriend.com/favicon.ico" alt="" /></em></p>tag:www.rhnh.net,2008:Post/8272010-08-15T05:30:00Z2010-08-17T16:34:23ZClass Table Inheritance and Eager Loading <p>Consider a typical class table inheritance table structure with <code>items</code> as the base class and <code>dvds</code> and <code>cars</code> as two subclasses. In addition to what is strictly required, <code>items</code> also has an <code>item_type</code> parameter. This denormalization is usually a good idea, I will save the justification for another post so please take it for granted for now.</p>
<p>The easiest way to map this relationship with Rails and ActiveRecord is to use composition, rather than trying to hook into the class loading code. Something akin to:</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>17<tt>
</tt>18<tt>
</tt>19<tt>
</tt><strong>20</strong><tt>
</tt>21<tt>
</tt>22<tt>
</tt>23<tt>
</tt>24<tt>
</tt>25<tt>
</tt>26<tt>
</tt>27<tt>
</tt>28<tt>
</tt>29<tt>
</tt><strong>30</strong><tt>
</tt>31<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">Item</span> < <span class="co">ActiveRecord</span>::<span class="co">Base</span><tt>
</tt> <span class="co">SUBCLASSES</span> = [<span class="sy">:dvd</span>, <span class="sy">:car</span>]<tt>
</tt> <span class="co">SUBCLASSES</span>.each <span class="r">do</span> |class_name|<tt>
</tt> has_one class_name<tt>
</tt> <span class="r">end</span><tt>
</tt><tt>
</tt> <span class="r">def</span> <span class="fu">description</span><tt>
</tt> send(item_type).description<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">Dvd</span> < <span class="co">ActiveRecord</span>::<span class="co">Base</span><tt>
</tt> belongs_to <span class="sy">:item</span><tt>
</tt><tt>
</tt> validates_presence_of <span class="sy">:title</span>, <span class="sy">:running_time</span><tt>
</tt> validates_numericality_of <span class="sy">:running_time</span><tt>
</tt><tt>
</tt> <span class="r">def</span> <span class="fu">description</span><tt>
</tt> title<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">Car</span> < <span class="co">ActiveRecord</span>::<span class="co">Base</span><tt>
</tt> belongs_to <span class="sy">:item</span><tt>
</tt><tt>
</tt> validates_presence_of <span class="sy">:make</span>, <span class="sy">:registration</span><tt>
</tt><tt>
</tt> <span class="r">def</span> <span class="fu">description</span><tt>
</tt> make<tt>
</tt> <span class="r">end</span><tt>
</tt><span class="r">end</span><tt>
</tt></pre></td>
</tr></table>
<p>A naive way to fetch all the items might look like this:</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></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="co">Item</span>.all(<span class="sy">:include</span> => <span class="co">Item</span>::<span class="co">SUBCLASSES</span>)<tt>
</tt></pre></td>
</tr></table>
<p>This will issue one initial query, then one for each subclass. (Since Rails 2.1, eager loading is done like this rather than joining.) This is inefficient, since at the point we preload the associations we already know which subclass tables we should be querying. There is no need to query all of them. A better way is to hook into the Rails eager loading ourselves to ensure that only the tables required are loaded:</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></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="co">Item</span>.all(opts).tap <span class="r">do</span> |items|<tt>
</tt> preload_associations(items, items.map(&<span class="sy">:item_type</span>).uniq)<tt>
</tt><span class="r">end</span><tt>
</tt></pre></td>
</tr></table>
<p>Wrapping that up in a class method on items is neat because we can then use it as a kicker at the end of named scopes or associations – <code>person.items.preloaded</code>, for instance.</p>
<p>Here are some tests demonstrating this:</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>17<tt>
</tt>18<tt>
</tt>19<tt>
</tt><strong>20</strong><tt>
</tt>21<tt>
</tt>22<tt>
</tt>23<tt>
</tt>24<tt>
</tt>25<tt>
</tt>26<tt>
</tt>27<tt>
</tt>28<tt>
</tt>29<tt>
</tt><strong>30</strong><tt>
</tt>31<tt>
</tt>32<tt>
</tt>33<tt>
</tt>34<tt>
</tt>35<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">test/test_helper</span><span class="dl">'</span></span><tt>
</tt><tt>
</tt><span class="r">class</span> <span class="cl">PersonTest</span> < <span class="co">ActiveRecord</span>::<span class="co">TestCase</span><tt>
</tt> setup <span class="r">do</span><tt>
</tt> item = <span class="co">Item</span>.create!(<span class="sy">:item_type</span> => <span class="s"><span class="dl">'</span><span class="k">dvd</span><span class="dl">'</span></span>)<tt>
</tt> dvd = <span class="co">Dvd</span>.create!(<span class="sy">:item</span> => item, <span class="sy">:title</span> => <span class="s"><span class="dl">'</span><span class="k">Food Inc.</span><span class="dl">'</span></span>)<tt>
</tt> <span class="r">end</span><tt>
</tt><tt>
</tt> test <span class="s"><span class="dl">'</span><span class="k">naive eager load</span><span class="dl">'</span></span> <span class="r">do</span><tt>
</tt> items = []<tt>
</tt> assert_queries(<span class="i">3</span>) { items = <span class="co">Item</span>.all(<span class="sy">:include</span> => <span class="co">Item</span>::<span class="co">SUBCLASSES</span>) }<tt>
</tt> assert_equal <span class="i">1</span>, items.size<tt>
</tt> assert_queries(<span class="i">0</span>) { items.map(&<span class="sy">:description</span>) }<tt>
</tt> <span class="r">end</span><tt>
</tt><tt>
</tt> test <span class="s"><span class="dl">'</span><span class="k">smart eager load</span><span class="dl">'</span></span> <span class="r">do</span><tt>
</tt> items = []<tt>
</tt> assert_queries(<span class="i">2</span>) { items = <span class="co">Item</span>.preloaded }<tt>
</tt> assert_equal <span class="i">1</span>, items.size<tt>
</tt> assert_queries(<span class="i">0</span>) { items.map(&<span class="sy">:description</span>) }<tt>
</tt> <span class="r">end</span><tt>
</tt><span class="r">end</span><tt>
</tt><tt>
</tt><span class="c"># Monkey patch stolen from activerecord/test/cases/helper.rb</span><tt>
</tt><span class="co">ActiveRecord</span>::<span class="co">Base</span>.connection.class.class_eval <span class="r">do</span><tt>
</tt> <span class="co">IGNORED_SQL</span> = [<span class="rx"><span class="dl">/</span><span class="k">^PRAGMA</span><span class="dl">/</span></span>, <span class="rx"><span class="dl">/</span><span class="k">^SELECT currval</span><span class="dl">/</span></span>, <span class="rx"><span class="dl">/</span><span class="k">^SELECT CAST</span><span class="dl">/</span></span>, <span class="rx"><span class="dl">/</span><span class="k">^SELECT @@IDENTITY</span><span class="dl">/</span></span>, <span class="rx"><span class="dl">/</span><span class="k">^SELECT @@ROWCOUNT</span><span class="dl">/</span></span>, <span class="rx"><span class="dl">/</span><span class="k">^SAVEPOINT</span><span class="dl">/</span></span>, <span class="rx"><span class="dl">/</span><span class="k">^ROLLBACK TO SAVEPOINT</span><span class="dl">/</span></span>, <span class="rx"><span class="dl">/</span><span class="k">^RELEASE SAVEPOINT</span><span class="dl">/</span></span>, <span class="rx"><span class="dl">/</span><span class="k">SHOW FIELDS</span><span class="dl">/</span></span>]<tt>
</tt><tt>
</tt> <span class="r">def</span> <span class="fu">execute_with_query_record</span>(sql, name = <span class="pc">nil</span>, &block)<tt>
</tt> <span class="gv">$queries_executed</span> ||= []<tt>
</tt> <span class="gv">$queries_executed</span> << sql <span class="r">unless</span> <span class="co">IGNORED_SQL</span>.any? { |r| sql =~ r }<tt>
</tt> execute_without_query_record(sql, name, &block)<tt>
</tt> <span class="r">end</span><tt>
</tt><tt>
</tt> alias_method_chain <span class="sy">:execute</span>, <span class="sy">:query_record</span><tt>
</tt><span class="r">end</span><tt>
</tt></pre></td>
</tr></table>
<p><em>I talk about this sort of thing in my “Your Database Is Your Friend” training sessions. They are happening throughout the US and UK in the coming months. One is likely coming to a city near you. Head on over to <a href="http://www.dbisyourfriend.com">www.dbisyourfriend.com</a> for more information and free screencasts <img src="http://www.dbisyourfriend.com/favicon.ico" alt="" /></em></p>tag:www.rhnh.net,2008:Post/8242010-07-21T22:19:27Z2010-07-21T22:19:27ZConcurrency with AASM, Isolation Levels<p>I’ve posted two guest articles over on the Engine Yard blog this week on database related topics:</p>
<ul>
<li><a href="http://www.engineyard.com/blog/2010/a-gentle-introduction-to-isolation-levels/">A Gentle Introduction To Isolation Levels</a></li>
<li><a href="http://www.engineyard.com/blog/2010/concurrency-and-the-aasm-gem/">Concurrency and the <span class="caps">AASM</span> Gem</a></li>
</ul>
<p>They’re in the same vein as what I’ve been posting here, so worth a read if you’ve been digging it.<br />
The US tour kicks off this Saturday in San Francisco, and there’s still a couple of spots available. You can still register over at <a href="http://www.dbisyourfriend.com">www.dbisyourfriend.com</a></p>
<p><em>“Your Database Is Your Friend” training sessions are happening throughout the US and UK in the coming months. One is likely coming to a city near you. For more information and free screencasts, head on over to <a href="http://www.dbisyourfriend.com">www.dbisyourfriend.com</a> <img src="http://www.dbisyourfriend.com/favicon.ico" alt="" /></em></p>tag:www.rhnh.net,2008:Post/8222010-07-06T21:55:05Z2010-07-06T21:55:05ZFive Tips For Adding Foreign Keys To Existing Apps<p>You’re convinced <a href="http://www.railsinside.com/misc/455-the-perils-of-opinionated-software-like-rails.html">foreign keys are a good idea</a>, but how should you retroactively add them to your production application? Here are some tips to help you out.</p>
<p><strong>Identify and fix orphan records.</strong> If orphan records exist, creating a foreign key will fail. Use the following <span class="caps">SQL</span> to identify children that reference a parent that doesn’t exist:</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></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">SELECT * FROM children LEFT JOIN parents ON parent_id = parents.id WHERE parents.id IS NULL<tt>
</tt></pre></td>
</tr></table>
<p><strong>Begin with new or unimportant relationships.</strong> With any new change, it’s best to walk before you run. Targeting the most important relationships in your application head on can quickly turn into a black hole. Adding foreign keys to new or low value relationships first means you have a smaller code base that is affected, and allows you to test your test suite and plugins for compatibility over a smaller area. Get this running in production early, so any issues will crop up early on low value code where they’ll be easier to fix. Be agile in your approach and iterate.</p>
<p><strong>Move away from fixtures and mocking in your tests.</strong> Rails fixture code is not designed to work well with foreign keys. (Fixtures are generally not a good idea regardless.) Also, the intense stubbing of models that was in vogue back when rspec first came on the scene doesn’t play nice either. The current best practice is to use object factories (such as <a href="http://github.com/notahat/machinist">Machinist</a>) to create your test data, and this works well with foreign keys.</p>
<p><strong>Use restrict rather than cascade for ON <span class="caps">DELETE</span>.</strong> You still want to keep <code>on_destroy</code> logic in your models, so even if conceptually a cascading delete makes sense, implement it using the <code>:dependent => :destroy</code> option to <code>has_many</code>, with a restrict option at the database level to ensure all cascading deletes run through your callbacks.</p>
<p><strong>Be pragmatic.</strong> Ideally every relationship will have a foreign key, but for that model filled with weird hacks and supported by a massive old school test suite, it may be just too much effort to get everything working smoothly with database constraints. In this case, set up a test suite that runs over your production data regularly to quickly identify any data problems that arise (see the <span class="caps">SQL</span> above).</p>
<p>Foreign keys give you confidence and piece of mind about your data and your application. Rails may be afraid of them, but that doesn’t mean you have to be.</p>
<p><em>July through September I am running full day training sessions in the US and UK on how to make use of your database and write solid Rails code, increasing your quality without compromising your velocity. Chances are I’m coming to your city, so check it out at <a href="http://www.dbisyourfriend.com/">http://www.dbisyourfriend.com</a> <img src="http://www.dbisyourfriend.com/favicon.ico" alt="" /></em></p>tag:www.rhnh.net,2008:Post/8212010-07-05T04:54:00Z2010-07-05T00:54:59Zacts_as_state_machine is not concurrent<p>Here is a short 4 minute screencast in which I show you how the acts as state machine (<span class="caps">AASM</span>) gem fails in a concurrent environment, and also how to fix it.</p>
<p><object width="600" height="375"><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=12968585&server=vimeo.com&show_title=0&show_byline=0&show_portrait=0&color=00ADEF&fullscreen=1" /><embed src="http://vimeo.com/moogaloop.swf?clip_id=12968585&server=vimeo.com&show_title=0&show_byline=0&show_portrait=0&color=00ADEF&fullscreen=1" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="600" height="375"></embed></object><br />
(If embedding doesn’t work or the text is too small to read, you can <a href="http://vimeo.com/12968585">grab a high resolution version direct from Vimeo</a>)</p>
<p>It’s a pretty safe bet that you want to obtain a lock before all state transitions, so you can use a bit of method aliasing to do just that. This gives you much neater code than the quick fix I show in the screencast, just make sure you understand what it is doing!</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>17<tt>
</tt>18<tt>
</tt>19<tt>
</tt><strong>20</strong><tt>
</tt>21<tt>
</tt>22<tt>
</tt>23<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">ActiveRecord::Base</span><tt>
</tt> <span class="r">def</span> <span class="pc">self</span>.obtain_lock_before_transitions<tt>
</tt> <span class="co">AASM</span>::<span class="co">StateMachine</span>[<span class="pc">self</span>].events.keys.each <span class="r">do</span> |t|<tt>
</tt> define_method(<span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>t<span class="idl">}</span></span><span class="k">_with_lock!</span><span class="dl">"</span></span>) <span class="r">do</span><tt>
</tt> transaction <span class="r">do</span><tt>
</tt> lock!<tt>
</tt> send(<span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>t<span class="idl">}</span></span><span class="k">_without_lock!</span><span class="dl">"</span></span>)<tt>
</tt> <span class="r">end</span><tt>
</tt> <span class="r">end</span><tt>
</tt> alias_method_chain <span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>t<span class="idl">}</span></span><span class="k">!</span><span class="dl">"</span></span>, <span class="sy">:lock</span><tt>
</tt> <span class="r">end</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">Tractor</span><tt>
</tt> <span class="c"># ...</span><tt>
</tt><tt>
</tt> aasm_event <span class="sy">:buy</span> <span class="r">do</span><tt>
</tt> transitions <span class="sy">:to</span> => <span class="sy">:bought</span>, <span class="sy">:from</span> => [<span class="sy">:for_sale</span>]<tt>
</tt> <span class="r">end</span><tt>
</tt><tt>
</tt> obtain_lock_before_transitions<tt>
</tt><span class="r">end</span><tt>
</tt></pre></td>
</tr></table>
<p><em>This is a small taste of my DB is your friend training course, that helps you build solid rails applications by finding the sweet spot between stored procedures and treating your database as a hash. July through September I am running full day sessions in the US and UK. Chances are I’m coming to your city. Check it out at <a href="http://www.dbisyourfriend.com/">http://www.dbisyourfriend.com</a> <img src="http://www.dbisyourfriend.com/favicon.ico" alt="" /></em></p>tag:www.rhnh.net,2008:Post/8182010-07-02T01:05:00Z2010-07-02T01:06:07ZThree Reasons Why You Shouldn't Use Single Table Inheritance <p><strong>It creates a cluttered data model.</strong> Why don’t we just have one table called <code>objects</code> and store everything as <span class="caps">STI</span>? <span class="caps">STI</span> tables have a tendency to grow and expand as an application develops, and become intimidating and unweildy as it isn’t clear which columns belong to which models.</p>
<p><strong>It forces you to use nullable columns.</strong> A comic book must have an illustrator, but regular books don’t have an illustrator. Subclassing <code>Book</code> with <code>Comic</code> using <span class="caps">STI</span> forces you to allow illustrator to be null at the database level (for books that aren’t comics), and pushes your data integrity up into the application layer, which is not ideal.</p>
<p><strong>It prevents you from efficiently indexing your data.</strong> Every index has to reference the type column, and you end up with indexes that are only relevant for a certain type.</p>
<p>The only time <span class="caps">STI</span> is the right answer is when you have models with exactly the same data, but different behaviour. You don’t compromise your data model, and everything stays neat and tidy. I have yet to see a case in the wild where this rule holds, though.</p>
<p>If you are using <span class="caps">STI</span> (or inheritance in general) to share code, you’re doing it wrong. Having many tables does not conflict with the Don’t-Repeat-Yourself principle. Ruby has modules, use them. (I once had a project where a 20 line hash drove the creation of migrations, models, data loaders and test blueprints.)</p>
<p>What you should be doing is using <a href="http://martinfowler.com/eaaCatalog/classTableInheritance.html">Class Table Inheritance</a>. Rails doesn’t “support it natively”, but that doesn’t particularly mean much since it’s a simple pattern to implement yourself, especially if you take advantage of named scopes and delegators. Your data model will be much easier to work with, easier to understand, and more performant.</p>
<p><em>I expand on this topic and guide you through a sample implementation in my DB is your friend training course. July through September I am running full day sessions in the US and UK. Chances are I’m coming to your city. Check it out at <a href="http://www.dbisyourfriend.com/">http://www.dbisyourfriend.com</a> <img src="http://www.dbisyourfriend.com/favicon.ico" alt="" /></em></p>tag:www.rhnh.net,2008:Post/8192010-06-30T02:12:00Z2010-06-30T02:12:01Zacts_as_list will break in production<p><code>acts_as_list</code> doesn’t work in a typical production deployment. It pretends to for a while, but every application will eventually have issues with it that result in real problems for your users. Here is a short 4 minute long screencast showing you how it breaks, and also a quick fix which will prevent your data from becoming corrupted.</p>
<p><object width="600" height="375"><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=12943499&server=vimeo.com&show_title=0&show_byline=0&show_portrait=0&color=00ADEF&fullscreen=1" /><embed src="http://vimeo.com/moogaloop.swf?clip_id=12943499&server=vimeo.com&show_title=0&show_byline=0&show_portrait=0&color=00ADEF&fullscreen=1" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="600" height="375"></embed></object></p>
<p>(<a href="http://vimeo.com/12943499">View it over at Vimeo</a> if embedding doesn’t work for you)</p>
<p>Here is the “quick fix” I apply in the screencast. It’s ugly, but it will work.</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></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="r">def</span> <span class="fu">move_down</span><tt>
</tt> <span class="co">Tractor</span>.transaction <span class="r">do</span><tt>
</tt> <span class="co">Tractor</span>.connection.execute(<span class="s"><span class="dl">"</span><span class="k">SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE</span><span class="dl">"</span></span>)<tt>
</tt> <span class="iv">@tractor</span> = <span class="co">Tractor</span>.find(params[<span class="sy">:id</span>])<tt>
</tt> <span class="iv">@tractor</span>.move_to_bottom<tt>
</tt> <span class="r">end</span><tt>
</tt> redirect_to(tractors_path)<tt>
</tt><span class="r">end</span><tt>
</tt></pre></td>
</tr></table>
<p>Some things to note when fixing your application in a nicer way:</p>
<ol>
<li>This is not MySQL specific, all databases will exhibit this behaviour.</li>
<li>The isolation level needs to be set as the first statement in the transaction (or globally, but you don’t want serializable globally!)</li>
<li>For bonus points, add a unique index to the position column, though you’ll have to re-implement most of <code>acts_as_list</code> to make it work.</li>
<li>It’s possible to do this under read committed, but it’s pretty complicated and optimised for concurrent access rather than individual performance.</li>
<li>Obtaining a row lock before moving will fix this specific issue, but won’t address all the edge cases.</li>
</ol>
<p>_This is a small taste of the type of thing I cover in my DB is your friend training course. July through September I am running full day sessions in the US and UK. Chances are I’m coming to your city. Check it out at <a href="http://www.dbisyourfriend.com/">http://www.dbisyourfriend.com</a> <img src="http://www.dbisyourfriend.com/favicon.ico" alt="" /> _</p>tag:www.rhnh.net,2008:Post/7992009-08-04T17:40:14Z2009-08-04T17:34:14ZActs_as_state_machine locking<p>consider the following!</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>17<tt>
</tt>18<tt>
</tt>19<tt>
</tt><strong>20</strong><tt>
</tt>21<tt>
</tt>22<tt>
</tt>23<tt>
</tt>24<tt>
</tt>25<tt>
</tt>26<tt>
</tt>27<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">Door</span> < <span class="co">ActiveRecord</span>::<span class="co">Base</span><tt>
</tt> acts_as_state_machine <span class="sy">:initial</span> => <span class="sy">:closed</span><tt>
</tt><tt>
</tt> state <span class="sy">:closed</span><tt>
</tt> state <span class="sy">:open</span>, <span class="sy">:enter</span> => <span class="sy">:say_hello</span><tt>
</tt><tt>
</tt> event <span class="sy">:open</span> <span class="r">do</span><tt>
</tt> transitions <span class="sy">:from</span> => <span class="sy">:closed</span>, <span class="sy">:to</span> => <span class="sy">:open</span><tt>
</tt> <span class="r">end</span><tt>
</tt><tt>
</tt> <span class="r">def</span> <span class="fu">say_hello</span><tt>
</tt> puts <span class="s"><span class="dl">"</span><span class="k">hello</span><span class="dl">"</span></span><tt>
</tt> <span class="r">end</span><tt>
</tt><span class="r">end</span><tt>
</tt><tt>
</tt>door = <span class="co">Door</span>.create!<tt>
</tt><tt>
</tt>fork <span class="r">do</span><tt>
</tt> transaction <span class="r">do</span><tt>
</tt> door.open!<tt>
</tt> <span class="r">end</span><tt>
</tt><span class="r">end</span><tt>
</tt><tt>
</tt>door.open!<tt>
</tt><tt>
</tt><span class="c"># >> hello</span><tt>
</tt><span class="c"># >> hello</span><tt>
</tt></pre></td>
</tr></table>
<p>It’s broken, you can only open a door once. This is a classic double-update problem. One way to solve is with pessimistic locking. I made some codes that automatically lock any object when you call an event on it.</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>17<tt>
</tt>18<tt>
</tt>19<tt>
</tt><strong>20</strong><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">ActiveRecord::Base</span><tt>
</tt> <span class="c"># Forces all state transition events to obtain a DB lock</span><tt>
</tt> <span class="r">def</span> <span class="pc">self</span>.obtain_lock_before_all_state_transitions<tt>
</tt> event_table.keys.each <span class="r">do</span> |transition|<tt>
</tt> define_method(<span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>transition<span class="idl">}</span></span><span class="k">_with_lock!</span><span class="dl">"</span></span>) <span class="r">do</span><tt>
</tt> <span class="pc">self</span>.class.transaction <span class="r">do</span><tt>
</tt> lock!<tt>
</tt> send(<span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>transition<span class="idl">}</span></span><span class="k">_without_lock!</span><span class="dl">"</span></span>)<tt>
</tt> <span class="r">end</span><tt>
</tt> <span class="r">end</span><tt>
</tt> alias_method_chain <span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>transition<span class="idl">}</span></span><span class="k">!</span><span class="dl">"</span></span>, <span class="sy">:lock</span><tt>
</tt> <span class="r">end</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">Door</span> < <span class="co">ActiveRecord</span>::<span class="co">Base</span><tt>
</tt> <span class="c"># ... as before</span><tt>
</tt><tt>
</tt> obtain_lock_before_all_state_transitions<tt>
</tt><span class="r">end</span><tt>
</tt></pre></td>
</tr></table>
<p>beware! Your state transitions can now throw <code>ActiveRecord::RecordNotFound</code> errors (from <code>lock!</code>), since the object may have been deleted before you got a chance to play with it.</p>
<p>If you’re not using any locking in your web app, you’re probably doing it wrong. Just sayin’.</p>tag:www.rhnh.net,2008:Post/3012007-12-13T09:40:00Z2008-05-17T13:27:02ZLogging SQL statistics in rails<p>When your sysadmin comes to you <del>whinging</del> with a valid concern that your app is reading 60 gazillion records from the DB, you kinda wish you had a bit more information than % time spent in the DB. So I wrote a plugin that counts both the number of selects/updates/inserts/deletes and also the number of records affected. [This plugin is no longer available, the code is below for posterity.]</p>
<p>That does the counting, you need to decide how to log it. I am personally quite partial to adding it to the request log line, thus getting stats per request:</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></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="c"># vendor/rails/actionpack/lib/action_controller/benchmarking.rb:75</span><tt>
</tt>log_message << <span class="s"><span class="dl">"</span><span class="k"> | Select Records: </span><span class="il"><span class="idl">#{</span><span class="co">ActiveRecord</span>::<span class="co">Base</span>.connection.select_record_count<span class="idl">}</span></span><span class="dl">"</span></span><tt>
</tt>log_message << <span class="s"><span class="dl">"</span><span class="k"> | Selects: </span><span class="il"><span class="idl">#{</span><span class="co">ActiveRecord</span>::<span class="co">Base</span>.connection.select_count<span class="idl">}</span></span><span class="dl">"</span></span><tt>
</tt><tt>
</tt><span class="co">ActiveRecord</span>::<span class="co">Base</span>.connection.reset_counters!<tt>
</tt></pre></td>
</tr></table>
<p>Don’t forget the last line, otherwise you get cumulative numbers. That may be handy, but I doubt it. We’re only logging selects because that’s all we care about at the moment. I am sure this will change in time.</p>
<p><strong><span class="caps">UPDATE</span>:</strong> Moved to github, bzr repo is no longer available</p>
<p><strong><span class="caps">UPDATE</span> 2:</strong> Pasted code inline below, it’s way old and probably doesn’t work anymore.</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>17<tt>
</tt>18<tt>
</tt>19<tt>
</tt><strong>20</strong><tt>
</tt>21<tt>
</tt>22<tt>
</tt>23<tt>
</tt>24<tt>
</tt>25<tt>
</tt>26<tt>
</tt>27<tt>
</tt>28<tt>
</tt>29<tt>
</tt><strong>30</strong><tt>
</tt>31<tt>
</tt>32<tt>
</tt>33<tt>
</tt>34<tt>
</tt>35<tt>
</tt>36<tt>
</tt>37<tt>
</tt>38<tt>
</tt>39<tt>
</tt><strong>40</strong><tt>
</tt>41<tt>
</tt>42<tt>
</tt>43<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="r">module</span> <span class="cl">ActiveRecord::ConnectionAdapters</span><tt>
</tt> <span class="r">class</span> <span class="cl">MysqlAdapter</span><tt>
</tt> <span class="r">class</span> << <span class="cl">self</span><tt>
</tt> <span class="r">def</span> <span class="fu">counters</span><tt>
</tt> <span class="iv">@counters</span> ||= []<tt>
</tt> <span class="r">end</span><tt>
</tt><tt>
</tt> <span class="r">def</span> <span class="fu">attr_accessor_with_default</span>(name, default)<tt>
</tt> attr_accessor name<tt>
</tt> define_method(name) <span class="r">do</span><tt>
</tt> instance_variable_get(<span class="sy"><span class="sy">:</span><span class="dl">"</span><span class="k">@</span><span class="il"><span class="idl">#{</span>name<span class="idl">}</span></span><span class="dl">"</span></span>) || default <tt>
</tt> <span class="r">end</span><tt>
</tt> <span class="r">end</span><tt>
</tt><tt>
</tt> <span class="r">def</span> <span class="fu">define_counter</span>(name, record_func = lambda {|ret| ret })<tt>
</tt> attr_accessor_with_default(<span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>name<span class="idl">}</span></span><span class="k">_count</span><span class="dl">"</span></span>, <span class="i">0</span>)<tt>
</tt> attr_accessor_with_default(<span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>name<span class="idl">}</span></span><span class="k">_record_count</span><span class="dl">"</span></span>, <span class="i">0</span>)<tt>
</tt><tt>
</tt> define_method(<span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>name<span class="idl">}</span></span><span class="k">_with_counting</span><span class="dl">"</span></span>) <span class="r">do</span> |*args|<tt>
</tt> ret = send(<span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>name<span class="idl">}</span></span><span class="k">_without_counting</span><span class="dl">"</span></span>, *args)<tt>
</tt> send(<span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>name<span class="idl">}</span></span><span class="k">_count=</span><span class="dl">"</span></span>, send(<span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>name<span class="idl">}</span></span><span class="k">_count</span><span class="dl">"</span></span>) + <span class="i">1</span>)<tt>
</tt> send(<span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>name<span class="idl">}</span></span><span class="k">_record_count=</span><span class="dl">"</span></span>, send(<span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>name<span class="idl">}</span></span><span class="k">_record_count</span><span class="dl">"</span></span>) + record_func[ret])<tt>
</tt> ret<tt>
</tt> <span class="r">end</span><tt>
</tt> alias_method_chain name, <span class="sy">:counting</span><tt>
</tt><tt>
</tt> <span class="pc">self</span>.counters << name<tt>
</tt> <span class="r">end</span><tt>
</tt> <span class="r">end</span><tt>
</tt><tt>
</tt> define_counter <span class="sy">:select</span>, lambda {|ret| ret.length }<tt>
</tt> define_counter <span class="sy">:update</span><tt>
</tt> define_counter <span class="sy">:insert</span><tt>
</tt> define_counter <span class="sy">:delete</span><tt>
</tt><tt>
</tt> <span class="r">def</span> <span class="fu">reset_counters!</span><tt>
</tt> <span class="pc">self</span>.class.counters.each <span class="r">do</span> |counter|<tt>
</tt> <span class="pc">self</span>.send(<span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>counter<span class="idl">}</span></span><span class="k">_count=</span><span class="dl">"</span></span>, <span class="i">0</span>)<tt>
</tt> <span class="pc">self</span>.send(<span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span>counter<span class="idl">}</span></span><span class="k">_record_count=</span><span class="dl">"</span></span>, <span class="i">0</span>)<tt>
</tt> <span class="r">end</span><tt>
</tt> <span class="r">end</span><tt>
</tt> <span class="r">end</span><tt>
</tt><span class="r">end</span><tt>
</tt></pre></td>
</tr></table>
tag:www.rhnh.net,2008:Post/1652007-09-26T23:16:00Z2007-10-01T10:14:31ZCounting ActiveRecord associations: count, size or length?<p>Short answer: <code>size</code>. Here’s why.</p>
<p><code>length</code> will fall through to the underlying array, which will force a load of the association</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></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">>> user.posts.length<tt>
</tt> <span class="co">Post</span> <span class="co">Load</span> (<span class="fl">0.620579</span>) <span class="co">SELECT</span> * <span class="co">FROM</span> posts <span class="co">WHERE</span> (posts.user_id = <span class="i">1321</span>) <tt>
</tt>=> <span class="i">162</span><tt>
</tt></pre></td>
</tr></table>
<p>This is bad. You loaded 162 objects into memory, just to count them. The DB can do this for us! That’s what <code>count</code> does.</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></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">>> user.posts.count<tt>
</tt> <span class="co">SQL</span> (<span class="fl">0.060506</span>) <span class="co">SELECT</span> count(*) <span class="co">AS</span> count_all <span class="co">FROM</span> posts <span class="co">WHERE</span> (posts.user_id = <span class="i">1321</span>) <tt>
</tt>=> <span class="i">162</span><tt>
</tt></pre></td>
</tr></table>
<p>Now we’re on to something. The problem is, <code>count</code> will <em>always</em> issue a count to the DB, which is kind of redundant if you’ve already loaded the association. That’s were <code>size</code> comes in. It’s got smarts. Observe!</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></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">>> <span class="co">User</span>.find(<span class="i">1321</span>).posts.size<tt>
</tt> <span class="co">User</span> <span class="co">Load</span> (<span class="fl">0.003610</span>) <span class="co">SELECT</span> * <span class="co">FROM</span> users <span class="co">WHERE</span> (users.id = <span class="i">1321</span>) <tt>
</tt> <span class="co">SQL</span> (<span class="fl">0.000544</span>) <span class="co">SELECT</span> count(*) <span class="co">AS</span> count_all <span class="co">FROM</span> posts <span class="co">WHERE</span> (posts.user_id = <span class="i">1321</span>) <tt>
</tt>=> <span class="i">162</span><tt>
</tt>>> <span class="co">User</span>.find(<span class="i">1321</span>, <span class="sy">:include</span> => <span class="sy">:posts</span>).posts.size <tt>
</tt> <span class="co">User</span> <span class="co">Load</span> <span class="co">Including</span> <span class="co">Associations</span> (<span class="fl">0.124950</span>) <span class="co">SELECT</span> ...<tt>
</tt>=> <span class="i">162</span><tt>
</tt></pre></td>
</tr></table>
<p>Notice it uses count, but if the association is already loaded (i.e. we already know how many objects there are), it uses length, for optimum DB usage.</p>
<p>But know that’s not all. There’s always more. If you also store the number of posts on the user object, as is common for performance reasons, <code>size</code> will use that also. Just make sure the column is named _association__count (i.e. posts_count).</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></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">>> <span class="co">User</span>.columns.collect(&<span class="sy">:name</span>).include?(<span class="s"><span class="dl">"</span><span class="k">posts_count</span><span class="dl">"</span></span>)<tt>
</tt>=> <span class="pc">true</span><tt>
</tt>>> <span class="co">User</span>.find(<span class="i">1321</span>).posts.size<tt>
</tt> <span class="co">User</span> <span class="co">Load</span> (<span class="fl">0.003869</span>) <span class="co">SELECT</span> * <span class="co">FROM</span> users <span class="co">WHERE</span> (users.id = <span class="i">1321</span>) <tt>
</tt>=> <span class="i">162</span><tt>
</tt></pre></td>
</tr></table>
<h3>The bad news</h3>
<p>So now you’re all excited, I better tell you why this is only fantastic until you start using has_many :through.</p>
<p>Now, the situation is slightly different between 1.2.x (r4605) and edge (r7639), so I’ll start with stable. Now, they may look the same but a normal has_many association and one with the :through option are actually implememted by two entirely separate classes under the hood. And it so happens that the has_many :through version kind of, well, doesn’t have quite the same smarts. It loads up the association just as length does (then falls through to <code>Array#size</code>). <del>Edge is sharp enough to use a <code>count</code>, but still doesn’t know about any caches you may be using. This was commited in r7237, so it’s pretty easy to patch in to stable. Or you can use this extension (on either branch – here is the <a href="http://dev.rubyonrails.org/ticket/9734">trac ticket</a>):</del> This patch was added to edge in <a href="http://dev.rubyonrails.org/changeset/7692">7692</a></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>17<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="r">module</span> <span class="cl">CoreExtensions::HasManyThroughAssociation</span><tt>
</tt> <span class="r">def</span> <span class="fu">size</span><tt>
</tt> <span class="r">return</span> <span class="iv">@owner</span>.send(<span class="sy">:read_attribute</span>, cached_counter_attribute_name) <span class="r">if</span> has_cached_counter?<tt>
</tt> <span class="r">return</span> <span class="iv">@target</span>.size <span class="r">if</span> loaded?<tt>
</tt> <span class="r">return</span> count<tt>
</tt> <span class="r">end</span><tt>
</tt><tt>
</tt> <span class="r">def</span> <span class="fu">has_cached_counter?</span><tt>
</tt> <span class="iv">@owner</span>.attribute_present?(cached_counter_attribute_name)<tt>
</tt> <span class="r">end</span><tt>
</tt><tt>
</tt> <span class="r">def</span> <span class="fu">cached_counter_attribute_name</span><tt>
</tt> <span class="s"><span class="dl">"</span><span class="il"><span class="idl">#{</span><span class="iv">@reflection</span>.name<span class="idl">}</span></span><span class="k">_count</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="co">ActiveRecord</span>::<span class="co">Associations</span>::<span class="co">HasManyThroughAssociation</span>.send(<span class="sy">:include</span>, <span class="co">CoreExtensions</span>::<span class="co">HasManyThroughAssociation</span>)<tt>
</tt></pre></td>
</tr></table>
<h3>How it doesn’t work</h3><table class="CodeRay"><tr>
<td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt>
</tt></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">user.posts.find(<span class="sy">:all</span>, <span class="sy">:conditions</span> => [<span class="s"><span class="dl">"</span><span class="k">reply_count > ?</span><span class="dl">"</span></span>, <span class="i">50</span>]).size<tt>
</tt></pre></td>
</tr></table>
<p><code>size</code> normally works because assocations use a proxy – when I call <code>user.posts</code> it won’t actually load any posts until I call a method that requires them. So user.posts.size can work without ever loading the posts because they aren’t required for the operation. The above code won’t work well because find does not use a proxy – it will straight away load the requested posts from the DB, without <code>size</code> getting a chance to send a <span class="caps">COUNT</span> instead. You may be better off moving this finder logic into an association so that <code>size</code> will work as expected. This also has the benefit that if you decide to add a counter cache later on you won’t have to change any code to use it.</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></pre></td>
<td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">has_many <span class="sy">:popular_posts</span>, <span class="sy">:class_name</span> => <span class="s"><span class="dl">"</span><span class="k">Post</span><span class="dl">"</span></span>, <span class="sy">:foreign_key</span> => <span class="s"><span class="dl">"</span><span class="k">post_id</span><span class="dl">"</span></span>, <span class="sy">:conditions</span> => [<span class="s"><span class="dl">"</span><span class="k">reply_count > ?</span><span class="dl">"</span></span>, <span class="i">50</span>]<tt>
</tt></pre></td>
</tr></table>
<p>So use <code>size</code> when counting associations unless you have a good reason not to. Most importantly thought, ensure you’re watching your development log so to be aware what <span class="caps">SQL</span> your app is generating.</p>
<p><span class="caps">UPDATE</span>: Added link to <a href="http://dev.rubyonrails.org/ticket/9734">my patch on trac</a></p>
<p><span class="caps">UPDATE 2</span>: ... which is now closed, see <a href="http://dev.rubyonrails.org/changeset/7692">r7692</a></p>