journaled 4.0.0 → 4.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +179 -53
- data/Rakefile +10 -24
- data/app/controllers/concerns/journaled/actor.rb +8 -5
- data/app/models/concerns/journaled/changes.rb +5 -5
- data/app/models/journaled/actor_uri_provider.rb +1 -2
- data/app/models/journaled/change.rb +12 -12
- data/app/models/journaled/change_writer.rb +3 -2
- data/app/models/journaled/event.rb +24 -2
- data/app/models/journaled/writer.rb +17 -11
- data/journaled_schemas/tagged_event.json +14 -0
- data/lib/journaled/current.rb +18 -0
- data/lib/journaled/relation_change_protection.rb +11 -10
- data/lib/journaled/rspec.rb +86 -0
- data/lib/journaled/version.rb +1 -1
- data/lib/journaled.rb +14 -2
- data/spec/dummy/config.ru +1 -1
- data/spec/jobs/journaled/delivery_job_spec.rb +10 -10
- data/spec/lib/journaled_spec.rb +4 -6
- data/spec/models/concerns/journaled/actor_spec.rb +8 -7
- data/spec/models/concerns/journaled/changes_spec.rb +1 -1
- data/spec/models/journaled/actor_uri_provider_spec.rb +6 -5
- data/spec/models/journaled/change_writer_spec.rb +1 -1
- data/spec/models/journaled/event_spec.rb +78 -2
- data/spec/models/journaled/json_schema_model/validator_spec.rb +6 -6
- data/spec/models/journaled/writer_spec.rb +79 -12
- data/spec/rails_helper.rb +1 -1
- data/spec/spec_helper.rb +4 -0
- metadata +26 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc6d5adec8f9ee32ba34d1ba8dae8cb3da062e39dd1b67d5fc3e219be116d675
|
4
|
+
data.tar.gz: ae77057a26203e90fbb8770e1b41864edcff69058cd80b65011eb414ddf2365a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 05a99d3d5d530bca784fb44fa290d0ff661430263fbde8fa882f8c98a9dda2eabe76c82df142b6477e68439066cba52d66d875c035a7af6e9d6a2938c8985b82
|
7
|
+
data.tar.gz: 7726aa5152e545cdba1a50ef475f8df0ce37fb97edc2d4fa146c74f73b41c7096434948afeecbe61bc042138cae9c3b94dda475adbf9c74d42892b1d6286e069
|
data/README.md
CHANGED
@@ -18,6 +18,8 @@ add scoped ordering capability at a future date (and would gladly
|
|
18
18
|
entertain pull requests), but it is presently only designed to provide a
|
19
19
|
durable, eventually consistent record that discrete events happened.
|
20
20
|
|
21
|
+
**See [upgrades](#upgrades) below if you're upgrading from an older `journaled` version!**
|
22
|
+
|
21
23
|
## Installation
|
22
24
|
|
23
25
|
1. If you haven't already,
|
@@ -83,52 +85,15 @@ app's Gemfile.
|
|
83
85
|
|
84
86
|
The AWS principal whose credentials are in the environment will need to be allowed to assume this role.
|
85
87
|
|
86
|
-
### Upgrading from 3.1.0
|
87
|
-
|
88
|
-
Versions of Journaled prior to 4.0 relied directly on environment variables for stream names, but now stream names are configured directly.
|
89
|
-
When upgrading, you can use the following configuration to maintain the previous behavior:
|
90
|
-
|
91
|
-
```ruby
|
92
|
-
Journaled.default_stream_name = ENV['JOURNALED_STREAM_NAME']
|
93
|
-
```
|
94
|
-
|
95
|
-
If you previously specified a `Journaled.default_app_name`, you would have required a more precise environment variable name (substitute `{{upcase_app_name}}`):
|
96
|
-
|
97
|
-
```ruby
|
98
|
-
Journaled.default_stream_name = ENV["{{upcase_app_name}}_JOURNALED_STREAM_NAME"]
|
99
|
-
```
|
100
|
-
|
101
|
-
And if you had defined any `journaled_app_name` methods on `Journaled::Event` instances, you can replace them with the following:
|
102
|
-
|
103
|
-
```ruby
|
104
|
-
def journaled_stream_name
|
105
|
-
ENV['{{upcase_app_name}}_JOURNALED_STREAM_NAME']
|
106
|
-
end
|
107
|
-
```
|
108
|
-
|
109
|
-
When upgrading from 3.1 or below, `Journaled::DeliveryJob` will handle any jobs that remain in the queue by accepting an `app_name` argument. **This behavior will be removed in version 5.0**, so it is recommended to upgrade one major version at a time.
|
110
|
-
|
111
|
-
### Upgrading from 2.5.0
|
112
|
-
|
113
|
-
Versions of Journaled prior to 3.0 relied direclty on `delayed_job` and a "performable" class called `Journaled::Delivery`.
|
114
|
-
In 3.0, this was superceded by an ActiveJob class called `Journaled::DeliveryJob`, but the `Journaled::Delivery` class was not removed until 4.0.
|
115
|
-
|
116
|
-
Therefore, when upgrading from 2.5.0 or below, it is recommended to first upgrade to 3.1.0 (to allow any `Journaled::Delivery` jobs to finish working off) before upgrading to 4.0+.
|
117
|
-
|
118
|
-
The upgrade to 3.1.0 will require a working ActiveJob config. ActiveJob can be configured globally by setting `ActiveJob::Base.queue_adapter`, or just for Journaled jobs by setting `Journaled::DeliveryJob.queue_adapter`.
|
119
|
-
The `:delayed_job` queue adapter will allow you to continue relying on `delayed_job`. You may also consider switching your app(s) to [`delayed`](https://github.com/Betterment/delayed) and using the `:delayed` queue adapter.
|
120
|
-
|
121
88
|
## Usage
|
122
89
|
|
123
90
|
### Configuration
|
124
91
|
|
125
92
|
Journaling provides a number of different configuation options that can be set in Ruby using an initializer. Those values are:
|
126
93
|
|
127
|
-
#### `Journaled.
|
94
|
+
#### `Journaled.default_stream_name `
|
128
95
|
|
129
|
-
This is described in the
|
130
|
-
This is the default value for events that do NOT specify their own `#journaled_app_name`. For events that define their own `#journaled_app_name` method, that will take precedence over this default.
|
131
|
-
Ex: `Journaled.default_app_name = 'my_app'`
|
96
|
+
This is described in the "Installation" section above, and is used to specify which stream name to use.
|
132
97
|
|
133
98
|
#### `Journaled.job_priority` (default: 20)
|
134
99
|
|
@@ -185,8 +150,8 @@ class ApplicationController < ActionController::Base
|
|
185
150
|
end
|
186
151
|
```
|
187
152
|
|
188
|
-
Your authenticated entity must respond to `#to_global_id`, which
|
189
|
-
|
153
|
+
Your authenticated entity must respond to `#to_global_id`, which ActiveRecords do by default.
|
154
|
+
This feature relies on `ActiveSupport::CurrentAttributes` under the hood.
|
190
155
|
|
191
156
|
Every time any of the specified attributes is modified, or a `User`
|
192
157
|
record is created or destroyed, an event will be sent to Kinesis with the following attributes:
|
@@ -214,18 +179,6 @@ journaling. Note that the less-frequently-used methods `toggle`,
|
|
214
179
|
`increment*`, `decrement*`, and `update_counters` are not intercepted at
|
215
180
|
this time.
|
216
181
|
|
217
|
-
#### Testing
|
218
|
-
|
219
|
-
If you use RSpec (and have required `journaled/rspec` in your
|
220
|
-
`spec/rails_helper.rb`), you can regression-protect important journaling
|
221
|
-
config with the `journal_changes_to` matcher:
|
222
|
-
|
223
|
-
```ruby
|
224
|
-
it "journals exactly these things or there will be heck to pay" do
|
225
|
-
expect(User).to journal_changes_to(:email, :first_name, :last_name, as: :identity_change)
|
226
|
-
end
|
227
|
-
```
|
228
|
-
|
229
182
|
### Custom Journaling
|
230
183
|
|
231
184
|
For every custom implementation of journaling in your application, define the JSON schema for the attributes in your event.
|
@@ -310,6 +263,40 @@ An event like the following will be journaled to kinesis:
|
|
310
263
|
}
|
311
264
|
```
|
312
265
|
|
266
|
+
### Tagged Events
|
267
|
+
|
268
|
+
Events may be optionally marked as "tagged." This will add a `tags` field, intended for tracing and
|
269
|
+
auditing purposes.
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
class MyEvent
|
273
|
+
include Journaled::Event
|
274
|
+
|
275
|
+
journal_attributes :attr_1, :attr_2, tagged: true
|
276
|
+
end
|
277
|
+
```
|
278
|
+
|
279
|
+
You may then use `Journaled.tag!` and `Journaled.tagged` inside of your
|
280
|
+
`ApplicationController` and `ApplicationJob` classes (or anywhere else!) to tag
|
281
|
+
all events with request and job metadata:
|
282
|
+
|
283
|
+
```ruby
|
284
|
+
class ApplicationController < ActionController::Base
|
285
|
+
before_action do
|
286
|
+
Journaled.tag!(request_id: request.request_id, current_user_id: current_user&.id)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
class ApplicationJob < ActiveJob::Base
|
291
|
+
around_perform do |job, perform|
|
292
|
+
Journaled.tagged(job_id: job.id) { perform.call }
|
293
|
+
end
|
294
|
+
end
|
295
|
+
```
|
296
|
+
|
297
|
+
This feature relies on `ActiveSupport::CurrentAttributes` under the hood, so these tags are local to
|
298
|
+
the current thread, and will be cleared at the end of each request request/job.
|
299
|
+
|
313
300
|
### Helper methods for custom events
|
314
301
|
|
315
302
|
Journaled provides a couple helper methods that may be useful in your
|
@@ -353,6 +340,145 @@ Returns one of the following in order of preference:
|
|
353
340
|
In order for this to be most useful, you must configure your controller
|
354
341
|
as described in [Change Journaling](#change-journaling) above.
|
355
342
|
|
343
|
+
### Testing
|
344
|
+
|
345
|
+
If you use RSpec, you can test for journaling behaviors with the
|
346
|
+
`journal_event(s)_including` and `journal_changes_to` matchers. First, make
|
347
|
+
sure to require `journaled/rspec` in your spec setup (e.g.
|
348
|
+
`spec/rails_helper.rb`):
|
349
|
+
|
350
|
+
```ruby
|
351
|
+
require 'journaled/rspec'
|
352
|
+
```
|
353
|
+
|
354
|
+
#### Checking for specific events
|
355
|
+
|
356
|
+
The `journal_event_including` and `journal_events_including` matchers allow you
|
357
|
+
to check for one or more matching event being journaled:
|
358
|
+
|
359
|
+
```ruby
|
360
|
+
expect { my_code }
|
361
|
+
.to journal_event_including(name: 'foo')
|
362
|
+
expect { my_code }
|
363
|
+
.to journal_events_including({ name: 'foo', value: 1 }, { name: 'foo', value: 2 })
|
364
|
+
```
|
365
|
+
|
366
|
+
This will only perform matches on the specified fields (and will not match one
|
367
|
+
way or the other against unspecified fields). These matchers will also ignore
|
368
|
+
any extraneous events that are not positively matched (as they may be unrelated
|
369
|
+
to behavior under test).
|
370
|
+
|
371
|
+
When writing tests, pairing every positive assertion with a negative assertion
|
372
|
+
is a good practice, and so negative matching is also supported (via both
|
373
|
+
`.not_to` and `.to not_`):
|
374
|
+
|
375
|
+
```ruby
|
376
|
+
expect { my_code }
|
377
|
+
.not_to journal_events_including({ name: 'foo' }, { name: 'bar' })
|
378
|
+
expect { my_code }
|
379
|
+
.to raise_error(SomeError)
|
380
|
+
.and not_journal_event_including(name: 'foo') # the `not_` variant can chain off of `.and`
|
381
|
+
```
|
382
|
+
|
383
|
+
Several chainable modifiers are also available:
|
384
|
+
|
385
|
+
```ruby
|
386
|
+
expect { my_code }.to journal_event_including(name: 'foo')
|
387
|
+
.with_schema_name('my_event_schema')
|
388
|
+
.with_partition_key(user.id)
|
389
|
+
.with_stream_name('my_stream_name')
|
390
|
+
.with_enqueue_opts(run_at: future_time)
|
391
|
+
.with_priority(999)
|
392
|
+
```
|
393
|
+
|
394
|
+
All of this can be chained together to test for multiple sets of events with
|
395
|
+
multiple sets of options:
|
396
|
+
|
397
|
+
```ruby
|
398
|
+
expect { subject.journal! }
|
399
|
+
.to journal_events_including({ name: 'event1', value: 300 }, { name: 'event2', value: 200 })
|
400
|
+
.with_priority(10)
|
401
|
+
.and journal_event_including(name: 'event3', value: 100)
|
402
|
+
.with_priority(20)
|
403
|
+
.and not_journal_event_including(name: 'other_event')
|
404
|
+
```
|
405
|
+
|
406
|
+
#### Checking for `Journaled::Changes` declarations
|
407
|
+
|
408
|
+
The `journal_changes_to` matcher checks against the list of attributes specified
|
409
|
+
on the model. It does not actually test that an event is emitted within a given
|
410
|
+
codepath, and is instead intended to guard against accidental regressions that
|
411
|
+
may impact external consumers of these events:
|
412
|
+
|
413
|
+
```ruby
|
414
|
+
it "journals exactly these things or there will be heck to pay" do
|
415
|
+
expect(User).to journal_changes_to(:email, :first_name, :last_name, as: :identity_change)
|
416
|
+
end
|
417
|
+
```
|
418
|
+
|
419
|
+
### Instrumentation
|
420
|
+
|
421
|
+
When an event is enqueued, an `ActiveSupport::Notification` titled
|
422
|
+
`journaled.event.enqueue` is emitted. Its payload will include the `:event` and
|
423
|
+
its background job `:priority`.
|
424
|
+
|
425
|
+
This can be forwarded along to your preferred monitoring solution via a Rails
|
426
|
+
initializer:
|
427
|
+
|
428
|
+
```ruby
|
429
|
+
ActiveSupport::Notifications.subscribe('journaled.event.enqueue') do |*args|
|
430
|
+
payload = ActiveSupport::Notifications::Event.new(*args).payload
|
431
|
+
journaled_event = payload[:event]
|
432
|
+
|
433
|
+
tags = { priority: payload[:priority], event_type: journaled_event.journaled_attributes[:event_type] }
|
434
|
+
|
435
|
+
Statsd.increment('journaled.event.enqueue', tags: tags.map { |k,v| "#{k.to_s[0..64]}:#{v.to_s[0..255]}" })
|
436
|
+
end
|
437
|
+
```
|
438
|
+
|
439
|
+
## Upgrades
|
440
|
+
|
441
|
+
Since this gem relies on background jobs (which can remain in the queue across
|
442
|
+
code releases), this gem generally aims to support jobs enqueued by the prior
|
443
|
+
gem version.
|
444
|
+
|
445
|
+
As such, **we always recommend upgrading only one major version at a time.**
|
446
|
+
|
447
|
+
### Upgrading from 3.1.0
|
448
|
+
|
449
|
+
Versions of Journaled prior to 4.0 relied directly on environment variables for stream names, but now stream names are configured directly.
|
450
|
+
When upgrading, you can use the following configuration to maintain the previous behavior:
|
451
|
+
|
452
|
+
```ruby
|
453
|
+
Journaled.default_stream_name = ENV['JOURNALED_STREAM_NAME']
|
454
|
+
```
|
455
|
+
|
456
|
+
If you previously specified a `Journaled.default_app_name`, you would have required a more precise environment variable name (substitute `{{upcase_app_name}}`):
|
457
|
+
|
458
|
+
```ruby
|
459
|
+
Journaled.default_stream_name = ENV["{{upcase_app_name}}_JOURNALED_STREAM_NAME"]
|
460
|
+
```
|
461
|
+
|
462
|
+
And if you had defined any `journaled_app_name` methods on `Journaled::Event` instances, you can replace them with the following:
|
463
|
+
|
464
|
+
```ruby
|
465
|
+
def journaled_stream_name
|
466
|
+
ENV['{{upcase_app_name}}_JOURNALED_STREAM_NAME']
|
467
|
+
end
|
468
|
+
```
|
469
|
+
|
470
|
+
When upgrading from 3.1 or below, `Journaled::DeliveryJob` will handle any jobs that remain in the queue by accepting an `app_name` argument. **This behavior will be removed in version 5.0**, so it is recommended to upgrade one major version at a time.
|
471
|
+
|
472
|
+
### Upgrading from 2.5.0
|
473
|
+
|
474
|
+
Versions of Journaled prior to 3.0 relied direclty on `delayed_job` and a "performable" class called `Journaled::Delivery`.
|
475
|
+
In 3.0, this was superceded by an ActiveJob class called `Journaled::DeliveryJob`, but the `Journaled::Delivery` class was not removed until 4.0.
|
476
|
+
|
477
|
+
Therefore, when upgrading from 2.5.0 or below, it is recommended to first upgrade to 3.1.0 (to allow any `Journaled::Delivery` jobs to finish working off) before upgrading to 4.0+.
|
478
|
+
|
479
|
+
The upgrade to 3.1.0 will require a working ActiveJob config. ActiveJob can be configured globally by setting `ActiveJob::Base.queue_adapter`, or just for Journaled jobs by setting `Journaled::DeliveryJob.queue_adapter`.
|
480
|
+
The `:delayed_job` queue adapter will allow you to continue relying on `delayed_job`. You may also consider switching your app(s) to [`delayed`](https://github.com/Betterment/delayed) and using the `:delayed` queue adapter.
|
481
|
+
|
356
482
|
## Future improvements & issue tracking
|
357
483
|
Suggestions for enhancements to this engine are currently being tracked via Github Issues. Please feel free to open an
|
358
484
|
issue for a desired feature, as well as for any observed bugs.
|
data/Rakefile
CHANGED
@@ -4,37 +4,23 @@ rescue LoadError
|
|
4
4
|
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
5
|
end
|
6
6
|
|
7
|
-
require 'rdoc/task'
|
8
|
-
|
9
|
-
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
-
rdoc.rdoc_dir = 'rdoc'
|
11
|
-
rdoc.title = 'Journaled'
|
12
|
-
rdoc.options << '--line-numbers'
|
13
|
-
rdoc.rdoc_files.include('README.rdoc')
|
14
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
-
end
|
16
|
-
|
17
|
-
APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
|
18
|
-
load 'rails/tasks/engine.rake'
|
19
|
-
|
20
7
|
Bundler::GemHelper.install_tasks
|
21
8
|
|
22
|
-
|
23
|
-
|
24
|
-
require 'rspec/core/rake_task'
|
25
|
-
RSpec::Core::RakeTask.new
|
9
|
+
require 'rubocop/rake_task'
|
10
|
+
RuboCop::RakeTask.new
|
26
11
|
|
27
|
-
|
28
|
-
|
12
|
+
require 'rspec/core'
|
13
|
+
require 'rspec/core/rake_task'
|
14
|
+
RSpec::Core::RakeTask.new(:spec)
|
29
15
|
|
30
|
-
|
16
|
+
def default_task
|
31
17
|
if ENV['APPRAISAL_INITIALIZED'] || ENV['CI']
|
32
|
-
|
18
|
+
%i(rubocop spec)
|
33
19
|
else
|
34
20
|
require 'appraisal'
|
35
21
|
Appraisal::Task.new
|
36
|
-
|
22
|
+
%i(appraisal)
|
37
23
|
end
|
38
|
-
|
39
|
-
task 'db:test:prepare' => 'db:setup'
|
40
24
|
end
|
25
|
+
|
26
|
+
task(:default).clear.enhance(default_task)
|
@@ -2,11 +2,14 @@ module Journaled::Actor
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
included do
|
5
|
-
class_attribute :_journaled_actor_method_name,
|
6
|
-
before_action
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
class_attribute :_journaled_actor_method_name, instance_writer: false
|
6
|
+
before_action :_set_journaled_actor_proc, if: :_journaled_actor_method_name?
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def _set_journaled_actor_proc
|
12
|
+
Journaled::Current.journaled_actor_proc = -> { send(self.class._journaled_actor_method_name) }
|
10
13
|
end
|
11
14
|
|
12
15
|
class_methods do
|
@@ -39,8 +39,8 @@ module Journaled::Changes
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
-
def update_columns(attributes, force: false)
|
43
|
-
unless force || self.class.journaled_attribute_names.empty?
|
42
|
+
def update_columns(attributes, opts = { force: false })
|
43
|
+
unless opts[:force] || self.class.journaled_attribute_names.empty?
|
44
44
|
conflicting_journaled_attribute_names = self.class.journaled_attribute_names & attributes.keys.map(&:to_sym)
|
45
45
|
raise(<<~ERROR) if conflicting_journaled_attribute_names.present?
|
46
46
|
#update_columns aborted by Journaled::Changes due to journaled attributes:
|
@@ -56,7 +56,7 @@ module Journaled::Changes
|
|
56
56
|
end
|
57
57
|
|
58
58
|
class_methods do
|
59
|
-
def journal_changes_to(*attribute_names, as:, enqueue_with: {}) # rubocop:disable Naming/
|
59
|
+
def journal_changes_to(*attribute_names, as:, enqueue_with: {}) # rubocop:disable Naming/MethodParameterName
|
60
60
|
if attribute_names.empty? || attribute_names.any? { |n| !n.is_a?(Symbol) }
|
61
61
|
raise "one or more symbol attribute_name arguments is required"
|
62
62
|
end
|
@@ -69,8 +69,8 @@ module Journaled::Changes
|
|
69
69
|
end
|
70
70
|
|
71
71
|
if Rails::VERSION::MAJOR > 5 || (Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR >= 2)
|
72
|
-
def delete(id_or_array, force: false)
|
73
|
-
if force || journaled_attribute_names.empty?
|
72
|
+
def delete(id_or_array, opts = { force: false })
|
73
|
+
if opts[:force] || journaled_attribute_names.empty?
|
74
74
|
where(primary_key => id_or_array).delete_all(force: true)
|
75
75
|
else
|
76
76
|
raise(<<~ERROR)
|
@@ -2,20 +2,20 @@ class Journaled::Change
|
|
2
2
|
include Journaled::Event
|
3
3
|
|
4
4
|
attr_reader :table_name,
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
5
|
+
:record_id,
|
6
|
+
:database_operation,
|
7
|
+
:logical_operation,
|
8
|
+
:changes,
|
9
|
+
:journaled_stream_name,
|
10
|
+
:journaled_enqueue_opts,
|
11
|
+
:actor
|
12
12
|
|
13
13
|
journal_attributes :table_name,
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
:record_id,
|
15
|
+
:database_operation,
|
16
|
+
:logical_operation,
|
17
|
+
:changes,
|
18
|
+
:actor
|
19
19
|
|
20
20
|
def initialize(table_name:,
|
21
21
|
record_id:,
|
@@ -1,5 +1,6 @@
|
|
1
1
|
class Journaled::ChangeWriter
|
2
2
|
attr_reader :model, :change_definition
|
3
|
+
|
3
4
|
delegate :attribute_names, :logical_operation, to: :change_definition
|
4
5
|
|
5
6
|
def initialize(model:, change_definition:)
|
@@ -52,8 +53,8 @@ class Journaled::ChangeWriter
|
|
52
53
|
private
|
53
54
|
|
54
55
|
def pluck_changed_values(change_hash, index:)
|
55
|
-
change_hash.
|
56
|
-
|
56
|
+
change_hash.transform_values do |v|
|
57
|
+
v[index]
|
57
58
|
end
|
58
59
|
end
|
59
60
|
|
@@ -39,12 +39,18 @@ module Journaled::Event
|
|
39
39
|
Journaled.default_stream_name
|
40
40
|
end
|
41
41
|
|
42
|
+
def tagged?
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
42
46
|
private
|
43
47
|
|
44
48
|
class_methods do
|
45
|
-
def journal_attributes(*args, enqueue_with: {})
|
49
|
+
def journal_attributes(*args, enqueue_with: {}, tagged: false)
|
46
50
|
journaled_attributes.concat(args)
|
47
51
|
journaled_enqueue_opts.merge!(enqueue_with)
|
52
|
+
|
53
|
+
include Tagged if tagged
|
48
54
|
end
|
49
55
|
|
50
56
|
def journaled_attributes
|
@@ -57,8 +63,24 @@ module Journaled::Event
|
|
57
63
|
end
|
58
64
|
|
59
65
|
included do
|
60
|
-
|
66
|
+
class_attribute :journaled_enqueue_opts, default: {}
|
61
67
|
|
62
68
|
journal_attributes :id, :event_type, :created_at
|
63
69
|
end
|
70
|
+
|
71
|
+
module Tagged
|
72
|
+
extend ActiveSupport::Concern
|
73
|
+
|
74
|
+
included do
|
75
|
+
journaled_attributes << :tags
|
76
|
+
end
|
77
|
+
|
78
|
+
def tags
|
79
|
+
Journaled::Current.tags
|
80
|
+
end
|
81
|
+
|
82
|
+
def tagged?
|
83
|
+
true
|
84
|
+
end
|
85
|
+
end
|
64
86
|
end
|
@@ -25,18 +25,28 @@ class Journaled::Writer
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def journal!
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
.perform_later(delivery_perform_args)
|
28
|
+
validate!
|
29
|
+
ActiveSupport::Notifications.instrument('journaled.event.enqueue', event: journaled_event, priority: job_opts[:priority]) do
|
30
|
+
Journaled::DeliveryJob.set(job_opts).perform_later(**delivery_perform_args)
|
31
|
+
end
|
33
32
|
end
|
34
33
|
|
35
34
|
private
|
36
35
|
|
37
36
|
attr_reader :journaled_event
|
37
|
+
|
38
38
|
delegate(*EVENT_METHOD_NAMES, to: :journaled_event)
|
39
39
|
|
40
|
+
def validate!
|
41
|
+
schema_validator('base_event').validate! serialized_event
|
42
|
+
schema_validator('tagged_event').validate! serialized_event if journaled_event.tagged?
|
43
|
+
schema_validator(journaled_schema_name).validate! serialized_event
|
44
|
+
end
|
45
|
+
|
46
|
+
def job_opts
|
47
|
+
journaled_enqueue_opts.reverse_merge(priority: Journaled.job_priority)
|
48
|
+
end
|
49
|
+
|
40
50
|
def delivery_perform_args
|
41
51
|
{
|
42
52
|
serialized_event: serialized_event,
|
@@ -49,12 +59,8 @@ class Journaled::Writer
|
|
49
59
|
@serialized_event ||= journaled_attributes.to_json
|
50
60
|
end
|
51
61
|
|
52
|
-
def
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
|
-
def base_event_json_schema_validator
|
57
|
-
@base_event_json_schema_validator ||= Journaled::JsonSchemaModel::Validator.new('base_event')
|
62
|
+
def schema_validator(schema_name)
|
63
|
+
Journaled::JsonSchemaModel::Validator.new(schema_name)
|
58
64
|
end
|
59
65
|
|
60
66
|
def respond_to_all?(object, method_names)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Journaled
|
2
|
+
class Current < ActiveSupport::CurrentAttributes
|
3
|
+
attribute :tags
|
4
|
+
attribute :journaled_actor_proc
|
5
|
+
|
6
|
+
def tags=(value)
|
7
|
+
super(value.freeze)
|
8
|
+
end
|
9
|
+
|
10
|
+
def tags
|
11
|
+
attributes[:tags] ||= {}.freeze
|
12
|
+
end
|
13
|
+
|
14
|
+
def actor
|
15
|
+
journaled_actor_proc&.call
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -1,14 +1,15 @@
|
|
1
1
|
module Journaled::RelationChangeProtection
|
2
|
-
def update_all(updates, force: false) # rubocop:disable Metrics/AbcSize
|
3
|
-
unless force || !@klass.respond_to?(:journaled_attribute_names) || @klass.journaled_attribute_names.empty?
|
4
|
-
conflicting_journaled_attribute_names =
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
2
|
+
def update_all(updates, opts = { force: false }) # rubocop:disable Metrics/AbcSize
|
3
|
+
unless opts[:force] || !@klass.respond_to?(:journaled_attribute_names) || @klass.journaled_attribute_names.empty?
|
4
|
+
conflicting_journaled_attribute_names = case updates
|
5
|
+
when Hash
|
6
|
+
@klass.journaled_attribute_names & updates.keys.map(&:to_sym)
|
7
|
+
when String
|
8
|
+
@klass.journaled_attribute_names.select do |a|
|
9
|
+
updates.match?(/\b(?<!')#{a}(?!')\b/)
|
10
|
+
end
|
11
|
+
else
|
12
|
+
raise "unsupported type '#{updates.class}' for 'updates'"
|
12
13
|
end
|
13
14
|
raise(<<~ERROR) if conflicting_journaled_attribute_names.present?
|
14
15
|
.update_all aborted by Journaled::Changes due to journaled attributes:
|