特定状态机的想法是在状态中嵌入验证声明。
state :orange do
validate :validate_core
end
只要对象转换为橙色,上述配置就会执行验证:validate_core。
event :orangify do
transition all => :orange
end
我了解您对回滚的担忧,但请记住,回滚是在事务中执行的,因此非常便宜。
record.orangify!
此外,请记住,您还可以使用不使用异常的非 bang 版本。
> c.orangify
(0.3ms) BEGIN
(0.3ms) ROLLBACK
=> false
也就是说,如果你想基于之前的转换使用不同的方法,那么你只需要知道如果回调返回 false,转换就会停止。
before_transition do
false
end
> c.orangify!
(0.2ms) BEGIN
(0.2ms) ROLLBACK
StateMachine::InvalidTransition: Cannot transition state via :cancel from :purchased (Reason(s): Transition halted)
请注意,事务始终会启动,但如果回调位于最开始,则可能不会执行任何查询。
before_transaction 接受一些参数。您可以生成对象和事务实例。
before_transition do |object, transaction|
object.validate_core
end
事实上你可以通过事件来限制它
before_transition all => :orange do |object, transaction|
object.validate_core # => false
end
在这种情况下,validate_core 应该是一个返回 true/false 的简单方法。如果你想使用定义的验证链,那么我想到的是在模型本身上调用valid?。
before_transition all => :orange do |object, transaction|
object.valid?
end
但是,请注意,您不能在事务范围之外运行事务。事实上,如果您检查perform 的代码,您会看到回调在事务内部。
# Runs each of the collection's transitions in parallel.
#
# All transitions will run through the following steps:
# 1. Before callbacks
# 2. Persist state
# 3. Invoke action
# 4. After callbacks (if configured)
# 5. Rollback (if action is unsuccessful)
#
# If a block is passed to this method, that block will be called instead
# of invoking each transition's action.
def perform(&block)
reset
if valid?
if use_event_attributes? && !block_given?
each do |transition|
transition.transient = true
transition.machine.write(object, :event_transition, transition)
end
run_actions
else
within_transaction do
catch(:halt) { run_callbacks(&block) }
rollback unless success?
end
end
end
# ...
end
要跳过事务,您应该对 state_machine 进行猴子修补,以便转换方法(例如 orangify!)在转换之前检查记录是否有效。
以下是您应该实现的目标的示例
# Override orangify! state machine action
# If the record is valid, then perform the actual transition,
# otherwise return early.
def orangify!(*args)
return false unless self.valid?
super
end
当然,您不能为每个方法手动执行此操作,这就是为什么您应该修改库以实现此结果。