collab 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ae9c092826099d0d81012ba20446f93518d9f998136d110ae71141bed2a58e22
4
- data.tar.gz: e6de0fca8c103f77bdf6677fb0ab993a8740eae2baf7292f28cf601a5946f4b7
3
+ metadata.gz: f8c6f4ca46b51f7fd05bacdea192bbd5ec7979b6c44f7528665beda336fb6d43
4
+ data.tar.gz: e2a0748fe7e1686a36a7ace934153433b1a499c18f8a0c930ed689564f9eb90e
5
5
  SHA512:
6
- metadata.gz: a9db9beb2c46f7b4818fd494075ddb31ee68bcb137c3a347f71acf754efc3788afbaf2b67248b9e7b1a9bd80a780015a2a1db5a5778533823d32a83134550954
7
- data.tar.gz: d65efb8027b52bf44c14e3a36829f66a845130dca3cffaf002b0ec0fe8cdeed2a137fd6e671aff6555c52ed03705b66e72f54fab5b2872aaa30d872f4f83d476
6
+ metadata.gz: 36feda3ab2009c1a954434893a1ca89eed1f9492b08a20af7872d720abceaf4351398b16a9c6ba63cccf76f318ba387b3be29c85cf0616aba52a20f8fed7f71c
7
+ data.tar.gz: bd79ab6de7c2c24185f581031d4b25f673d5ef037f2f3794d4b06cb2b2906c772227d8ad9aa3ec4b63b28d82f0a48f2fdd3a54f6835db919b67b628b0c83fdb0
@@ -0,0 +1,7 @@
1
+ class Collab::CommitJob < ::Collab.config.base_job.constantize
2
+ queue_as :default
3
+
4
+ def perform(document, data)
5
+ document.commits.from_json(data).apply!
6
+ end
7
+ end
@@ -1,19 +1,15 @@
1
- require "collab/railtie"
1
+ require "collab/version"
2
+ require "collab/config"
3
+ require "collab/js"
4
+ require "collab/engine"
2
5
 
3
6
  module Collab
4
- def self.config
5
- @config ||= Collab::Config.new
6
- return @config unless block_given?
7
- yield @config
8
- end
9
-
10
- autoload "Config", "collab/config"
11
- autoload "Bridge", "collab/bridge"
7
+ autoload "Channel", "collab/channel"
12
8
  autoload "HasCollaborativeDocument", "collab/has_collaborative_document"
13
9
 
14
10
  module Models
15
11
  autoload "Base", "collab/models/base"
16
12
  autoload "Document", "collab/models/document"
17
- autoload "DocumentTransaction", "collab/models/document_transaction"
13
+ autoload "Commit", "collab/models/commit"
18
14
  end
19
15
  end
@@ -10,19 +10,20 @@ module Collab
10
10
 
11
11
  stream_for document
12
12
 
13
- transactions = document.transactions
14
- .where("document_version > ?", starting_version)
15
- .order(document_version: :asc)
16
- .load
13
+ commits = document.commits
14
+ .where("document_version > ?", starting_version)
15
+ .order(document_version: :asc)
16
+ .load
17
17
 
18
- raise "invalid version" unless transactions.first.document_version == (starting_version + 1) unless transactions.empty?
19
-
20
- transactions.lazy.map(&:as_json).each(method(:transmit))
18
+ unless commits.empty?
19
+ raise "invalid version" unless commits.first.document_version == (starting_version + 1)
20
+ commits.lazy.map(&:as_json).each(method(:transmit))
21
+ end
21
22
  end
22
23
 
23
- def submit(data)
24
- authorize_submit!(data)
25
- document.perform_transaction_later(data)
24
+ def commit(data)
25
+ authorize_commit!(data)
26
+ document.commit_later(data)
26
27
  end
27
28
 
28
29
  def unsubscribed
@@ -1,5 +1,34 @@
1
1
  module Collab
2
+ @config_mutex = Mutex.new
3
+
4
+ def self.config
5
+ if block_given?
6
+ @config_mutex.synchronize do
7
+ @config ||= ::Collab::Config.new
8
+ raise "[Collab] Tried to configure gem after first use" if @config.frozen?
9
+ yield @config
10
+ end
11
+ else
12
+ raise "[Collab] Missing configuration - Have you run `rails g collab:install` yet?" unless @config
13
+ @config.freeze # really weird stuff could happen if the config changes after first use, so freeze config
14
+ end
15
+ end
16
+
2
17
  class Config
3
- attr_accessor :max_transactions, :application_job, :queue_document_transaction_job_as, :schema_package, :application_record, :document_transaction_model, :document_model, :channel_name
18
+ attr_accessor :base_record,
19
+ :base_job,
20
+ :channel,
21
+ :commit_job,
22
+ :commit_model,
23
+ :document_model,
24
+ :max_commit_history_length,
25
+ :num_js_processes,
26
+ :schema_package
27
+
28
+ def initialize
29
+ self.commit_job = "Collab::CommitJob"
30
+ self.document_model = "Collab::Models::Document"
31
+ self.commit_model = "Collab::Models::Commit"
32
+ end
4
33
  end
5
34
  end
@@ -0,0 +1,6 @@
1
+ if defined?(Rails)
2
+ module Collab
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -8,7 +8,7 @@ module Collab
8
8
 
9
9
  define_method attach_as do
10
10
  super() || begin
11
- document = self.__send__("build_#{attach_as}", schema_name: schema, document: blank_document.dup)
11
+ document = self.__send__("build_#{attach_as}", schema_name: schema, content: blank_document.dup)
12
12
  document.save!
13
13
  document
14
14
  end
@@ -0,0 +1,93 @@
1
+ require "json"
2
+
3
+ module Collab
4
+ module JS
5
+ @queue = Queue.new
6
+ @queue_initialized = false
7
+ @queue_initialization_mutex = Mutex.new
8
+
9
+ class <<self
10
+ def queue
11
+ initialize_queue unless @queue_initialized
12
+ @queue
13
+ end
14
+
15
+ # Calls the block given with a JS process acquired from the queue
16
+ # Will block until a JS process is available
17
+ def with_js
18
+ js = queue.pop
19
+ yield js
20
+ ensure
21
+ queue << js
22
+ end
23
+
24
+ def call(name, data = nil, schema_name:)
25
+ with_js { |js| js.call(name, *arguments, &block) }
26
+ end
27
+
28
+ def apply_commit(document, commit, schema_name:)
29
+ call("applyCommit", {doc: document, commit: commit}, schema_name: schema_name)
30
+ end
31
+
32
+ def html_to_document(html, schema_name:)
33
+ call("htmlToDoc", html, schema_name: schema_name)
34
+ end
35
+
36
+ def document_to_html(document, schema_name:)
37
+ call("docToHtml", document, schema_name: schema_name)
38
+ end
39
+
40
+ private
41
+ # Thread-safe initialization of the NodeJS process queue
42
+ def initialize_queue
43
+ @queue_initialization_mutex.synchronize do
44
+ unless @queue_initialized
45
+ ::Collab.config.num_js_processes.times { @queue << ::Collab::JS::JSProcess.new }
46
+ @queue_initialized = true
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ class JSProcess
53
+ def initialize
54
+ @node = if defined?(Rails)
55
+ Dir.chdir(Rails.root) { open_node }
56
+ else
57
+ open_node
58
+ end
59
+ end
60
+
61
+ def call(name, data = nil, schema_name:)
62
+ req = {name: name, data: data, schemaPackage: ::Collab.config.schema_package, schemaName: schema_name}
63
+ @node.puts(JSON.generate(req))
64
+ res = JSON.parse(@node.gets)
65
+ raise ::Collab::JS::JSRuntimeError.new(res["error"]) if res["error"]
66
+ res["result"]
67
+ end
68
+
69
+ private
70
+ def open_node
71
+ IO.popen(["node", "-e", "require('rails-collab-server')"], "r+")
72
+ end
73
+ end
74
+
75
+ class JSRuntimeError < StandardError
76
+ def initialize(data)
77
+ @js_backtrace = data["stack"].split("\n").map{|f| "JavaScript #{f.strip}"} if data["stack"]
78
+
79
+ super(data["name"] + ": " + data["message"])
80
+ end
81
+
82
+ def backtrace
83
+ return unless val = super
84
+
85
+ if @js_backtrace
86
+ @js_backtrace + val
87
+ else
88
+ val
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -1,5 +1,5 @@
1
1
  module Collab
2
- class Models::Base < ::Collab.config.application_record.constantize
2
+ class Models::Base < ::Collab.config.base_record.constantize
3
3
  self.abstract_class = true
4
4
  self.table_name_prefix = 'collab_'
5
5
  end
@@ -0,0 +1,53 @@
1
+ module Collab
2
+ class Models::Commit < ::Collab::Models::Base
3
+ belongs_to :document, class_name: ::Collab.config.document_model
4
+
5
+ validates :steps, presence: true
6
+ validates :document_version, presence: true
7
+ validates :ref, length: { maximum: 36 }
8
+
9
+ after_create_commit :broadcast
10
+
11
+ def self.from_json(data)
12
+ new(document_version: data["v"]&.to_i, steps: data["steps"], ref: data["ref"])
13
+ end
14
+
15
+ def as_json
16
+ {
17
+ v: document_version,
18
+ steps: steps,
19
+ ref: ref
20
+ }
21
+ end
22
+
23
+ def apply_later
24
+ raise "cannot apply persisted commit" if self.persisted?
25
+ raise "commit not valid" unless self.valid?
26
+ return false if self.document.document_version != self.document_version
27
+
28
+ ::Collab.config.commit_job.constantize.perform_later(self.document, as_json)
29
+ end
30
+
31
+ def apply!
32
+ raise "cannot apply persisted commit" if self.persisted?
33
+ raise "commit not valid" unless self.valid?
34
+ return false if self.document.document_version != self.document_version # optimization, prevents need to apply lock if outdated
35
+
36
+ self.document.with_lock do
37
+ return false if self.document.document_version != self.document_version
38
+
39
+ return false unless result = ::Collab::JS.apply_commit(self.document, self.to_json, schema_name: self.document.schema_name)
40
+
41
+ self.document.document = result["doc"]
42
+ self.document.document_version = self.document_version
43
+
44
+ self.document.save!
45
+ self.save!
46
+ end
47
+ end
48
+
49
+ def broadcast
50
+ ::Collab.config.channel_name.constantize.broadcast_to(document, as_json)
51
+ end
52
+ end
53
+ end
@@ -1,73 +1,52 @@
1
1
  module Collab
2
2
  class Models::Document < ::Collab::Models::Base
3
3
  belongs_to :attached, polymorphic: true
4
- has_many :transactions, class_name: ::Collab.config.document_transaction_model, foreign_key: :document_id
4
+ has_many :commits, class_name: ::Collab.config.commit_model, foreign_key: :document_id
5
5
 
6
- validates :document, presence: true
6
+ validates :content, presence: true
7
7
  validates :document_version, presence: true, numericality: {only_integer: true, greater_than_or_equal_to: 0}
8
8
  validates :schema_name, presence: true
9
9
 
10
- before_save :nullify_serialized_html, unless: :serialized_html_fresh?
11
- after_save :delete_old_transactions
10
+ after_save :delete_old_commits
12
11
 
13
- # The already-serialized html version of this document
14
- def serialized_html
15
- return super if serialized_html_fresh?
16
- end
17
-
18
- def serialized_html_fresh?
19
- serialized_html_version == document_version
20
- end
21
-
22
- # Serialize the document to html - will be cached if possible (somewhat expensive)
12
+ # Serialize the document to html, uses cached if possible.
13
+ # Note that this may lock the document
23
14
  def to_html
24
15
  return serialized_html if serialized_html
25
16
 
26
- serialized_html = ::Collab::Bridge.current.document_to_html(self.document, schema_name: schema_name)
27
- self.update! serialized_html: serialized_html, serialized_html_version: document_version
28
- serialized_html
17
+ serialized_version = self.document_version
18
+ ::Collab::JS.document_to_html(self.content, schema_name: schema_name).tap do |serialized_html|
19
+ Thread.new do # use a thread to prevent deadlocks and avoid incuring the cost of an inline-write
20
+ self.with_lock do
21
+ self.update_attribute(:serialized_html, serialized_html) if serialized_version == self.version and self.serialized_html.nil?
22
+ end
23
+ end
24
+ end
29
25
  end
30
26
 
31
27
  def from_html(html)
32
- self.document = ::Collab::Bridge.current.html_to_document(html, schema_name: schema_name)
33
- end
34
-
35
- def perform_transaction_later(data)
36
- ::Collab::DocumentTransactionJob.perform_later(@document, data)
28
+ self.content = ::Collab::JS.html_to_document(html, schema_name: schema_name)
37
29
  end
38
30
 
39
- def apply_transaction_now(data)
40
- return unless data["v"].is_a?(Integer)
41
-
42
- return if (data["v"] - 1) != self.document_version # if expired between queue and perform, there is no need to aquire a lock. this is an optimization - not a guarantee
43
- with_lock do
44
- return if (data["v"] - 1) != self.document_version # now that we've aquired a lock to the record, ensure that we're still accessing the correct version
45
-
46
- transaction_result = ::Collab::Bridge.current.apply_transaction(self.document, data, schema_name: self.schema_name)
47
- return unless transaction_result # check to make sure the transaction succeeded
48
-
49
- self.document = transaction_result["doc"]
50
- self.document_version = data["v"]
51
- save!
52
-
53
- transactions.create! document_version: self.document_version, steps: data["steps"], ref: data["ref"]
54
- end
31
+ def commit_later(data)
32
+ commits.from_json(data).apply_later
55
33
  end
56
34
 
57
35
  def as_json
58
- {id: id, document: document, version: self.document_version}
36
+ {id: id, content: content, version: document_version}
59
37
  end
60
38
 
61
- private
62
-
63
- def nullify_serialized_html
39
+ def content_will_change!
40
+ super
64
41
  self.serialized_html = nil
65
42
  end
66
43
 
67
- def delete_old_transactions
68
- cutoff = document_version - ::Collab.config.max_transactions
44
+ private
45
+
46
+ def delete_old_commits
47
+ cutoff = document_version - ::Collab.config.max_commit_history_length
69
48
  return if cutoff <= 0
70
- transactions.where("document_version < ?", cutoff).delete_all
49
+ commits.where("document_version < ?", cutoff).delete_all
71
50
  end
72
51
  end
73
52
  end
@@ -1,3 +1,3 @@
1
1
  module Collab
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.1'
3
3
  end
@@ -17,8 +17,10 @@ module Collab
17
17
  end
18
18
 
19
19
  def copy_files
20
+ @primary_key_type = Rails.application.config.generators.active_record[:primary_key_type]
21
+
20
22
  migration_template(
21
- "create_collab_tables.rb",
23
+ "create_collab_tables.rb.erb",
22
24
  "db/migrate/create_collab_tables.rb",
23
25
  )
24
26
 
@@ -4,16 +4,19 @@ class CollabDocumentChannel < ApplicationCable::Channel
4
4
  private
5
5
 
6
6
  # Find the document to subscribe to based on the params passed to the channel
7
- # Authorization may also be performed here (raise an error)
7
+ # Authorization may also be performed here (raise an error to prevent subscription)
8
8
  def find_document
9
9
  Collab::Models::Document.find(params[:document_id]).tap do |document|
10
- raise "authorization failed"
10
+ # TODO: Replace with your own authorization logic
11
+ raise "authorization not implemented"
11
12
  end
12
13
  end
13
14
 
14
- # Called when a client submits a transaction in order to update a document
15
- # You should throw an error if unauthorized
16
- def authorize_submit!
17
- raise "authorization failed"
15
+ # Called a commit is first received for processing
16
+ # Throw an error to prevent the commit from being processed
17
+ # You should consider adding some type of rate-limiting here
18
+ def authorize_commit!(data)
19
+ # TODO: Replace with your own authorization logic
20
+ raise "authorization not implemented"
18
21
  end
19
22
  end
@@ -0,0 +1,28 @@
1
+ class CreateCollabTables < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :collab_documents<%= ", id: #{@primary_key_type.inspect}" if @primary_key_type %> do |t|
4
+ t.references :attached, null: false, index: false, polymorphic: true<%= ", type: #{@primary_key_type.inspect}" if @primary_key_type %>
5
+ t.string :attached_as, null: false
6
+
7
+ t.index [:attached_type, :attached_id, :attached_as], name: "index_collab_documents_on_attached"
8
+
9
+ t.jsonb :content, null: false
10
+ t.string :schema_name, null: false
11
+ t.integer :document_version, null: false, default: 0
12
+ t.text :serialized_html
13
+
14
+ t.timestamps
15
+ end
16
+
17
+ create_table :collab_commits, id: false do |t|
18
+ t.references :document, null: false, foreign_key: {to_table: :collab_documents}, index: false<%= ", type: #{@primary_key_type.inspect}" if @primary_key_type %>
19
+ t.integer :document_version, null: false
20
+ t.index [:document_id, :document_version], unique: true, order: {document_version: :asc}, name: "index_collab_commits"
21
+
22
+ t.jsonb :steps, array: true, null: false
23
+ t.string :ref
24
+
25
+ t.datetime :created_at, null: false
26
+ end
27
+ end
28
+ end
@@ -4,27 +4,22 @@ Collab.config do |c|
4
4
  # To use a Git repo, see https://docs.npmjs.com/files/package.json#git-urls-as-dependencies
5
5
  c.schema_package = "prosemirror-schema-basic"
6
6
  # How many old transactions to keep per document
7
- c.max_transactions = 250
7
+ c.max_commit_history_length = 250
8
+ # How many NodeJS child processes to run (shared among all threads)
9
+ c.num_js_processes = 3
8
10
 
9
- # ActionCable settings
10
- # ====================
11
11
  # The document channel to use for collaboration
12
- # If you change this, you must pass {channel: "[ChannelName]"} as subscription params to the Javascript client
13
- c.channel_name = "::CollabDocumentChannel"
12
+ # If you change this, you must pass the value as {channel: "[ChannelName]"} in the params from the ActionCable client
13
+ c.channel = "CollabDocumentChannel"
14
14
 
15
- # ActionJob settings
16
- # ==================
17
- # The base job class to use
18
- c.application_job = "::ApplicationJob"
19
- # The job queue to use for DocumentTransaction jobs
20
- c.queue_document_transaction_job_as = :default
15
+ # The class which jobs in the gem should inherit from
16
+ c.base_job = "ApplicationJob"
17
+ # The jobs to use, if you want to implement your own jobs
18
+ # c.commit_job = "..."
21
19
 
22
-
23
- # ActiveRecord settings
24
- # =====================
25
20
  # The class which models in the gem should inherit from
26
- c.application_record = "::ApplicationRecord"
27
- # If you want to use your own document model or document transaction model,
28
- c.document_model = "::Collab::Models::Document"
29
- c.document_transaction_model = "::Collab::Models::DocumentTransaction"
21
+ c.base_record = "ApplicationRecord"
22
+ # The models to use, if you want to implement your own models
23
+ # c.document_model = "..."
24
+ # c.commit_model = "..."
30
25
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: collab
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Aubin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-31 00:00:00.000000000 Z
11
+ date: 2020-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -52,20 +52,20 @@ extensions: []
52
52
  extra_rdoc_files: []
53
53
  files:
54
54
  - Rakefile
55
- - app/jobs/collab/document_transaction_job.rb
55
+ - app/jobs/collab/commit_job.rb
56
56
  - lib/collab.rb
57
- - lib/collab/bridge.rb
58
57
  - lib/collab/channel.rb
59
58
  - lib/collab/config.rb
59
+ - lib/collab/engine.rb
60
60
  - lib/collab/has_collaborative_document.rb
61
+ - lib/collab/js.rb
61
62
  - lib/collab/models/base.rb
63
+ - lib/collab/models/commit.rb
62
64
  - lib/collab/models/document.rb
63
- - lib/collab/models/document_transaction.rb
64
- - lib/collab/railtie.rb
65
65
  - lib/collab/version.rb
66
66
  - lib/generators/collab/install/install_generator.rb
67
67
  - lib/generators/collab/install/templates/channel.rb
68
- - lib/generators/collab/install/templates/create_collab_tables.rb
68
+ - lib/generators/collab/install/templates/create_collab_tables.rb.erb
69
69
  - lib/generators/collab/install/templates/initializer.rb
70
70
  - lib/tasks/collab_tasks.rake
71
71
  homepage: https://github.com/benaubin/rails-collab
@@ -1,7 +0,0 @@
1
- class Collab::DocumentTransactionJob < ::Collab.config.application_job.constantize
2
- queue_as ::Collab.config.queue_document_transaction_job_as
3
-
4
- def perform(document, data)
5
- document.apply_transaction_now(data)
6
- end
7
- end
@@ -1,54 +0,0 @@
1
- require "collab/config"
2
- require "json"
3
-
4
- module Collab
5
- class Bridge
6
- class JSRuntimeError < StandardError
7
- def initialize(data)
8
- @js_backtrace = data["stack"].split("\n").map{|f| "JavaScript #{f.strip}"} if data["stack"]
9
-
10
- super(data["name"] + ": " + data["message"])
11
- end
12
-
13
- def backtrace
14
- return unless val = super
15
-
16
- if @js_backtrace
17
- @js_backtrace + val
18
- else
19
- val
20
- end
21
- end
22
- end
23
-
24
- def initialize
25
- @node = Dir.chdir(Rails.root) do
26
- IO.popen(["node", "-e", "require('rails-collab-server')"], "r+")
27
- end
28
- end
29
-
30
- def self.current
31
- @current ||= new
32
- end
33
-
34
- def call(name, data = nil, schema_name:)
35
- req = {name: name, data: data, schemaPackage: ::Collab.config.schema_package, schemaName: schema_name}
36
- @node.puts(JSON.generate(req))
37
- res = JSON.parse(@node.gets)
38
- raise ::Collab::Bridge::JSRuntimeError.new(res["error"]) if res["error"]
39
- res["result"]
40
- end
41
-
42
- def apply_transaction(document, transaction, schema_name:)
43
- call("applyTransaction", {doc: document, data: transaction}, schema_name: schema_name)
44
- end
45
-
46
- def html_to_document(html, schema_name:)
47
- call("htmlToDoc", html, schema_name: schema_name)
48
- end
49
-
50
- def document_to_html(document, schema_name:)
51
- call("docToHtml", document, schema_name: schema_name)
52
- end
53
- end
54
- end
@@ -1,22 +0,0 @@
1
- module Collab
2
- class Models::DocumentTransaction < ::Collab::Models::Base
3
- belongs_to :document, class_name: ::Collab.config.document_model
4
-
5
- validates :steps, presence: true
6
- validates :document_version, presence: true
7
-
8
- after_create_commit :broadcast
9
-
10
- def as_json
11
- {
12
- v: document_version,
13
- steps: steps,
14
- ref: ref
15
- }
16
- end
17
-
18
- def broadcast
19
- ::Collab.config.channel_name.constantize.broadcast_to(document, as_json)
20
- end
21
- end
22
- end
@@ -1,4 +0,0 @@
1
- module Collab
2
- class Railtie < ::Rails::Railtie
3
- end
4
- end
@@ -1,32 +0,0 @@
1
- class CreateCollabTables < ActiveRecord::Migration[6.0]
2
- def change
3
- create_table :collab_documents, id: :uuid do |t|
4
- t.references :attached, null: false, index: false, type: :uuid, polymorphic: true
5
- t.string :attached_as
6
- t.index [:attached_type, :attached_id, :attached_as], name: "index_collaborative_documents_on_attached"
7
-
8
- t.string :schema_name, null: false
9
-
10
- t.jsonb :document, null: false
11
- t.integer :document_version, null: false, default: 0
12
-
13
- t.text :serialized_html
14
- t.integer :serialized_html_version
15
-
16
- t.timestamps
17
- end
18
-
19
- create_table :collab_document_transactions, id: false do |t|
20
- t.references :document, null: false, foreign_key: {to_table: :collab_documents}, type: :uuid, index: false
21
-
22
- t.jsonb :steps, array: true, null: false
23
- t.integer :document_version, null: false
24
-
25
- t.string :ref
26
-
27
- t.index [:document_id, :document_version], unique: true, order: {document_version: :asc}, name: "index_collaborative_document_transactions"
28
-
29
- t.timestamps
30
- end
31
- end
32
- end