Как реализовать ресурс RESTful для конечного автомата или конечных автоматов

Я новичок в Rails и REST, и я пытаюсь понять, как лучше выставить ресурс, который поддерживается объектом домена, у которого есть конечный автомат (другими словами, конечный автомат).

Я видел несколько гемов для превращения класса модели в конечный автомат, таких как aasm, переходы, рабочий процесс, но ни один из них не описывает примеры того, как они на самом деле используются в ресурс-ориентированном контроллере. Кажется, все они подразумевают, что переходы состояний инициируются "событием", которое на самом деле является вызовом метода. Вот некоторые вопросы, с которыми это связано:

  1. Действие обновления (метод PUT) не подходит, поскольку предполагается, что PUT идемпотентен. Единственное, что было бы возможно, это если бы государство было отправлено как часть представления. Это не соответствует "событию". Это правильно?
  2. Поскольку события не являются идемпотентными, необходимо использовать метод POST. Но к какому ресурсу? Есть ли субресурс для каждого возможного события? Или есть один (/updatestate), который принимает в качестве своего представления событие для запуска и какие-либо параметры для события?
  3. Поскольку состояние ресурса изменяется с помощью события, которое может быть инициировано другим ресурсом, должно ли действие create принимать изменения атрибута состояния (или любых других атрибутов, которые зависят от конечного автомата)?
  4. [Обновленный вопрос] Что такое хороший способ выставить переходы в пользовательском интерфейсе? Поскольку события не являются состояниями, может показаться, что не имеет смысла обновлять атрибут состояния (и любой другой атрибут, который зависит от переходов состояний). Означает ли это, что эти атрибуты должны игнорироваться в действии обновления?

4 ответа

Решение
  • Действие обновления (метод PUT) не подходит, поскольку предполагается, что PUT идемпотентен. Единственное, что было бы возможно, это если бы государство было отправлено как часть представления. Это не соответствует "событию". Это правильно?

Правильный.

  • Поскольку события не являются идемпотентными, необходимо использовать метод POST. Но к какому ресурсу? Есть ли субресурс для каждого возможного события? Или есть один (/updatestate), который принимает в качестве своего представления событие для запуска и какие-либо параметры для события?

Вы можете сделать это обоими способами. Вы можете поддерживать оба в одном и том же приложении, при этом различия в типах событий определяются либо входящим документом, либо получающим ресурсом. Лично я предпочел бы делать это, различая типы документов, но это только мое мнение. Если вы идете по пути нескольких ресурсов, убедитесь, что они доступны для обнаружения (т. Е. Ссылки на каждый из них, описанные в документе, возвращаются при получении их родительского ресурса).

  • Поскольку состояние ресурса изменяется событием, которое может быть вызвано другим ресурсом, должно ли действие create принимать изменения атрибута состояния (или любых других атрибутов, которые зависят от конечного автомата)?

Вам решать; нет никакой реальной причины, почему вы должны обращать пристальное внимание на какой-либо конкретный атрибут при создании. (Вы могли бы рационализировать это, сказав, что состояние изменяется на надлежащее начальное состояние для конечного автомата сразу после создания.) В конечных автоматах, которые я сделал, в любом случае создание было с помощью POST (и другого - довольно сложного - документ), так что все это было спорным, но если вы разрешите несколько начальных состояний, то имеет смысл воспользоваться подсказкой "это мое предпочтительное начальное состояние" в документе создания. Чтобы быть понятным, просто потому, что пользователь хочет, это не значит, что вы должны это делать; хотите ли вы пожаловаться пользователю, отклонив его предложение.

  • Элемент списка

[Сток ответ.]

Немного опоздал на вечеринку здесь, но я исследовал эту проблему для себя и обнаружил, что гем, который я сейчас использую для управления моими конечными автоматами ( state_machine by pluginaweek), имеет несколько методов, которые прекрасно справляются с этой проблемой.

При использовании с 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"

Это позволяет вам просто добавить state_event поле в формах редактирования вашего ресурса и получить переходы состояния так же легко, как обновить любой другой атрибут.

Теперь мы, очевидно, все еще используем PUT для запуска событий, используя этот метод, который не является RESTful. Драгоценный камень, однако, предоставляет интересный пример, который, по крайней мере, "чувствует" довольно RESTful, несмотря на то, что он использует тот же не-RESTful метод под прикрытием.

Как вы можете видеть здесь и здесь, возможности самоанализа драгоценного камня позволяют вам представлять в ваших формах либо событие, которое вы хотели бы запустить, либо имя результирующего состояния этого события.

<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, но позволяет легко представить его в пользовательском интерфейсе.

Чистота этого метода в сочетании с внутренними конфликтами при попытке преобразовать конечный автомат, основанный на событиях, в простой ресурс RESTful был достаточным, чтобы удовлетворить меня, так что, надеюсь, он также даст вам некоторую информацию.

Немного опоздал на вечеринку здесь и далеко от эксперта, так как у меня похожий запрос, но...

Как насчет того, чтобы сделать мероприятие ресурсом?

Так что вместо...

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)

С прослушивателем pub / sub между Order и OrderEvent или обратным вызовом, который пытается вызвать это событие в Order и записывает сообщения перехода. Это также дает вам удобный аудит всех событий изменения состояния.

Идея, украденная у Виллема Бергена в Шопифи

Я что-то пропустил? Извините, изо всех сил пытаюсь понять это сам.

Если у вашего ресурса есть какой-либо атрибут статуса, вы можете использовать метод 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
Другие вопросы по тегам