Как реализовать ресурс RESTful для конечного автомата или конечных автоматов - PullRequest
27 голосов
/ 08 апреля 2011

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

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

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

Ответы [ 4 ]

9 голосов
/ 31 мая 2012

Немного опоздал на вечеринку здесь, но я исследовал эту проблему для себя и обнаружил, что гем, который я сейчас использую для управления моими машинами состояний ( state_machine от 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 был достаточным, чтобы удовлетворить меня, так что, надеюсь, он также даст вам некоторое представление.

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

Correct.

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

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

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

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

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

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

3 голосов
/ 23 февраля 2015

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

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

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

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 и записывает сообщения перехода.Это также дает вам удобный аудит всех событий изменения состояния.

Идея, украденная у Виллем Берген в Shopify

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

2 голосов
/ 08 апреля 2011

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