Finite state machines in Ruby
The article is the authorship of the Habrauser preprocessor , which could not publish it for all obvious reasons. So all the pluses to him :)
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, the conditions of this transition, actions performed during the transition or after. I've always had a bad time with theory, so I won’t go into it anymore; instead, for those who are interested in details, I can recommend watching Wikipedia (as without it) http://en.wikipedia.org/wiki/Finite-state_machine and http://ru.wikipedia.org/wiki/Final_automaton, and from there already drip as much as you want. In practice, this can be used a lot where, from parsing strings (hello Ragel ), to the User model in your web application.
Now I want to talk about the implementation of state machine in ruby language. There is such a wonderful site ruby-toolbox.com , by which you can fairly accurately judge what is now popular in the world of rubies. In the State machines section, in the first place we see the gem aasm from rubyist. By the way, it just so happened that the quality of a ruby library can almost always be judged by its popularity, in any case, in areas where there is library competition. Well, so it happened. 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 for it is very scarce, even on the western web I could not find any more or less complete description of this library. So I will allow myself, in essence, to write a small manual for it.
So, let's start with an example from the library itself (this is all the documentation).
What is now generated for us: If the object is inherited from ActiveRecord :: Base, then the persistence component of aams is mixed in with it. It is for her that bang methods are relevant in the first place. conversation.view! not only translates the current state of the object, but also saves it in the database. Also, no one bothers you to define 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 more examples. What we see. First callbacks. For transition, these are: guard and: on_transition. If: guard true, then the transition will fail; if not, then no. : on_transition is executed during the transition. For example, this means that you cannot go to the next state in this callback.
Event -: success, which is executed after the transition is completed successfully.
In state -: enter and: exit, they are executed, respectively, upon entering and exiting the state (it does not matter through which event and through which 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 parameters (the name of the event and the name of the state in which the object was transferred).
From this example we also see that the event can any number of transitions to be determined. It will be executed the one with: from matches the current state, and: guard returns true.
That’s basically all. Here is an example of a small, but very flexible and expandable ruby library. Well, at the end of a little amateur performance.
http://github.com/preprocessor/aasm
A mechanism for storing states in the database as integers has been implemented. Performance and all that. Easy to use: Named scopes continue to work as they should. http://github.com/preprocessor/railroad_xing Fork fork (Ruby developers gentlemen, let's keep on githabe their projects at least in some form. The trend, after all). Adds support for aasm. As a result, we get:
 Why is this needed? With such a schema, it is often much easier to understand and discuss the code. However, its drawing will take 5-10 minutes. And if 10 models 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 mortal with the original. So you can follow and use github.com/royw/railroad_xing/tree/master
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, the conditions of this transition, actions performed during the transition or after. I've always had a bad time with theory, so I won’t go into it anymore; instead, for those who are interested in details, I can recommend watching Wikipedia (as without it) http://en.wikipedia.org/wiki/Finite-state_machine and http://ru.wikipedia.org/wiki/Final_automaton, and from there already drip as much as you want. In practice, this can be used a lot where, from parsing strings (hello Ragel ), to the User model in your web application.
Now I want to talk about the implementation of state machine in ruby language. There is such a wonderful site ruby-toolbox.com , by which you can fairly accurately judge what is now popular in the world of rubies. In the State machines section, in the first place we see the gem aasm from rubyist. By the way, it just so happened that the quality of a ruby library can almost always be judged by its popularity, in any case, in areas where there is library competition. Well, so it happened. 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 for it is very scarce, even on the western web I could not find any more or less complete description of this library. So I will allow myself, in essence, to write a small manual for it.
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 is now generated for us: If the object is inherited from ActiveRecord :: Base, then the persistence component of aams is mixed in with it. It is for her that bang methods are relevant in the first place. conversation.view! not only translates the current state of the object, but also saves it in the database. Also, no one bothers you to define 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 more examples. What we see. First callbacks. For transition, these are: guard and: on_transition. If: guard true, then the transition will fail; if not, then no. : on_transition is executed during the transition. For example, this means that you cannot go to the next state in this callback.
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 для всех закрытых бесед.
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
Event -: success, which is executed after the transition is completed successfully.
In state -: enter and: exit, they are executed, respectively, upon entering and exiting the state (it does not matter through which event and through which 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 parameters (the name of the event and the name of the state in which the object was transferred).
From this example we also see that the event can any number of transitions to be determined. It will be executed the one with: from matches the current state, and: guard returns true.
That’s basically all. Here is an example of a small, but very flexible and expandable ruby library. Well, at the end of a little amateur performance.
http://github.com/preprocessor/aasm
A mechanism for storing states in the database as integers has been implemented. Performance and all that. Easy to use: Named scopes continue to work as they should. http://github.com/preprocessor/railroad_xing Fork fork (Ruby developers gentlemen, let's keep on githabe their projects at least in some form. The trend, after all). Adds support for aasm. As a result, we get:
aasm_state :unread, :integer => 0
aasm_state :read, :integer => 1
aasm_state :closed, :integer => 2
Conversation.aasm_integers[:read] => 1
 Why is this needed? With such a schema, it is often much easier to understand and discuss the code. However, its drawing will take 5-10 minutes. And if 10 models 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 mortal with the original. So you can follow and use github.com/royw/railroad_xing/tree/master