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

Gal*_*lex 27 rest ruby-on-rails state-machine

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

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

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

Don*_*ows 9

  • 更新操作(PUT方法)不合适,因为PUT被认为是幂等的.唯一可能的是,如果州是作为代表的一部分发送的.这与"事件"不一致.它是否正确?

正确.

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

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

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

由你决定; 没有必要密切关注创作的任何特定属性的真正原因.(你可以通过说状态在创建之后立即改变到状态机的正确初始状态来合理化.)在我已经完成的状态机中,无论​​如何都是通过POST创建的(并且是一个不同的 - 相当复杂 - 所以整个事情都没有实际意义,但如果你允许多个初始状态,那么在创建文档中采用"这是我首选的起始状态"提示是有意义的.要明确,仅仅因为用户想要它并不意味着你必须这样做; 当您拒绝他们的建议时,您是否想要向用户投诉.

  • 项目清单

[股票回答.]


Luk*_*uke 9

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

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

# 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"
Run Code Online (Sandbox Code Playgroud)

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

现在我们显然仍然使用PUT来使用这种方法触发事件,这种方法不是RESTful.然而,gem确实提供了一个有趣的例子,至少"感觉"非常RESTful,尽管它使用相同的非RESTful方法.

正如您在此处此处所看到的,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>
Run Code Online (Sandbox Code Playgroud)

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

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


Kev*_*onk 7

参加这里的聚会有点晚了,而且距离专家还很远,因为我有类似的疑问,但是......

将事件作为资源怎么样?

所以而不是...

PUT /order/53?state_event="pay" #Order.update_attributes({state_event: "pay})
Run Code Online (Sandbox Code Playgroud)

你会...

POST /order/53/pay     #OrderEvent.create(event_name: :pay)
POST /order/53/cancel  #OrderEvent.create(event_name: :cancel)
Run Code Online (Sandbox Code Playgroud)

在 Order 和 OrderEvent 之间使用发布/订阅侦听器或尝试在 Order 上触发该事件并记录转换消息的回调。它还可以让您方便地审核所有状态更改事件。

Shopify 的 Willem Bergen的创意被盗

我错过了什么吗?抱歉,我自己很难理解这一点。