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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4cefb08a9d7ab294f9084f150f89cda921982fffdb0ec7b0a5bc702ede1d23b8
4
- data.tar.gz: 21a484a10c58a3f551e95af064b5f718b9496ba7840ac6c1ac84142b859942db
3
+ metadata.gz: dc6d5adec8f9ee32ba34d1ba8dae8cb3da062e39dd1b67d5fc3e219be116d675
4
+ data.tar.gz: ae77057a26203e90fbb8770e1b41864edcff69058cd80b65011eb414ddf2365a
5
5
  SHA512:
6
- metadata.gz: 59d07f836a83dde4e4a24a8a5f326ff5cbc7a751d92fb6df66ee22b5b31ac1aa63326675167d45bcd8c572a0e4afeb831774a468eb45097a71c6940a79ce2681
7
- data.tar.gz: 807c603b1085819ed003ed8053f81b6fec59ba895c920bc24ac178b0d3188fac554a2ed9b2dcb80ceceb2a26fc5effe6af5d52819f66d041ad7959cfc30957da
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.default_app_name`
94
+ #### `Journaled.default_stream_name `
128
95
 
129
- This is described in the proceeding paragraph and is used to specify which app name to use, which corresponds to which Journaled Stream to send events too.
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
- ActiveRecords do by default.
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
- if %w(development test).include? Rails.env
23
- require 'rspec/core'
24
- require 'rspec/core/rake_task'
25
- RSpec::Core::RakeTask.new
9
+ require 'rubocop/rake_task'
10
+ RuboCop::RakeTask.new
26
11
 
27
- require 'rubocop/rake_task'
28
- RuboCop::RakeTask.new
12
+ require 'rspec/core'
13
+ require 'rspec/core/rake_task'
14
+ RSpec::Core::RakeTask.new(:spec)
29
15
 
30
- task(:default).clear
16
+ def default_task
31
17
  if ENV['APPRAISAL_INITIALIZED'] || ENV['CI']
32
- task default: %i(rubocop spec)
18
+ %i(rubocop spec)
33
19
  else
34
20
  require 'appraisal'
35
21
  Appraisal::Task.new
36
- task default: :appraisal
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, instance_accessor: false, instance_predicate: false
6
- before_action do
7
- RequestStore.store[:journaled_actor_proc] = self.class._journaled_actor_method_name &&
8
- -> { send(self.class._journaled_actor_method_name) }
9
- end
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/UncommunicativeMethodParamName
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)
@@ -8,8 +8,7 @@ class Journaled::ActorUriProvider
8
8
  private
9
9
 
10
10
  def actor_global_id_uri
11
- actor = RequestStore.store[:journaled_actor_proc]&.call
12
- actor.to_global_id.to_s if actor
11
+ Journaled::Current.actor&.to_global_id&.to_s
13
12
  end
14
13
 
15
14
  def fallback_global_id_uri
@@ -2,20 +2,20 @@ class Journaled::Change
2
2
  include Journaled::Event
3
3
 
4
4
  attr_reader :table_name,
5
- :record_id,
6
- :database_operation,
7
- :logical_operation,
8
- :changes,
9
- :journaled_stream_name,
10
- :journaled_enqueue_opts,
11
- :actor
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
- :record_id,
15
- :database_operation,
16
- :logical_operation,
17
- :changes,
18
- :actor
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.each_with_object({}) do |(k, v), result|
56
- result[k] = v[index]
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
- cattr_accessor(:journaled_enqueue_opts, instance_writer: false) { {} }
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
- base_event_json_schema_validator.validate! serialized_event
29
- json_schema_validator.validate! serialized_event
30
- Journaled::DeliveryJob
31
- .set(journaled_enqueue_opts.reverse_merge(priority: Journaled.job_priority))
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 json_schema_validator
53
- @json_schema_validator ||= Journaled::JsonSchemaModel::Validator.new(journaled_schema_name)
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,14 @@
1
+ {
2
+ "type": "object",
3
+ "title": "tagged_event",
4
+ "additionalProperties": true,
5
+ "required": [
6
+ "tags"
7
+ ],
8
+ "properties": {
9
+ "tags": {
10
+ "type": "object",
11
+ "additionalProperties": true
12
+ }
13
+ }
14
+ }
@@ -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, Metrics/PerceivedComplexity
3
- unless force || !@klass.respond_to?(:journaled_attribute_names) || @klass.journaled_attribute_names.empty?
4
- conflicting_journaled_attribute_names = if updates.is_a?(Hash)
5
- @klass.journaled_attribute_names & updates.keys.map(&:to_sym)
6
- elsif updates.is_a?(String)
7
- @klass.journaled_attribute_names.select do |a|
8
- updates.match?(/\b(?<!')#{a}(?!')\b/)
9
- end
10
- else
11
- raise "unsupported type '#{updates.class}' for 'updates'"
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: