在state_machine gem上持久化之前的验证

时间:2013-11-23 02:09:11

标签: ruby-on-rails ruby validation ruby-on-rails-4 state-machine

state_machine gem转换之前执行验证的正确语法是什么?

我尝试了以下内容,

before_transition :apple => :orange do
  validate :validate_core
end

def validate_core
  if core.things.blank?
    errors.add(:core, 'must have one thing')
  end
end

但是我收到以下错误,

undefined method `validate' for #<StateMachine::Machine:0x007ffed73e0bd8>

我也试过把它写成,

state :orange do
  validate :validate_core
end

但是这会在保存记录后导致回滚,这不太理想。我想首先阻止状态机转换到:orange

核心问题是,在我的控制器中,我的逻辑依赖于object.save的结果。我对状态机的验证直到初始保存之后才会启动,因此保存返回为真,控制器继续逻辑,如果对象无效,则不应该命中。

我通过手动测试有效性以及检查保存来解决这个问题,但感觉应该有一种方法可以在对象保存之前激活验证。

6 个答案:

答案 0 :(得分:27)

该特定状态机的想法是在州内嵌入验证声明。

state :orange do
  validate :validate_core
end

只要对象转换为橙色,上面的配置就会执行验证:validate_core

event :orangify do
  transition all => :orange
end

我理解您对回滚的担忧,但请记住,回滚是在事务中执行的,因此它非常便宜。

record.orangify!

此外,请记住,您也可以使用不使用例外的非爆炸版本。

> 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

当然,你不能手动为每种方法做到这一点,这就是你应该猴子修补库以实现这个结果的原因。

答案 1 :(得分:2)

您可以通过执行以下操作来尝试取消转换到下一个状态:

before_transition :apple => :orange do
  if core.things.blank?
    errors.add(:core, 'must have one thing')
    throw :halt
  end
end

这样,如果core.things为空,则核心会出现错误,转换将被取消。我认为它也不会对数据库进行任何更改。虽然没有尝试过这段代码,但只是阅读了它的来源。鉴于上面的代码,可能会导致更多代码捕获异常,下面的方法怎么样?

def orange_with_validation
  if core.things.blank? && apple?
    errors.add(:core, 'must have one thing')
  else
    #transition to orange state
    orange
  end
end

在转换为橙色状态之前,您可以在要验证的位置使用上面的代码。此方法允许您解决state_machine的回调的限制。在控制器中使用它可以为向导表单提供动力,这将阻止您的表单进入下一步,并在验证失败时避免任何数据库命中。

答案 2 :(得分:0)

我还是新手,但不是

 validates

而不是

validate

http://edgeguides.rubyonrails.org/active_record_validations.html

另外只是阅读你必须在状态中进行验证的文档,我从来没有使用过state_machine,但我认为是这样的:

state :orange do
      validates_presence_of   :apple
end

答案 3 :(得分:0)

Rails正在寻找一种方法'验证'状态。但验证是一种积极的记录方法。所有模型都从活动记录继承,但状态不继承,因此它没有验证方法。解决这个问题的方法是定义一个类方法并在状态中调用它。所以,假设您的模型名为Fruit,您可以使用类似的东西

class Fruit < ActiveRecord::Base
    def self.do_the_validation
        validate :validate_core
    end

    before_transition :apple => :orange, :do => :do_the_validation
end

我不确定你是否需要自己。此外,第二行可能需要:

self.validate :validate_core

我认为这应该有效。话虽如此,是否有任何理由让您在过渡之前进行验证?为什么不单独进行验证呢?它应该始终验证。

答案 4 :(得分:0)

validate方法是你的模型的类方法,所以你不能从传递给state_machine类方法的块中调用他,因为你有新的上下文。

试试这个:

YourModel < AR::B
  validate :validate_core

  state_machine :state, :initial => :some_state do
    before_transition :apple => :orange do |model, transition|
      model.valid?
    end
  end

  def validate_core
    if core.things.blank?
      errors.add(:core, 'must have one thing')
    end
  end
end

答案 5 :(得分:0)

“停止[ping]状态机从转换为:橙色”的目标听起来像过渡时的守卫。 state_machine支持以下:if和:除非转换定义上的选项。与ActiveModel验证器一样,这些选项的值可以是lambda或表示要在对象上调用的方法名称的符号。

event :orangify
  transition :apple => :orange, :if => lambda{|thing| thing.validate_core }
  # OR transition :apple => :orange, :if => :validate_core
end