Ce package contient Aasm, une bibliothèque pour ajouter des machines d'état finies aux classes Ruby.
Aasm a commencé comme le plugin ACTS_AS_STATE_MACHINE mais a évolué en une bibliothèque plus générique qui ne cible plus uniquement les modèles ActiveRecord. Il fournit actuellement des adaptateurs pour de nombreux ORM, mais il peut être utilisé pour n'importe quelle classe Ruby, quelle que soit la classe parentale qu'elle a (le cas échéant).
Jetez un œil à la lecture_from_version_3_to_4 pour que les détails passent de la version 3.x à 4.0 d' Aasm .
L'ajout d'une machine d'état est aussi simple que d'inclure le module AASM et de commencer à définir les états et les événements avec leurs transitions :
class Job
include AASM
aasm do
state :sleeping , initial : true
state :running , :cleaning
event :run do
transitions from : :sleeping , to : :running
end
event :clean do
transitions from : :running , to : :cleaning
end
event :sleep do
transitions from : [ :running , :cleaning ] , to : :sleeping
end
end
end Cela vous fournit quelques méthodes publiques pour les instances du Job de classe:
job = Job . new
job . sleeping? # => true
job . may_run? # => true
job . run
job . running? # => true
job . sleeping? # => false
job . may_run? # => false
job . run # => raises AASM::InvalidTransition Si vous n'aimez pas les exceptions et que vous préférez un simple true ou false comme réponse, dites à Aasm de ne pas être pleurnichard :
class Job
...
aasm whiny_transitions : false do
...
end
end
job . running? # => true
job . may_run? # => false
job . run # => falseLors du licenciement d'un événement, vous pouvez passer un bloc à la méthode, il ne sera appelé que si la transition réussit:
job . run do
job . user . notify_job_ran # Will be called if job.may_run? is true
endVous pouvez définir un certain nombre de rappels pour vos événements, transitions et états. Ces méthodes, procs ou classes seront appelés lorsque certains critères seront remplis, comme entrer dans un état particulier:
class Job
include AASM
aasm do
state :sleeping , initial : true , before_enter : :do_something
state :running , before_enter : Proc . new { do_something && notify_somebody }
state :finished
after_all_transitions :log_status_change
event :run , after : :notify_somebody do
before do
log ( 'Preparing to run' )
end
transitions from : :sleeping , to : :running , after : Proc . new { |* args | set_process ( * args ) }
transitions from : :running , to : :finished , after : LogRunTime
end
event :sleep do
after do
...
end
error do | e |
...
end
transitions from : :running , to : :sleeping
end
end
def log_status_change
puts "changing from #{ aasm . from_state } to #{ aasm . to_state } (event: #{ aasm . current_event } )"
end
def set_process ( name )
...
end
def do_something
...
end
def notify_somebody
...
end
end
class LogRunTime
def call
log "Job was running for X seconds"
end
end Dans ce cas, do_something est appelé avant d'entrer dans l'état sleeping , tandis que notify_somebody est appelé après la fin de la run (du sleeping à running ).
Aasm initialisera également LogRunTime et exécutera la méthode call pour vous après la transition de running à finished dans l'exemple ci-dessus. Vous pouvez transmettre des arguments à la classe en définissant une méthode initialisée dessus, comme ceci:
Notez que les Procs sont exécutés dans le contexte d'un enregistrement, cela signifie que vous n'avez pas besoin de vous attendre à l'enregistrement en tant qu'argument, il suffit d'appeler les méthodes dont vous avez besoin.
class LogRunTime
# optional args parameter can be omitted, but if you define initialize
# you must accept the model instance as the first parameter to it.
def initialize ( job , args = { } )
@job = job
end
def call
log "Job was running for #{ @job . run_time } seconds"
end
end Vous pouvez transmettre des paramètres aux événements:
job = Job . new
job . run ( :defragmentation ) Tous les gardes et après les rappels recevront ces paramètres. Dans ce cas, set_process serait appelé avec :defragmentation .
Si le premier argument de l'événement est un état (par exemple :running ou :finished ), le premier argument est consommé et la machine d'état tentera de passer à cet état. Ajouter un paramètre séparé de virgule pour les gardes et les rappels
job = Job . new
job . run ( :running , :defragmentation ) Dans ce cas, set_process ne sera pas appelé, Job passera à l'état de course et le rappel recevra :defragmentation en tant que paramètre
En cas d'erreur pendant le traitement de l'événement, l'erreur est sauvée et transmise à :error , qui peut le gérer ou le réénerger pour une propagation supplémentaire.
En outre, vous pouvez définir une méthode qui sera appelée en cas d'échec de l'événement:
def aasm_event_failed ( event_name , old_state_name )
# use custom exception/messages, report metrics, etc
end Pendant la transition :after le rappel (et de manière fiable seulement, ou dans le rappel mondial after_all_transitions ), vous pouvez accéder à l'état d'origine (l'état) et à l'état cible (à l'état), comme ceci:
def set_process ( name )
logger . info "from #{ aasm . from_state } to #{ aasm . to_state } "
end Ici, vous pouvez voir une liste de tous les rappels possibles, ainsi que leur ordre d'appel:
begin
event before_all_events
event before
event guards
transition guards
old_state before_exit
old_state exit
after_all_transitions
transition after
new_state before_enter
new_state enter
... update state ...
event before_success # if persist successful
transition success # if persist successful, database update not guaranteed
event success # if persist successful, database update not guaranteed
old_state after_exit
new_state after_enter
event after
event after_all_events
rescue
event error
event error_on_all_events
ensure
event ensure
event ensure_on_all_events
end Utilisez le rappel after_commit de l'événement s'il doit être licencié après la mise à jour de la base de données.
Lors de l'exécution des rappels, vous pouvez facilement récupérer le nom de l'événement déclenché à l'aide aasm.current_event :
# taken the example callback from above
def do_something
puts "triggered #{ aasm . current_event } "
endet puis
job = Job . new
# without bang
job . sleep # => triggered :sleep
# with bang
job . sleep! # => triggered :sleep! Supposons que vous souhaitez autoriser des transitions particulières uniquement si une condition définie est donnée. Pour cela, vous pouvez configurer un garde par transition, qui fonctionnera avant d'exécuter la transition. Si le garde revient false la transition sera refusée (augmenter AASM::InvalidTransition ):
class Cleaner
include AASM
aasm do
state :idle , initial : true
state :cleaning
event :clean do
transitions from : :idle , to : :cleaning , guard : :cleaning_needed?
end
event :clean_if_needed do
transitions from : :idle , to : :cleaning do
guard do
cleaning_needed?
end
end
transitions from : :idle , to : :idle
end
event :clean_if_dirty do
transitions from : :idle , to : :cleaning , guard : :if_dirty?
end
end
def cleaning_needed?
false
end
def if_dirty? ( status )
status == :dirty
end
end
job = Cleaner . new
job . may_clean? # => false
job . clean # => raises AASM::InvalidTransition
job . may_clean_if_needed? # => true
job . clean_if_needed! # idle
job . clean_if_dirty ( :clean ) # => raises AASM::InvalidTransition
job . clean_if_dirty ( :dirty ) # => trueVous pouvez même fournir un certain nombre de gardes, qui doivent tous réussir à continuer
def walked_the_dog? ; ... ; end
event :sleep do
transitions from : :running , to : :sleeping , guards : [ :cleaning_needed? , :walked_the_dog? ]
endSi vous souhaitez fournir des gardes pour toutes les transitions dans un événement, vous pouvez utiliser des gardes d'événements
event :sleep , guards : [ :walked_the_dog? ] do
transitions from : :running , to : :sleeping , guards : [ :cleaning_needed? ]
transitions from : :cleaning , to : :sleeping
end Si vous préférez une syntaxe de garde plus rubis, vous pouvez utiliser if et unless que:
event :clean do
transitions from : :running , to : :cleaning , if : :cleaning_needed?
end
event :sleep do
transitions from : :running , to : :sleeping , unless : :cleaning_needed?
end
end Vous pouvez invoquer une classe au lieu d'une méthode si la classe répond à call
event :sleep do
transitions from : :running , to : :sleeping , guards : Dog
end class Dog
def call
cleaning_needed? && walked?
end
...
endEn cas de transitions multiples pour un événement, la première transition qui a réussi à terminer empêchera d'autres transitions dans le même événement d'être traitées.
require 'aasm'
class Job
include AASM
aasm do
state :stage1 , initial : true
state :stage2
state :stage3
state :completed
event :stage1_completed do
transitions from : :stage1 , to : :stage3 , guard : :stage2_completed?
transitions from : :stage1 , to : :stage2
end
end
def stage2_completed?
true
end
end
job = Job . new
job . stage1_completed
job . aasm . current_state # stage3 Vous pouvez définir la transition de tout état défini en omettant from :
event :abort do
transitions to : :aborted
endVous pouvez définir le nom d'affichage pour l'état en utilisant: l'option d'affichage
class Job
include AASM
aasm do
state :stage1 , initial : true , display : 'First Stage'
state :stage2
state :stage3
end
end
job = Job . new
job . aasm . human_state Plusieurs machines d'État par classe sont prises en charge. Sachez cependant qu'Aasm a été construit avec une machine d'état par classe à l'esprit. Néanmoins, voici comment le faire (voir ci-dessous). Veuillez noter que vous devrez spécifier des colonnes de base de données pour savoir où vos états pertinents seront stockés - nous avons spécifié deux colonnes move_state et work_state dans l'exemple ci-dessous. Voir le nom de colonne et la section Migration pour plus d'informations.
class SimpleMultipleExample
include AASM
aasm ( :move , column : 'move_state' ) do
state :standing , initial : true
state :walking
state :running
event :walk do
transitions from : :standing , to : :walking
end
event :run do
transitions from : [ :standing , :walking ] , to : :running
end
event :hold do
transitions from : [ :walking , :running ] , to : :standing
end
end
aasm ( :work , column : 'work_state' ) do
state :sleeping , initial : true
state :processing
event :start do
transitions from : :sleeping , to : :processing
end
event :stop do
transitions from : :processing , to : :sleeping
end
end
end
simple = SimpleMultipleExample . new
simple . aasm ( :move ) . current_state
# => :standing
simple . aasm ( :work ) . current_state
# => :sleeping
simple . start
simple . aasm ( :move ) . current_state
# => :standing
simple . aasm ( :work ) . current_state
# => :processing L'AASM n'interdit pas de définir le même événement dans plus d'une machine d'état. Si aucun espace de noms n'est fourni, la dernière définition "gagne" et remplace les définitions précédentes. Néanmoins, un avertissement est émis: SimpleMultipleExample: overriding method 'run'! .
Alternativement, vous pouvez fournir un espace de noms pour chaque machine d'état:
class NamespacedMultipleExample
include AASM
aasm ( :status ) do
state :unapproved , initial : true
state :approved
event :approve do
transitions from : :unapproved , to : :approved
end
event :unapprove do
transitions from : :approved , to : :unapproved
end
end
aasm ( :review_status , namespace : :review ) do
state :unapproved , initial : true
state :approved
event :approve do
transitions from : :unapproved , to : :approved
end
event :unapprove do
transitions from : :approved , to : :unapproved
end
end
end
namespaced = NamespacedMultipleExample . new
namespaced . aasm ( :status ) . current_state
# => :unapproved
namespaced . aasm ( :review_status ) . current_state
# => :unapproved
namespaced . approve_review
namespaced . aasm ( :review_status ) . current_state
# => :approved Toutes les méthodes aasm AASM au niveau de la classe et de l'instance acceptent un sélecteur de machine d'état. Ainsi, par exemple, pour utiliser l'inspection au niveau de la classe, vous devez utiliser
SimpleMultipleExample . aasm ( :move ) . states . map ( & :name )
# => [:standing, :walking, :running]Permettre à un événement d'être lié à un autre
class Example
include AASM
aasm ( :work ) do
state :sleeping , initial : true
state :processing
event :start do
transitions from : :sleeping , to : :processing
end
event :stop do
transitions from : :processing , to : :sleeping
end
end
aasm ( :question ) do
state :answered , initial : true
state :asked
event :ask , binding_event : :start do
transitions from : :answered , to : :asked
end
event :answer , binding_event : :stop do
transitions from : :asked , to : :answered
end
end
end
example = Example . new
example . aasm ( :work ) . current_state #=> :sleeping
example . aasm ( :question ) . current_state #=> :answered
example . ask
example . aasm ( :work ) . current_state #=> :processing
example . aasm ( :question ) . current_state #=> :askedAASM génère automatiquement des constantes pour chaque statut afin que vous n'ayez pas à les définir explicitement.
class Foo
include AASM
aasm do
state :initialized
state :calculated
state :finalized
end
end
> Foo :: STATE_INITIALIZED
#=> :initialized
> Foo :: STATE_CALCULATED
#=> :calculated AASM vous permet d'étendre facilement AASM::Base à vos propres fins d'application.
Supposons que nous ayons une logique commune sur de nombreux modèles AASM. Nous pouvons incarner cette logique dans une sous-classe d' AASM::Base .
class CustomAASMBase < AASM :: Base
# A custom transition that we want available across many AASM models.
def count_transitions!
klass . class_eval do
aasm with_klass : CustomAASMBase do
after_all_transitions :increment_transition_count
end
end
end
# A custom annotation that we want available across many AASM models.
def requires_guards!
klass . class_eval do
attr_reader :authorizable_called ,
:transition_count ,
:fillable_called
def authorizable?
@authorizable_called = true
end
def fillable?
@fillable_called = true
end
def increment_transition_count
@transition_count ||= 0
@transition_count += 1
end
end
end
end Lorsque nous déclarons notre modèle qui a une machine à états AASM, nous déclarons simplement le bloc Aasm avec une clé :with_klass de notre propre classe.
class SimpleCustomExample
include AASM
# Let's build an AASM state machine with our custom class.
aasm with_klass : CustomAASMBase do
requires_guards!
count_transitions!
state :initialised , initial : true
state :filled_out
state :authorised
event :fill_out do
transitions from : :initialised , to : :filled_out , guard : :fillable?
end
event :authorise do
transitions from : :filled_out , to : :authorised , guard : :authorizable?
end
end
endAASM est livré avec le support pour ActiveRecord et permet la persistance automatique de l'état de l'objet dans la base de données.
Ajoutez gem 'after_commit_everywhere', '~> 1.0' à votre gemfile.
class Job < ActiveRecord :: Base
include AASM
aasm do # default column: aasm_state
state :sleeping , initial : true
state :running
event :run do
transitions from : :sleeping , to : :running
end
event :sleep do
transitions from : :running , to : :sleeping
end
end
endVous pouvez dire à Aasm de savourer automatiquement l'objet ou de le laisser non enregistré
job = Job . new
job . run # not saved
job . run! # saved
# or
job . aasm . fire ( :run ) # not saved
job . aasm . fire! ( :run ) # saved L'épargne comprend l'exécution de toutes les validations sur la classe Job . Si le drapeau whiny_persistence est défini sur true , une exception est élevée en cas d'échec. Si le drapeau whiny_persistence est défini sur false , Méthodes avec un retour BAND true si la transition d'état est réussie ou false si une erreur se produit.
Si vous voulez vous assurer que l'état est enregistré sans exécuter de validations (et peut-être peut-être en persiste un état d'objet non valide), dites simplement à Aasm de sauter les validations. Sachez que lors du saut de validations, seule la colonne d'état sera mise à jour dans la base de données (tout comme ActiveRecord update_column fonctionne).
class Job < ActiveRecord :: Base
include AASM
aasm skip_validation_on_save : true do
state :sleeping , initial : true
state :running
event :run do
transitions from : :sleeping , to : :running
end
event :sleep do
transitions from : :running , to : :sleeping
end
end
end En outre, vous pouvez ignorer la validation au niveau de l'instance avec some_event_name_without_validation! méthode. Avec cela, vous avez la flexibilité d'avoir une validation pour toutes vos transitions par défaut, puis de le sauter partout où il est nécessaire. Veuillez noter que seule la colonne d'état sera mise à jour comme mentionné dans l'exemple ci-dessus.
job . run_without_validation!Si vous souhaitez vous assurer que la colonne AASM pour le stockage de l'état n'est pas directement attribuée, configurez AASM pour ne pas autoriser une affectation directe, comme ceci:
class Job < ActiveRecord :: Base
include AASM
aasm no_direct_assignment : true do
state :sleeping , initial : true
state :running
event :run do
transitions from : :sleeping , to : :running
end
end
endrésultant en ceci:
job = Job . create
job . aasm_state # => 'sleeping'
job . aasm_state = :running # => raises AASM::NoDirectAssignmentError
job . aasm_state # => 'sleeping' Vous pouvez dire à Aasm d'essayer d'écrire un horodatage chaque fois qu'un nouvel état est entré. Si timestamps: true est défini, Aasm recherchera un champ nommé comme le nouvel état plus _at et essaiera de le remplir:
class Job < ActiveRecord :: Base
include AASM
aasm timestamps : true do
state :sleeping , initial : true
state :running
event :run do
transitions from : :sleeping , to : :running
end
end
endrésultant en ceci:
job = Job . create
job . running_at # => nil
job . run!
job . running_at # => 2020-02-20 20:00:00Les champs d'horodatage manquants sont ignorés silencieusement, il n'est donc pas nécessaire d'avoir des setters (tels que les colonnes ActiveRecord) pour tous les états lors de l'utilisation de cette option.
Vous pouvez utiliser des énumérations dans Rails 4.1+ pour votre colonne d'état:
class Job < ActiveRecord :: Base
include AASM
enum state : {
sleeping : 5 ,
running : 99
}
aasm column : :state , enum : true do
state :sleeping , initial : true
state :running
end
end Vous pouvez transmettre explicitement le nom de la méthode qui donne accès au mappage d'énumération en tant que valeur de enum , ou vous pouvez simplement le définir sur true . Dans ce dernier cas, Aasm essaiera d'utiliser le nom de la colonne pluralisée pour accéder aux états éventuels de l'énumération.
De plus, si votre colonne a un type entier (ce qui est normalement le cas lorsque vous travaillez avec des énumérations Rails), vous pouvez omettre :enum --- Aasm-détecte automatiquement cette situation et le support énuméré activé. Si quelque chose ne va pas, vous pouvez désactiver la fonctionnalité d'énumération et retomber au comportement par défaut en définissant :enum à false .
AASM soutient également la suite en plus activeRecord et Mongoid .
class Job < Sequel :: Model
include AASM
aasm do # default column: aasm_state
...
end
endCependant, ce n'est pas encore aussi complet que ActiveRecord . Par exemple, il y a encore des portées définies. Voir les portées automatiques.
Étant donné que la version 4.8.0 AASM prend également en charge la dynamoïde en tant que Persistance Orm.
Aasm soutient également la persistance de MongoDB si vous utilisez mongoïde. Assurez-vous d'inclure Mongoid :: Document avant d'inclure AASM.
class Job
include Mongoid :: Document
include AASM
field :aasm_state
aasm do
...
end
endAASM prend également en charge la persistance de repenser si vous utilisez le nobrainer. Assurez-vous d'inclure le document NoBrainer :: avant d'inclure AASM.
class Job
include NoBrainer :: Document
include AASM
field :aasm_state
aasm do
...
end
endAasm prend également en charge la persistance dans Redis via redis :: objets. Assurez-vous d'inclure Redis :: objets avant d'inclure AASM. Notez que les événements non-bang fonctionneront comme des événements Bang, persistant les changements à chaque appel.
class User
include Redis :: Objects
include AASM
aasm do
end
endAASM créera automatiquement des méthodes de portée pour chaque état du modèle.
class Job < ActiveRecord :: Base
include AASM
aasm do
state :sleeping , initial : true
state :running
state :cleaning
end
def self . sleeping
"This method name is already in use"
end
end class JobsController < ApplicationController
def index
@running_jobs = Job . running
@recent_cleaning_jobs = Job . cleaning . where ( 'created_at >= ?' , 3 . days . ago )
# @sleeping_jobs = Job.sleeping #=> "This method name is already in use"
end
end Si vous n'avez pas besoin de lunettes (ou si vous n'en voulez tout simplement pas), désactivez leur création lorsque vous définissez les états AASM , comme ceci:
class Job < ActiveRecord :: Base
include AASM
aasm create_scopes : false do
state :sleeping , initial : true
state :running
state :cleaning
end
endPuisque la version 3.0.13 AASM prend en charge les transactions ActiveRecord. Ainsi, chaque fois qu'un rappel de transition ou la mise à jour de l'état échoue, toutes les modifications de tout enregistrement de base de données sont annulées. MongoDB ne prend pas en charge les transactions.
Il existe actuellement 3 rappels transactionnels qui peuvent être gérés sur l'événement et 2 rappels transactionnels pour tous les événements.
event before_all_transactions
event before_transaction
event aasm_fire_event ( within transaction )
event after_commit ( if event successful )
event after_transaction
event after_all_transactions Si vous souhaitez vous assurer qu'une action en fonction ne se produit qu'après engager la transaction, utilisez le rappel after_commit avec les méthodes de sauvegarde automatique (Bang), comme ceci:
class Job < ActiveRecord :: Base
include AASM
aasm do
state :sleeping , initial : true
state :running
event :run , after_commit : :notify_about_running_job do
transitions from : :sleeping , to : :running
end
end
def notify_about_running_job
...
end
end
job = Job . where ( state : 'sleeping' ) . first!
job . run! # Saves the model and triggers the after_commit callback Notez que ce qui suit n'exécutera pas les rappels after_commit car la méthode de sauvegarde automatique n'est pas utilisée:
job = Job . where ( state : 'sleeping' ) . first!
job . run
job . save! #notify_about_running_job is not run Veuillez noter que :after_commit Aasm Callbacks se comporte autour de l'implémentation personnalisée du modèle de transaction plutôt qu'une transaction DB réelle. Ce fait provoque toujours les conditions de course et les appels de rappel redondants dans la transaction imbriquée. Afin de résoudre ce problème, il est fortement recommandé d'ajouter gem 'after_commit_everywhere', '~> 1.0' à votre Gemfile .
Si vous souhaitez résumer les changements d'état dans une propre transaction, le comportement de cette transaction imbriquée pourrait être déroutant. Jetez un œil aux transactions imbriquées activeRecord si vous voulez en savoir plus à ce sujet. Néanmoins, AASM nécessite par défaut une nouvelle transaction(requires_new: true) . Vous pouvez remplacer ce comportement en modifiant la configuration
class Job < ActiveRecord :: Base
include AASM
aasm requires_new_transaction : false do
...
end
...
end Ce qui conduit ensuite à transaction(requires_new: false) , les rails par défaut.
De plus, si vous ne souhaitez pas envelopper vos actions ActiveRecord, vous pouvez spécifier l'indicateur use_transactions . Cela peut être utile si vous souhaitez persister des choses dans la base de données qui se produisent à la suite d'une transaction ou d'un rappel, même lorsque une erreur se produit. L'indicateur use_transactions est vrai par défaut.
class Job < ActiveRecord :: Base
include AASM
aasm use_transactions : false do
...
end
...
end AASM prend en charge le verrouillage pessimiste activeRecord via with_lock pour les couches de persistance de la base de données.
| Option | But |
|---|---|
false (par défaut) | Aucun verrouillage n'est obtenu |
true | Obtenez un verrouillage pessimiste bloquant, par exemple FOR UPDATE |
| Chaîne | Obtenez un verrou basé sur la chaîne SQL, par exemple FOR UPDATE NOWAIT |
class Job < ActiveRecord :: Base
include AASM
aasm requires_lock : true do
...
end
...
end class Job < ActiveRecord :: Base
include AASM
aasm requires_lock : 'FOR UPDATE NOWAIT' do
...
end
...
end En tant que par défaut, Aasm utilise la colonne aasm_state pour stocker les états. Vous pouvez remplacer cela en définissant votre nom de colonne préféré, en utilisant :column comme ceci:
class Job < ActiveRecord :: Base
include AASM
aasm column : :my_state do
...
end
aasm :another_state_machine , column : :second_state do
...
end
end Quel que soit le nom de colonne utilisé, assurez-vous d'ajouter une migration pour fournir cette colonne (de type string ). N'ajoutez pas de valeur par défaut pour la colonne au niveau de la base de données. Si vous ajoutez une valeur par défaut dans la base de données, les rappels AASM sur l'état initial ne seront pas licenciés lors de l'instanciation du modèle.
class AddJobState < ActiveRecord :: Migration
def self . up
add_column :jobs , :aasm_state , :string
end
def self . down
remove_column :jobs , :aasm_state
end
endLa modification de l'état de journalisation peut être effectuée à l'aide de la gemme Paper_trail
Un exemple d'implémentation peut être trouvé ici https://github.com/nitsujri/aasm-papertrail-example
AASM prend en charge les méthodes de requête pour les états et les événements
Compte tenu de la classe Job suivante:
class Job
include AASM
aasm do
state :sleeping , initial : true
state :running , :cleaning
event :run do
transitions from : :sleeping , to : :running
end
event :clean do
transitions from : :running , to : :cleaning , guard : :cleaning_needed?
end
event :sleep do
transitions from : [ :running , :cleaning ] , to : :sleeping
end
end
def cleaning_needed?
false
end
end # show all states
Job . aasm . states . map ( & :name )
#=> [:sleeping, :running, :cleaning]
job = Job . new
# show all permitted states (from initial state)
job . aasm . states ( permitted : true ) . map ( & :name )
#=> [:running]
# List all the permitted transitions(event and state pairs) from initial state
job . aasm . permitted_transitions
#=> [{ :event => :run, :state => :running }]
job . run
job . aasm . states ( permitted : true ) . map ( & :name )
#=> [:sleeping]
# show all non permitted states
job . aasm . states ( permitted : false ) . map ( & :name )
#=> [:cleaning]
# show all possible (triggerable) events from the current state
job . aasm . events . map ( & :name )
#=> [:clean, :sleep]
# show all permitted events
job . aasm . events ( permitted : true ) . map ( & :name )
#=> [:sleep]
# show all non permitted events
job . aasm . events ( permitted : false ) . map ( & :name )
#=> [:clean]
# show all possible events except a specific one
job . aasm . events ( reject : :sleep ) . map ( & :name )
#=> [:clean]
# list states for select
Job . aasm . states_for_select
#=> [["Sleeping", "sleeping"], ["Running", "running"], ["Cleaning", "cleaning"]]
# show permitted states with guard parameter
job . aasm . states ( { permitted : true } , guard_parameter ) . map ( & :name ) Les avertissements sont par défaut imprimés à STDERR . Si vous souhaitez enregistrer ces avertissements à une autre sortie, utilisez
class Job
include AASM
aasm logger : Rails . logger do
...
end
end Vous pouvez masquer les avertissements en définissant AASM::Configuration.hide_warnings = true
Prend maintenant en charge CodeDataQuery! Cependant, je suis toujours en train de soumettre mes mises à jour de compatibilité dans leur référentiel. En attendant, vous pouvez utiliser ma fourchette, il peut encore y avoir des problèmes mineurs, mais j'ai l'intention de l'utiliser de manière approfondie moi-même, donc les correctifs devraient venir vite.
Avertissements:
AASM fournit quelques correspondants pour RSpec:
transition_from ,have_state , allow_eventallow_transition_to . require 'aasm/rspec' dans votre fichier spec_helper.rb . # classes with only the default state machine
job = Job . new
expect ( job ) . to transition_from ( :sleeping ) . to ( :running ) . on_event ( :run )
expect ( job ) . not_to transition_from ( :sleeping ) . to ( :cleaning ) . on_event ( :run )
expect ( job ) . to have_state ( :sleeping )
expect ( job ) . not_to have_state ( :running )
expect ( job ) . to allow_event :run
expect ( job ) . to_not allow_event :clean
expect ( job ) . to allow_transition_to ( :running )
expect ( job ) . to_not allow_transition_to ( :cleaning )
# on_event also accept multiple arguments
expect ( job ) . to transition_from ( :sleeping ) . to ( :running ) . on_event ( :run , :defragmentation )
# classes with multiple state machine
multiple = SimpleMultipleExample . new
expect ( multiple ) . to transition_from ( :standing ) . to ( :walking ) . on_event ( :walk ) . on ( :move )
expect ( multiple ) . to_not transition_from ( :standing ) . to ( :running ) . on_event ( :walk ) . on ( :move )
expect ( multiple ) . to have_state ( :standing ) . on ( :move )
expect ( multiple ) . not_to have_state ( :walking ) . on ( :move )
expect ( multiple ) . to allow_event ( :walk ) . on ( :move )
expect ( multiple ) . to_not allow_event ( :hold ) . on ( :move )
expect ( multiple ) . to allow_transition_to ( :walking ) . on ( :move )
expect ( multiple ) . to_not allow_transition_to ( :running ) . on ( :move )
expect ( multiple ) . to transition_from ( :sleeping ) . to ( :processing ) . on_event ( :start ) . on ( :work )
expect ( multiple ) . to_not transition_from ( :sleeping ) . to ( :sleeping ) . on_event ( :start ) . on ( :work )
expect ( multiple ) . to have_state ( :sleeping ) . on ( :work )
expect ( multiple ) . not_to have_state ( :processing ) . on ( :work )
expect ( multiple ) . to allow_event ( :start ) . on ( :move )
expect ( multiple ) . to_not allow_event ( :stop ) . on ( :move )
expect ( multiple ) . to allow_transition_to ( :processing ) . on ( :move )
expect ( multiple ) . to_not allow_transition_to ( :sleeping ) . on ( :move )
# allow_event also accepts arguments
expect ( job ) . to allow_event ( :run ) . with ( :defragmentation ) AASM fournit des affirmations et des attentes de type RSPEC pour Minitest.
Liste des assertions prises en charge: assert_have_state , refute_have_state , assert_transitions_from , refute_transitions_from , assert_event_allowed refute_event_allowed assert_transition_to_allowed , refute_transition_to_allowed .
Ajoutez require 'aasm/minitest' à votre fichier test_helper.rb et utilisez-les comme ceci:
# classes with only the default state machine
job = Job . new
assert_transitions_from job , :sleeping , to : :running , on_event : :run
refute_transitions_from job , :sleeping , to : :cleaning , on_event : :run
assert_have_state job , :sleeping
refute_have_state job , :running
assert_event_allowed job , :run
refute_event_allowed job , :clean
assert_transition_to_allowed job , :running
refute_transition_to_allowed job , :cleaning
# on_event also accept arguments
assert_transitions_from job , :sleeping , :defragmentation , to : :running , on_event : :run
# classes with multiple state machine
multiple = SimpleMultipleExample . new
assert_transitions_from multiple , :standing , to : :walking , on_event : :walk , on : :move
refute_transitions_from multiple , :standing , to : :running , on_event : :walk , on : :move
assert_have_state multiple , :standing , on : :move
refute_have_state multiple , :walking , on : :move
assert_event_allowed multiple , :walk , on : :move
refute_event_allowed multiple , :hold , on : :move
assert_transition_to_allowed multiple , :walking , on : :move
refute_transition_to_allowed multiple , :running , on : :move
assert_transitions_from multiple , :sleeping , to : :processing , on_event : :start , on : :work
refute_transitions_from multiple , :sleeping , to : :sleeping , on_event : :start , on : :work
assert_have_state multiple , :sleeping , on : :work
refute_have_state multiple , :processing , on : :work
assert_event_allowed multiple , :start , on : :move
refute_event_allowed multiple , :stop , on : :move
assert_transition_to_allowed multiple , :processing , on : :move
refute_transition_to_allowed multiple , :sleeping , on : :move Liste des attentes prises en charge: must_transition_from , wont_transition_from , must_have_state , wont_have_state , must_allow_event , wont_allow_event , must_allow_transition_to , wont_allow_transition_to .
Ajouter require 'aasm/minitest_spec' dans votre fichier test_helper.rb et utilisez-les comme ceci:
# classes with only the default state machine
job = Job . new
job . must_transition_from :sleeping , to : :running , on_event : :run
job . wont_transition_from :sleeping , to : :cleaning , on_event : :run
job . must_have_state :sleeping
job . wont_have_state :running
job . must_allow_event :run
job . wont_allow_event :clean
job . must_allow_transition_to :running
job . wont_allow_transition_to :cleaning
# on_event also accept arguments
job . must_transition_from :sleeping , :defragmentation , to : :running , on_event : :run
# classes with multiple state machine
multiple = SimpleMultipleExample . new
multiple . must_transition_from :standing , to : :walking , on_event : :walk , on : :move
multiple . wont_transition_from :standing , to : :running , on_event : :walk , on : :move
multiple . must_have_state :standing , on : :move
multiple . wont_have_state :walking , on : :move
multiple . must_allow_event :walk , on : :move
multiple . wont_allow_event :hold , on : :move
multiple . must_allow_transition_to :walking , on : :move
multiple . wont_allow_transition_to :running , on : :move
multiple . must_transition_from :sleeping , to : :processing , on_event : :start , on : :work
multiple . wont_transition_from :sleeping , to : :sleeping , on_event : :start , on : :work
multiple . must_have_state :sleeping , on : :work
multiple . wont_have_state :processing , on : :work
multiple . must_allow_event :start , on : :move
multiple . wont_allow_event :stop , on : :move
multiple . must_allow_transition_to :processing , on : :move
multiple . wont_allow_transition_to :sleeping , on : :move % gem install aasm # Gemfile
gem 'aasm'% rake build
% sudo gem install pkg/aasm-x.y.z.gemAprès avoir installé AASM, vous pouvez exécuter un générateur:
% rails generate aasm NAME [COLUMN_NAME]Remplacez le nom par le nom du modèle, Column_name est facultatif (par défaut est 'AASM_STATE'). Cela créera un modèle (si l'on n'existe pas) et le configurera avec un bloc AASM. Pour ACTIVERECORD ORM, un fichier de migration est ajouté pour ajouter une colonne d'état AASM à la table.
Exécutez facilement la suite de tests sur Docker
1. docker-compose build aasm
2. docker-compose run --rm aasm
Jetez un œil au Changelog pour plus de détails sur les modifications récentes de la version actuelle.
N'hésitez pas à
aasm )Ce logiciel est fourni "tel quel" et sans aucune garantie expresse ou implicite, y compris, sans limitation, les garanties implicites de marchandibilité et d'adéquation à un usage particulier.
Copyright (C) 2006-2017 Scott Barron
L'autorisation est accordée gratuitement à toute personne qui obtient une copie de ce logiciel et des fichiers de documentation associés (le "logiciel"), pour traiter le logiciel sans restriction, y compris sans limiter les droits d'utilisation, de copie, de modification, de fusion, de publication, de distribution, de sublince et / ou de vendre des copies des conditions suivantes.
L'avis de droit d'auteur ci-dessus et le présent avis d'autorisation sont inclus dans toutes les copies ou des parties substantielles du logiciel.
Le logiciel est fourni "tel quel", sans garantie d'aucune sorte, express ou implicite, y compris, mais sans s'y limiter, les garanties de qualité marchande, d'adéquation à un usage particulier et de non-contrefaçon. En aucun cas, les auteurs ou les titulaires de droits d'auteur ne seront pas responsables de toute réclamation, dommage ou autre responsabilité, que ce soit dans une action de contrat, de délit ou autre, découlant de, ou en relation avec le logiciel ou l'utilisation ou d'autres transactions dans le logiciel.