diff --git a/rails_event_store/lib/rails_event_store/client.rb b/rails_event_store/lib/rails_event_store/client.rb index 5086a764d8..355db3fdc0 100644 --- a/rails_event_store/lib/rails_event_store/client.rb +++ b/rails_event_store/lib/rails_event_store/client.rb @@ -1,16 +1,34 @@ module RailsEventStore class Client < RubyEventStore::Client + attr_reader :request_metadata + def initialize(repository: RailsEventStoreActiveRecord::EventRepository.new, mapper: RubyEventStore::Mappers::Default.new, event_broker: EventBroker.new(dispatcher: ActiveJobDispatcher.new), + request_metadata: default_request_metadata, page_size: PAGE_SIZE) - capture_metadata = ->{ Thread.current[:rails_event_store] } super(repository: repository, mapper: mapper, event_broker: event_broker, - page_size: page_size, - metadata_proc: capture_metadata) + page_size: page_size) + @request_metadata = request_metadata + end + + def with_request_metadata(env, &block) + with_metadata(request_metadata.call(env)) do + block.call + end end + private + def default_request_metadata + ->(env) do + request = ActionDispatch::Request.new(env) + { + remote_ip: request.remote_ip, + request_id: request.uuid + } + end + end end -end \ No newline at end of file +end diff --git a/rails_event_store/lib/rails_event_store/middleware.rb b/rails_event_store/lib/rails_event_store/middleware.rb index 1774ba56ec..e43ee2dc44 100644 --- a/rails_event_store/lib/rails_event_store/middleware.rb +++ b/rails_event_store/lib/rails_event_store/middleware.rb @@ -1,15 +1,23 @@ module RailsEventStore class Middleware - def initialize(app, request_metadata_proc) + def initialize(app) @app = app - @request_metadata_proc = request_metadata_proc end def call(env) - Thread.current[:rails_event_store] = @request_metadata_proc.(env) - @app.call(env) - ensure - Thread.current[:rails_event_store] = nil + if config.respond_to?(:event_store) + config.event_store.with_request_metadata(env) do + @app.call(env) + end + else + @app.call(env) + end + end + + private + + def config + Rails.application.config end end end diff --git a/rails_event_store/lib/rails_event_store/railtie.rb b/rails_event_store/lib/rails_event_store/railtie.rb index 780c3a87c7..ac0c513cd1 100644 --- a/rails_event_store/lib/rails_event_store/railtie.rb +++ b/rails_event_store/lib/rails_event_store/railtie.rb @@ -4,28 +4,7 @@ module RailsEventStore class Railtie < ::Rails::Railtie initializer 'rails_event_store.middleware' do |rails| - rails.middleware.use(::RailsEventStore::Middleware, RailsConfig.new(rails.config).request_metadata) - end - - class RailsConfig - def initialize(config) - @config = config - end - - def request_metadata - return default_request_metadata unless @config.respond_to?(:rails_event_store) - @config.rails_event_store.fetch(:request_metadata, default_request_metadata) - end - - private - def default_request_metadata - ->(env) do - request = ActionDispatch::Request.new(env) - { remote_ip: request.remote_ip, - request_id: request.uuid, - } - end - end + rails.middleware.use(::RailsEventStore::Middleware) end end end diff --git a/rails_event_store/spec/client_spec.rb b/rails_event_store/spec/client_spec.rb new file mode 100644 index 0000000000..4e2a3bb9b8 --- /dev/null +++ b/rails_event_store/spec/client_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +module RailsEventStore + RSpec.describe Client do + TestEvent = Class.new(RailsEventStore::Event) + + specify 'has default request metadata proc if no custom one provided' do + client = Client.new + expect(client.request_metadata.call({ + 'action_dispatch.request_id' => 'dummy_id', + 'action_dispatch.remote_ip' => 'dummy_ip' + })).to eq({ + remote_ip: 'dummy_ip', + request_id: 'dummy_id' + }) + end + + specify 'allows to set custom request metadata proc' do + client = Client.new( + request_metadata: -> env { {server_name: env['SERVER_NAME']} } + ) + expect(client.request_metadata.call({ + 'SERVER_NAME' => 'example.org' + })).to eq({ + server_name: 'example.org' + }) + end + + specify 'published event metadata will be enriched by metadata provided in request metadata when executed inside a with_request_metadata block' do + client = Client.new( + repository: InMemoryRepository.new, + ) + event = TestEvent.new + client.with_request_metadata( + 'action_dispatch.request_id' => 'dummy_id', + 'action_dispatch.remote_ip' => 'dummy_ip' + ) do + client.publish_event(event) + end + published = client.read_all_streams_forward + expect(published.size).to eq(1) + expect(published.first.metadata[:remote_ip]).to eq('dummy_ip') + expect(published.first.metadata[:request_id]).to eq('dummy_id') + expect(published.first.metadata[:timestamp]).to be_a Time + end + end +end diff --git a/rails_event_store/spec/middleware_integration_spec.rb b/rails_event_store/spec/middleware_integration_spec.rb index 05fc6f66a1..b4c90a70aa 100644 --- a/rails_event_store/spec/middleware_integration_spec.rb +++ b/rails_event_store/spec/middleware_integration_spec.rb @@ -2,21 +2,41 @@ require 'rails_event_store/middleware' require 'rack/test' require 'rack/lint' +require 'support/test_application' module RailsEventStore RSpec.describe Middleware do DummyEvent = Class.new(RailsEventStore::Event) - specify do + specify 'works without event store instance' do event_store = Client.new + request = ::Rack::MockRequest.new(Middleware.new(app)) + request.get('/') + + event_store.read_all_streams_forward.map(&:metadata).each do |metadata| + expect(metadata.keys).to eq([:timestamp]) + expect(metadata[:timestamp]).to be_a(Time) + end + end - request = ::Rack::MockRequest.new(Middleware.new( - ->(env) { event_store.publish_event(DummyEvent.new); [200, {}, ["Hello World from #{env["SERVER_NAME"]}"]] }, - ->(env) { { server_name: env['SERVER_NAME'] }})) + specify 'sets domain events metadata for events published with global event store instance' do + event_store = Client.new( + request_metadata: -> env { {server_name: env['SERVER_NAME']} } + ) + app.config.event_store = event_store + + request = ::Rack::MockRequest.new(Middleware.new(app)) request.get('/') event_store.read_all_streams_forward.map(&:metadata).each do |metadata| - expect(metadata[:server_name]).to eq('example.org') + expect(metadata[:server_name]).to eq('example.org') + expect(metadata[:timestamp]).to be_a(Time) + end + end + + def app + TestApplication.tap do |app| + app.routes.draw { root(to: ->(env) {event_store.publish_event(DummyEvent.new); [200, {}, ['']]}) } end end end diff --git a/rails_event_store/spec/middleware_spec.rb b/rails_event_store/spec/middleware_spec.rb index 8586fc6f90..e93037fd28 100644 --- a/rails_event_store/spec/middleware_spec.rb +++ b/rails_event_store/spec/middleware_spec.rb @@ -1,42 +1,39 @@ require 'spec_helper' require 'rails_event_store/middleware' -require 'rack/lint' +require 'support/test_application' module RailsEventStore RSpec.describe Middleware do - specify 'lint' do - request = ::Rack::MockRequest.new(::Rack::Lint.new(Middleware.new( - ->(env) { [200, {}, ['Hello World']] }, - ->(env) { { kaka: 'dudu' } } ))) - - expect { request.get('/') }.to_not raise_error + before do + allow(Rails.application).to receive(:config).and_return(configuration) end - - specify do - request = ::Rack::MockRequest.new(Middleware.new( - ->(env) { [200, {}, ['Hello World']] }, - ->(env) { { kaka: 'dudu' } } )) - request.get('/') - - expect(Thread.current[:rails_event_store]).to be_nil + specify 'calls app within with_request_metadata block when app has configured the event store instance' do + Rails.application.config.event_store = event_store = Client.new + expect(event_store).to receive(:with_request_metadata).with(dummy_env).and_call_original + expect(app).to receive(:call).with(dummy_env).and_call_original + middleware = Middleware.new(app) + expect(middleware.call(dummy_env)).to eq([204, {}, ['']]) end - specify do - request = ::Rack::MockRequest.new(Middleware.new( - ->(env) { raise }, - ->(env) { { kaka: 'dudu' } } )) + specify 'just calls the app when app has not configured the event store instance' do + expect(app).to receive(:call).with(dummy_env).and_call_original + middleware = Middleware.new(app) + expect(middleware.call(dummy_env)).to eq([204, {}, ['']]) + end - expect { request.get('/') }.to raise_error(RuntimeError) - expect(Thread.current[:rails_event_store]).to be_nil + def dummy_env + { + 'action_dispatch.request_id' => 'dummy_id', + 'action_dispatch.remote_ip' => 'dummy_ip' + } end - specify do - request = ::Rack::MockRequest.new(Middleware.new( - ->(env) { [200, {}, ['Hello World']] }, - ->(env) { raise } )) + def configuration + @configuration ||= FakeConfiguration.new + end - expect { request.get('/') }.to raise_error(RuntimeError) - expect(Thread.current[:rails_event_store]).to be_nil + def app + @app ||= -> _ { [204, {}, ['']] } end end end diff --git a/rails_event_store/spec/railtie_spec.rb b/rails_event_store/spec/railtie_spec.rb deleted file mode 100644 index 84473f7b0c..0000000000 --- a/rails_event_store/spec/railtie_spec.rb +++ /dev/null @@ -1,50 +0,0 @@ -require 'spec_helper' -require 'action_controller/railtie' -require 'rails_event_store/railtie' - -module RailsEventStore - RSpec.describe Railtie::RailsConfig do - specify 'no config, use defaults' do - rails_config = Railtie::RailsConfig.new(app_configuration) - - expect(rails_config.request_metadata.(dummy_env)) - .to(eq({ - request_id: 'dummy_id', - remote_ip: 'dummy_ip' - })) - end - - specify 'config present' do - app_configuration.rails_event_store = { request_metadata: kaka_dudu } - rails_config = Railtie::RailsConfig.new(app_configuration) - - expect(rails_config.request_metadata.(dummy_env)).to eq({ kaka: 'dudu' }) - end - - specify 'config present, no callable' do - app_configuration.rails_event_store = {} - rails_config = Railtie::RailsConfig.new(app_configuration) - - expect(rails_config.request_metadata.(dummy_env)) - .to(eq({ - request_id: 'dummy_id', - remote_ip: 'dummy_ip' - })) - end - - def app_configuration - @app_configuration ||= FakeConfiguration.new - end - - def kaka_dudu - ->(env) { { kaka: 'dudu' } } - end - - def dummy_env - { - 'action_dispatch.request_id' => 'dummy_id', - 'action_dispatch.remote_ip' => 'dummy_ip' - } - end - end -end diff --git a/rails_event_store/spec/request_metadata_spec.rb b/rails_event_store/spec/request_metadata_spec.rb deleted file mode 100644 index 2eec4a8dd4..0000000000 --- a/rails_event_store/spec/request_metadata_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'spec_helper' -require 'support/test_rails' - -module RailsEventStore - FoobarEvent = Class.new(RailsEventStore::Event) - UUID_REGEX = /\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/ - - RSpec.describe Client do - specify 'no config' do - event_store = Client.new - - TestRails.new.(->{ event_store.publish_event(FoobarEvent.new) }) - - expect(event_store.read_all_streams_forward).to_not be_empty - event_store.read_all_streams_forward.map(&:metadata).each do |metadata| - expect(metadata[:remote_ip]).to eq('127.0.0.1') - expect(metadata[:request_id]).to match(UUID_REGEX) - end - end - end -end diff --git a/rails_event_store/spec/spec_helper.rb b/rails_event_store/spec/spec_helper.rb index 4d981859c1..799073985a 100644 --- a/rails_event_store/spec/spec_helper.rb +++ b/rails_event_store/spec/spec_helper.rb @@ -4,7 +4,6 @@ require 'support/mutant_timeout' require 'support/fake_configuration' - MigrationCode = File.read( File.expand_path('../../../rails_event_store_active_record/lib/rails_event_store_active_record/generators/templates/migration_template.rb', __FILE__) ) migration_version = Gem::Version.new(ActiveRecord::VERSION::STRING) < Gem::Version.new("5.0.0") ? "" : "[4.2]" MigrationCode.gsub!("<%= migration_version %>", migration_version) @@ -22,4 +21,4 @@ end $verbose = ENV.has_key?('VERBOSE') ? true : false -ActiveJob::Base.logger = nil unless $verbose \ No newline at end of file +ActiveJob::Base.logger = nil unless $verbose diff --git a/rails_event_store/spec/support/test_application.rb b/rails_event_store/spec/support/test_application.rb new file mode 100644 index 0000000000..43e47e5843 --- /dev/null +++ b/rails_event_store/spec/support/test_application.rb @@ -0,0 +1,10 @@ +require 'action_controller/railtie' +require 'rails_event_store/railtie' +require 'securerandom' + +class TestApplication < Rails::Application + config.eager_load = false + config.secret_key_base = SecureRandom.hex(16) + initialize! + routes.default_url_options = { host: 'example.org' } +end diff --git a/rails_event_store/spec/support/test_rails.rb b/rails_event_store/spec/support/test_rails.rb deleted file mode 100644 index 706c147643..0000000000 --- a/rails_event_store/spec/support/test_rails.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'rack/test' -require 'action_controller/railtie' -require 'rails_event_store/railtie' -require 'securerandom' - -class TestRails - include Rack::Test::Methods - - attr_reader :app - - def initialize(test_config = {}) - @app = Class.new(::Rails::Application) do - def self.name - "TestRails::Application" - end - end - @test_config = test_config - end - - def call(action) - @test_config - .merge( - { eager_load: false, - secret_key_base: SecureRandom.hex - }) - .each { |k, v| app.config.send("#{k}=", v) } - app.initialize! - app.routes.draw { root(to: ->(env) { action.(); [200, {}, ['']] }) } - app.default_url_options = { host: 'example.com' } - get('/') - end -end - diff --git a/railseventstore.org/source/docs/request_metadata.html.md b/railseventstore.org/source/docs/request_metadata.html.md index bb2011e8af..9932109f85 100644 --- a/railseventstore.org/source/docs/request_metadata.html.md +++ b/railseventstore.org/source/docs/request_metadata.html.md @@ -1,6 +1,10 @@ # Logging request metadata -In Rails environment, every event is enhanced with the request metadata provided by `rack` server. This can help with debugging and building an audit log from events for the future use. +In Rails environment, every event is enhanced with the request metadata provided by `rack` server as long as you configure your event store instance in `config.event_store`. This can help with debugging and building an audit log from events for the future use. + +## Setup + +In order to enhance your events with metadata, you need to setup your client as described in [Installation](/docs/install). ## Defaults @@ -31,9 +35,7 @@ my_event.metadata[:request_id] # unique ID You can configure which metadata you'd like to catch. To do so, you need to provide a `lambda` which takes Rack environment and returns a metadata hash/object. -This can be configurable using `rails_event_store.request_metadata` field in your Rails configuration. - -You should set it up globally (`config/application.rb`) or locally for each environment (`config/environments/test.rb`, `config/environments/development.rb`, `config/environments/production.rb`, ...). If you don't provide your own, the default implementation will be used. +This can be configurable when instantinating the `RailsEventStore::Client` instance with `request_metadata` option. Here is an example of such configuration (in `config/application.rb`), replicating the default behaviour: @@ -54,15 +56,49 @@ Bundler.require(*Rails.groups) module YourAppName class Application < Rails::Application - config.x.rails_event_store.request_metadata = -> (env) do - request = ActionDispatch::Request.new(env) - { remote_ip: request.remote_ip, - request_id: request.uuid, - } - end + config.event_store = RailsEventStore::Client.new( + request_metadata: -> (env) do + request = ActionDispatch::Request.new(env) + { remote_ip: request.remote_ip, + request_id: request.uuid, + } + ) + # ... end end ``` You can read more about your possible options by reading [ActionDispatch::Request](http://api.rubyonrails.org/classes/ActionDispatch/Request.html) documentation. + +## Passing your own metadata using `with_metadata` method + +Apart from using the middleware, you can also set your metadata with `RubyEventStore::Client#with_metadata` method. You can specify custom metadata that will be added to all events published inside a block: + +```ruby +event_store.with_metadata(remote_ip: '1.2.3.4', request_id: SecureRandom.uuid) do + event_store.publish(MyEvent.new(data: {foo: 'bar'})) +end + +my_event = event_store.read_all_events(RailsEventStore::GLOBAL_STREAM).last + +my_event.metadata[:remote_ip] #=> '1.2.3.4' +my_event.metadata[:request_id] #=> unique ID +``` + +When using `with_metadata`, the `timestamp` is still added to the metadata unless you explicitly specify it on your own. Additionally, if you are nesting `with_metadata` blocks or also using the middleware & `request_metadata` lambda, your metadata passed as `with_metadata` argument will be merged with the result of `rails_event_store.request_metadata` proc: + +```ruby +event_store.with_metadata(causation_id: 1234567890) do + event_store.with_metadata(correlation_id: 987654321) do + event_store.publish(MyEvent.new(data: {foo: 'bar'})) + end +end + +my_event = event_store.read_all_events(RailsEventStore::GLOBAL_STREAM).last +my_event.metadata[:remote_ip] #=> your IP from request metadata proc +my_event.metadata[:request_id #=> unique ID from request metadata proc +my_event.metadata[:causation_id] #=> 1234567890 from with_metadata argument +my_event.metadata[:correlation_id] #=> 987654321 from with_metadata argument +my_event.metadata[:timestamp] #=> a timestamp +``` diff --git a/ruby_event_store/lib/ruby_event_store/client.rb b/ruby_event_store/lib/ruby_event_store/client.rb index 965520216c..30b1a43500 100644 --- a/ruby_event_store/lib/ruby_event_store/client.rb +++ b/ruby_event_store/lib/ruby_event_store/client.rb @@ -10,6 +10,7 @@ def initialize(repository:, @mapper = mapper @event_broker = event_broker @page_size = page_size + warn "`RubyEventStore::Client#metadata_proc` has been deprecated. Use `RubyEventStore::Client#with_metadata` instead." if metadata_proc @metadata_proc = metadata_proc @clock = clock end @@ -171,6 +172,14 @@ def within(&block) Within.new(block, event_broker) end + def with_metadata(metadata, &block) + previous_metadata = metadata() + self.metadata = (previous_metadata || {}).merge(metadata) + block.call if block_given? + ensure + self.metadata = previous_metadata + end + private def serialized_events(events) @@ -198,13 +207,26 @@ def normalize_to_array(events) end def enrich_event_metadata(event) - event.metadata[:timestamp] ||= clock.() if metadata_proc md = metadata_proc.call || {} md.each{|k,v| event.metadata[k]=(v) } end + if metadata + metadata.each { |key, value| event.metadata[key] = value } + end + event.metadata[:timestamp] ||= clock.call end attr_reader :repository, :mapper, :event_broker, :clock, :metadata_proc, :page_size + + protected + + def metadata + Thread.current["ruby_event_store_#{hash}"] + end + + def metadata=(value) + Thread.current["ruby_event_store_#{hash}"] = value + end end end diff --git a/ruby_event_store/spec/client_spec.rb b/ruby_event_store/spec/client_spec.rb index 5ea0788c06..9ff2a89070 100644 --- a/ruby_event_store/spec/client_spec.rb +++ b/ruby_event_store/spec/client_spec.rb @@ -5,6 +5,13 @@ module RubyEventStore RSpec.describe Client do + specify 'deprecates using metadata_proc' do + deprecation_warning = "`RubyEventStore::Client#metadata_proc` has been deprecated. Use `RubyEventStore::Client#with_metadata` instead.\n" + expect { + RubyEventStore::Client.new(repository: InMemoryRepository.new, metadata_proc: ->{ {} }) + }.to output(deprecation_warning).to_stderr + end + specify 'publish_event returns :ok when success' do client = RubyEventStore::Client.new(repository: InMemoryRepository.new) expect(client.publish_event(TestEvent.new)).to eq(:ok) @@ -126,17 +133,142 @@ module RubyEventStore end specify 'published event metadata will be enriched by proc execution' do - client = RubyEventStore::Client.new(repository: InMemoryRepository.new, metadata_proc: ->{ {request_id: '127.0.0.1'} }) + client = silence_warnings { RubyEventStore::Client.new(repository: InMemoryRepository.new, metadata_proc: ->{ {request_id: '127.0.0.1'} }) } event = TestEvent.new - client.publish_event(event) + client.append_to_stream(event) published = client.read_all_streams_forward expect(published.size).to eq(1) expect(published.first.metadata[:request_id]).to eq('127.0.0.1') expect(published.first.metadata[:timestamp]).to be_a Time end + specify 'published event metadata will be enriched by metadata provided in with_metadata when executed inside a block' do + client = RubyEventStore::Client.new(repository: InMemoryRepository.new) + event = TestEvent.new + client.with_metadata(request_ip: '127.0.0.1') do + client.publish_event(event) + end + published = client.read_all_streams_forward + expect(published.size).to eq(1) + expect(published.first.metadata[:request_ip]).to eq('127.0.0.1') + expect(published.first.metadata[:timestamp]).to be_a Time + end + + specify 'published event metadata will not be enriched by metadata provided in with_metadata when published outside a block' do + client = RubyEventStore::Client.new(repository: InMemoryRepository.new) + event = TestEvent.new + client.with_metadata(request_ip: '127.0.0.1') + client.publish_event(event) + published = client.read_all_streams_forward + expect(published.size).to eq(1) + expect(published.first.metadata[:request_ip]).to be_nil + expect(published.first.metadata[:timestamp]).to be_a Time + end + + specify 'published event metadata will be enriched by nested metadata provided in with_metadata' do + client = RubyEventStore::Client.new(repository: InMemoryRepository.new) + client.with_metadata(request_ip: '127.0.0.1') do + client.publish_event(TestEvent.new) + client.with_metadata(request_ip: '1.2.3.4', nested: true) do + client.publish_event(TestEvent.new) + client.with_metadata(deeply_nested: true) do + client.publish_event(TestEvent.new) + end + end + client.publish_event(TestEvent.new) + end + client.publish_event(TestEvent.new) + published = client.read_all_streams_forward + expect(published.size).to eq(5) + expect(published[0].metadata.keys).to match_array([:timestamp, :request_ip]) + expect(published[0].metadata[:request_ip]).to eq('127.0.0.1') + expect(published[0].metadata[:timestamp]).to be_a Time + expect(published[1].metadata.keys).to match_array([:timestamp, :request_ip, :nested]) + expect(published[1].metadata[:request_ip]).to eq('1.2.3.4') + expect(published[1].metadata[:nested]).to eq true + expect(published[1].metadata[:timestamp]).to be_a Time + expect(published[2].metadata.keys).to match_array([:timestamp, :request_ip, :nested, :deeply_nested]) + expect(published[2].metadata[:request_ip]).to eq('1.2.3.4') + expect(published[2].metadata[:nested]).to eq true + expect(published[2].metadata[:deeply_nested]).to eq true + expect(published[2].metadata[:timestamp]).to be_a Time + expect(published[3].metadata.keys).to match_array([:timestamp, :request_ip]) + expect(published[3].metadata[:request_ip]).to eq('127.0.0.1') + expect(published[3].metadata[:timestamp]).to be_a Time + expect(published[4].metadata.keys).to match_array([:timestamp]) + expect(published[4].metadata[:timestamp]).to be_a Time + end + + specify 'with_metadata is merged when nested' do + client = RubyEventStore::Client.new(repository: InMemoryRepository.new) + client.with_metadata(remote_ip: '127.0.0.1') do + client.publish_event(TestEvent.new) + client.with_metadata(remote_ip: '192.168.0.1', request_id: '1234567890') do + client.publish_event(TestEvent.new) + end + client.publish_event(TestEvent.new) + end + published = client.read_all_streams_forward + expect(published.size).to eq(3) + expect(published[0].metadata.keys).to match_array([:timestamp, :remote_ip]) + expect(published[0].metadata[:remote_ip]).to eq('127.0.0.1') + expect(published[0].metadata[:timestamp]).to be_a Time + expect(published[1].metadata.keys).to match_array([:timestamp, :remote_ip, :request_id]) + expect(published[1].metadata[:timestamp]).to be_a Time + expect(published[1].metadata[:remote_ip]).to eq('192.168.0.1') + expect(published[1].metadata[:request_id]).to eq('1234567890') + expect(published[2].metadata.keys).to match_array([:timestamp, :remote_ip]) + expect(published[2].metadata[:remote_ip]).to eq('127.0.0.1') + expect(published[2].metadata[:timestamp]).to be_a Time + end + + specify 'when both metadata_proc & with_metadata block are used, the event\'s metadata will be enriched first from the proc and then from with_metadata argument' do + client = silence_warnings { RubyEventStore::Client.new(repository: InMemoryRepository.new, metadata_proc: ->{ {proc: true, request_ip: '127.0.0.1'} }) } + event = TestEvent.new + client.with_metadata(request_ip: '1.2.3.4', meta: true) do + client.append_to_stream(event) + end + published = client.read_all_streams_forward + expect(published.size).to eq(1) + expect(published.first.metadata[:request_ip]).to eq('1.2.3.4') + expect(published.first.metadata[:proc]).to eq(true) + expect(published.first.metadata[:meta]).to eq(true) + expect(published.first.metadata[:timestamp]).to be_a Time + end + + specify 'metadata is bound to the current instance and does not leak to others' do + client_a = RubyEventStore::Client.new(repository: InMemoryRepository.new) + client_b = RubyEventStore::Client.new(repository: InMemoryRepository.new) + + client_a.with_metadata(client: 'a') do + client_b.with_metadata(client: 'b') do + client_a.publish_event(TestEvent.new) + client_b.publish_event(TestEvent.new) + end + end + + published_a = client_a.read_all_streams_forward + published_b = client_b.read_all_streams_forward + expect(published_a.size).to eq(1) + expect(published_b.size).to eq(1) + expect(published_a.last.metadata[:client]).to eq('a') + expect(published_b.last.metadata[:client]).to eq('b') + end + + specify 'timestamp can be overwritten by using with_metadata' do + client = RubyEventStore::Client.new(repository: InMemoryRepository.new) + event = TestEvent.new + client.with_metadata(timestamp: '2018-01-01T00:00:00Z') do + client.append_to_stream(event) + end + published = client.read_all_streams_forward + expect(published.size).to eq(1) + expect(published.first.metadata.to_h.keys).to eq([:timestamp]) + expect(published.first.metadata[:timestamp]).to eq('2018-01-01T00:00:00Z') + end + specify 'only timestamp set inn metadata when event stored in stream if metadata proc return nil' do - client = RubyEventStore::Client.new(repository: InMemoryRepository.new, metadata_proc: ->{ nil }) + client = silence_warnings { RubyEventStore::Client.new(repository: InMemoryRepository.new, metadata_proc: ->{ nil }) } event = TestEvent.new client.append_to_stream(event) published = client.read_all_streams_forward diff --git a/ruby_event_store/spec/support/rspec_defaults.rb b/ruby_event_store/spec/support/rspec_defaults.rb index f5f30996e5..ffd9d2693d 100644 --- a/ruby_event_store/spec/support/rspec_defaults.rb +++ b/ruby_event_store/spec/support/rspec_defaults.rb @@ -1,4 +1,7 @@ +require_relative './silence_warnings' + RSpec.configure do |config| + config.include SilenceWarnings config.expect_with :rspec do |expectations| expectations.include_chain_clauses_in_custom_matcher_descriptions = true end diff --git a/ruby_event_store/spec/support/silence_warnings.rb b/ruby_event_store/spec/support/silence_warnings.rb new file mode 100644 index 0000000000..f1715ba4b0 --- /dev/null +++ b/ruby_event_store/spec/support/silence_warnings.rb @@ -0,0 +1,8 @@ +module SilenceWarnings + def silence_warnings + old_verbose, $VERBOSE = $VERBOSE, nil + yield + ensure + $VERBOSE = old_verbose + end +end