journaled 4.1.0 → 5.0.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/README.md +148 -46
- data/Rakefile +10 -24
- data/app/jobs/journaled/delivery_job.rb +17 -28
- data/app/models/concerns/journaled/changes.rb +5 -5
- data/app/models/journaled/change.rb +12 -12
- data/app/models/journaled/change_writer.rb +3 -2
- data/app/models/journaled/event.rb +1 -1
- data/app/models/journaled/writer.rb +32 -15
- data/lib/journaled/connection.rb +48 -0
- data/lib/journaled/engine.rb +5 -0
- data/lib/journaled/errors.rb +3 -0
- data/lib/journaled/relation_change_protection.rb +11 -10
- data/lib/journaled/rspec.rb +86 -0
- data/lib/journaled/transaction_ext.rb +31 -0
- data/lib/journaled/version.rb +1 -1
- data/lib/journaled.rb +17 -13
- metadata +54 -97
- data/spec/dummy/README.rdoc +0 -28
- data/spec/dummy/Rakefile +0 -6
- data/spec/dummy/bin/bundle +0 -3
- data/spec/dummy/bin/rails +0 -4
- data/spec/dummy/bin/rake +0 -4
- data/spec/dummy/config/application.rb +0 -25
- data/spec/dummy/config/boot.rb +0 -5
- data/spec/dummy/config/database.yml +0 -6
- data/spec/dummy/config/environment.rb +0 -5
- data/spec/dummy/config/environments/development.rb +0 -24
- data/spec/dummy/config/environments/test.rb +0 -37
- data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/spec/dummy/config/initializers/cookies_serializer.rb +0 -3
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +0 -4
- data/spec/dummy/config/initializers/inflections.rb +0 -16
- data/spec/dummy/config/initializers/mime_types.rb +0 -4
- data/spec/dummy/config/initializers/session_store.rb +0 -3
- data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/spec/dummy/config/locales/en.yml +0 -23
- data/spec/dummy/config/routes.rb +0 -56
- data/spec/dummy/config/secrets.yml +0 -22
- data/spec/dummy/config.ru +0 -4
- data/spec/dummy/db/schema.rb +0 -18
- data/spec/dummy/public/404.html +0 -67
- data/spec/dummy/public/422.html +0 -67
- data/spec/dummy/public/500.html +0 -66
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/jobs/journaled/delivery_job_spec.rb +0 -276
- data/spec/lib/journaled_spec.rb +0 -91
- data/spec/models/concerns/journaled/actor_spec.rb +0 -47
- data/spec/models/concerns/journaled/changes_spec.rb +0 -106
- data/spec/models/database_change_protection_spec.rb +0 -109
- data/spec/models/journaled/actor_uri_provider_spec.rb +0 -42
- data/spec/models/journaled/change_writer_spec.rb +0 -281
- data/spec/models/journaled/event_spec.rb +0 -236
- data/spec/models/journaled/json_schema_model/validator_spec.rb +0 -133
- data/spec/models/journaled/writer_spec.rb +0 -174
- data/spec/rails_helper.rb +0 -19
- data/spec/spec_helper.rb +0 -20
- data/spec/support/environment_spec_helper.rb +0 -16
data/spec/lib/journaled_spec.rb
DELETED
@@ -1,91 +0,0 @@
|
|
1
|
-
require 'rails_helper'
|
2
|
-
|
3
|
-
RSpec.describe Journaled do
|
4
|
-
it "is enabled in production" do
|
5
|
-
allow(Rails).to receive(:env).and_return("production")
|
6
|
-
expect(described_class).to be_enabled
|
7
|
-
end
|
8
|
-
|
9
|
-
it "is disabled in development" do
|
10
|
-
allow(Rails).to receive(:env).and_return("development")
|
11
|
-
expect(described_class).not_to be_enabled
|
12
|
-
end
|
13
|
-
|
14
|
-
it "is disabled in test" do
|
15
|
-
allow(Rails).to receive(:env).and_return("test")
|
16
|
-
expect(described_class).not_to be_enabled
|
17
|
-
end
|
18
|
-
|
19
|
-
it "is enabled in whatevs" do
|
20
|
-
allow(Rails).to receive(:env).and_return("whatevs")
|
21
|
-
expect(described_class).to be_enabled
|
22
|
-
end
|
23
|
-
|
24
|
-
it "is enabled when explicitly enabled in development" do
|
25
|
-
with_env(JOURNALED_ENABLED: true) do
|
26
|
-
allow(Rails).to receive(:env).and_return("development")
|
27
|
-
expect(described_class).to be_enabled
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
it "is disabled when explicitly disabled in production" do
|
32
|
-
with_env(JOURNALED_ENABLED: false) do
|
33
|
-
allow(Rails).to receive(:env).and_return("production")
|
34
|
-
expect(described_class).not_to be_enabled
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
it "is disabled when explicitly disabled with empty string" do
|
39
|
-
with_env(JOURNALED_ENABLED: '') do
|
40
|
-
allow(Rails).to receive(:env).and_return("production")
|
41
|
-
expect(described_class).not_to be_enabled
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
describe "#actor_uri" do
|
46
|
-
it "delegates to ActorUriProvider" do
|
47
|
-
allow(Journaled::ActorUriProvider).to receive(:instance)
|
48
|
-
.and_return(instance_double(Journaled::ActorUriProvider, actor_uri: "my actor uri"))
|
49
|
-
expect(described_class.actor_uri).to eq "my actor uri"
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
describe '.detect_queue_adapter!' do
|
54
|
-
it 'raises an error unless the queue adapter is DB-backed' do
|
55
|
-
expect { described_class.detect_queue_adapter! }.to raise_error <<~MSG
|
56
|
-
Journaled has detected an unsupported ActiveJob queue adapter: `:test`
|
57
|
-
|
58
|
-
Journaled jobs must be enqueued transactionally to your primary database.
|
59
|
-
|
60
|
-
Please install the appropriate gems and set `queue_adapter` to one of the following:
|
61
|
-
- `:delayed`
|
62
|
-
- `:delayed_job`
|
63
|
-
- `:good_job`
|
64
|
-
- `:que`
|
65
|
-
|
66
|
-
Read more at https://github.com/Betterment/journaled
|
67
|
-
MSG
|
68
|
-
end
|
69
|
-
|
70
|
-
context 'when the queue adapter is supported' do
|
71
|
-
before do
|
72
|
-
stub_const("ActiveJob::QueueAdapters::DelayedAdapter", Class.new)
|
73
|
-
ActiveJob::Base.disable_test_adapter
|
74
|
-
ActiveJob::Base.queue_adapter = :delayed
|
75
|
-
end
|
76
|
-
|
77
|
-
around do |example|
|
78
|
-
begin
|
79
|
-
example.run
|
80
|
-
ensure
|
81
|
-
ActiveJob::Base.queue_adapter = :test
|
82
|
-
ActiveJob::Base.enable_test_adapter(ActiveJob::QueueAdapters::TestAdapter.new)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
it 'does not raise an error' do
|
87
|
-
expect { described_class.detect_queue_adapter! }.not_to raise_error
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
require 'rails_helper'
|
2
|
-
|
3
|
-
# This is a controller mixin, but testing as a model spec!
|
4
|
-
RSpec.describe Journaled::Actor do
|
5
|
-
let(:user) { double("User") }
|
6
|
-
let(:klass) do
|
7
|
-
Class.new do
|
8
|
-
cattr_accessor(:before_actions) { [] }
|
9
|
-
|
10
|
-
def self.before_action(method_name, _opts)
|
11
|
-
before_actions << method_name
|
12
|
-
end
|
13
|
-
|
14
|
-
include Journaled::Actor
|
15
|
-
|
16
|
-
self.journaled_actor = :current_user
|
17
|
-
|
18
|
-
def current_user
|
19
|
-
nil
|
20
|
-
end
|
21
|
-
|
22
|
-
def trigger_before_actions
|
23
|
-
before_actions.each { |method_name| send(method_name) }
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
subject { klass.new }
|
29
|
-
|
30
|
-
it "Stores a thunk returning nil if current_user returns nil" do
|
31
|
-
subject.trigger_before_actions
|
32
|
-
|
33
|
-
allow(subject).to receive(:current_user).and_return(nil)
|
34
|
-
|
35
|
-
expect(Journaled::Current.journaled_actor_proc.call).to eq nil
|
36
|
-
expect(Journaled::Current.actor).to eq nil
|
37
|
-
end
|
38
|
-
|
39
|
-
it "Stores a thunk returning current_user if it is set when called" do
|
40
|
-
subject.trigger_before_actions
|
41
|
-
|
42
|
-
allow(subject).to receive(:current_user).and_return(user)
|
43
|
-
|
44
|
-
expect(Journaled::Current.journaled_actor_proc.call).to eq user
|
45
|
-
expect(Journaled::Current.actor).to eq user
|
46
|
-
end
|
47
|
-
end
|
@@ -1,106 +0,0 @@
|
|
1
|
-
require 'rails_helper'
|
2
|
-
|
3
|
-
RSpec.describe Journaled::Changes do
|
4
|
-
let(:klass) do
|
5
|
-
Class.new do
|
6
|
-
cattr_accessor :after_create_hooks
|
7
|
-
self.after_create_hooks = []
|
8
|
-
cattr_accessor :after_save_hooks
|
9
|
-
self.after_save_hooks = []
|
10
|
-
cattr_accessor :after_destroy_hooks
|
11
|
-
self.after_destroy_hooks = []
|
12
|
-
|
13
|
-
def self.after_create(&hook)
|
14
|
-
after_create_hooks << hook
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.after_save(opts, &hook)
|
18
|
-
# This is a back-door assertion to prevent regressions in the module's hook definition behavior
|
19
|
-
raise "expected `unless: :saved_change_to_id?`" unless opts[:unless] == :saved_change_to_id?
|
20
|
-
|
21
|
-
after_save_hooks << hook
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.after_destroy(&hook)
|
25
|
-
after_destroy_hooks << hook
|
26
|
-
end
|
27
|
-
|
28
|
-
include Journaled::Changes
|
29
|
-
journal_changes_to :my_heart, as: :change_of_heart
|
30
|
-
|
31
|
-
def trigger_after_create_hooks
|
32
|
-
after_create_hooks.each { |proc| instance_eval(&proc) }
|
33
|
-
end
|
34
|
-
|
35
|
-
def trigger_after_save_hooks
|
36
|
-
after_save_hooks.each { |proc| instance_eval(&proc) }
|
37
|
-
end
|
38
|
-
|
39
|
-
def trigger_after_destroy_hooks
|
40
|
-
after_destroy_hooks.each { |proc| instance_eval(&proc) }
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
subject { klass.new }
|
46
|
-
|
47
|
-
let(:change_writer) { double(Journaled::ChangeWriter, create: true, update: true, delete: true) }
|
48
|
-
|
49
|
-
before do
|
50
|
-
allow(Journaled::ChangeWriter).to receive(:new) do |opts|
|
51
|
-
expect(opts[:model]).to eq(subject)
|
52
|
-
expect(opts[:change_definition].logical_operation).to eq(:change_of_heart)
|
53
|
-
change_writer
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
it "can be asserted on with our matcher" do
|
58
|
-
expect(klass).to journal_changes_to(:my_heart, as: :change_of_heart)
|
59
|
-
|
60
|
-
expect(klass).not_to journal_changes_to(:foobaloo, as: :an_event_to_remember)
|
61
|
-
|
62
|
-
expect {
|
63
|
-
expect(klass).to journal_changes_to(:foobaloo, as: :an_event_to_remember)
|
64
|
-
}.to raise_error(/> to journal changes to :foobaloo as :an_event_to_remember/)
|
65
|
-
|
66
|
-
expect {
|
67
|
-
expect(klass).not_to journal_changes_to(:my_heart, as: :change_of_heart)
|
68
|
-
}.to raise_error(/> not to journal changes to :my_heart as :change_of_heart/)
|
69
|
-
end
|
70
|
-
|
71
|
-
it "has a single change definition" do
|
72
|
-
expect(klass._journaled_change_definitions.length).to eq 1
|
73
|
-
end
|
74
|
-
|
75
|
-
it "journals create events on create" do
|
76
|
-
subject.trigger_after_create_hooks
|
77
|
-
|
78
|
-
expect(change_writer).to have_received(:create)
|
79
|
-
expect(Journaled::ChangeWriter).to have_received(:new)
|
80
|
-
end
|
81
|
-
|
82
|
-
it "journals update events on save" do
|
83
|
-
subject.trigger_after_save_hooks
|
84
|
-
|
85
|
-
expect(change_writer).to have_received(:update)
|
86
|
-
expect(Journaled::ChangeWriter).to have_received(:new)
|
87
|
-
end
|
88
|
-
|
89
|
-
it "journals delete events on destroy" do
|
90
|
-
subject.trigger_after_destroy_hooks
|
91
|
-
|
92
|
-
expect(change_writer).to have_received(:delete)
|
93
|
-
expect(Journaled::ChangeWriter).to have_received(:new)
|
94
|
-
end
|
95
|
-
|
96
|
-
context 'when DJ opts are provided' do
|
97
|
-
before do
|
98
|
-
klass.journal_changes_to :thing, as: :other_thing, enqueue_with: { asdf: 1, foo: 'bar' }
|
99
|
-
end
|
100
|
-
|
101
|
-
it 'sets them on the model' do
|
102
|
-
expect(klass.journaled_enqueue_opts).to eq(asdf: 1, foo: 'bar')
|
103
|
-
expect(klass.new.journaled_enqueue_opts).to eq(asdf: 1, foo: 'bar')
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
@@ -1,109 +0,0 @@
|
|
1
|
-
require 'rails_helper'
|
2
|
-
|
3
|
-
if Rails::VERSION::MAJOR > 5 || (Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR >= 2)
|
4
|
-
# rubocop:disable Rails/SkipsModelValidations
|
5
|
-
RSpec.describe "Raw database change protection" do
|
6
|
-
let(:journaled_class) do
|
7
|
-
Class.new(ActiveRecord::Base) do
|
8
|
-
include Journaled::Changes
|
9
|
-
|
10
|
-
self.table_name = 'widgets'
|
11
|
-
|
12
|
-
journal_changes_to :name, as: :attempt
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
let(:journaled_class_with_no_journaled_columns) do
|
17
|
-
Class.new(ActiveRecord::Base) do
|
18
|
-
include Journaled::Changes
|
19
|
-
|
20
|
-
self.table_name = 'widgets'
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
describe "the relation" do
|
25
|
-
describe "#update_all" do
|
26
|
-
it "refuses on journaled columns passed as hash" do
|
27
|
-
expect { journaled_class.update_all(name: nil) }.to raise_error(/aborted by Journaled/)
|
28
|
-
end
|
29
|
-
|
30
|
-
it "refuses on journaled columns passed as string" do
|
31
|
-
expect { journaled_class.update_all("\"name\" = NULL") }.to raise_error(/aborted by Journaled/)
|
32
|
-
expect { journaled_class.update_all("name = null") }.to raise_error(/aborted by Journaled/)
|
33
|
-
expect { journaled_class.update_all("widgets.name = null") }.to raise_error(/aborted by Journaled/)
|
34
|
-
expect { journaled_class.update_all("other_column = 'name'") }.not_to raise_error
|
35
|
-
end
|
36
|
-
|
37
|
-
it "succeeds on unjournaled columns" do
|
38
|
-
expect { journaled_class.update_all(other_column: "") }.not_to raise_error
|
39
|
-
end
|
40
|
-
|
41
|
-
it "succeeds when forced on journaled columns" do
|
42
|
-
expect { journaled_class.update_all({ name: nil }, force: true) }.not_to raise_error
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
describe "#delete" do
|
47
|
-
it "refuses if journaled columns exist" do
|
48
|
-
expect { journaled_class.delete(1) }.to raise_error(/aborted by Journaled/)
|
49
|
-
end
|
50
|
-
|
51
|
-
it "succeeds if no journaled columns exist" do
|
52
|
-
expect { journaled_class_with_no_journaled_columns.delete(1) }.not_to raise_error
|
53
|
-
end
|
54
|
-
|
55
|
-
it "succeeds if journaled columns exist when forced" do
|
56
|
-
expect { journaled_class.delete(1, force: true) }.not_to raise_error
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
describe "#delete_all" do
|
61
|
-
it "refuses if journaled columns exist" do
|
62
|
-
expect { journaled_class.delete_all }.to raise_error(/aborted by Journaled/)
|
63
|
-
end
|
64
|
-
|
65
|
-
it "succeeds if no journaled columns exist" do
|
66
|
-
expect { journaled_class_with_no_journaled_columns.delete_all }.not_to raise_error
|
67
|
-
end
|
68
|
-
|
69
|
-
it "succeeds if journaled columns exist when forced" do
|
70
|
-
expect { journaled_class.delete_all(force: true) }.not_to raise_error
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
describe "an instance" do
|
76
|
-
subject { journaled_class.create!(name: 'foo') }
|
77
|
-
|
78
|
-
describe "#update_columns" do
|
79
|
-
it "refuses on journaled columns" do
|
80
|
-
expect { subject.update_columns(name: nil) }.to raise_error(/aborted by Journaled/)
|
81
|
-
end
|
82
|
-
|
83
|
-
it "succeeds on unjournaled columns" do
|
84
|
-
expect { subject.update_columns(other_column: "") }.not_to raise_error
|
85
|
-
end
|
86
|
-
|
87
|
-
it "succeeds when forced on journaled columns" do
|
88
|
-
expect { subject.update_columns({ name: nil }, force: true) }.not_to raise_error
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
describe "#delete" do
|
93
|
-
it "refuses if journaled columns exist" do
|
94
|
-
expect { subject.delete }.to raise_error(/aborted by Journaled/)
|
95
|
-
end
|
96
|
-
|
97
|
-
it "succeeds if no journaled columns exist" do
|
98
|
-
instance = journaled_class_with_no_journaled_columns.create!
|
99
|
-
expect { instance.delete }.not_to raise_error
|
100
|
-
end
|
101
|
-
|
102
|
-
it "succeeds if journaled columns exist when forced" do
|
103
|
-
expect { subject.delete(force: true) }.not_to raise_error
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
# rubocop:enable Rails/SkipsModelValidations
|
109
|
-
end
|
@@ -1,42 +0,0 @@
|
|
1
|
-
require 'rails_helper'
|
2
|
-
|
3
|
-
RSpec.describe Journaled::ActorUriProvider do
|
4
|
-
describe "#actor_uri" do
|
5
|
-
let(:current_attributes) { double(:[] => nil) }
|
6
|
-
let(:actor) { double(to_global_id: actor_gid) }
|
7
|
-
let(:actor_gid) { double(to_s: "my_fancy_gid") }
|
8
|
-
let(:program_name) { "/usr/local/bin/puma_or_something" }
|
9
|
-
|
10
|
-
subject { described_class.instance }
|
11
|
-
|
12
|
-
around do |example|
|
13
|
-
orig_program_name = $PROGRAM_NAME
|
14
|
-
$PROGRAM_NAME = program_name
|
15
|
-
example.run
|
16
|
-
$PROGRAM_NAME = orig_program_name
|
17
|
-
end
|
18
|
-
|
19
|
-
before do
|
20
|
-
allow(Journaled::Current.instance)
|
21
|
-
.to receive(:attributes).and_return(current_attributes)
|
22
|
-
end
|
23
|
-
|
24
|
-
it "returns the global ID of the entity returned by Current.journaled_actor_proc.call if set" do
|
25
|
-
allow(current_attributes).to receive(:[]).and_return(-> { actor })
|
26
|
-
expect(subject.actor_uri).to eq("my_fancy_gid")
|
27
|
-
expect(current_attributes).to have_received(:[]).with(:journaled_actor_proc)
|
28
|
-
end
|
29
|
-
|
30
|
-
context "when running in rake" do
|
31
|
-
let(:program_name) { "rake" }
|
32
|
-
it "slurps up command line username if available" do
|
33
|
-
allow(Etc).to receive(:getlogin).and_return("my_unix_username")
|
34
|
-
expect(subject.actor_uri).to eq("gid://local/my_unix_username")
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
it "falls back to printing out a GID of bare app name" do
|
39
|
-
expect(subject.actor_uri).to eq("gid://dummy")
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
@@ -1,281 +0,0 @@
|
|
1
|
-
require 'rails_helper'
|
2
|
-
|
3
|
-
RSpec.describe Journaled::ChangeWriter do
|
4
|
-
let(:model) do
|
5
|
-
now = Time.zone.now
|
6
|
-
double(
|
7
|
-
"Soldier",
|
8
|
-
id: 28_473,
|
9
|
-
class: model_class,
|
10
|
-
attributes: {
|
11
|
-
"name" => "bob",
|
12
|
-
"rank" => "first lieutenant",
|
13
|
-
"serial_number" => "foobar",
|
14
|
-
"last_sign_in_at" => now,
|
15
|
-
},
|
16
|
-
saved_changes: {
|
17
|
-
"name" => %w(bill bob),
|
18
|
-
"last_sign_in_at" => now,
|
19
|
-
},
|
20
|
-
journaled_enqueue_opts: {},
|
21
|
-
)
|
22
|
-
end
|
23
|
-
|
24
|
-
let(:model_class) do
|
25
|
-
double(
|
26
|
-
"SoldierClass",
|
27
|
-
table_name: "soldiers",
|
28
|
-
attribute_names: %w(id name rank serial_number last_sign_in_at),
|
29
|
-
)
|
30
|
-
end
|
31
|
-
|
32
|
-
let(:change_definition) do
|
33
|
-
Journaled::ChangeDefinition.new(
|
34
|
-
attribute_names: %i(name rank serial_number),
|
35
|
-
logical_operation: "identity_change",
|
36
|
-
)
|
37
|
-
end
|
38
|
-
|
39
|
-
let(:faulty_change_definition) do
|
40
|
-
Journaled::ChangeDefinition.new(
|
41
|
-
attribute_names: %i(name rank serial_number nonexistent_thingie),
|
42
|
-
logical_operation: "identity_change",
|
43
|
-
)
|
44
|
-
end
|
45
|
-
|
46
|
-
subject { described_class.new(model: model, change_definition: change_definition) }
|
47
|
-
|
48
|
-
it "fails to instantiate with an undefined attribute_name" do
|
49
|
-
expect { described_class.new(model: model, change_definition: faulty_change_definition) }.to raise_error(/\bnonexistent_thingie\n/)
|
50
|
-
end
|
51
|
-
|
52
|
-
describe "#relevant_attributes" do
|
53
|
-
let(:model) do
|
54
|
-
double(
|
55
|
-
"Soldier",
|
56
|
-
id: 28_473,
|
57
|
-
class: model_class,
|
58
|
-
attributes: {
|
59
|
-
"name" => "bill",
|
60
|
-
"rank" => "first lieutenant",
|
61
|
-
"serial_number" => "foobar",
|
62
|
-
"last_sign_in_at" => Time.zone.now,
|
63
|
-
},
|
64
|
-
saved_changes: {},
|
65
|
-
journaled_enqueue_opts: {},
|
66
|
-
)
|
67
|
-
end
|
68
|
-
|
69
|
-
it "returns all relevant attributes regardless of saved changes" do
|
70
|
-
expect(subject.relevant_attributes).to eq(
|
71
|
-
"name" => "bill",
|
72
|
-
"rank" => "first lieutenant",
|
73
|
-
"serial_number" => "foobar",
|
74
|
-
)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
describe "#relevant_unperturbed_attributes" do
|
79
|
-
let(:model) do
|
80
|
-
double(
|
81
|
-
"Soldier",
|
82
|
-
id: 28_473,
|
83
|
-
class: model_class,
|
84
|
-
attributes: {
|
85
|
-
"name" => "bill",
|
86
|
-
"rank" => "first lieutenant",
|
87
|
-
"serial_number" => "foobar",
|
88
|
-
"last_sign_in_at" => Time.zone.now,
|
89
|
-
},
|
90
|
-
changes: {
|
91
|
-
"name" => %w(bob bill),
|
92
|
-
},
|
93
|
-
journaled_enqueue_opts: {},
|
94
|
-
)
|
95
|
-
end
|
96
|
-
|
97
|
-
it "returns the pre-change value of the attributes, regardless of whether they changed" do
|
98
|
-
expect(subject.relevant_unperturbed_attributes).to eq(
|
99
|
-
"name" => "bob",
|
100
|
-
"rank" => "first lieutenant",
|
101
|
-
"serial_number" => "foobar",
|
102
|
-
)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
describe "#relevant_changed_attributes" do
|
107
|
-
it "returns only relevant changes" do
|
108
|
-
expect(subject.relevant_changed_attributes).to eq("name" => "bob")
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
describe "#actor_uri" do
|
113
|
-
it "delegates to ActorUriProvider" do
|
114
|
-
allow(Journaled::ActorUriProvider).to receive(:instance).and_return(double(actor_uri: "my actor uri"))
|
115
|
-
expect(Journaled.actor_uri).to eq "my actor uri"
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
describe "#journaled_change_for" do
|
120
|
-
it "stores passed changes serialized to json" do
|
121
|
-
expect(subject.journaled_change_for("update", "name" => "bob").changes).to eq('{"name":"bob"}')
|
122
|
-
end
|
123
|
-
|
124
|
-
it "stores the model's table_name" do
|
125
|
-
expect(subject.journaled_change_for("update", {}).table_name).to eq("soldiers")
|
126
|
-
end
|
127
|
-
|
128
|
-
it "converts the model's record_id to a string" do
|
129
|
-
expect(subject.journaled_change_for("update", {}).record_id).to eq("28473")
|
130
|
-
end
|
131
|
-
|
132
|
-
it "stuffs the database operation directly" do
|
133
|
-
expect(subject.journaled_change_for("update", {}).database_operation).to eq("update")
|
134
|
-
expect(subject.journaled_change_for("delete", {}).database_operation).to eq("delete")
|
135
|
-
end
|
136
|
-
|
137
|
-
it "includes logical_operation" do
|
138
|
-
expect(subject.journaled_change_for("update", {}).logical_operation).to eq("identity_change")
|
139
|
-
end
|
140
|
-
|
141
|
-
it "doesn't set journaled_stream_name if model class doesn't respond to it" do
|
142
|
-
expect(subject.journaled_change_for("update", {}).journaled_stream_name).to eq(nil)
|
143
|
-
end
|
144
|
-
|
145
|
-
context "with journaled default app name set" do
|
146
|
-
around do |example|
|
147
|
-
orig_app_name = Journaled.default_stream_name
|
148
|
-
Journaled.default_stream_name = "foo"
|
149
|
-
example.run
|
150
|
-
Journaled.default_stream_name = orig_app_name
|
151
|
-
end
|
152
|
-
|
153
|
-
it "passes through default" do
|
154
|
-
expect(subject.journaled_change_for("update", {}).journaled_stream_name).to eq("foo")
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
context "when model class defines journaled_stream_name" do
|
159
|
-
before do
|
160
|
-
allow(model_class).to receive(:journaled_stream_name).and_return("my_app_events")
|
161
|
-
end
|
162
|
-
|
163
|
-
it "sets journaled_stream_name if model_class responds to it" do
|
164
|
-
expect(subject.journaled_change_for("update", {}).journaled_stream_name).to eq("my_app_events")
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
context "with journaling stubbed" do
|
170
|
-
let(:journaled_change) { instance_double(Journaled::Change, journal!: true) }
|
171
|
-
|
172
|
-
before do
|
173
|
-
allow(Journaled::Change).to receive(:new).and_return(nil) # must be restubbed to work in context
|
174
|
-
end
|
175
|
-
|
176
|
-
describe "#create" do
|
177
|
-
let(:model) do
|
178
|
-
double(
|
179
|
-
"Soldier",
|
180
|
-
id: 28_473,
|
181
|
-
class: model_class,
|
182
|
-
attributes: {
|
183
|
-
"name" => "bill",
|
184
|
-
"rank" => "first lieutenant",
|
185
|
-
"serial_number" => "foobar",
|
186
|
-
"last_sign_in_at" => Time.zone.now,
|
187
|
-
},
|
188
|
-
saved_changes: {},
|
189
|
-
journaled_enqueue_opts: {},
|
190
|
-
)
|
191
|
-
end
|
192
|
-
|
193
|
-
it "always journals all relevant attributes, even if unchanged" do
|
194
|
-
allow(Journaled::Change).to receive(:new) do |opts|
|
195
|
-
expect(opts[:changes]).to eq '{"name":"bill","rank":"first lieutenant","serial_number":"foobar"}'
|
196
|
-
journaled_change
|
197
|
-
end
|
198
|
-
|
199
|
-
subject.create
|
200
|
-
|
201
|
-
expect(Journaled::Change).to have_received(:new)
|
202
|
-
expect(journaled_change).to have_received(:journal!)
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
describe "#update" do
|
207
|
-
it "journals only relevant changes" do
|
208
|
-
allow(Journaled::Change).to receive(:new) do |opts|
|
209
|
-
expect(opts[:changes]).to eq '{"name":"bob"}'
|
210
|
-
journaled_change
|
211
|
-
end
|
212
|
-
|
213
|
-
subject.update
|
214
|
-
|
215
|
-
expect(Journaled::Change).to have_received(:new)
|
216
|
-
expect(journaled_change).to have_received(:journal!)
|
217
|
-
end
|
218
|
-
|
219
|
-
context "with no changes" do
|
220
|
-
let(:model) do
|
221
|
-
double(
|
222
|
-
"Soldier",
|
223
|
-
id: 28_473,
|
224
|
-
class: model_class,
|
225
|
-
attributes: {
|
226
|
-
"name" => "bill",
|
227
|
-
"rank" => "first lieutenant",
|
228
|
-
"serial_number" => "foobar",
|
229
|
-
"last_sign_in_at" => Time.zone.now,
|
230
|
-
},
|
231
|
-
saved_changes: {},
|
232
|
-
)
|
233
|
-
end
|
234
|
-
|
235
|
-
it "doesn't journal" do
|
236
|
-
subject.update
|
237
|
-
|
238
|
-
expect(Journaled::Change).not_to have_received(:new)
|
239
|
-
expect(journaled_change).not_to have_received(:journal!)
|
240
|
-
end
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
describe "#delete" do
|
245
|
-
let(:model) do
|
246
|
-
now = Time.zone.now
|
247
|
-
double(
|
248
|
-
"Soldier",
|
249
|
-
id: 28_473,
|
250
|
-
class: model_class,
|
251
|
-
attributes: {
|
252
|
-
"name" => "bob",
|
253
|
-
"rank" => "first lieutenant",
|
254
|
-
"serial_number" => "foobar",
|
255
|
-
"last_sign_in_at" => now,
|
256
|
-
},
|
257
|
-
changes: {
|
258
|
-
"name" => %w(bill bob),
|
259
|
-
},
|
260
|
-
journaled_enqueue_opts: {},
|
261
|
-
)
|
262
|
-
end
|
263
|
-
|
264
|
-
it "journals the unperturbed values of all relevant attributes" do
|
265
|
-
allow(Journaled::Change).to receive(:new) do |opts|
|
266
|
-
expect(JSON.parse(opts[:changes])).to eq(
|
267
|
-
"name" => "bill",
|
268
|
-
"rank" => "first lieutenant",
|
269
|
-
"serial_number" => "foobar",
|
270
|
-
)
|
271
|
-
journaled_change
|
272
|
-
end
|
273
|
-
|
274
|
-
subject.delete
|
275
|
-
|
276
|
-
expect(Journaled::Change).to have_received(:new)
|
277
|
-
expect(journaled_change).to have_received(:journal!)
|
278
|
-
end
|
279
|
-
end
|
280
|
-
end
|
281
|
-
end
|