eventsimple 2.3.0 → 2.5.0
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 +12 -0
- data/Gemfile.lock +2 -2
- data/app/helpers/eventsimple/entities_helper.rb +16 -0
- data/app/views/eventsimple/entities/show.html.erb +4 -4
- data/app/views/layouts/eventsimple/application.html.erb +13 -0
- data/lib/eventsimple/entity.rb +4 -11
- data/lib/eventsimple/event.rb +6 -12
- data/lib/eventsimple/readonly.rb +116 -0
- data/lib/eventsimple/version.rb +1 -1
- data/lib/eventsimple.rb +7 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c49589e4a0e45a9149f99568927f76b0d17b1e6b22f73bb079edd1b27ea856cd
|
|
4
|
+
data.tar.gz: 95794663749d9759b1ddbb6867c6774ffc8df60a30449c408958fb68f7160dc3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c134a1dfdf5e16e888b2c9018756819417205f924609b095001cbd2802e1c97e27f0268b93c6eada4c036162cf4086b33c72127c2539d9fcd085cca7ca11b24f
|
|
7
|
+
data.tar.gz: d8edcf526493068375fc706d04180f206c73fcb4fc57848e1fc6c5cabfcb09690235f3550a5f7b6ac67f230e94deb56f6a5a91097a43476660f8e5b4aabfb84f
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## 2.5.0 - 2026-04-26
|
|
10
|
+
### Added
|
|
11
|
+
- Entities and events now raise `ActiveRecord::ReadOnlyRecord` when persistence methods that bypass ActiveRecord's built-in readonly check are invoked (`delete`, `update_column(s)`, `update_attribute(!)`, `touch`, `toggle!`, `increment!`, `decrement!`). Relation-level bulk SQL (`insert` / `insert_all` / `upsert` / `upsert_all` / `update_all` / `delete_all` / `touch_all`) is guarded the same way.
|
|
12
|
+
- `Eventsimple.enable_writes! { }` is the single API for opting in to writes (instance saves/updates and relation bulk SQL). While active, `readonly?` is bypassed for event-sourced models so `save`/`update`/`destroy` work inside the block.
|
|
13
|
+
|
|
14
|
+
### Removed
|
|
15
|
+
- Model instance `#enable_writes!` — calling it raises `Eventsimple::DeprecatedInstanceEnableWrites`. Use `Eventsimple.enable_writes! { ... }` instead.
|
|
16
|
+
|
|
17
|
+
## 2.4.0 - 2026-04-25
|
|
18
|
+
### Added
|
|
19
|
+
- Collapsible JSON viewer for Hash and Array values in the admin UI.
|
|
20
|
+
|
|
9
21
|
## 2.3.0 - 2026-02-24
|
|
10
22
|
### Added
|
|
11
23
|
- Per-event metadata class: pass `metadata_class:` to `drives_events_for` to use a custom metadata class for that event (Class or String).
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
eventsimple (2.
|
|
4
|
+
eventsimple (2.5.0)
|
|
5
5
|
concurrent-ruby (>= 1.2.3)
|
|
6
6
|
dry-struct (>= 1.8.0)
|
|
7
7
|
dry-types (>= 1.7.0)
|
|
@@ -458,7 +458,7 @@ CHECKSUMS
|
|
|
458
458
|
dry-types (1.9.0) sha256=7b656fe0a78d2432500ae1f29fefd6762f5a032ca7000e4f36bc111453d45d4d
|
|
459
459
|
erb (6.0.1) sha256=28ecdd99c5472aebd5674d6061e3c6b0a45c049578b071e5a52c2a7f13c197e5
|
|
460
460
|
erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
|
|
461
|
-
eventsimple (2.
|
|
461
|
+
eventsimple (2.5.0)
|
|
462
462
|
factory_bot (6.5.6) sha256=12beb373214dccc086a7a63763d6718c49769d5606f0501e0a4442676917e077
|
|
463
463
|
factory_bot_rails (6.5.1) sha256=d3cc4851eae4dea8a665ec4a4516895045e710554d2b5ac9e68b94d351bc6d68
|
|
464
464
|
ffi (1.17.3-aarch64-linux-gnu) sha256=28ad573df26560f0aedd8a90c3371279a0b2bd0b4e834b16a2baa10bd7a97068
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Eventsimple
|
|
4
|
+
module EntitiesHelper
|
|
5
|
+
def render_value(value)
|
|
6
|
+
case value
|
|
7
|
+
when nil
|
|
8
|
+
content_tag(:code, '-', class: 'entity-property')
|
|
9
|
+
when Hash, Array
|
|
10
|
+
content_tag(:div, '', class: 'json-viewer-target', data: { json: value.to_json })
|
|
11
|
+
else
|
|
12
|
+
content_tag(:code, value.to_s, class: 'entity-property')
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
<% @entity_changes.each do |property| %>
|
|
33
33
|
<tr class="<%= property[:is_changed] ? 'table-info' : '' %>">
|
|
34
34
|
<th scope="row"><%= property[:label] %></th>
|
|
35
|
-
<td
|
|
36
|
-
<td
|
|
35
|
+
<td><%= render_value(property[:historical_value]) %></td>
|
|
36
|
+
<td><%= render_value(property[:current_value]) %></td>
|
|
37
37
|
</tr>
|
|
38
38
|
<% end %>
|
|
39
39
|
</tbody>
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
<% @selected_event.data.to_h.each do |attr_name, attr_value| %>
|
|
65
65
|
<tr>
|
|
66
66
|
<td> <%= attr_name %></td>
|
|
67
|
-
<td
|
|
67
|
+
<td><%= render_value(attr_value) %></td>
|
|
68
68
|
</tr>
|
|
69
69
|
<% end %>
|
|
70
70
|
<% end %>
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
<% @selected_event.metadata.to_h.each do |attr_name, attr_value| %>
|
|
76
76
|
<tr>
|
|
77
77
|
<td> <%= attr_name %></td>
|
|
78
|
-
<td
|
|
78
|
+
<td><%= render_value(attr_value) %></td>
|
|
79
79
|
</tr>
|
|
80
80
|
<% end %>
|
|
81
81
|
</tbody>
|
|
@@ -25,6 +25,15 @@
|
|
|
25
25
|
integrity: "sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=",
|
|
26
26
|
crossorigin: "anonymous"
|
|
27
27
|
%>
|
|
28
|
+
<%= stylesheet_link_tag "https://cdn.jsdelivr.net/npm/jquery.json-viewer@1.5.0/json-viewer/jquery.json-viewer.css",
|
|
29
|
+
media: "all",
|
|
30
|
+
integrity: "sha256-rXfxviikI1RGZM3px6piq9ZL0YZuO5ETcO8+toY+DDY=",
|
|
31
|
+
crossorigin: "anonymous"
|
|
32
|
+
%>
|
|
33
|
+
<%= javascript_include_tag "https://cdn.jsdelivr.net/npm/jquery.json-viewer@1.5.0/json-viewer/jquery.json-viewer.js",
|
|
34
|
+
integrity: "sha256-Tbq6mI1/ZeudUckwCGhWMlV8S/Ae9cQRIf/yBDVg0Ek=",
|
|
35
|
+
crossorigin: "anonymous"
|
|
36
|
+
%>
|
|
28
37
|
|
|
29
38
|
<%= render partial: 'eventsimple/shared/style' %>
|
|
30
39
|
</head>
|
|
@@ -63,6 +72,10 @@
|
|
|
63
72
|
query.set(param, value);
|
|
64
73
|
link.attr("href",window.location.pathname + '?' + query.toString());
|
|
65
74
|
});
|
|
75
|
+
|
|
76
|
+
$('.json-viewer-target').each(function () {
|
|
77
|
+
$(this).jsonViewer($(this).data('json'), { collapsed: true, withQuotes: true, withLinks: false });
|
|
78
|
+
});
|
|
66
79
|
});
|
|
67
80
|
</script>
|
|
68
81
|
|
data/lib/eventsimple/entity.rb
CHANGED
|
@@ -25,7 +25,7 @@ module Eventsimple
|
|
|
25
25
|
has_many :events, class_name: event_klass.name.to_s,
|
|
26
26
|
foreign_key: :aggregate_id,
|
|
27
27
|
primary_key: aggregate_id,
|
|
28
|
-
dependent: :
|
|
28
|
+
dependent: :restrict_with_exception,
|
|
29
29
|
inverse_of: model_name.element.to_sym,
|
|
30
30
|
autosave: false,
|
|
31
31
|
validate: false
|
|
@@ -45,8 +45,11 @@ module Eventsimple
|
|
|
45
45
|
|
|
46
46
|
Eventsimple.configuration.ui_visible_model_names |= [name]
|
|
47
47
|
|
|
48
|
+
include Readonly
|
|
48
49
|
include InstanceMethods
|
|
49
50
|
extend ClassMethods
|
|
51
|
+
|
|
52
|
+
Readonly.install_relation_guards!(self)
|
|
50
53
|
end
|
|
51
54
|
|
|
52
55
|
module InstanceMethods
|
|
@@ -56,16 +59,6 @@ module Eventsimple
|
|
|
56
59
|
attributes == reprojected.attributes
|
|
57
60
|
end
|
|
58
61
|
|
|
59
|
-
def enable_writes!(&block)
|
|
60
|
-
was_readonly = @readonly
|
|
61
|
-
@readonly = false
|
|
62
|
-
|
|
63
|
-
return unless block
|
|
64
|
-
|
|
65
|
-
yield self
|
|
66
|
-
@readonly = was_readonly
|
|
67
|
-
end
|
|
68
|
-
|
|
69
62
|
def reproject(at: nil, skip_deleted: false)
|
|
70
63
|
event_history = at ? events.where('created_at <= ?', at).load : events.load
|
|
71
64
|
|
data/lib/eventsimple/event.rb
CHANGED
|
@@ -75,8 +75,11 @@ module Eventsimple
|
|
|
75
75
|
after_create :dispatch
|
|
76
76
|
after_create :readonly!
|
|
77
77
|
|
|
78
|
+
include Readonly
|
|
78
79
|
include InstanceMethods
|
|
79
80
|
extend ClassMethods
|
|
81
|
+
|
|
82
|
+
Readonly.install_relation_guards!(self)
|
|
80
83
|
end
|
|
81
84
|
|
|
82
85
|
module InstanceMethods
|
|
@@ -123,8 +126,9 @@ module Eventsimple
|
|
|
123
126
|
apply_timestamps(aggregate)
|
|
124
127
|
apply(aggregate)
|
|
125
128
|
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
Eventsimple.enable_writes! do
|
|
130
|
+
aggregate.save!
|
|
131
|
+
end
|
|
128
132
|
aggregate.readonly!
|
|
129
133
|
|
|
130
134
|
self.aggregate = aggregate
|
|
@@ -141,16 +145,6 @@ module Eventsimple
|
|
|
141
145
|
def aggregate=(aggregate)
|
|
142
146
|
public_send(:"#{_aggregate_klass.model_name.element}=", aggregate)
|
|
143
147
|
end
|
|
144
|
-
|
|
145
|
-
def enable_writes!(&block)
|
|
146
|
-
was_readonly = @readonly
|
|
147
|
-
@readonly = false
|
|
148
|
-
|
|
149
|
-
return unless block
|
|
150
|
-
|
|
151
|
-
yield self
|
|
152
|
-
@readonly = was_readonly
|
|
153
|
-
end
|
|
154
148
|
end
|
|
155
149
|
|
|
156
150
|
module ClassMethods
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Eventsimple
|
|
4
|
+
# Raised when +#enable_writes!+ is called on a model instance. Use
|
|
5
|
+
# {Eventsimple.enable_writes!} instead.
|
|
6
|
+
DeprecatedInstanceEnableWrites = Class.new(StandardError)
|
|
7
|
+
|
|
8
|
+
# Closes gaps where writes bypass +readonly!+ (instance helpers like +delete+,
|
|
9
|
+
# +update_column+, relation SQL like +upsert+ / +delete_all+ / +update_all+).
|
|
10
|
+
# Raises +ActiveRecord::ReadOnlyRecord+ unless {Eventsimple.enable_writes!} is active.
|
|
11
|
+
module Readonly
|
|
12
|
+
THREAD_KEY = :eventsimple_class_writes_depth
|
|
13
|
+
|
|
14
|
+
module RelationMethods
|
|
15
|
+
RELATION_WRITE_METHODS = %i[
|
|
16
|
+
insert insert!
|
|
17
|
+
insert_all insert_all!
|
|
18
|
+
upsert upsert_all
|
|
19
|
+
update_all delete_all touch_all
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
22
|
+
RELATION_WRITE_METHODS.each do |method_name|
|
|
23
|
+
define_method(method_name) do |*args, **kwargs, &block|
|
|
24
|
+
_raise_relation_readonly! unless Readonly.class_writes_enabled?
|
|
25
|
+
super(*args, **kwargs, &block)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def _raise_relation_readonly!
|
|
32
|
+
raise ActiveRecord::ReadOnlyRecord, "#{model.name} is marked as readonly"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class << self
|
|
37
|
+
def install_relation_guards!(model_class)
|
|
38
|
+
ActiveRecord::Delegation.delegated_classes.each do |relation_base|
|
|
39
|
+
delegate_class = model_class.relation_delegate_class(relation_base)
|
|
40
|
+
next if delegate_class.ancestors.include?(RelationMethods)
|
|
41
|
+
|
|
42
|
+
delegate_class.prepend(RelationMethods)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def enable_writes!
|
|
47
|
+
Thread.current[THREAD_KEY] = Thread.current[THREAD_KEY].to_i + 1
|
|
48
|
+
yield
|
|
49
|
+
ensure
|
|
50
|
+
d = Thread.current[THREAD_KEY].to_i - 1
|
|
51
|
+
Thread.current[THREAD_KEY] = d.positive? ? d : nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def class_writes_enabled?
|
|
55
|
+
Thread.current[THREAD_KEY].to_i.positive?
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def readonly?
|
|
60
|
+
return false if Readonly.class_writes_enabled?
|
|
61
|
+
|
|
62
|
+
super
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def enable_writes!(&_block)
|
|
66
|
+
raise DeprecatedInstanceEnableWrites, <<~MSG.squish
|
|
67
|
+
Use Eventsimple.enable_writes! { ... } instead of #{self.class.name}#enable_writes!
|
|
68
|
+
MSG
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def delete
|
|
72
|
+
_raise_readonly_record_error if readonly?
|
|
73
|
+
super
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def update_column(...)
|
|
77
|
+
_raise_readonly_record_error if readonly?
|
|
78
|
+
super
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def update_columns(...)
|
|
82
|
+
_raise_readonly_record_error if readonly?
|
|
83
|
+
super
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def update_attribute(...)
|
|
87
|
+
_raise_readonly_record_error if readonly?
|
|
88
|
+
super
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def update_attribute!(...)
|
|
92
|
+
_raise_readonly_record_error if readonly?
|
|
93
|
+
super
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def increment!(...)
|
|
97
|
+
_raise_readonly_record_error if readonly?
|
|
98
|
+
super
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def decrement!(...)
|
|
102
|
+
_raise_readonly_record_error if readonly?
|
|
103
|
+
super
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def toggle!(...)
|
|
107
|
+
_raise_readonly_record_error if readonly?
|
|
108
|
+
super
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def touch(...)
|
|
112
|
+
_raise_readonly_record_error if readonly?
|
|
113
|
+
super
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
data/lib/eventsimple/version.rb
CHANGED
data/lib/eventsimple.rb
CHANGED
|
@@ -21,6 +21,7 @@ require 'eventsimple/event_dispatcher'
|
|
|
21
21
|
require 'eventsimple/reactor'
|
|
22
22
|
require 'eventsimple/invalid_transition'
|
|
23
23
|
|
|
24
|
+
require 'eventsimple/readonly'
|
|
24
25
|
require 'eventsimple/entity'
|
|
25
26
|
require 'eventsimple/event'
|
|
26
27
|
|
|
@@ -36,5 +37,11 @@ module Eventsimple
|
|
|
36
37
|
def configure
|
|
37
38
|
yield(configuration)
|
|
38
39
|
end
|
|
40
|
+
|
|
41
|
+
# Unlocks writes for event-sourced models for the duration of the block (instance +save+,
|
|
42
|
+
# relation +update_all+, +upsert+, +delete_all+, etc.).
|
|
43
|
+
def enable_writes!(&block)
|
|
44
|
+
Readonly.enable_writes!(&block)
|
|
45
|
+
end
|
|
39
46
|
end
|
|
40
47
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: eventsimple
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Zulfiqar Ali
|
|
@@ -330,6 +330,7 @@ files:
|
|
|
330
330
|
- app/controllers/eventsimple/entities_controller.rb
|
|
331
331
|
- app/controllers/eventsimple/home_controller.rb
|
|
332
332
|
- app/controllers/eventsimple/models_controller.rb
|
|
333
|
+
- app/helpers/eventsimple/entities_helper.rb
|
|
333
334
|
- app/views/eventsimple/entities/show.html.erb
|
|
334
335
|
- app/views/eventsimple/home/index.html.erb
|
|
335
336
|
- app/views/eventsimple/models/show.html.erb
|
|
@@ -362,6 +363,7 @@ files:
|
|
|
362
363
|
- lib/eventsimple/outbox/models/cursor.rb
|
|
363
364
|
- lib/eventsimple/reactor.rb
|
|
364
365
|
- lib/eventsimple/reactor_worker.rb
|
|
366
|
+
- lib/eventsimple/readonly.rb
|
|
365
367
|
- lib/eventsimple/support/spec_helpers.rb
|
|
366
368
|
- lib/eventsimple/types.rb
|
|
367
369
|
- lib/eventsimple/types/encrypted_type.rb
|