journaled 4.0.0 → 4.3.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 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: