工作流程或 AASM 等 gem 的最佳实践

发布于 2024-11-18 07:11:55 字数 478 浏览 5 评论 0原文

如果您想更新所有属性,但还需要工作流程/AASM 回调才能正确触发,我想知道你们如何在控制器中使用工作流程或 AASM gem。

目前,我这样使用它:

  class ModelController < ApplicationController
    def update
      @model = model.find(params[:id])

      if params[:application]['state'].present?
        if params[:application]['state'] == "published"
          @model.publish!
        end
      end
      if @model.update_attributes(params[:application]); ... end
    end
  end

感觉不对,什么是更好的解决方案?

i would like to know how you guys use the workflow or the AASM gem in the controller if you want to update all attributes, but also need the workflow/AASM callbacks to fire properly.

currently, i use it like this:

  class ModelController < ApplicationController
    def update
      @model = model.find(params[:id])

      if params[:application]['state'].present?
        if params[:application]['state'] == "published"
          @model.publish!
        end
      end
      if @model.update_attributes(params[:application]); ... end
    end
  end

that does not feel right, what would be a better solution ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

彩虹直至黑白 2024-11-25 07:11:55

我通常定义多个操作来处​​理从一种状态到另一种状态的转换,并具有明确的名称。在你的情况下,我建议你添加一个 publish 操作:

def publish
  # as the comment below states: your action 
  # will have to do some error catching and possibly
  # redirecting; this goes only to illustrate my point
  @story = Story.find(params[:id])
  if @story.may_publish?
    @story.publish!
  else
   # Throw an error as transition is not legal
  end
end

在你的 routes.rb 中声明:

resources :stories do
  member do
    put :publish
  end
end

现在你的路线准确地反映了故事发生的情况:/stories /1234/发布

I usually define multiple actions that handle the transition from one state to another and have explicit names. In your case I would suggest you add a publish action:

def publish
  # as the comment below states: your action 
  # will have to do some error catching and possibly
  # redirecting; this goes only to illustrate my point
  @story = Story.find(params[:id])
  if @story.may_publish?
    @story.publish!
  else
   # Throw an error as transition is not legal
  end
end

Declare that in your routes.rb:

resources :stories do
  member do
    put :publish
  end
end

Now your route reflects exactly what happens to a story: /stories/1234/publish

耶耶耶 2024-11-25 07:11:55

您可以覆盖模型 aasm_state setter (或我的示例中的 status ),以便它可以接受事件名称。然后我们检查它是否是一个有效的事件,然后检查转换是否有效。如果不是,我们将添加正确的错误消息。

请求规格 型号

it "should cancel" do
  put "/api/ampaigns/#{@campaign.id}", {campaign: {status: "cancel"}, format: :json}, valid_session
  response.code.should == "204"
end

规格

it "should invoke the cancel method" do
  campaign.update_attribute(:status, "cancel")
  campaign.canceled?.should be_true
end
it "should add an error for illegal transition" do
  campaign.update_attribute(:status, "complete")
  campaign.errors.should include :status
  campaign.errors[:status].should == ["status cannot transition from pending to complete"]
end
it "should add an error for invalid status type" do
  campaign.update_attribute(:status, "foobar")
  campaign.errors.should include :status
  campaign.errors[:status].should == ["status of foobar is not valid.  Legal values are pending, active, canceled, completed"]
end

型号

class Campaign < ActiveRecord::Base
  include AASM
  aasm column: :status do
    state :pending, :initial => true
    state :active
    state :canceled
    state :completed
    # Events
    event :activate do
      transitions from: :pending, to: :active
    end
    event :complete do
      transitions from: :active, to: [:completed]
    end
    event :cancel do
      transitions from: [:pending, :active], to: :canceled
    end
  end
  def status=(value)
    if self.class.method_defined?(value)
      if self.send("may_#{value}?")
        self.send(value)
      else
        errors.add(:status, "status cannot transition from #{status} to #{value}")
      end

    else
      errors.add(:status, "status of #{value} is not valid.  Legal values are #{aasm.states.map(&:name).join(", ")}")
    end
  end
end

You can override the models aasm_state setter (or status in my example) so it can accept event names. Then we check to see if it's a valid event then check to see if the transition is valid. If they are not we add the correct error message.

A request spec

it "should cancel" do
  put "/api/ampaigns/#{@campaign.id}", {campaign: {status: "cancel"}, format: :json}, valid_session
  response.code.should == "204"
end

The Model Spec

it "should invoke the cancel method" do
  campaign.update_attribute(:status, "cancel")
  campaign.canceled?.should be_true
end
it "should add an error for illegal transition" do
  campaign.update_attribute(:status, "complete")
  campaign.errors.should include :status
  campaign.errors[:status].should == ["status cannot transition from pending to complete"]
end
it "should add an error for invalid status type" do
  campaign.update_attribute(:status, "foobar")
  campaign.errors.should include :status
  campaign.errors[:status].should == ["status of foobar is not valid.  Legal values are pending, active, canceled, completed"]
end

The model

class Campaign < ActiveRecord::Base
  include AASM
  aasm column: :status do
    state :pending, :initial => true
    state :active
    state :canceled
    state :completed
    # Events
    event :activate do
      transitions from: :pending, to: :active
    end
    event :complete do
      transitions from: :active, to: [:completed]
    end
    event :cancel do
      transitions from: [:pending, :active], to: :canceled
    end
  end
  def status=(value)
    if self.class.method_defined?(value)
      if self.send("may_#{value}?")
        self.send(value)
      else
        errors.add(:status, "status cannot transition from #{status} to #{value}")
      end

    else
      errors.add(:status, "status of #{value} is not valid.  Legal values are #{aasm.states.map(&:name).join(", ")}")
    end
  end
end
始终不够 2024-11-25 07:11:55

这是一件小事,但如果该东西不存在,则哈希返回 nil,因此您可以删除对 Present 的调用吗?

当然,我知道这不是你要问的。一种替代方法是在模型中放置一个 before 过滤器,并在那里检查状态。这会让你的控制器对你的状态的底层存储视而不见。

顺便说一句,我们在这里使用 AASM,我喜欢它:)

It's a small thing but a hash return nil if the thing is not present, so you could remove the call to present?

I realize that's not what you're asking, of course. One alternative is to put a before filter in the model and do your check for status there. That leaves your controller blind to the underlying storage of your status.

On a side note, we use AASM here and I love it :)

反差帅 2024-11-25 07:11:55

我希望我的模型在更新后返回新状态,这是我能想到的最简单的方法,而无需控制器中的大量“脂肪”,并且如果您的工作流程发生变化,它会让前进变得更容易:

class Article < ActiveRecord::Base
  include Workflow
  attr_accessible :workflow_state, :workflow_event # etc
  validates_inclusion_of :workflow_event, in: %w(submit approve reject), allow_nil: true
  after_validation :send_workflow_event

  def workflow_event
    @workflow_event
  end

  def workflow_event=(workflow_event)
    @workflow_event = workflow_event
  end

  # this method should be private, normally, but I wanted to 
  # group the meaningful code together for this example
  def send_workflow_event
    if @workflow_event && self.send("can_#{@workflow_event}?")
      self.send("#{@worklow_event}!")
    end
  end

  # I pulled this from the workflow website, to use that example instead.
  workflow do
    state :new do
      event :submit, :transitions_to => :awaiting_review
    end
    state :awaiting_review do
      event :review, :transitions_to => :being_reviewed
    end
    state :being_reviewed do
      event :accept, :transitions_to => :accepted
      event :reject, :transitions_to => :rejected
    end
    state :accepted
    state :rejected
  end
end

I wanted my model to return the new state after being updated, and this was the easiest way I could think to do this without a lot of "fat" in the controllers, and it makes it easier going forward if your workflow changes:

class Article < ActiveRecord::Base
  include Workflow
  attr_accessible :workflow_state, :workflow_event # etc
  validates_inclusion_of :workflow_event, in: %w(submit approve reject), allow_nil: true
  after_validation :send_workflow_event

  def workflow_event
    @workflow_event
  end

  def workflow_event=(workflow_event)
    @workflow_event = workflow_event
  end

  # this method should be private, normally, but I wanted to 
  # group the meaningful code together for this example
  def send_workflow_event
    if @workflow_event && self.send("can_#{@workflow_event}?")
      self.send("#{@worklow_event}!")
    end
  end

  # I pulled this from the workflow website, to use that example instead.
  workflow do
    state :new do
      event :submit, :transitions_to => :awaiting_review
    end
    state :awaiting_review do
      event :review, :transitions_to => :being_reviewed
    end
    state :being_reviewed do
      event :accept, :transitions_to => :accepted
      event :reject, :transitions_to => :rejected
    end
    state :accepted
    state :rejected
  end
end
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文