📜 ⬆️ ⬇️

Ruby State Machines

Article for the authorship of habrayuser preprocessor , which could not publish it for all understandable reason. So all the pluses to him :)

A finite state machine (Finite-state machine) is such a thing that describes the behavior of an object with a finite number of states. Ways of transition from one state to another, conditions of this transition, actions performed during the transition or after. With theory, I have always been bad, so I will not go into it anymore, instead, for those who are interested in the details, I can recommend to see Wikipedia (as without it) http://en.wikipedia.org/wiki/Finite-state_machine and http://ru.wikipedia.org/wiki/Konechny_avtomat , and from there already drip as much as you want. In practice, this can be used in many places, from parsing lines (hello to Ragel ) to the User model in your web application.

I now want to talk about the implementation of state machine in ruby ​​language. There is such a wonderful site, ruby-toolbox.com , by which one can judge with sufficient accuracy that what is popular in the world of Ruby now. In the State machines section in the first place we see gem aasm from rubyist. By the way, it just so happens that the quality of a Ruby library can almost always be judged by its popularity, at least in areas where there is competition among libraries. Well, that's how it turned out. aasm is really good, unlike its popular predecessor ( acts_as_state_machine ) can work not only (and for some, not so much) with ActiveRecord, but also with any ruby-object. That's just the documentation to it is very scanty, even in the western web I could not find any more or less complete description of this library. So let me, in fact, write to her a small manual.

So, let's start with an example from the library itself (this is all the documentation).
')
class Conversation < ActiveRecord::Base
include AASM

aasm_initial_state :unread

aasm_state :unread
aasm_state :read
aasm_state :closed

aasm_event :view do
transitions :to => :read, :from => [:unread]
end

aasm_event :close do
transitions :to => :closed, :from => [:read, :unread]
end
end



What for us now generated:

conversation = Conversation.new

conversation.aasm_current_state => :unread

conversation.view # :read

conversation.view! # :read aasm_write_state,
conversation.read? # true or false. “ read?”

conversation.closed # named scopes , scope .


If the object is inherited from ActiveRecord :: Base, then the persistence component of aams is mixed into it. It is for her that bang-methods are first of all relevant. conversation.view! will not only translate the current state of the object, but also save it to the database. Also, no one bothers you to determine aasm_write_state for any object and do everything in it that the soul desires (just like aasm_read_state).

Let's look at a couple of examples.

aasm_state :waiting, :enter => :start_timer
aasm_state :selecting_cards
aasm_state :made_turn, :exit => lambda { unseletcted_cards.each { |c| c.destroy }

aasm_event :go do
transitions :to => :selecting_cards, :from => [:ready], :guard => :attacking?
transitions :to => :waiting, :from => [:ready], :guard => :defending?
end

aasm_event :make_turn, :success => :after_make_turn do
transitions :to => :made_turn, :from => [:selecting_cards], :on_transition => :do_make_turn
end


What we see. First callbacks.
The transition is: guard and: on_transition. If: guard is true, then the transition is executed, if not, then no. : on_transition is executed during the transition. For example, this means that you cannot make the transition to the next state in this callback.
At event -: success, executed after successful completion of the transition.
At state -: enter and: exit, are executed, respectively, at an input and an output from steyt (it is unimportant through what event and through what transition).
Any of these callbacks can be either Symbol or Proc, in general, like everywhere else.

The object itself has aasm_event_fired and aasm_event_failed. If one of them is defined on the object, then aasm_event_failed will be called with one parameter (the name of the event), and aasm_event_fired with two (the name of the event and the name of the state in which the object passed)

From this example, we also see that an event can have any number of transitions. The one with: from will match the current state will be executed, and: guard returns true.

But in general, that's all. We have before us an example of a small, but very flexible and expandable library for Ruby. Well, at the end of a little amateur.

http://github.com/preprocessor/aasm

Implemented a mechanism for storing states in the database in the form of integers. Performance and all that. It’s easy to use:

aasm_state :unread, :integer => 0
aasm_state :read, :integer => 1
aasm_state :closed, :integer => 2

Conversation.aasm_integers[:read] => 1


Named scopes continue to work as it should.

http://github.com/preprocessor/railroad_xing

Fork fork (Messrs. Ruby developers, let's keep our projects on the githaba, at least in some form. Trend after all). Adds support aasm. As a result, we obtain:



What is it for? With such a schema, it is often much easier to understand and discuss the code. However, her drawing will take 5-10 minutes. And if models 10 and often change? Naturally no one draws them. But if everything is automatic and convenient, then why not.

Good luck.

Upd. My fork railroad_xing is now the same as the original. So you can follow and use github.com/royw/railroad_xing/tree/master

Source: https://habr.com/ru/post/64403/


All Articles