diff --git a/ruby_event_store/lib/ruby_event_store.rb b/ruby_event_store/lib/ruby_event_store.rb index f57650bf2e..32661cf828 100644 --- a/ruby_event_store/lib/ruby_event_store.rb +++ b/ruby_event_store/lib/ruby_event_store.rb @@ -10,7 +10,6 @@ require_relative "ruby_event_store/client" require_relative "ruby_event_store/metadata" require_relative "ruby_event_store/specification" -require_relative "ruby_event_store/specification_result" require_relative "ruby_event_store/specification_reader" require_relative "ruby_event_store/event" require_relative "ruby_event_store/stream" diff --git a/ruby_event_store/lib/ruby_event_store/specification.rb b/ruby_event_store/lib/ruby_event_store/specification.rb index 7096c94455..cf0401ec34 100644 --- a/ruby_event_store/lib/ruby_event_store/specification.rb +++ b/ruby_event_store/lib/ruby_event_store/specification.rb @@ -3,11 +3,59 @@ module RubyEventStore # Used for building and executing the query specification. class Specification + Result = + Data.define( + :direction, + :start, + :stop, + :older_than, + :older_than_or_equal, + :newer_than, + :newer_than_or_equal, + :time_sort_by, + :limit, + :stream, + :read_as, + :batch_size, + :with_ids, + :with_types, + ) do + def limit? = !limit.infinite? + def with_ids? = !with_ids.nil? + def with_types? = !with_types.empty? + def forward? = direction.equal?(:forward) + def backward? = direction.equal?(:backward) + def batched? = read_as.equal?(:batch) + def first? = read_as.equal?(:first) + def last? = read_as.equal?(:last) + def all? = read_as.equal?(:all) + def time_sort_by_as_at? = time_sort_by.equal?(:as_at) + def time_sort_by_as_of? = time_sort_by.equal?(:as_of) + end + DEFAULT_BATCH_SIZE = 100 # @api private # @private - def initialize(reader, result = SpecificationResult.new) + def initialize( + reader, + result = SpecificationResult.new( + direction: :forward, + start: nil, + stop: nil, + older_than: nil, + older_than_or_equal: nil, + newer_than: nil, + newer_than_or_equal: nil, + time_sort_by: nil, + limit: Float::INFINITY, + stream: Stream.new(GLOBAL_STREAM), + read_as: :all, + batch_size: DEFAULT_BATCH_SIZE, + with_ids: nil, + with_types: [], + ) + ) @reader = reader @result = result end @@ -18,7 +66,7 @@ def initialize(reader, result = SpecificationResult.new) # @param stream_name [String] name of the stream to get events from # @return [Specification] def stream(stream_name) - Specification.new(reader, result.dup { |r| r.stream = Stream.new(stream_name) }) + Specification.new(reader, result.with(stream: Stream.new(stream_name))) end # Limits the query to events before or after another event. @@ -28,7 +76,8 @@ def stream(stream_name) # @return [Specification] def from(start) raise InvalidPageStart if start.nil? || start.empty? - Specification.new(reader, result.dup { |r| r.start = start }) + + Specification.new(reader, result.with(start: start)) end # Limits the query to events before or after another event. @@ -38,7 +87,8 @@ def from(start) # @return [Specification] def to(stop) raise InvalidPageStop if stop.nil? || stop.empty? - Specification.new(reader, result.dup { |r| r.stop = stop }) + + Specification.new(reader, result.with(stop: stop)) end # Limits the query to events that occurred before given time. @@ -48,13 +98,8 @@ def to(stop) # @return [Specification] def older_than(time) raise ArgumentError unless time.respond_to?(:to_time) - Specification.new( - reader, - result.dup do |r| - r.older_than = time - r.older_than_or_equal = nil - end, - ) + + Specification.new(reader, result.with(older_than: time, older_than_or_equal: nil)) end # Limits the query to events that occurred on or before given time. @@ -64,13 +109,8 @@ def older_than(time) # @return [Specification] def older_than_or_equal(time) raise ArgumentError unless time.respond_to?(:to_time) - Specification.new( - reader, - result.dup do |r| - r.older_than = nil - r.older_than_or_equal = time - end, - ) + + Specification.new(reader, result.with(older_than: nil, older_than_or_equal: time)) end # Limits the query to events that occurred after given time. @@ -80,13 +120,8 @@ def older_than_or_equal(time) # @return [Specification] def newer_than(time) raise ArgumentError unless time.respond_to?(:to_time) - Specification.new( - reader, - result.dup do |r| - r.newer_than_or_equal = nil - r.newer_than = time - end, - ) + + Specification.new(reader, result.with(newer_than: time, newer_than_or_equal: nil)) end # Limits the query to events that occurred on or after given time. @@ -96,13 +131,8 @@ def newer_than(time) # @return [Specification] def newer_than_or_equal(time) raise ArgumentError unless time.respond_to?(:to_time) - Specification.new( - reader, - result.dup do |r| - r.newer_than_or_equal = time - r.newer_than = nil - end, - ) + + Specification.new(reader, result.with(newer_than: nil, newer_than_or_equal: time)) end # Limits the query to events within given time range. @@ -123,7 +153,7 @@ def between(time_range) # # @return [Specification] def as_at - Specification.new(reader, result.dup { |r| r.time_sort_by = :as_at }) + Specification.new(reader, result.with(time_sort_by: :as_at)) end # Sets the order of time sorting using validity time @@ -131,7 +161,7 @@ def as_at # # @return [Specification] def as_of - Specification.new(reader, result.dup { |r| r.time_sort_by = :as_of }) + Specification.new(reader, result.with(time_sort_by: :as_of)) end # Sets the order of reading events to ascending (forward from the start). @@ -139,7 +169,7 @@ def as_of # # @return [Specification] def forward - Specification.new(reader, result.dup { |r| r.direction = :forward }) + Specification.new(reader, result.with(direction: :forward)) end # Sets the order of reading events to descending (backward from the start). @@ -147,7 +177,7 @@ def forward # # @return [Specification] def backward - Specification.new(reader, result.dup { |r| r.direction = :backward }) + Specification.new(reader, result.with(direction: :backward)) end # Limits the query to specified number of events. @@ -157,7 +187,8 @@ def backward # @return [Specification] def limit(count) raise InvalidPageSize unless count && count > 0 - Specification.new(reader, result.dup { |r| r.count = count }) + + Specification.new(reader, result.with(limit: count)) end # Executes the query based on the specification built up to this point. @@ -166,10 +197,10 @@ def limit(count) # # @yield [Array] batch of events # @return [Enumerator, nil] Enumerator is returned when block not given - def each_batch + def each_batch(&block) return to_enum(:each_batch) unless block_given? - reader.each(in_batches(result.batch_size).result) { |batch| yield batch } + reader.each(in_batches(result.batch_size).result, &block) end # Executes the query based on the specification built up to this point. @@ -178,10 +209,10 @@ def each_batch # # @yield [Event] event # @return [Enumerator, nil] Enumerator is returned when block not given - def each + def each(&block) return to_enum unless block_given? - each_batch { |batch| batch.each { |event| yield event } } + each_batch { |batch| batch.each(&block) } end # Executes the query based on the specification built up to this point @@ -191,6 +222,7 @@ def each # @return [Array] of mapped result def map(&block) raise ArgumentError.new("Block must be given") unless block_given? + each.map(&block) end @@ -202,6 +234,7 @@ def map(&block) # @return reduce result as defined by block given def reduce(accumulator = nil, &block) raise ArgumentError.new("Block must be given") unless block_given? + each.reduce(accumulator, &block) end @@ -236,13 +269,7 @@ def to_a # @param batch_size [Integer] number of events to read in a single batch # @return [Specification] def in_batches(batch_size = DEFAULT_BATCH_SIZE) - Specification.new( - reader, - result.dup do |r| - r.read_as = :batch - r.batch_size = batch_size - end, - ) + Specification.new(reader, result.with(read_as: :batch, batch_size: batch_size)) end alias in_batches_of in_batches @@ -251,7 +278,7 @@ def in_batches(batch_size = DEFAULT_BATCH_SIZE) # # @return [Specification] def read_first - Specification.new(reader, result.dup { |r| r.read_as = :first }) + Specification.new(reader, result.with(read_as: :first)) end # Specifies that only last event should be read. @@ -259,7 +286,7 @@ def read_first # # @return [Specification] def read_last - Specification.new(reader, result.dup { |r| r.read_as = :last }) + Specification.new(reader, result.with(read_as: :last)) end # Executes the query based on the specification built up to this point. @@ -286,9 +313,9 @@ def last # @types [Class, Array(Class)] types of event to look for. # @return [Specification] def of_type(*types) - Specification.new(reader, result.dup { |r| r.with_types = types.flatten }) + Specification.new(reader, result.with(with_types: types.flatten.map(&:to_s))) end - alias_method :of_types, :of_type + alias of_types of_type # Limits the query to certain events by given even ids. # {http://railseventstore.org/docs/read/ Find out more}. @@ -296,7 +323,7 @@ def of_type(*types) # @param event_ids [Array(String)] ids of event to look for. # @return [Specification] def with_id(event_ids) - Specification.new(reader, result.dup { |r| r.with_ids = event_ids }) + Specification.new(reader, result.with(with_ids: event_ids)) end # Reads single event from repository. @@ -336,4 +363,6 @@ def events(event_ids) attr_reader :reader end + + SpecificationResult = Specification::Result end diff --git a/ruby_event_store/lib/ruby_event_store/specification_result.rb b/ruby_event_store/lib/ruby_event_store/specification_result.rb deleted file mode 100644 index 0bb12cd180..0000000000 --- a/ruby_event_store/lib/ruby_event_store/specification_result.rb +++ /dev/null @@ -1,312 +0,0 @@ -# frozen_string_literal: true - -module RubyEventStore - class SpecificationResult - def initialize( - direction: :forward, - start: nil, - stop: nil, - older_than: nil, - older_than_or_equal: nil, - newer_than: nil, - newer_than_or_equal: nil, - time_sort_by: nil, - count: nil, - stream: Stream.new(GLOBAL_STREAM), - read_as: :all, - batch_size: Specification::DEFAULT_BATCH_SIZE, - with_ids: nil, - with_types: nil - ) - @attributes = - Struct.new( - :direction, - :start, - :stop, - :older_than, - :older_than_or_equal, - :newer_than, - :newer_than_or_equal, - :time_sort_by, - :count, - :stream, - :read_as, - :batch_size, - :with_ids, - :with_types, - ).new( - direction, - start, - stop, - older_than, - older_than_or_equal, - newer_than, - newer_than_or_equal, - time_sort_by, - count, - stream, - read_as, - batch_size, - with_ids, - with_types, - ) - freeze - end - - # Limited results. True if number of read elements are limited - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Boolean] - def limit? - !attributes.count.nil? - end - - # Results limit or infinity if limit not defined - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Integer|Infinity] - def limit - attributes.count || Float::INFINITY - end - - # Stream definition. Stream to be read or nil - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Stream|nil] - def stream - attributes.stream - end - - # Starting position. Event id of starting event - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [String] - def start - attributes.start - end - - # Stop position. Event id of stopping event - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [String|Symbol] - def stop - attributes.stop - end - - # Ending time. - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Time] - def older_than - attributes.older_than - end - - # Ending time. - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Time] - def older_than_or_equal - attributes.older_than_or_equal - end - - # Starting time. - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Time] - def newer_than - attributes.newer_than - end - - # Starting time. - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Time] - def newer_than_or_equal - attributes.newer_than_or_equal - end - - # Time sorting strategy. Nil when not specified. - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Symbol] - def time_sort_by - attributes.time_sort_by - end - - # Read direction. True is reading forward - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Boolean] - def forward? - get_direction.equal?(:forward) - end - - # Read direction. True is reading backward - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Boolean] - def backward? - get_direction.equal?(:backward) - end - - # Size of batch to read (only for :batch read strategy) - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Integer] - def batch_size - attributes.batch_size - end - - # Ids of specified event to be read (if any given) - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Array|nil] - def with_ids - attributes.with_ids - end - - # Read by specified ids. True if event ids have been specified. - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Boolean] - def with_ids? - !with_ids.nil? - end - - # Event types to be read (if any given) - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Array|nil] - def with_types - attributes.with_types&.map(&:to_s) - end - - # Read by specified event types. True if event types have been specified. - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Boolean] - def with_types? - !(with_types || []).empty? - end - - # Read strategy. True if items will be read in batches - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Boolean] - def batched? - attributes.read_as.equal?(:batch) - end - - # Read strategy. True if first item will be read - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Boolean] - def first? - attributes.read_as.equal?(:first) - end - - # Read strategy. True if last item will be read - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Boolean] - def last? - attributes.read_as.equal?(:last) - end - - # Read strategy. True if all items will be read - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Boolean] - def all? - attributes.read_as.equal?(:all) - end - - # Read strategy. True if results will be sorted by timestamp - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Boolean] - def time_sort_by_as_at? - time_sort_by.equal?(:as_at) - end - - # Read strategy. True if results will be sorted by valid_at - # {http://railseventstore.org/docs/read/ Find out more}. - # - # @return [Boolean] - def time_sort_by_as_of? - time_sort_by.equal?(:as_of) - end - - # Clone [SpecificationResult] - # If block is given cloned attributes might be modified. - # - # @return [SpecificationResult] - def dup - new_attributes = attributes.dup - yield new_attributes if block_given? - SpecificationResult.new(**new_attributes.to_h) - end - - # Two specification attributess are equal if: - # * they are of the same class - # * have identical data (verified with eql? method) - # - # @param other_spec [SpecificationResult, Object] object to compare - # - # @return [TrueClass, FalseClass] - def ==(other_spec) - other_spec.hash.eql?(hash) - end - - # Generates a Fixnum hash value for this object. This function - # have the property that a.eql?(b) implies a.hash == b.hash. - # - # The hash value is used along with eql? by the Hash class to - # determine if two objects reference the same hash key. - # - # This hash is based on - # * class - # * direction - # * start - # * stop - # * older_than - # * older_than_or_equal - # * newer_than - # * newer_than_or_equal - # * time_sort_by - # * count - # * stream - # * read_as - # * batch_size - # * with_ids - # * with_types - # - # @return [Integer] - def hash - [ - get_direction, - start, - stop, - older_than, - older_than_or_equal, - newer_than, - newer_than_or_equal, - time_sort_by, - limit, - stream, - attributes.read_as, - batch_size, - with_ids, - with_types, - ].hash ^ self.class.hash - end - - private - - attr_reader :attributes - - def get_direction - attributes.direction - end - end -end diff --git a/ruby_event_store/spec/specification_spec.rb b/ruby_event_store/spec/specification_spec.rb index b2ab5312dd..7535ef5140 100644 --- a/ruby_event_store/spec/specification_spec.rb +++ b/ruby_event_store/spec/specification_spec.rb @@ -67,7 +67,8 @@ module RubyEventStore specify { expect(specification.with_id([event_id]).result.with_ids?).to be(true) } specify { expect(specification.with_id([]).result.with_ids?).to be(true) } - specify { expect(specification.result.with_types).to be_nil } + specify { expect(specification.result.with_types).to eq([]) } + specify { expect(specification.of_type.result.with_types).to eq([]) } specify { expect(specification.of_type([TestEvent]).result.with_types).to eq(["TestEvent"]) } specify { expect(specification.result.with_types?).to be(false) } specify { expect(specification.of_type([TestEvent]).result.with_types?).to be(true) } @@ -644,97 +645,6 @@ module RubyEventStore specify { expect(specification.result.frozen?).to be(true) } specify { expect(specification.backward.result.frozen?).to be(true) } - specify "#hash" do - expect(specification.result.hash).to eq(specification.forward.result.hash) - expect(specification.forward.result.hash).not_to eq(specification.backward.result.hash) - - expect(specification.read_first.result.hash).to eq(specification.read_first.result.hash) - expect(specification.read_last.result.hash).to eq(specification.read_last.result.hash) - expect(specification.read_first.result.hash).not_to eq(specification.read_last.result.hash) - - expect(specification.result.hash).not_to eq(specification.limit(10).result.hash) - expect(specification.in_batches.result.hash).to eq( - specification.in_batches(Specification::DEFAULT_BATCH_SIZE).result.hash, - ) - expect(specification.in_batches.result.hash).not_to eq(specification.in_batches(10).result.hash) - expect(specification.result.hash).to eq(specification.stream(GLOBAL_STREAM).result.hash) - expect(specification.result.hash).not_to eq(specification.stream("dummy").result.hash) - - expect(specification.with_id(event_id).result.hash).to eq(specification.with_id(event_id).result.hash) - expect(specification.with_id(event_id).result.hash).not_to eq( - specification.with_id(SecureRandom.uuid).result.hash, - ) - - expect(specification.of_type([TestEvent]).result.hash).to eq(specification.of_type([TestEvent]).result.hash) - expect(specification.of_type([TestEvent]).result.hash).not_to eq( - specification.of_type([OrderCreated]).result.hash, - ) - - expect(specification.result.hash).not_to eq(specification.as_at.result.hash) - expect(specification.result.hash).not_to eq(specification.as_of.result.hash) - expect(specification.as_at.result.hash).not_to eq(specification.as_of.result.hash) - - with_event_of_id(event_id) do - expect(specification.from(event_id).result.hash).not_to eq(specification.result.hash) - expect(specification.to(event_id).result.hash).not_to eq(specification.result.hash) - end - - expect(specification.older_than(target_date).result.hash).not_to eq(specification.result.hash) - expect(specification.older_than_or_equal(target_date).result.hash).not_to eq(specification.result.hash) - expect(specification.newer_than(target_date).result.hash).not_to eq(specification.result.hash) - expect(specification.newer_than_or_equal(target_date).result.hash).not_to eq(specification.result.hash) - - expect(specification.result.hash).not_to eq( - [ - SpecificationResult, - :forward, - nil, - nil, - nil, - nil, - nil, - nil, - nil, - Float::INFINITY, - Stream.new(GLOBAL_STREAM), - :all, - Specification::DEFAULT_BATCH_SIZE, - nil, - nil, - ].hash, - ) - - expect(Class.new(SpecificationResult).new.hash).not_to eq(specification.result.hash) - end - - specify "#eql?" do - expect(specification.result).to eq(specification.forward.result) - expect(specification.forward.result).not_to eq(specification.backward.result) - - expect(specification.read_first.result).to eq(specification.read_first.result) - expect(specification.read_last.result).to eq(specification.read_last.result) - expect(specification.read_first.result).not_to eq(specification.read_last.result) - - expect(specification.result).not_to eq(specification.limit(10).result) - expect(specification.in_batches.result).to eq(specification.in_batches(Specification::DEFAULT_BATCH_SIZE).result) - expect(specification.in_batches.result).not_to eq(specification.in_batches(10).result) - expect(specification.result).to eq(specification.stream(GLOBAL_STREAM).result) - expect(specification.result).not_to eq(specification.stream("dummy").result) - - expect(specification.with_id(event_id).result).to eq(specification.with_id(event_id).result) - expect(specification.with_id(event_id).result).not_to eq(specification.with_id(SecureRandom.uuid).result) - - with_event_of_id(event_id) do - expect(specification.from(event_id).result).not_to eq(specification.result) - expect(specification.older_than(target_date).result).not_to eq(specification.result) - end - end - - specify "#dup" do - expect(specification.result.dup).to eq(specification.result) - specification.result.dup { |result| expect(result.object_id).not_to eq(specification.result.object_id) } - end - specify "#count" do expect(specification.count).to eq(0) (1..3).each { repository.append_to_stream([test_record], Stream.new(stream_name), ExpectedVersion.any) }