tag:www.rhnh.net,2008:/acts_as_state_machine Acts As State Machine - Xavier Shay's Blog 2010-07-05T00:54:59Z Enki Xavier Shay notreal@rhnh.net tag:www.rhnh.net,2008:Post/821 2010-07-05T04:54:00Z 2010-07-05T00:54:59Z acts_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&amp;server=vimeo.com&amp;show_title=0&amp;show_byline=0&amp;show_portrait=0&amp;color=00ADEF&amp;fullscreen=1" /><embed src="http://vimeo.com/moogaloop.swf?clip_id=12968585&amp;server=vimeo.com&amp;show_title=0&amp;show_byline=0&amp;show_portrait=0&amp;color=00ADEF&amp;fullscreen=1" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="600" height="375"></embed></object><br /> (If embedding doesn&#8217;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&#8217;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">&quot;</span><span class="il"><span class="idl">#{</span>t<span class="idl">}</span></span><span class="k">_with_lock!</span><span class="dl">&quot;</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">&quot;</span><span class="il"><span class="idl">#{</span>t<span class="idl">}</span></span><span class="k">_without_lock!</span><span class="dl">&quot;</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">&quot;</span><span class="il"><span class="idl">#{</span>t<span class="idl">}</span></span><span class="k">!</span><span class="dl">&quot;</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> =&gt; <span class="sy">:bought</span>, <span class="sy">:from</span> =&gt; [<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&#8217;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/799 2009-08-04T17:40:14Z 2009-08-04T17:34:14Z Acts_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> &lt; <span class="co">ActiveRecord</span>::<span class="co">Base</span><tt> </tt> acts_as_state_machine <span class="sy">:initial</span> =&gt; <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> =&gt; <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> =&gt; <span class="sy">:closed</span>, <span class="sy">:to</span> =&gt; <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">&quot;</span><span class="k">hello</span><span class="dl">&quot;</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"># &gt;&gt; hello</span><tt> </tt><span class="c"># &gt;&gt; hello</span><tt> </tt></pre></td> </tr></table> <p>It&#8217;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">&quot;</span><span class="il"><span class="idl">#{</span>transition<span class="idl">}</span></span><span class="k">_with_lock!</span><span class="dl">&quot;</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">&quot;</span><span class="il"><span class="idl">#{</span>transition<span class="idl">}</span></span><span class="k">_without_lock!</span><span class="dl">&quot;</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">&quot;</span><span class="il"><span class="idl">#{</span>transition<span class="idl">}</span></span><span class="k">!</span><span class="dl">&quot;</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> &lt; <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&#8217;re not using any locking in your web app, you&#8217;re probably doing it wrong. Just sayin&#8217;.</p>