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

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

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

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

person Galex    schedule 08.04.2011    source источник
comment
Мой совет: не сводите себя с ума, пытаясь удовлетворить REST-троллей 100% так называемой RESTfulness. REST - удобный инструмент для создания API, но большинство сложных API вынуждены нарушать REST, особенно идемпотентность PUT.   -  person Kyle Wild    schedule 08.04.2011
comment
@dorkitude: Кажется, существует много уровней RESTfulness, но по крайней мере соблюдайте основные правила: GET / PUT / DELETE для идемпотента, POST для неидемпотента, коды ошибок HTTP для сбоев, указатели на подресурсы, описываемые родительскими ресурсами . Это тоже (относительно) простые части.   -  person Donal Fellows    schedule 08.04.2011
comment
REST тролль здесь: если вы не соблюдаете ограничения REST, не называйте его RESTfull, а RESTlike;)   -  person Derick Schoonbee    schedule 08.04.2011


Ответы (4)


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

Правильный.

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

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

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

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

  • Пункт списка

[Стандартный ответ.]

person Donal Fellows    schedule 08.04.2011
comment
@Galex: Честно говоря, нет реального мнения о том, как отобразить ваш графический интерфейс. Однако, если вы сделаете установку состояния на себя чем-то, что всегда является бездействующим действием, вы получите идемпотентность и можете использовать PUT. Возможно, вы не захотите делать это из-за других вещей, которые происходят, но это подойдет достаточно хорошо. - person Donal Fellows; 08.04.2011
comment
Боролся с подобной проблемой. Ваш ответ был очень полезным. Спасибо. - person BrunoF; 21.12.2016

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

person Luke    schedule 31.05.2012
comment
Очень полезный ответ. Документацию по методу state_event мне как-то не попадалось. Однако я, возможно, столкнулся с ограничением этой техники. Вы не можете передавать аргументы событию перехода, когда вы запускаете его с помощью метода state_event. Это означает, что вы не можете записывать такую ​​информацию, как current_user, инициировавший событие (например, stackoverflow.com/a/6347369/574190 < / а>). - person David Tuite; 24.03.2013

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

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

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

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

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

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

person Kevin Monk    schedule 23.02.2015