volute
Vos luttes partent en fumée
Vers des flûtes enchantées Et de cruelles espérances
Me lancent Des dagues et des lances En toute innocenceJ’cloue des clous sur des nuages Un marteau au fond du garage
J’cloue des clous sur des nuages Sans échafaudage
volutes Alain Bashung (funny auto-translation)
This started out in a notebook. I wanted to do something about ‘state’ and ‘lifecyle’, something like a state machine for multiple objects or a rule system for families of resources.
It ended up as something that feels like the subset of an aspect oriented framework. Subset because in its most vanilla usage it only cares about calls to set methods, and there is no “before” (at first glance).
This Ruby gem is named volute. Here is an example of its usage :
require 'volute'
class Package
include Volute
attr_accessor :location
attr_accessor :delivered
end
volute Package do
# filters non Package instance out
volute :delivered => true do
# applies when delivered switches to true
object.emit_invoice
end
volute :location do
# filters out changes that are not for the :location attribute
volute 'SFO' => :any, 'FRA' => :any do
# SFO and FRA are our international hubs, for any package transiting from there,
# initial 'international' tracking
TrackingSystem.track_international(object)
end
end
end
The “include Volute” reworks the attr_accessor, and triggers the evaluation of the volutes on each set.
It’s not always necessary to include Volute. Here is a example of [business] rules derived from Ruleby (volute is very dumb compared to Ruleby, the example doesn’t do justice to that project).
Here is the diagnosis example with volute :
require 'volute'
class Patient
attr_reader :name
attr_accessor :fever
attr_accessor :rash
attr_accessor :spots
attr_accessor :sore_throat
attr_accessor :innoculated
attr_accessor :diagnosis
def initialize(name)
@name = name
@symptoms = {}
@innoculated = false
end
def diagnose!
Volute.apply(self) # trigger evalution of volutes
return @diagnosis
end
end
volute Patient do
volute :fever => :high, :spots => :true, :innoculated => true do
object.diagnosis = 'measles'
over # prevent further evals
end
volute :spots => true do
object.diagnosis = 'allergy (spots but no measles)'
end
volute :rash => true do
object.diagnosis = 'allergy (rash)'
end
volute :sore_throat => true, :fever => :mild do
object.diagnosis = 'flu'
end
volute :sore_throat => true, :fever => :high do
object.diagnosis = 'flu'
end
end
pat = Patient.new('alice')
pat.rash = true
puts "#{pat.name} : diagnosed with #{pat.diagnose!}"
pat = Patient.new('bob')
pat.sore_throat = true
pat.fever = :high
puts "#{pat.name} : diagnosed with #{pat.diagnose!}"
The readme for volute is quite extensive. There are a few examples included.
You’ve already seen the diagnosis one, it aimed at using volute for some mini rule engine (nothing fancy at all).
There is an example about a simple (kph = km/h) equation where each time an attribute is changed, other attributes get adjusted.
The light example simply keeps tracks of the last time an attribute got modified.
There is a book in a bookshop state machine example with a variation, to explore state volutes and transition volutes.
Note that state machines built with volute are incomplete, they know nothing about further transitions (could I even call them state machines ? Rather a ‘state transition system’).
Traffic could be fun : it tracks the state of two “lines” and adjust the colour of their lights according to the number of cars waiting. It’s more in line with the “multiple objects” goal I had initially (still here somehow, but examples with 1 class are smaller).
Not sure if it’s worth moving logic out of objects, but it is a fun experiment.
http://github.com/jmettraux/volute