Reputation: 822
Suppose there is an object with 4 states
:new
:in_process
:done
:verified
There is also a method that should only be executed when the object is in a state greater than :in_process
How do I go about doing this check? I thought it might be something
def some_action
return unless my_object.state > :in_process
#do some work
end
But that just compares the strings.
Am I missing something or is there an actual way of performing a check like this?
Thanks.
Upvotes: 3
Views: 1919
Reputation: 422
If one takes care in defining the correct order in AASM and makes sure not to override any states (to specify extra options, for example), it is possible to use them.
The following mixin defines scopes like Model.done_or_before
and Model.in_process_or_after
, as well as methods like m.done_or_before?
.
module AASMLinearity
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def aasm(*args, &block)
r = super(*args, &block)
if block
states = r.state_machine.states.map(&:name)
column = r.attribute_name
states.each_with_index do |state, i|
scope "#{state}_or_after", ->{ where(column => states[i..-1]) }
scope "#{state}_or_before", ->{ where(column => states[0..i]) }
define_method "#{state}_or_after?", ->{ states[i..-1].include? read_attribute(column).to_sym }
define_method "#{state}_or_before?", ->{ states[0..i].include? read_attribute(column).to_sym }
end
end
r
end
end
end
You can put this in something like app/models/concerns/aasm_linearity.rb
, and include AASMLinearity
after include AASM
, but before the statemachine definition.
Upvotes: 0
Reputation: 3033
Ignoring the issue of non-linear state machines, I've found the following to work well for my needs on a few projects with simple state machines:
# Check if the stage is in or before the supplied stage (stage_to_check).
def in_or_before_stage?(stage_to_check)
if stage_to_check.present? && self.stage.present?
STAGES_IN_ORDER.reverse.lazy.drop_while { |stg| stg != stage_to_check }.include?(self.stage)
else
false
end
end
and the other check is sometimes desired as well:
# Check if the stage is in or after the supplied stage (stage_to_check).
def in_or_after_stage?(stage_to_check)
if stage_to_check.present? && self.stage.present?
# Get all the stages that are in and after the stage we want to check (stage_to_check),
# and then see if the stage is in that list (well, technically in a lazy enumerable).
STAGES_IN_ORDER.lazy.drop_while { |stg| stg != stage_to_check }.include?(self.stage)
else
false
end
end
Where "STAGES_IN_ORDER" is just an array with the stages in order from initial to final.
We're just removing items from the list and then checking if our object's current stage is in our resulting list. If we want to know if it's in or before some stage we remove the later stages until we reach our supplied test stage, if we want to know if it's after some given stage we remove items from the front of the list.
I realize you probably don't need this answer anymore, but hopefully it helps someone =]
Upvotes: 2
Reputation: 3298
The problem here is that you don't have order inside state machine. You need to provide and declare one.
I would stick to this solution:
first declare constant in your model, containing states (in order!), so:
STATES = [:new, :in_process, :done, :verified]
And later, inside your model:
def current_state_index
return state_index(self.state)
end
def state_index(state)
return STATES.index(state)
end
def some_action
return unless current_state_index > state_index(:in_process)
#do some work end
end
Upvotes: 1