Dynamic State Machine in Ruby? Do State Machines Have to be Classes? - ruby

Question is, are state machines always defined statically (on classes)? Or is there a way for me to have it so each instance of the class with has it's own set of states?
I'm checking out Stonepath for implementing a Task Engine. I don't really see the distinction between "states" and "tasks" in there, so I'm thinking I could just map a Task directly to a state. This would allow me to be able to define task-lists (or workflows) dynamically, without having to do things like:
aasm_event :evaluate do
transitions :to => :in_evaluation, :from => :pending
end
aasm_event :accept do
transitions :to => :accepted, :from => :pending
end
aasm_event :reject do
transitions :to => :rejected, :from => :pending
end
Instead, a WorkItem (the main workflow/task manager model), would just have many tasks. Then the tasks would work like states, so I could do something like this:
aasm_initial_state :initial
tasks.each do |task|
aasm_state task.name.to_sym
end
previous_state = nil
tasks.each do |tasks|
aasm_event task.name.to_sym do
transitions :to => "#{task.name}_phase".to_sym, :from => previous_state ? "#{task.name}_phase" : "initial"
end
previous_state = state
end
However, I can't do that with the aasm gem because those methods (aasm_state and aasm_event) are class methods, so every instance of the class with that state machine has the same states. I want it so a "WorkItem" or "TaskList" dynmically creates a sequence of states and transitions based on the tasks it has.
This would allow me to dynamically define workflows and just have states map to tasks.
Are state machines ever used like this? It seems that this ruby workflow gem is similar to what I'm describing.
Update: I can see doing something like the following, but it seems sort of hackish:
#implementation_state_machine = Class::new do
include AASM
aasm_initial_state :initial
tasks.each { |state| aasm_state :"#{task.name}"}
# ...
end
... where a property on my model would be implementation_state_machine. I'd have to override method_missing to delegate state-related methods (accepted_phase?) to the implementation anonymous class.

Yeah, that does seem very hacky and quite messy. I wrote a new gem recently that allows you to use dynamic 'to' transitions with a decision setting.
So instead of building your events and transitions dynamically, would be it be possible to map them out first, and use the decide setting to allow the transition decide which new state to enter? You can also wrap your from transition in an array so you wouldn't need to do :from => previous_state ? "#{task.name}_phase" : "initial", you could just do :from => [ :cool_task_phase, :initial ]
I find that setting out your transitions and events out first, allows you to get a greater picture on what your model is doing.
Check it out at http://github.com/ryanza/stateflow
Hopefully you can find some use out of this.

In my implementation state machine is a hash https://github.com/mpapis/state_attr
state_attr :state, {
nil => :first,
:first => [:second, :third],
:second => :last,
:third => nil,
}
you can define as many state attributes as you like
BTW: in the background there is still a class but only as a proxy to attribute

Related

state_machine using after_failure with ActiveRecord transactions

I'm using ruby state_machine to manage processing documents. As the document progresses the SM states manage the extra validations needed.
If any step fails, such as the document being the wrong dimensions, I want to record this so I can inform a user who is waiting for the conversion to complete.
My plan was to use SM after_failure to fill in a failed_at timestamp in the model. The polling user interface would see the processing has failed and display an appropriate message.
My issue is that if a transition fails it rolls back the transaction including the after_failure method. This means the user never finds out that the processing failed and unfortunately sits in front of their screen indefinitely.
In the example below I want the :fail_transaction method to run despite the transition failing. I'm open to any ideas or alternate ways to notify the user of failure.
Notes:
The :use_transaction => false option for state machine doesn't seem
to make a difference.
I considered putting the fail conversion into a worker queue to escape the transaction but as I'm using delayed_job so queue entries are also wound back.
This project is shamefully still on Rails 2.3.x & ruby 1.8.x
Example code:
state_machine :state, :initial => :new do
state :new
state :original_uploaded do
validates_presence_of :original_file
end
state :original_converted do
validates_presence_of :pdf
end
state :conversion_complete do
validates :pdf_dimensions_correct
end
event :process do
transition :new => :original_uploaded
transition :original_uploaded => :original_converted
transition :original_converted => :conversion_complete
end
after_transition :on => :process, :do => :process
before_transition any => :original_converted, :do => :convert_original_to_pdf
before_transition any => :conversion_complete, :do => :extract_pdf_metadata
after_failure :on => :process, :do => :fail_conversion
end
def fail_conversion
update_attribute(:failed_at, Time.now) # Should update dispite being invalid state
end
def convert_original_to_pdf
#...
end
def extract_pdf_metadata
#...
end
def pdf_dimensions_correct
errors.add(...)
end
Thanks in advance!

How can I cause the state_machine block to use the default accessor instead of my custom accessor?

I'm implementing pluginaweek's state_machine gem. I narrowed the code down to just this so I can understand the problem more easily. Let's assume I only have one status for now:
class Event < ActiveRecord::Base
state_machine :status, :initial => :active_offer do
end
def status
'Active Offer'
end
end
The error I'm getting when creating new Event objects either via seeding or via the browser is {:status=>["is invalid"]}.
The plan is to include a condition for all the different statuses and return a custom string to the view. The entire project's views currently use the .status syntax so I'm trying to smoothly install this. I started this solution based on reading the api docs:
http://api.rubyonrails.org/classes/ActiveRecord/Base.html#label-Overwriting+default+accessors
This is my main goal:
def status
if read_attribute(:status) == 'active_offer' && self.start_date > Time.now
'Active Offer'
elsif read_attribute(:status) == 'active_offer' && self.start_date < Time.now
'Expired'
else read_attribute(:status) == 'cancelled'
'Cancelled'
end
end
What can I do to make the state_machine block use the normal accessor so it gets the database value?
THE SILVER BULLET SOLUTION:
My main problem was the accessor override status. When state_machine code was running, it was reading the current status through my accessor override and therefore getting a custom string returned, which was an invalid status. I had to make state_machine use :state. I didn't do this originally because I already had a :state attr for provinces and such, so I migrated that to :address_state.
Pretty sure you just need to change the definition's name:
class Event < ActiveRecord::Base
state_machine :status, :initial => :active_offer do
end
def active_offer
'Active Offer'
end
end
EDIT:
If I understand you correctly, which I'm not certain I do, this will work:
state_machine :status, initial: :active_offer do
event :expire do
transition all => :expired
end
event :cancel do
transition all => :cancelled
end
end
Then you can do your if... statements, etc., in the controller or something, and transition them with something like #event.expire or if #event.expired. If you want it automated you'll need something like whenever.

Ruby add dynamic events using AASM

I've got a class in a program which is handling game states. I'm actually handling it with AASM so to create an event I have to use something like aasm_event :name ... inside the class.
I need to be able to load other files who dynamically have to add events and states to the class.
How is it possible ?
Thank you in advance.
The following should work, unless aasm_state and aasm_event are protected or private:
# game.rb
class Game
include AASM
aasm_initial_state :start
end
# add the require after the class definition, else it will complain of a missing constant
require "other_file"
# other_file.rb
Game.aasm_state :another_state
Game.aasm_event do
transitions :to => :another_state, :from => [:start]
end

DataMapper filter records by association count

With the following model, I'm looking for an efficient and straightforward way to return all of the Tasks that have 0 parent tasks (the top-level tasks, essentially). I'll eventually want to return things like 0 child tasks as well, so a general solution would be great. Is this possible using existing DataMapper functionality, or will I need to define a method to filter the results manually?
class Task
include DataMapper::Resource
property :id, Serial
property :name , String, :required => true
#Any link of type parent where this task is the target, represents a parent of this task
has n, :links_to_parents, 'Task::Link', :child_key => [ :target_id ], :type => 'Parent'
#Any link of type parent where this task is the source, represents a child of this task
has n, :links_to_children, 'Task::Link', :child_key => [ :source_id ], :type => 'Parent'
has n, :parents, self,
:through => :links_to_parents,
:via => :source
has n, :children, self,
:through => :links_to_children,
:via => :target
def add_parent(parent)
parents.concat(Array(parent))
save
self
end
def add_child(child)
children.concat(Array(child))
save
self
end
class Link
include DataMapper::Resource
storage_names[:default] = 'task_links'
belongs_to :source, 'Task', :key => true
belongs_to :target, 'Task', :key => true
property :type, String
end
end
I would like to be able to define a shared method on the Task class like:
def self.without_parents
#Code to return collection here
end
Thanks!
DataMapper falls down in these scenarios, since effectively what you're looking for is the LEFT JOIN query where everything on the right is NULL.
SELECT tasks.* FROM tasks LEFT JOIN parents_tasks ON parents_tasks.task_id = task.id WHERE parents_tasks.task_id IS NULL
You parents/children situation makes no different here, since they are both n:n mappings.
The most efficient you'll get with DataMapper alone (at least in version 1.x) is:
Task.all(:parents => nil)
Which will execute two queries. The first being a relatively simple SELECT from the n:n pivot table (WHERE task_id NOT NULL), and the second being a gigantic NOT IN for all of the id's returned in the first query... which is ultimately not what you're looking for.
I think you're going to have to write the SQL yourself unfortunately ;)
EDIT | https://github.com/datamapper/dm-ar-finders and it's find_by_sql method may be of interest. If field name abstraction is important to you, you can reference things like Model.storage_name and Model.some_property.field in your SQL.

When is a Ruby class not that Ruby class?

I have this code in my controller for a Rails app:
def delete
object = model.datamapper_class.first(:sourced_id => params[:sourced_id])
if object.blank?
render :xml => "No #{resource} with sourced_id #{params[:sourced_id]}", :status => :not_found and return
end
object.destroy
render :xml => "", :status => :no_content
rescue MysqlError => e
puts "raised MysqlError #{e.message}"
render :xml => e.message, :status => :unprocessable_entity and return
rescue Mysql::Error => e
puts "raised Mysql::Error #{e.message}"
render :xml => e.message, :status => :unprocessable_entity and return
rescue Exception => e
puts "not a MysqlError, instead it was a #{e.class.name}"
render :xml => e.message, :status => :unprocessable_entity and return
end
When I run my spec to make sure my foreign key constraints work, I get this:
not a MysqlError, instead it was a MysqlError
What could be going on here?
Some ancestor information: When I change the rescue to give me this:
puts MysqlError.ancestors
puts "****"
puts Mysql::Error.ancestors
puts "****"
puts e.class.ancestors
This is what I get:
Mysql::Error
StandardError
Exception
ActiveSupport::Dependencies::Blamable ...
****
Mysql::Error
StandardError
Exception
ActiveSupport::Dependencies::Blamable ...
****
MysqlError
StandardError
Exception
ActiveSupport::Dependencies::Blamable ...
Could there be an alias in the global namespace that makes the MysqlError class unreachable?
Ruby classes are just objects, so comparison is based on object identity (ie, the same pointer under the hood).
Not sure what's happening in your case, but I'd try debugging in a few locations and seeing what object ids and ancestors you get for MysqlError. I suspect that there's two such objects in different modules and your catch clause is referencing the wrong one.
Edit:
That is quite strange. My guess now is that MysqlError or one of it's ancestors has been included at two different points along your controllers own class chain, and that's somehow tripping up the exception catching.
Theory #2 would be that since rails redefines const_missing to do auto-requires, where you'd expect to get an UndefinedConstant exception in the exception handling clauses is instead finding something by that name god knows where in the source tree. You should be able to see if that's the case by testing with the auto requiring off (ie do some debugs in both dev and prod mode).
There is a syntax for forcing your reference to start from the root which may be of some help if you can figure out the right one to be referencing:
::Foo::Bar
Rant:
This sort of thing is where I think some of the flaws of ruby show. Under the hood Ruby's object model and scoping is all object structures pointing to each other, in a way that's quite similar to javascript or other prototype based languages. But this is surfaced inconsistently in the class/module syntax you use in the language. It seems like with some careful refactoring you could make this stuff clearer as well as simplify the language, though this would of course be highly incompatible with existing code.
Tip:
When using puts for debugging, try doing puts foo.inspect as this will display it in the way you're used to from irb.
This was a simple class redefinition bug. Ruby lets you redefine a top-level constant, but it doesn't destroy the original constant when you do it. Objects that still hold references to that constant can still use it, so it could still be used to generate exceptions, like in the issue I was having.
Since my redefinition was happening in dependencies, I solved this by searching for the original class in the Object space, and hanging on to a reference to it to use when catching exceptions. I added this line to my controller:
ObjectSpace.each_object(Class){|k| ##mysql_error = k if k.name == 'MysqlError'}
That gets a reference to the original version of MysqlError. Then I was able to do this:
rescue ##mysql_error => e
render :xml => e.message, :status => :unprocessable_entity and return
This happens because the mysql gem is getting loaded after MysqlError has already been defined. Here is some test console joy:
Loading test environment (Rails 2.3.2)
>> MysqlError.object_id
=> 58446850
>> require 'mysql'
C:/Ruby/lib/ruby/gems/1.8/gems/mysql-2.7.3-x86-mswin32/ext/mysql.so: warning: already initialized constant MysqlError
=> true
>> MysqlError.object_id
=> 58886080
>> ObjectSpace._id2ref(MysqlError.object_id)
=> Mysql::Error
You can do this in IRB without a require pretty easily; here's a trick that works because irb doesn't look up Hash by name every time you declare a Hash literal:
irb(main):001:0> Hash = Class.new
(irb):1: warning: already initialized constant Hash
=> Hash
irb(main):002:0> hash = {:test => true}
=> {:test=>true}
irb(main):003:0> hash.class
=> Hash
irb(main):004:0> hash.is_a? Hash
=> false
I can see why you might want to do this, it could be used like alias_method_chain for the global namespace. You could add a mutex to a class that isn't threadsafe, for example, and not need to change old code to reference your threadsafe version. But I do wish RSpec hadn't silenced that warning.

Resources