state_machine gem の永続化前の検証 質問する

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。 状態マシンの検証は最初の保存が完了するまで開始されないため、保存は true として返され、オブジェクトが有効でない場合、コントローラは実行すべきではないロジックに進みます。

私は保存の確認に加えて手動で有効性をテストすることでこの問題を回避しましたが、オブジェクトが保存される前に検証を実行する方法があるべきだと感じています。

ベストアンサー1

この特定のステート マシンの考え方は、検証宣言をステート内に埋め込むことです。

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 

ただし、before 遷移に基づいて別のアプローチを使用する場合は、コールバックが 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

この場合、ただし、は true/false を返す単純なメソッドになるはずです。定義された検証チェーンを使用する場合、モデル自体でvalidate_core呼び出すことが考えられます。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

もちろん、各メソッドを手動で行うことはできません。すべきこの結果を達成するには、ライブラリにモンキーパッチを適用します。

おすすめ記事