<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Daniel Zambrano</title>
    <description>The latest articles on Forem by Daniel Zambrano (@caciquecoder).</description>
    <link>https://forem.com/caciquecoder</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F440792%2F821c7308-f623-4f8e-b101-f05fcf56ec70.png</url>
      <title>Forem: Daniel Zambrano</title>
      <link>https://forem.com/caciquecoder</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/caciquecoder"/>
    <language>en</language>
    <item>
      <title>Implementing a State Machine as a service with aasm gem.</title>
      <dc:creator>Daniel Zambrano</dc:creator>
      <pubDate>Thu, 06 Aug 2020 00:27:29 +0000</pubDate>
      <link>https://forem.com/caciquecoder/implementing-a-state-machine-as-a-service-with-aasm-gem-3c9p</link>
      <guid>https://forem.com/caciquecoder/implementing-a-state-machine-as-a-service-with-aasm-gem-3c9p</guid>
      <description>&lt;p&gt;I recognise the &lt;a href="https://github.com/aasm/aasm"&gt;gem aasm&lt;/a&gt; is one of the most useful gems when we want to explain how our objects or logic will change among states and which conditions will be. However, after many years I’ve seen how many developers are still shooting themselves with this gem.&lt;/p&gt;

&lt;p&gt;The original code has been done in collaboration with &lt;a href="https://dev.to/martinbjeldbak"&gt;Martin Madsen&lt;/a&gt;, the code present in this blog is a generalization from the original, still, it is a similar concept.&lt;/p&gt;

&lt;p&gt;I’ve seen with this gem is how they use the ActiveRecord implementation for every case. Let’s be clear, this implementation is great when the state and dependencies related are only models and you are not creating circular dependencies.&lt;/p&gt;

&lt;p&gt;Multiple times we are working with the next scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We have a booking for a room when the user is starting this booking its state is &lt;strong&gt;draft&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Later on, the user is confirming the date we will change it by &lt;strong&gt;pre-reserved&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;When the payment has been done the state will be booked.
Finally, when the booking has been fulfilled, the status will be &lt;strong&gt;fulfilled&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, we might have more transitions between those cases, but with just 4 this example will be enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  ActiveRecord Implementation
&lt;/h2&gt;

&lt;p&gt;Usually, your code if you are implementing the ActiveRecord approach your code it will look&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Booking&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;AASM&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:provider&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:client&lt;/span&gt;
  &lt;span class="n"&gt;aasm&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="ss"&gt;:draft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;initial: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;after: :notify_draft&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="ss"&gt;:pre_reserved&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="ss"&gt;:booked&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;after: :notify_reservation&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="ss"&gt;:fulfilled&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ss"&gt;:pre_reserve!&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;transitions&lt;/span&gt; &lt;span class="ss"&gt;from: :draft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="ss"&gt;to: :pre_reserved&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="ss"&gt;after: :notify_draft&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ss"&gt;:book&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;transitions&lt;/span&gt; &lt;span class="ss"&gt;from: :pre_reserved&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;to: :booked&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;after: :process_book&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;notify_draft&lt;/span&gt;
    &lt;span class="no"&gt;Bookings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Someone is interested'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_book&lt;/span&gt;
    &lt;span class="no"&gt;Bookings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Bookings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'your reservation has been done'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="no"&gt;Bookings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'you got a new reservation'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Pros and Cons
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Booking model is prone to become a &lt;a href="https://refactoring.guru/es/smells/large-class"&gt;god object&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Single-responsibility_principle"&gt;Single responsibility principle&lt;/a&gt; is being violated. After changing states we are adding business logic. Booking is responsible of persistency, changing states(we can consider this as persistency if we don’t want to be strict), sending emails -when sending emails-, and releasing payments.&lt;/li&gt;
&lt;li&gt;Testing will become harder, more items to stub.&lt;/li&gt;
&lt;li&gt;Wrong dependency direction. Entities now will know about other services. Therefore, more dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  State machine as a service.
&lt;/h2&gt;

&lt;p&gt;A great approach is implementing the state machine as a service and injecting the booking we want to modify.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Bookings&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StateService&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;AASM&lt;/span&gt;
    &lt;span class="nb"&gt;attr_reader&lt;/span&gt; &lt;span class="ss"&gt;:booking&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;booking&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@booking&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;booking&lt;/span&gt;

      &lt;span class="c1"&gt;# we need to initialize the state machine base on the current booking&lt;/span&gt;
      &lt;span class="n"&gt;aasm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;booking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;aasm&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="ss"&gt;:draft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;initial: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;after: :notify_draft&lt;/span&gt;
      &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="ss"&gt;:pre_reserved&lt;/span&gt;
      &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="ss"&gt;:booked&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;after: :notify_reservation&lt;/span&gt;
      &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="ss"&gt;:fulfilled&lt;/span&gt;

     &lt;span class="c1"&gt;# This callback could be something different, will dependent of your code&lt;/span&gt;
     &lt;span class="n"&gt;after_all_transitions&lt;/span&gt; &lt;span class="ss"&gt;:save_state&lt;/span&gt;
      &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ss"&gt;:pre_reserve!&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;transitions&lt;/span&gt; &lt;span class="ss"&gt;from: :draft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
          &lt;span class="ss"&gt;to: :pre_reserved&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
          &lt;span class="ss"&gt;after: :notify_draft&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ss"&gt;:book&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;transitions&lt;/span&gt; &lt;span class="ss"&gt;from: :pre_reserved&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;to: :booked&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;after: :process_book&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;notify_draft&lt;/span&gt;
      &lt;span class="no"&gt;Bookings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Someone is interested'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_book&lt;/span&gt;
      &lt;span class="no"&gt;Bookings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Bookings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;booking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'your reservation has been done'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="no"&gt;Bookings&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;booking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"you've got a new reservation"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save_state&lt;/span&gt; 
      &lt;span class="n"&gt;booking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_attributes!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="n"&gt;aasm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To achieve this implementation we only need to be aware of 2 sections. Everything else is your own code&lt;/p&gt;

&lt;p&gt;The first piece of code is your initialiser, you will need to indicate what is the current state for you machine, your instance should have the value. Don't let you misguide you as a default value, it is the value for your entity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;aasm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Also when your changes are done, you need to make sure you will make it persistent, it is not mandatory this hook, but saving is a must.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;aasm&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;after_all_transitions&lt;/span&gt; &lt;span class="ss"&gt;:save_state&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save_state&lt;/span&gt; 
  &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_attributes!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="n"&gt;aasm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Pros
&lt;/h3&gt;

&lt;p&gt;Considering this approach we will get two huge benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Booking model is responsible only data persistency&lt;/li&gt;
&lt;li&gt;This StateService is our actual State machine and the dependency direction is proper, because is a service knowing about other services, and the model booking won’t know about Email or Payments&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>patterns</category>
    </item>
  </channel>
</rss>
