acta 0.2.0 → 0.4.0.alpha.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 +4 -4
- data/CHANGELOG.md +116 -0
- data/README.md +229 -33
- data/RELEASING.md +107 -0
- data/docs/README.md +32 -0
- data/docs/event_driven_pub_sub.md +258 -0
- data/docs/upcasters.md +303 -0
- data/gemfiles/rails_7_2.gemfile +20 -0
- data/gemfiles/rails_8_0.gemfile +20 -0
- data/gemfiles/rails_8_1.gemfile +20 -0
- data/lib/acta/errors.rb +38 -0
- data/lib/acta/events_query.rb +51 -4
- data/lib/acta/model.rb +7 -7
- data/lib/acta/reactor.rb +22 -0
- data/lib/acta/record.rb +49 -1
- data/lib/acta/testing/dsl.rb +53 -0
- data/lib/acta/testing.rb +33 -0
- data/lib/acta/types/array.rb +35 -0
- data/lib/acta/types/model.rb +39 -0
- data/lib/acta/upcaster.rb +239 -0
- data/lib/acta/version.rb +1 -1
- data/lib/acta.rb +37 -4
- data/lib/generators/acta/install/install_generator.rb +6 -6
- metadata +23 -16
- data/PLAN.md +0 -158
- data/lib/acta/array_type.rb +0 -30
- data/lib/acta/model_type.rb +0 -32
data/lib/acta.rb
CHANGED
|
@@ -17,6 +17,7 @@ require_relative "acta/projection"
|
|
|
17
17
|
require_relative "acta/reactor"
|
|
18
18
|
require_relative "acta/reactor_job"
|
|
19
19
|
require_relative "acta/command"
|
|
20
|
+
require_relative "acta/upcaster"
|
|
20
21
|
require_relative "acta/projection_managed"
|
|
21
22
|
require_relative "acta/railtie" if defined?(::Rails::Railtie)
|
|
22
23
|
|
|
@@ -26,6 +27,18 @@ ActiveSupport.on_load(:active_record) do
|
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
module Acta
|
|
30
|
+
# Global default for `Acta::Reactor` ActiveJob queue. Per-class
|
|
31
|
+
# `queue_as :foo` declarations on a Reactor override this. Apps with
|
|
32
|
+
# queue priority discipline typically set this to e.g. `:fast` in an
|
|
33
|
+
# initializer; left nil, ActiveJob's `:default` queue is used.
|
|
34
|
+
class << self
|
|
35
|
+
attr_writer :reactor_queue
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.reactor_queue
|
|
39
|
+
@reactor_queue
|
|
40
|
+
end
|
|
41
|
+
|
|
29
42
|
def self.adapter
|
|
30
43
|
@adapter ||= Adapters.for(Record.connection)
|
|
31
44
|
end
|
|
@@ -117,7 +130,10 @@ module Acta
|
|
|
117
130
|
event:,
|
|
118
131
|
reactor_class: registration[:handler_class]
|
|
119
132
|
) do
|
|
120
|
-
ReactorJob
|
|
133
|
+
job = ReactorJob
|
|
134
|
+
queue = registration[:handler_class].queue_name
|
|
135
|
+
job = job.set(queue: queue) if queue
|
|
136
|
+
job.perform_later(
|
|
121
137
|
event_uuid: event.uuid,
|
|
122
138
|
reactor_class: registration[:handler_class].name,
|
|
123
139
|
event_class: event.class.name
|
|
@@ -140,12 +156,29 @@ module Acta
|
|
|
140
156
|
projection_classes << klass unless projection_classes.include?(klass)
|
|
141
157
|
end
|
|
142
158
|
|
|
159
|
+
# Register a set of upcasters (a module/class that `include Acta::Upcaster`
|
|
160
|
+
# and declares `upcasts(...)` blocks). Idempotent — re-registering the
|
|
161
|
+
# same class is a no-op. See `Acta::Upcaster`.
|
|
162
|
+
def self.register_upcaster(klass)
|
|
163
|
+
upcaster_registry.register(klass)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def self.upcaster_registry
|
|
167
|
+
@upcaster_registry ||= Upcaster::Registry.new
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def self.reset_upcasters!
|
|
171
|
+
upcaster_registry.clear!
|
|
172
|
+
end
|
|
173
|
+
|
|
143
174
|
def self.rebuild!
|
|
144
175
|
Projection.applying! { truncate_projections! }
|
|
176
|
+
context = Upcaster::Context.new
|
|
145
177
|
Record.order(:id).find_each do |record|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
178
|
+
events.upcast_and_hydrate(record, context).each do |event|
|
|
179
|
+
dispatch(event, kind: :projection)
|
|
180
|
+
end
|
|
181
|
+
rescue ProjectionError, ReplayHaltedByUpcaster, FutureSchemaVersion
|
|
149
182
|
raise
|
|
150
183
|
rescue StandardError => e
|
|
151
184
|
raise ReplayError.new(record:, original: e)
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "rails/generators"
|
|
4
|
-
require "rails/generators/migration"
|
|
5
4
|
require "rails/generators/active_record"
|
|
5
|
+
require "rails/generators/active_record/migration"
|
|
6
6
|
|
|
7
7
|
module Acta
|
|
8
8
|
module Generators
|
|
9
9
|
class InstallGenerator < Rails::Generators::Base
|
|
10
|
-
include
|
|
10
|
+
include ActiveRecord::Generators::Migration
|
|
11
11
|
|
|
12
12
|
source_root File.expand_path("templates", __dir__)
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
end
|
|
14
|
+
class_option :database, type: :string, aliases: %i[--db],
|
|
15
|
+
desc: "The database for the events migration. By default, the current environment's primary database is used."
|
|
17
16
|
|
|
18
17
|
def create_migration_file
|
|
19
|
-
migration_template "create_acta_events.rb.tt",
|
|
18
|
+
migration_template "create_acta_events.rb.tt",
|
|
19
|
+
File.join(db_migrate_path, "create_acta_events.rb")
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: acta
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0.alpha.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tom Gladhill
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-05-23 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activejob
|
|
@@ -15,56 +15,56 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '7.2'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
25
|
+
version: '7.2'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: activemodel
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '
|
|
32
|
+
version: '7.2'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '
|
|
39
|
+
version: '7.2'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: activerecord
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
44
|
- - ">="
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '
|
|
46
|
+
version: '7.2'
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '
|
|
53
|
+
version: '7.2'
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: activesupport
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
57
57
|
requirements:
|
|
58
58
|
- - ">="
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '
|
|
60
|
+
version: '7.2'
|
|
61
61
|
type: :runtime
|
|
62
62
|
prerelease: false
|
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
|
65
65
|
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: '
|
|
67
|
+
version: '7.2'
|
|
68
68
|
description: |-
|
|
69
69
|
Acta ships a small, opinionated set of primitives for event-driven and
|
|
70
70
|
event-sourced Rails applications: events, handlers, projections, reactors,
|
|
@@ -80,8 +80,8 @@ files:
|
|
|
80
80
|
- ".tool-versions"
|
|
81
81
|
- CHANGELOG.md
|
|
82
82
|
- LICENSE
|
|
83
|
-
- PLAN.md
|
|
84
83
|
- README.md
|
|
84
|
+
- RELEASING.md
|
|
85
85
|
- Rakefile
|
|
86
86
|
- app/controllers/acta/web/application_controller.rb
|
|
87
87
|
- app/controllers/acta/web/events_controller.rb
|
|
@@ -90,13 +90,18 @@ files:
|
|
|
90
90
|
- app/views/acta/web/events/show.html.erb
|
|
91
91
|
- app/views/layouts/acta/web/application.html.erb
|
|
92
92
|
- config/routes.rb
|
|
93
|
+
- docs/README.md
|
|
94
|
+
- docs/event_driven_pub_sub.md
|
|
95
|
+
- docs/upcasters.md
|
|
96
|
+
- gemfiles/rails_7_2.gemfile
|
|
97
|
+
- gemfiles/rails_8_0.gemfile
|
|
98
|
+
- gemfiles/rails_8_1.gemfile
|
|
93
99
|
- lib/acta.rb
|
|
94
100
|
- lib/acta/actor.rb
|
|
95
101
|
- lib/acta/adapters.rb
|
|
96
102
|
- lib/acta/adapters/base.rb
|
|
97
103
|
- lib/acta/adapters/postgres.rb
|
|
98
104
|
- lib/acta/adapters/sqlite.rb
|
|
99
|
-
- lib/acta/array_type.rb
|
|
100
105
|
- lib/acta/command.rb
|
|
101
106
|
- lib/acta/current.rb
|
|
102
107
|
- lib/acta/errors.rb
|
|
@@ -104,7 +109,6 @@ files:
|
|
|
104
109
|
- lib/acta/events_query.rb
|
|
105
110
|
- lib/acta/handler.rb
|
|
106
111
|
- lib/acta/model.rb
|
|
107
|
-
- lib/acta/model_type.rb
|
|
108
112
|
- lib/acta/projection.rb
|
|
109
113
|
- lib/acta/projection_managed.rb
|
|
110
114
|
- lib/acta/railtie.rb
|
|
@@ -116,7 +120,10 @@ files:
|
|
|
116
120
|
- lib/acta/testing.rb
|
|
117
121
|
- lib/acta/testing/dsl.rb
|
|
118
122
|
- lib/acta/testing/matchers.rb
|
|
123
|
+
- lib/acta/types/array.rb
|
|
119
124
|
- lib/acta/types/encrypted_string.rb
|
|
125
|
+
- lib/acta/types/model.rb
|
|
126
|
+
- lib/acta/upcaster.rb
|
|
120
127
|
- lib/acta/version.rb
|
|
121
128
|
- lib/acta/web.rb
|
|
122
129
|
- lib/acta/web/engine.rb
|
|
@@ -128,10 +135,10 @@ homepage: https://github.com/whoojemaflip/acta
|
|
|
128
135
|
licenses:
|
|
129
136
|
- MIT
|
|
130
137
|
metadata:
|
|
131
|
-
homepage_uri: https://github.com/whoojemaflip/acta
|
|
132
138
|
source_code_uri: https://github.com/whoojemaflip/acta
|
|
133
139
|
changelog_uri: https://github.com/whoojemaflip/acta/blob/main/CHANGELOG.md
|
|
134
140
|
bug_tracker_uri: https://github.com/whoojemaflip/acta/issues
|
|
141
|
+
rubygems_mfa_required: 'true'
|
|
135
142
|
rdoc_options: []
|
|
136
143
|
require_paths:
|
|
137
144
|
- lib
|
|
@@ -139,14 +146,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
139
146
|
requirements:
|
|
140
147
|
- - ">="
|
|
141
148
|
- !ruby/object:Gem::Version
|
|
142
|
-
version: '3.
|
|
149
|
+
version: '3.2'
|
|
143
150
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
144
151
|
requirements:
|
|
145
152
|
- - ">="
|
|
146
153
|
- !ruby/object:Gem::Version
|
|
147
154
|
version: '0'
|
|
148
155
|
requirements: []
|
|
149
|
-
rubygems_version: 3.
|
|
156
|
+
rubygems_version: 3.6.2
|
|
150
157
|
specification_version: 4
|
|
151
158
|
summary: Lightweight event-driven and event-sourced primitives for Rails.
|
|
152
159
|
test_files: []
|
data/PLAN.md
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
# Acta Implementation Plan
|
|
2
|
-
|
|
3
|
-
Companion to the design doc (private, at `~/Sites/Journal/ideas/event_source_rails.md`).
|
|
4
|
-
This file is version-controlled alongside the code and tracks the milestone
|
|
5
|
-
breakdown for reaching v1.0.
|
|
6
|
-
|
|
7
|
-
## Conventions
|
|
8
|
-
|
|
9
|
-
- **Ruby hash shorthand** (Ruby 3.1+): `{ name:, age: }` when variables in
|
|
10
|
-
scope match keys. Applied everywhere.
|
|
11
|
-
- **RSpec** exclusively for v1. Minitest matcher support considered post-v1.
|
|
12
|
-
- **TDD**: every milestone is a sequence of small red → green → refactor
|
|
13
|
-
cycles. One behaviour per commit where practical.
|
|
14
|
-
- **Rubocop**: `rubocop-rails-omakase` style. Clean before each commit.
|
|
15
|
-
- **Commits**: atomic, imperative mood, one logical change each.
|
|
16
|
-
- **Semver** from v0.1. Public API stability begins at v1.0.
|
|
17
|
-
|
|
18
|
-
## Environment
|
|
19
|
-
|
|
20
|
-
- Ruby: 3.4+
|
|
21
|
-
- Rails floor: 8.1+
|
|
22
|
-
- Local: `~/Sites/acta`
|
|
23
|
-
- Remote: `git@github.com:whoojemaflip/acta.git`
|
|
24
|
-
|
|
25
|
-
## Milestone breakdown
|
|
26
|
-
|
|
27
|
-
Each milestone is independently shippable.
|
|
28
|
-
|
|
29
|
-
### M0 — Scaffolding ✅
|
|
30
|
-
|
|
31
|
-
Gem skeleton, RSpec, rubocop-rails-omakase, CI, README, LICENSE, CHANGELOG,
|
|
32
|
-
`PLAN.md` in repo. Baseline green build.
|
|
33
|
-
|
|
34
|
-
### M1 — First emit (the round-trip milestone) ✅
|
|
35
|
-
|
|
36
|
-
**Goal:** `Acta.emit(event)` persists a row; `Acta.events.last` reads it back.
|
|
37
|
-
|
|
38
|
-
1. Adapter seam — spec `Acta::Adapters::Base` interface; SQLite adapter stub.
|
|
39
|
-
2. Migration generator — `rails g acta:install` creates the events table.
|
|
40
|
-
3. `Acta::Model` — AM::Attributes + AM::Model + `to_acta_hash` / `from_acta_hash`
|
|
41
|
-
+ `validate!` in initialize raising `Acta::InvalidEvent`.
|
|
42
|
-
4. `Acta::Event < Acta::Model` — adds `uuid`, `event_type`, `event_version`,
|
|
43
|
-
`occurred_at`, `recorded_at`, `actor`.
|
|
44
|
-
5. `Acta::Actor` value object — `type`, `id`, `source`, `metadata`.
|
|
45
|
-
6. `Acta::Current` — CurrentAttributes with `actor`.
|
|
46
|
-
7. `Acta.configure` — connection + single-store `:default` registration
|
|
47
|
-
(latent store concept).
|
|
48
|
-
8. `Acta.emit(event)` — strict on missing actor (`Acta::MissingActor`);
|
|
49
|
-
persists via adapter; returns the persisted event.
|
|
50
|
-
9. `Acta.events` — query API returning `Acta::Event` instances from the log.
|
|
51
|
-
10. Error leaves so far: `Error`, `InvalidEvent`, `MissingActor`,
|
|
52
|
-
`ConfigurationError`, `AdapterError`.
|
|
53
|
-
|
|
54
|
-
**Checkpoint:** end-to-end spec that configures, emits, queries, asserts.
|
|
55
|
-
|
|
56
|
-
### M2 — Streams & concurrency ✅
|
|
57
|
-
|
|
58
|
-
1. Stream DSL — `stream :order, key: :order_id` on event classes.
|
|
59
|
-
2. Sequence calculation in SQLite adapter (BEGIN IMMEDIATE + SELECT MAX).
|
|
60
|
-
3. `ConcurrencyConflict` on unique-index violation.
|
|
61
|
-
4. Stream-scoped query — `Acta.events.for_stream(type:, key:)`.
|
|
62
|
-
5. `on_concurrent_write :raise` machinery (wires into M6 commands).
|
|
63
|
-
|
|
64
|
-
### M3 — Handlers & dispatch ✅
|
|
65
|
-
|
|
66
|
-
1. `Acta::Handler` base class + `on EventClass do |event| ... end` DSL.
|
|
67
|
-
2. Auto-registration via inheritance + Rails `eager_load_paths`.
|
|
68
|
-
3. Dispatch on emit (sync base handlers).
|
|
69
|
-
4. Registry isolation for specs — `Acta.reset_handlers!`.
|
|
70
|
-
|
|
71
|
-
### M4 — Projections ✅
|
|
72
|
-
|
|
73
|
-
1. `Acta::Projection < Acta::Handler` with sync+transactional contract.
|
|
74
|
-
2. Projections run inside emit transaction.
|
|
75
|
-
3. `ProjectionError` wraps underlying exception + projection class.
|
|
76
|
-
4. `Acta.rebuild!` — truncate projections, replay log, re-run projections.
|
|
77
|
-
5. Replay skips reactors (prep for M5).
|
|
78
|
-
|
|
79
|
-
### M5 — Reactors ✅
|
|
80
|
-
|
|
81
|
-
1. `Acta::Reactor < Acta::Handler` with after-commit + ActiveJob default.
|
|
82
|
-
2. `Acta::ReactorJob` — loads event by uuid, dispatches to reactor class.
|
|
83
|
-
3. `sync true` opt-in.
|
|
84
|
-
4. Skip on replay.
|
|
85
|
-
5. Actor propagation via `Acta::Current` serialized into ActiveJob.
|
|
86
|
-
|
|
87
|
-
### M6 — Commands ✅
|
|
88
|
-
|
|
89
|
-
1. `Acta::Command < Acta::Model` — param validation via AM::Attributes.
|
|
90
|
-
2. `stream :order, key: :order_id` on command — declares aggregate identity.
|
|
91
|
-
3. `on_concurrent_write :raise` — captures stream sequence at instantiation.
|
|
92
|
-
4. `.call(**params)` entry; `emit event` as instance method;
|
|
93
|
-
`InvalidCommand` on validation failure.
|
|
94
|
-
5. Auto-loading from `app/commands/`.
|
|
95
|
-
|
|
96
|
-
### M7 — Testing DSL (`Acta::Testing`) ✅
|
|
97
|
-
|
|
98
|
-
1. RSpec matchers: `emit(EventClass).with(...)`, `emit_events([...])`,
|
|
99
|
-
`not_to emit_any_events`.
|
|
100
|
-
2. `given_events { ... }` — seeds the log directly without running reactors.
|
|
101
|
-
3. `when_command(cmd)` — runs command, captures emitted events.
|
|
102
|
-
4. `then_emitted(EventClass, **attrs)` / `then_emitted_nothing_else`.
|
|
103
|
-
5. `Acta.test_mode { ... }` — inline reactors for the block.
|
|
104
|
-
6. Replay determinism helper —
|
|
105
|
-
`expect_projections_deterministic { ... }`.
|
|
106
|
-
|
|
107
|
-
### M8 — `Acta::Serializable` (AR piggyback) ✅
|
|
108
|
-
|
|
109
|
-
1. Concern adding `to_acta_hash` / `self.from_acta_hash(hash)` on AR classes.
|
|
110
|
-
2. `acta_serialize only: / except:` configuration.
|
|
111
|
-
3. Type dispatch for AR classes in event attributes.
|
|
112
|
-
4. Arrays of AR (`array_of:`) support.
|
|
113
|
-
5. Nested AR-in-AR round-trip.
|
|
114
|
-
6. STI support via `type` column capture.
|
|
115
|
-
7. Schema-drift tolerance — filter unknown keys on deserialize.
|
|
116
|
-
|
|
117
|
-
### M9 — Postgres adapter ✅
|
|
118
|
-
|
|
119
|
-
1. `Acta::Adapters::Postgres` implementation.
|
|
120
|
-
2. Advisory locks (`pg_advisory_xact_lock(hashtext(...))`) per stream.
|
|
121
|
-
3. `jsonb` column type + `uuid` column type + `gen_random_uuid()`.
|
|
122
|
-
4. Shared behaviour specs — `it_behaves_like "an Acta adapter"`.
|
|
123
|
-
5. CI matrix with both SQLite and Postgres.
|
|
124
|
-
6. Concurrency-specific specs exercising genuine concurrent writers.
|
|
125
|
-
|
|
126
|
-
### M10 — v1.0 polish ✅
|
|
127
|
-
|
|
128
|
-
1. Observability via `ActiveSupport::Notifications` —
|
|
129
|
-
`acta.event_emitted`, `acta.projection_applied`, `acta.reactor_enqueued`.
|
|
130
|
-
2. Remaining error leaves — `UnknownEventType`, `ReplayError`, gaps.
|
|
131
|
-
3. README rewrite with full worked examples.
|
|
132
|
-
4. Tag v1.0.0.
|
|
133
|
-
|
|
134
|
-
## Milestone dependencies
|
|
135
|
-
|
|
136
|
-
```
|
|
137
|
-
M0 → M1 → M2 → M3 → M4 ─┐
|
|
138
|
-
└──→ M5 ─┐
|
|
139
|
-
M6 ──────┴─→ M7 → M8 → M9 → M10
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
M4 and M5 are independent after M3. M6 can start after M2. M8 benefits from
|
|
143
|
-
M7. M9 can start anytime after M1 but is most valuable after M8.
|
|
144
|
-
|
|
145
|
-
## Out of scope for v1
|
|
146
|
-
|
|
147
|
-
- Upcasters (column reserved)
|
|
148
|
-
- Multi-store (latent concept, not exposed)
|
|
149
|
-
- MySQL adapter
|
|
150
|
-
- `Acta::Saga` / process managers
|
|
151
|
-
- LISTEN/NOTIFY or other pub/sub transport
|
|
152
|
-
- Snapshots
|
|
153
|
-
|
|
154
|
-
## Quality gates per commit
|
|
155
|
-
|
|
156
|
-
- `bundle exec rspec` green
|
|
157
|
-
- `bundle exec rubocop` clean
|
|
158
|
-
- Change has a spec unless it's pure refactoring
|
data/lib/acta/array_type.rb
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "active_model/type"
|
|
4
|
-
|
|
5
|
-
module Acta
|
|
6
|
-
class ArrayType < ActiveModel::Type::Value
|
|
7
|
-
def initialize(element_type)
|
|
8
|
-
super()
|
|
9
|
-
@element_type = element_type
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def cast(value)
|
|
13
|
-
return nil if value.nil?
|
|
14
|
-
|
|
15
|
-
Array(value).map { |el| @element_type.cast(el) }
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def serialize(value)
|
|
19
|
-
return nil if value.nil?
|
|
20
|
-
|
|
21
|
-
value.map { |el| @element_type.serialize(el) }
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def deserialize(value)
|
|
25
|
-
return nil if value.nil?
|
|
26
|
-
|
|
27
|
-
value.map { |el| @element_type.deserialize(el) }
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
data/lib/acta/model_type.rb
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "active_model/type"
|
|
4
|
-
|
|
5
|
-
module Acta
|
|
6
|
-
class ModelType < ActiveModel::Type::Value
|
|
7
|
-
def initialize(wrapped_class)
|
|
8
|
-
super()
|
|
9
|
-
@wrapped_class = wrapped_class
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def cast(value)
|
|
13
|
-
case value
|
|
14
|
-
when nil then nil
|
|
15
|
-
when @wrapped_class then value
|
|
16
|
-
when Hash then @wrapped_class.from_acta_hash(value)
|
|
17
|
-
else
|
|
18
|
-
raise ArgumentError, "Cannot cast #{value.class} (#{value.inspect}) to #{@wrapped_class}"
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def serialize(value)
|
|
23
|
-
return nil if value.nil?
|
|
24
|
-
|
|
25
|
-
value.to_acta_hash
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def deserialize(value)
|
|
29
|
-
cast(value)
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|