遷移前に検証を実行するための正しい構文は何ですか?ステートマシン宝石?
私は次のことを試しました、
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
もちろん、各メソッドを手動で行うことはできません。すべきこの結果を達成するには、ライブラリにモンキーパッチを適用します。