tag:www.rhnh.net,2008:/acts_as_state_machineActs As State Machine - Xavier Shay's Blog2010-07-05T00:54:59ZEnkiXavier Shaynotreal@rhnh.nettag: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/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>