How to implement a RESTful resource for a state machine or finite automata

前端 未结 4 543
误落风尘
误落风尘 2021-01-01 10:33

I\'m a Rails and REST newbie and I\'m trying to figure how best to expose a resource that is backed by a domain object that has a state machine (in other words is a finite a

4条回答
  •  清歌不尽
    2021-01-01 10:38

    A little late to the party here, but I was researching this exact issue for myself and found that the gem I'm currently using to manage my state machines (state_machine by pluginaweek) has some methods that deal with this issue quite nicely.

    When used with ActiveRecord (and I'm assuming other persistence layers as well), it provides a #state_event= method that accepts a string representation of the event you would like to fire. See documentation here.

    # For example,
    
    vehicle = Vehicle.create          # => #
    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.state                                       # => "idling"
    

    This allows you to simply add a state_event field in your resource's edit forms and get state transitions as easily as updating any other attribute.

    Now we're obviously still using PUT to trigger events using this method, which isn't RESTful. The gem does, however, provide an interesting example that at least "feels" quite RESTful, despite it using the same non-RESTful method under the covers.

    As you can see here and here, the gem's introspection capabilities allow you to present in your forms either the event you would like to fire or the name of that event's resulting state.

    <%= f.label :state %>
    <%= f.collection_select :state_event, @user.state_transitions, :event, :human_to_name, :include_blank => @user.human_state_name %>
    <%= f.label :access_state %>
    <%= f.collection_select :access_state_event, @user.access_state_transitions, :event, :human_event, :include_blank => "don't change" %>

    Using the latter technique, you get simple form-based updating of the model's state to any valid next state without having to write any extra code. It's not technically RESTful, but it allows you to easily present it that way in the UI.

    The cleanliness of this technique combined with the inherent conflicts in trying to cast an event-based state machine into a simple RESTful resource was enough to satisfy me, so hopefully it provides some insight to you as well.

提交回复
热议问题