arrow_back Volver
Inicio keyboard_arrow_right Artículos keyboard_arrow_right Artículo

Máquina de estados con Ruby on Rails

Eduardo Ismael Garcia

Full Stack Developer at Código Facilito.

av_timer 4 Min. de lectura

remove_red_eye 24800 visitas

calendar_today 04 Septiembre 2018

Introducción (Opcional).

Cuando me encontraba estudiando la universidad tuve la oportunidad de cursar la materia de Automatas, materia en la cual trabajamos con máquinas de estados. Regularmente cuando trabajamos con este tipo de temas, temas teóricos, el primer paso era la explicación, posteriormente realizamos algo práctico. Si no mal recuerdo programe una máquina de estados en Java, proyecto que al día de hoy me daría mucha verguenza mostrar, principalmente por mi código espantoso. Después de finalizar el tema, nunca más volví a trabajar con una máquina de estados, hasta que me incorporé al equipo de códigofacilito, fue aquí donde me percate que las máquinas de estados pueden ser implementadas en todas partes, en cajeros automáticos, al momento de pagar el mercado en el super, al manejar un auto, etc... muy probablemente alguno de tus procesos puede ser automatizado con una máquina de estados, es por ello que en esta ocasión aprenderemos a crear máquinas de estados con Ruby on Rails.

Para este post estaré trabajando con la versión 5 de Ruby on Rails.

Máquinas de estados finita.

Una máquina de estados finita no es más que la forma en la cual podemos modelar el comportamiento de una sistema. Por ejemplo, el proceso de publicar un libro puediese ser el siguiente.

En este pequeño diagrama podemos encontrar cuatro estado : en borrador, verificado, rechazado y publicado. El libro comenzará en un estado por default, en este caso en borrador.

Con estos cuatro estados podemos definir las siguientes reglas:

  • Un libro podrá ser publicado si anteriormente fue verificado.
  • Un libro podrá ser rechazado si anteriormente fue verificado.
  • Un libro en borrador podrá ser verificado.

Para que nuestro sistema pueda pasar de un estado a otro necesitamos una transición. Una transición la representaremos por una línea, la cual finalizará con una flecha.

Bien, ahora, representemos esta maquina de estados en Rails.

Modelos

Lo primero que haremos será crear nuestro modelo Book.

rails g model Book state:string

Los estados los almacenaremos en un atributo de tipo string. Con esto tenemos la flexibilidad que el día de mañana podamos agregar más estados al modelo.

Es posible trabar la máquina de estos con enums.

Para este post estaremos trabajando con la gema assm (Act as State Machine).

#Gemfile
gem 'aasm'
bundle install

Dentro de nuestro modelo definimos nuestra máquina de estados.

class Book < ApplicationRecord
  include AASM

  #Columna con la cual manejaremos los estados!
  aasm column: 'state' do 
    state :draft, initial: true
    state :verified
    state :published
    state :rejected
  end

end

Una vez hemos colocamos todos los estados e indicado cuál será el estado inicial, el siguiente paso es definir todas las transiciones. En este caso nuestra máquina de estados poseerá tres transiciones.

  • De borrador a verificado.
  • De verificado a publicado.
  • De verificado a rechazado.
aasm column: 'state' do

    ...

    event :verify do
      transitions from: :draft, to: :verified
    end

    event :reject do
      transitions from: :verified, to: :rejected
    end

    event :publish do
      transitions from: :verified, to: :published
    end

end

Los eventos serán los acontecimientos que modifiquen el estado del sistema. Todo cambio de estado deberá hacerse a través de un evento. Los eventos serán representados mediante métodos.

Bien, estos serían los pasos mínimos para definir nuestra máquina de estados.

Ahora es momento de probar. En nuestra consola creamos un nuevo libro.

book = Book.create
book.state
=> "draft"

Cómo observamos el estado inicial de nuestro libro será draft, tal y como lo hemos indicado en nuestro modelo.

Ahora hagamos un cambio de estado.

book = Book.last
book.verify
=> true
book.state
=> "verified"

Si queremos persistir el cambio de estado en nuestra base de datos, finalizamos los eventos(métodos) con el signo de admiración !.

book = Book.last
book.verify!
(1.2ms)  begin transaction
SQL (1.9ms)  UPDATE "books" SET "state" = ?, "updated_at" = ? WHERE "books"."id" = ?  [["state", "verified"], ["updated_at", "2018-09-01 21:55:47.314368"], ["id", 1]]
(1.0ms)  commit transaction

Una vez el libro se encuentra verificado ya es posible publicarlo o rechazarlo.

Si queremos conocer si nuestro objeto se encuentra en un estado en especifico, únicamente realizamos la pregunta.

book.draft?
=> false
book.verified?
=> true
book.rejected?
=> false
book.published?
=> false

Callbacks

Muy probablemente en cada cambio de estado necesitemos ejecutar ciertas acciones, por ejemplo, si un libro fue publicado, que mejor que notificar al autor de la buena noticia. En estos caso necesitaremos hacer uso de los callbacks.

Los callbacks los podemos implementar a nivel máquina de estado o a nivel de transición.

A nivel máquina de estado usaremos los métodos:

  • before_all_transactions
  • after_all_transactions

A nivel maquina de transición usaremos los métodos:

  • before_transaction
  • after_transaction
  • after_commit (Después de persistir los cambios)
aasm column: 'state' do 
    state :draft, initial: true
    state :verified
    state :published
    state :rejected

    after_all_transactions :after_transactions

    ...

    event :publish,
        before_transaction: :before_transaction,
        after_commit: :send_congratulations_mail do
          transitions from: :verified, to: :published
    end

end

def before_transaction
    puts "Antes de la transacción!!"
end

def after_transactions
    puts "Después de la transacción!!"
end

def send_congratulations_mail
    puts "Aquí debemos de enviar nuestro mail!"
end

Validaciones

Realizar validaciones en nuestros modelos es de suma importancia, con las máquinas de estados no es la excepción. Aunque por si sola la máquina de estados ya nos provee de ciertas validaciones, por ejemplo, no permitir pasar de un estado a otro sin una transición valida, habrá ocasiones en las que necesitemos validar más que solo la transición, en esos casos haremos huzo de los guards.

Podemos validar una transición a partir del resultado de un método. El cambio de estado se hará, siempre y cuando el método regrese verdadero.

aasm column: 'state' do 
    ...

    event :verify, guard: :is_valid_to_verify? do
      transitions from: :draft, to: :verified
   end

    ...

end

...

def is_valid_to_verify?
    true
end

Si nosotros queremos asegurarnos que es posible realizar una transición, nos apoyaremos de los métodos "may". Estos métodos tendrán la siguiente estructura.

may_ + evento + ?

book.may_publish?
=> true
book.may_reject?
=> true
book.may_verify?
=> false

Conclusión

Las máquinas de estados nos permiten crear modelos muchos más robustos, de tal forma que tengamos un control sobre todos los posibles cambios que puedan suscitarse a lo largo de algún proceso. Con las máquinas de estado podemos definir de forma concreta qué hacer en caso de 😃.

Bootcamp Ciencia de Datos

12 semanas de formación intensiva en los conocimientos, fundamentos, y herramientas que necesitas para ser científico de datos

Más información