geekq / workflow

Ruby finite-state-machine-inspired API for modeling workflow
MIT License
1.74k stars 207 forks source link

Problem with sequel model update #217

Closed deemytch closed 4 years ago

deemytch commented 4 years ago

Throws Sequel::UniqueConstraintViolation: PG::UniqueViolation when updating the model. Working ok without including Workflow into model. gem workflow-sequel is not working too, it is outdated, and that code is copied from it.

ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux] postgresql 11.4 workflow 2.0.2 sequel 5.22.0

Below is the example test code.

CREATE TABLE sequelbags (
  id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  client_id BIGINT,
  token VARCHAR(256),
  jobstatus VARCHAR(32)
);
ALTER TABLE sequelbags OWNER TO user;
CREATE INDEX IF NOT EXISTS jobstatus_idx ON sequelbags ( jobstatus );

Here you need to connect to database.

Sequel::Model.db = Sequel.connect 'postgres://user:password@localhost/blog'

Then you define the model class.

class Sequelbag < Sequel::Model
  include Workflow
  workflow_column :jobstatus

  workflow do
    state :new do
      event :run, transition_to: :working
    end
    state :working do
      event :done, transition_to: :shutdown
      event :error, transition_to: :failed
    end
    state :shutdown
    state :failed
  end

  def load_workflow_state
    send(self.class.workflow_column)
  end

  def persist_workflow_state(new_value)
    send("#{self.class.workflow_column}=", new_value)
    save(changed: true, validate: false)
    # save(changed: true, columns: [self.class.workflow_column], validate: false)
  end

  def before_validation
    send("#{self.class.workflow_column}=", current_state.to_s) unless send(self.class.workflow_column)
    super
  end

end

If the class does not include Workflow and so on all updates on my models are working fine. And that's all, floks.

x = Sequelbag.create
#<Sequelbag @values={:id=>2, :client_id=>nil, :token=>nil, :jobstatus=>"new"}>
x0.update client_id: 1
Sequel::UniqueConstraintViolation: PG::UniqueViolation: ОШИБКА:  повторяющееся значение ключа нарушает ограничение уникальности "sequelbags_pkey" DETAIL:  Ключ "(id)=(2)" уже существует.
from project/vendor/ruby/2.6.0/gems/sequel-5.22.0/lib/sequel/adapters/postgres.rb:152:in `async_exec'
Caused by PG::UniqueViolation: ОШИБКА:  повторяющееся значение ключа нарушает ограничение уникальности "sequelbags_pkey" DETAIL:  Ключ "(id)=(2)" уже существует.                                                                                     
from project/vendor/ruby/2.6.0/gems/sequel-5.22.0/lib/sequel/adapters/postgres.rb:152:in `async_exec'
geekq commented 4 years ago

You are right: to use the latest workflow gem in version 2 or later, all persistence libraries, e.g. workflow-sequel need to be updated.

  1. You can provide a fix to workflow-sequel (they have instructions on Contributing) or implement your own persistence as described in https://github.com/geekq/workflow#custom-workflow-state-persistence
  2. You can try using older version of workflow. It is still available in github and via rubygems. Please read https://github.com/geekq/workflow#state-persistence-with-activerecord
deemytch commented 4 years ago

Владимир, but the bug that I opened is about that persistence with sequel, exactly as described in docs does not works. And I can't get why.

geekq commented 4 years ago
  1. UniqueViolation... Ключ "(id)=(2)" уже существует looks like a record with the same id already exists. Does error happens directly on Sequelbag.create ? Is it supposed to save the record immediately? persist_workflow_state should not cause this since it is not called for new records but only in process_event! https://github.com/geekq/workflow/blob/develop/lib/workflow.rb#L97-L125

  2. Can you please provide a complete minimal application (if it is a Rails-Application) or even a self-contained single-file script demonstrating the problem + Gemfile so I can easily reproduce?

deemytch commented 4 years ago

Yes, and this appears when updating exisiting record and only when the class contains include Workflow. I will make the script soon.

deemytch commented 4 years ago

https://github.com/deemytch/workflow-sequel-bag Edit createdb.sql and sequelbag.rb if you need to change user/password/database. Run main.sh there.

geekq commented 4 years ago

:+1: was able to reproduce.

Now try to change state name from :new to something else like :neww and see what happens!

geekq commented 4 years ago

Background: if you define some workflow states and events, a bunch of convinience methods are created behind the scene. Looks like one of them collides with the methods used by the Sequel::Model implementation - likely new?

How I debugged:

  1. run your script - can reproduce the problem
  2. commented out actions in persitence callbacks like persist_workflow_state, before_validation etc. Problem was still there.
  3. removed everything workflow-related - problem diappeared
  4. added include Workflow - still works
  5. added workflow_column and an empty workflow do block - still works
  6. added states one by one starting with the last one - only the :new state had a problem
  7. rename the state

I should probably mention it in the documentation.

deemytch commented 4 years ago

Где были мои глаза? Володя, спасибо!