如何为状态机或有限自动机实现RESTful资源

时间:2011-04-08 06:36:23

标签: ruby-on-rails rest state-machine

我是一个Rails和REST新手,我正在试图找出如何最好地公开由具有状态机的域对象支持的资源(换句话说,它是一个有限的自动机)。

我已经看到了许多用于使模型类成为状态机的宝石,例如aasm,过渡,工作流,但它们都没有记录它们如何在面向资源的控制器中实际使用的示例。它们似乎都暗示状态转换是由“事件”触发的,这实际上是一种方法调用。我对这意味着的一些问题是:

  1. 更新操作(PUT方法)不合适,因为PUT被认为是幂等的。唯一可能的是,如果州是作为代表的一部分发送的。这与“事件”不一致。这是对的吗?
  2. 因为事件不是幂等的,所以必须使用POST。但是,到哪个资源?每个可能的事件都有子资源吗?或者,是否有一个(/ updatestate)将触发事件和事件的任何参数作为其表示?
  3. 由于资源的状态是由另一个资源可能触发的事件修改的,因此create action是否应接受对state属性(或依赖于状态机的任何其他属性)的更改?
  4. [更新的问题]在UI中公开转换的好方法是什么?由于事件不是状态,因此似乎允许更新状态属性(以及依赖于状态转换的任何其他属性)是没有意义的。这是否意味着在更新操作中应忽略这些属性?

4 个答案:

答案 0 :(得分:9)

  
      
  • 更新操作(PUT方法)不合适,因为PUT被认为是幂等的。唯一可能的是,如果州是作为代表的一部分发送的。这与“事件”不一致。这是对的吗?
  •   

正确。

  
      
  • 因为事件不是幂等的,所以必须使用POST。但是,到哪个资源?每个可能的事件都有子资源吗?或者,是否有一个(/ updatestate)将触发事件和事件的任何参数作为其表示?
  •   

你可以两种方式做到这一点。您可以在同一个应用程序中同时支持,事件类型的变化由传入文档或接收资源确定。就个人而言,我更愿意通过不同的文档类型来实现,但这只是我的意见。如果你的的去多资源路线,确保他们发现(即通过有链接到每个文件中描述他们回来,当你得到他们的父资源)。

  
      
  • 由于资源的状态是由另一个资源可能触发的事件修改的,因此create action是否应接受对state属性(或依赖于状态机的任何其他属性)的更改?
  •   

由你决定;没有必要密切关注创作的任何特定属性的真正原因。在状态机我做了(你可以说的是,状态变化到适当的初始状态,状态机创建后立即理顺这一点),创立是由POST反正(和不同的 - 非常复杂 - 文件),所以整个事情是毫无意义的,但如果你允许多个初始状态的话很有道理采取“这是我的首选起始状态”创建文档提示。要明确,仅仅因为用户想要它并不意味着你必须这样做;当您拒绝他们的建议时,您是否想向用户投诉。

  
      
  • 列表项
  •   

[股票回答。]

答案 1 :(得分:9)

这里的派对有点晚了,但是我正在为自己研究这个确切的问题,并发现我目前用来管理我的状态机(state_machine by pluginaweek)的gem有一些处理这个问题的方法非常好。

当与ActiveRecord一起使用时(我也假设其他持久层),它提供了一个#state_event=方法,它接受您要触发的事件的字符串表示。请参阅文档here

# For example,

vehicle = Vehicle.create          # => #<Vehicle id: 1, name: nil, state: "parked">
vehicle.state_event               # => nil
vehicle.state_event = 'invalid'
vehicle.valid?                    # => false
vehicle.errors.full_messages      # => ["State event is invalid"]

vehicle.state_event = 'ignite'
vehicle.valid?                    # => true
vehicle.save                      # => true
vehicle.state                     # => "idling"
vehicle.state_event               # => nil

# Note that this can also be done on a mass-assignment basis:

vehicle = Vehicle.create(:state_event => 'ignite')  # => #<Vehicle id: 1, name: nil, state: "idling">
vehicle.state                                       # => "idling"

这使您可以在资源的编辑表单中简单地添加state_event字段,并像更新任何其他属性一样轻松获取状态转换。

现在我们显然仍在使用PUT来触发使用此方法的事件,这不是RESTful。然而,gem确实提供an interesting example至少“感觉”非常RESTful,尽管它使用相同的非RESTful方法。

正如您可以看到herehere一样,gem的内省功能允许您在表单中显示您想要触发的事件该名称事件的结果状态。

<div class="field">
  <%= f.label :state %><br />
  <%= f.collection_select :state_event, @user.state_transitions, :event, :human_to_name, :include_blank => @user.human_state_name %>
</div>

<div class="field">
  <%= f.label :access_state %><br />
  <%= f.collection_select :access_state_event, @user.access_state_transitions, :event, :human_event, :include_blank => "don't change" %>
</div>

使用后一种技术,您可以将模型状态的简单基于表单的更新转换为任何有效的下一状态,而无需编写任何额外的代码。它在技术上并不是RESTful,但它允许您在UI中以这种方式轻松呈现它。

这种技术的清晰度与尝试将基于事件的状态机转换为简单的RESTful资源时的固有冲突相结合,足以让我满意,所以希望它也能为您提供一些见解。

答案 2 :(得分:3)

在这里参加聚会的时间比较迟,而且我的专家也有类似的问题,但是......

如何将活动作为资源?

所以不是......

PUT /order/53?state_event="pay" #Order.update_attributes({state_event: "pay})

你会...

POST /order/53/pay     #OrderEvent.create(event_name: :pay)
POST /order/53/cancel  #OrderEvent.create(event_name: :cancel)

使用Order和OrderEvent或回调之间的发布/订阅侦听器尝试在Order上触发该事件并记录转换消息。它还为您提供了所有状态变化事件的便捷审计。

来自Willem Bergen at Shopify

的想法被盗

我错过了什么吗?对不起,我自己也在努力理解这一点。

答案 3 :(得分:2)

如果您的资源具有某种状态属性,则可以使用名为micro-PUT的技术来更新其状态。

PUT /Customer/1/Status
Content-Type: text/plain

Closed

=> 200 OK
Content-Location: /Customer/1

您可以将资源状态建模为集合,并在这些集合之间移动资源。

GET /Customer/1
=>
Content-Type: application/vnd.acme.customer+xml
200 OK


POST /ClosedCustomers
Content-Type: application/vnd.acme.customer+xml
=>
200 OK

POST /OpenCustomers
Content-Type: application/vnd.acme.customer+xml
=>
200 OK

您始终可以使用新的PATCH方法

PATCH /Customer/1
Content-Type: application/x-www-form-urlencoded
Status=Closed
=>
200 OK