journaled 4.3.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 +18 -0
- data/app/jobs/journaled/delivery_job.rb +17 -28
- data/app/models/journaled/writer.rb +30 -18
- data/lib/journaled/connection.rb +48 -0
- data/lib/journaled/engine.rb +5 -0
- data/lib/journaled/errors.rb +3 -0
- data/lib/journaled/transaction_ext.rb +31 -0
- data/lib/journaled/version.rb +1 -1
- data/lib/journaled.rb +15 -11
- metadata +26 -84
- 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 -89
- 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 -212
- data/spec/rails_helper.rb +0 -19
- data/spec/spec_helper.rb +0 -24
- data/spec/support/environment_spec_helper.rb +0 -16
    
        data/spec/lib/journaled_spec.rb
    DELETED
    
    | @@ -1,89 +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 | 
            -
                    example.run
         | 
| 79 | 
            -
                  ensure
         | 
| 80 | 
            -
                    ActiveJob::Base.queue_adapter = :test
         | 
| 81 | 
            -
                    ActiveJob::Base.enable_test_adapter(ActiveJob::QueueAdapters::TestAdapter.new)
         | 
| 82 | 
            -
                  end
         | 
| 83 | 
            -
             | 
| 84 | 
            -
                  it 'does not raise an error' do
         | 
| 85 | 
            -
                    expect { described_class.detect_queue_adapter! }.not_to raise_error
         | 
| 86 | 
            -
                  end
         | 
| 87 | 
            -
                end
         | 
| 88 | 
            -
              end
         | 
| 89 | 
            -
            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 be_nil
         | 
| 36 | 
            -
                expect(Journaled::Current.actor).to be_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) { instance_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 be_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
         |