ruby_event_store-rom 1.3.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/lib/ruby_event_store/rom/changesets/create_events.rb +10 -18
  4. data/lib/ruby_event_store/rom/changesets/create_stream_entries.rb +6 -11
  5. data/lib/ruby_event_store/rom/changesets/update_events.rb +32 -19
  6. data/lib/ruby_event_store/rom/event_repository.rb +53 -51
  7. data/lib/ruby_event_store/rom/index_violation_detector.rb +29 -0
  8. data/lib/ruby_event_store/rom/mappers/event_to_serialized_record.rb +4 -4
  9. data/lib/ruby_event_store/rom/mappers/stream_entry_to_serialized_record.rb +5 -4
  10. data/lib/ruby_event_store/rom/rake_task.rb +5 -0
  11. data/lib/ruby_event_store/rom/relations/events.rb +81 -0
  12. data/lib/ruby_event_store/rom/relations/stream_entries.rb +90 -0
  13. data/lib/ruby_event_store/rom/repositories/events.rb +43 -29
  14. data/lib/ruby_event_store/rom/repositories/stream_entries.rb +7 -13
  15. data/lib/ruby_event_store/rom/{adapters/sql/tasks → tasks}/migration_tasks.rake +9 -9
  16. data/lib/ruby_event_store/rom/types.rb +2 -2
  17. data/lib/ruby_event_store/rom/unit_of_work.rb +29 -13
  18. data/lib/ruby_event_store/rom/version.rb +1 -1
  19. data/lib/ruby_event_store/rom.rb +29 -102
  20. data/lib/ruby_event_store-rom.rb +1 -1
  21. metadata +29 -48
  22. data/.rubocop.yml +0 -1
  23. data/.rubocop_todo.yml +0 -84
  24. data/CHANGELOG.md +0 -9
  25. data/Gemfile +0 -12
  26. data/Makefile +0 -57
  27. data/Rakefile +0 -20
  28. data/db/migrate/20180327044629_create_ruby_event_store_tables.rb +0 -54
  29. data/db/migrate/20181026152045_index_by_event_type.rb +0 -9
  30. data/lib/ruby_event_store/rom/adapters/memory/changesets/create_events.rb +0 -19
  31. data/lib/ruby_event_store/rom/adapters/memory/changesets/create_stream_entries.rb +0 -19
  32. data/lib/ruby_event_store/rom/adapters/memory/changesets/update_events.rb +0 -18
  33. data/lib/ruby_event_store/rom/adapters/memory/relations/events.rb +0 -56
  34. data/lib/ruby_event_store/rom/adapters/memory/relations/stream_entries.rb +0 -114
  35. data/lib/ruby_event_store/rom/adapters/memory/unit_of_work.rb +0 -36
  36. data/lib/ruby_event_store/rom/adapters/sql/changesets/create_events.rb +0 -15
  37. data/lib/ruby_event_store/rom/adapters/sql/changesets/update_events.rb +0 -41
  38. data/lib/ruby_event_store/rom/adapters/sql/index_violation_detector.rb +0 -31
  39. data/lib/ruby_event_store/rom/adapters/sql/rake_task.rb +0 -5
  40. data/lib/ruby_event_store/rom/adapters/sql/relations/events.rb +0 -27
  41. data/lib/ruby_event_store/rom/adapters/sql/relations/stream_entries.rb +0 -72
  42. data/lib/ruby_event_store/rom/memory.rb +0 -82
  43. data/lib/ruby_event_store/rom/sql.rb +0 -169
  44. data/lib/ruby_event_store/rom/tuple_uniqueness_error.rb +0 -21
  45. data/lib/ruby_event_store/spec/rom/event_repository_lint.rb +0 -176
  46. data/lib/ruby_event_store/spec/rom/relations/events_lint.rb +0 -75
  47. data/lib/ruby_event_store/spec/rom/relations/stream_entries_lint.rb +0 -198
  48. data/lib/ruby_event_store/spec/rom/spec_helper_lint.rb +0 -15
  49. data/lib/ruby_event_store/spec/rom/unit_of_work_lint.rb +0 -37
  50. data/ruby_event_store-rom.gemspec +0 -37
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 301643330ee509f7bb50b8eaab0c25ae261f57fbaeb8e6482c0c5e08e963aa6a
4
- data.tar.gz: fb44e5a4cea7fb9fc1ddcc2d20b3e9f2e1ca31f4d55d3d12385ddc3c5ceb7ed9
3
+ metadata.gz: 48f28d42768890a56d1b3b7656acdc5ac79c3cfe268292eccfbebc629a039c3d
4
+ data.tar.gz: ac9401345cbbfe5181822e94f87af9d0dfdabfa85b961665d66fd49f0a47a589
5
5
  SHA512:
6
- metadata.gz: d0678314a60a994e96459db4016d5f5d3b5282a2bef564e1f77ed038d47640882739aa5190913aca160707ca8e63c68ac8597419199c1cea8fdf0e0bcf4e605a
7
- data.tar.gz: b5b4749ffc9e53e85f9eba739777b23340bd8e628cb3d10a204cf42c33876ea0989a7dc58674b2912ac8dd0c936672c0fef5c5c2992dc70225788d746fadd4f4
6
+ metadata.gz: f30117d5b3a3fb046bacc5c13c5c83fb9ea115647eb906bd3649a5cb1b2d8cce9b4e36f9508224c4b4a32f65ca97d4edc803572049f8dfd27e248424c0995900
7
+ data.tar.gz: b9e646749e2631f476f27afcf33033cd73e3488d19b408f04b8694436099a643e3a2474c6525f646f94dc4b924f42e057a8036e81e77077569df65d5d3130ccc
data/README.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # RubyEventStore ROM Event Repository
2
2
 
3
+ ![RubyEventStore ROM Event Repository](https://github.com/RailsEventStore/rails_event_store/workflows/ruby_event_store-rom/badge.svg)
4
+
3
5
  A Ruby Object Model (ROM) implementation of events repository for [Ruby Event Store](https://github.com/RailsEventStore/rails_event_store).
4
6
 
5
7
  This version of the ROM adapter supports [rom-sql](https://github.com/rom-rb/rom-sql) at this time. It is an alternative to the ActiveRecord `EventRepository` implementation used in `rails_event_store` gem.
6
8
 
7
9
  [Read the docs to get started.](http://railseventstore.org/docs/repository/)
8
10
 
9
- _Additonal backing stores via ROM are being tracked here: [#299](https://github.com/RailsEventStore/rails_event_store/issues/299)._
@@ -4,27 +4,19 @@ module RubyEventStore
4
4
  module ROM
5
5
  module Changesets
6
6
  class CreateEvents < ::ROM::Changeset::Create
7
- module Defaults
8
- def self.included(base)
9
- base.class_eval do
10
- relation :events
7
+ relation :events
11
8
 
12
- # Convert to Hash
13
- map(&:to_h)
14
-
15
- map do
16
- rename_keys event_id: :id
17
- accept_keys %i[id data metadata event_type]
18
- end
19
-
20
- map do |tuple|
21
- Hash(created_at: RubyEventStore::ROM::Types::DateTime.call(nil)).merge(tuple)
22
- end
23
- end
24
- end
9
+ map(&:to_h)
10
+ map do
11
+ rename_keys timestamp: :created_at
12
+ map_value :created_at, ->(time) { Time.iso8601(time).localtime }
13
+ map_value :valid_at, ->(time) { Time.iso8601(time).localtime }
14
+ accept_keys %i[event_id data metadata event_type created_at valid_at]
25
15
  end
26
16
 
27
- include Defaults
17
+ def commit
18
+ relation.multi_insert(to_a)
19
+ end
28
20
  end
29
21
  end
30
22
  end
@@ -4,19 +4,14 @@ module RubyEventStore
4
4
  module ROM
5
5
  module Changesets
6
6
  class CreateStreamEntries < ::ROM::Changeset::Create
7
- module Defaults
8
- def self.included(base)
9
- base.class_eval do
10
- relation :stream_entries
7
+ relation :stream_entries
11
8
 
12
- map do |tuple|
13
- Hash(created_at: RubyEventStore::ROM::Types::DateTime.call(nil)).merge(tuple)
14
- end
15
- end
16
- end
9
+ map do |tuple|
10
+ Hash(created_at: RubyEventStore::ROM::Types::DateTime.call(nil)).merge(tuple)
11
+ end
12
+ map do
13
+ map_value :created_at, ->(datetime) { datetime.to_time.localtime }
17
14
  end
18
-
19
- include Defaults
20
15
  end
21
16
  end
22
17
  end
@@ -4,27 +4,40 @@ module RubyEventStore
4
4
  module ROM
5
5
  module Changesets
6
6
  class UpdateEvents < ::ROM::Changeset::Update
7
- module Defaults
8
- def self.included(base)
9
- base.class_eval do
10
- relation :events
11
-
12
- # Convert to Hash
13
- map(&:to_h)
14
-
15
- map do
16
- rename_keys event_id: :id
17
- accept_keys %i[id data metadata event_type created_at]
18
- end
19
-
20
- map do |tuple|
21
- Hash(created_at: RubyEventStore::ROM::Types::DateTime.call(nil)).merge(tuple)
22
- end
23
- end
24
- end
7
+ relation :events
8
+
9
+ map(&:to_h)
10
+ map do
11
+ rename_keys timestamp: :created_at
12
+ map_value :created_at, ->(time) { Time.iso8601(time).localtime }
13
+ map_value :valid_at, ->(time) { Time.iso8601(time).localtime }
14
+ accept_keys %i[event_id data metadata event_type created_at valid_at]
15
+ end
16
+
17
+ UPSERT_COLUMNS = %i[event_type data metadata valid_at].freeze
18
+
19
+ def commit
20
+ supports_on_duplicate_key_update? ? commit_on_duplicate_key_update : commit_insert_conflict_update
21
+ end
22
+
23
+ private
24
+
25
+ def supports_on_duplicate_key_update?
26
+ relation.dataset.db.adapter_scheme =~ /mysql/
27
+ end
28
+
29
+ def commit_on_duplicate_key_update
30
+ relation.dataset.on_duplicate_key_update(*UPSERT_COLUMNS).multi_insert(to_a)
25
31
  end
26
32
 
27
- include Defaults
33
+ def commit_insert_conflict_update
34
+ relation.dataset.insert_conflict(
35
+ target: :event_id,
36
+ update: UPSERT_COLUMNS.each_with_object({}) do |column, memo|
37
+ memo[column] = Sequel[:excluded][column]
38
+ end
39
+ ).multi_insert(to_a)
40
+ end
28
41
  end
29
42
  end
30
43
  end
@@ -1,40 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ruby_event_store/rom/unit_of_work'
4
- require 'forwardable'
5
-
6
3
  module RubyEventStore
7
4
  module ROM
8
5
  class EventRepository
9
- extend Forwardable
10
-
11
- def_delegator :@rom, :handle_error, :guard_for
12
- def_delegators :@rom, :unit_of_work
13
-
14
- def initialize(rom: ROM.env)
15
- raise ArgumentError, 'Must specify rom' unless rom && rom.instance_of?(Env)
16
-
17
- @rom = rom
18
- @events = Repositories::Events.new(rom.rom_container)
19
- @stream_entries = Repositories::StreamEntries.new(rom.rom_container)
6
+ def initialize(rom:, serializer:)
7
+ @serializer = serializer
8
+ @events = Repositories::Events.new(rom)
9
+ @stream_entries = Repositories::StreamEntries.new(rom)
10
+ @unit_of_work = UnitOfWork.new(rom.gateways.fetch(:default))
20
11
  end
21
12
 
22
- def append_to_stream(events, stream, expected_version)
23
- events = Array(events)
24
- event_ids = events.map(&:event_id)
25
-
26
- guard_for(:unique_violation) do
27
- unit_of_work do |changesets|
28
- # Create changesets inside transaction because
29
- # we want to find the last position (a.k.a. version)
30
- # again if the transaction is retried due to a
31
- # deadlock in MySQL
32
- changesets << @events.create_changeset(events)
13
+ def append_to_stream(records, stream, expected_version)
14
+ serialized_records = records.map { |record| record.serialize(@serializer) }
15
+ event_ids = records.map(&:event_id)
16
+
17
+ handle_unique_violation do
18
+ @unit_of_work.call do |changesets|
19
+ changesets << @events.create_changeset(serialized_records)
33
20
  changesets << @stream_entries.create_changeset(
34
21
  event_ids,
35
22
  stream,
36
- @stream_entries.resolve_version(stream, expected_version),
37
- global_stream: true
23
+ @stream_entries.resolve_version(stream, expected_version)
38
24
  )
39
25
  end
40
26
  end
@@ -43,15 +29,10 @@ module RubyEventStore
43
29
  end
44
30
 
45
31
  def link_to_stream(event_ids, stream, expected_version)
46
- event_ids = Array(event_ids)
32
+ validate_event_ids(event_ids)
47
33
 
48
- # Validate event IDs
49
- @events
50
- .find_nonexistent_pks(event_ids)
51
- .each { |id| raise EventNotFound, id }
52
-
53
- guard_for(:unique_violation) do
54
- unit_of_work do |changesets|
34
+ handle_unique_violation do
35
+ @unit_of_work.call do |changesets|
55
36
  changesets << @stream_entries.create_changeset(
56
37
  event_ids,
57
38
  stream,
@@ -63,44 +44,65 @@ module RubyEventStore
63
44
  self
64
45
  end
65
46
 
47
+ def position_in_stream(event_id, stream)
48
+ @stream_entries.position_in_stream(event_id, stream)
49
+ end
50
+
51
+ def global_position(event_id)
52
+ @events.global_position(event_id)
53
+ end
54
+
66
55
  def delete_stream(stream)
67
56
  @stream_entries.delete(stream)
68
57
  end
69
58
 
70
59
  def has_event?(event_id)
71
- guard_for(:not_found, event_id, swallow: EventNotFound) { @events.exist?(event_id) } || false
60
+ @events.exist?(event_id)
61
+ rescue Sequel::DatabaseError => doh
62
+ raise doh unless doh.message =~ /PG::InvalidTextRepresentation.*uuid/
63
+ false
72
64
  end
73
65
 
74
66
  def last_stream_event(stream)
75
- @events.last_stream_event(stream)
67
+ @events.last_stream_event(stream, @serializer)
76
68
  end
77
69
 
78
70
  def read(specification)
79
- raise ReservedInternalName if specification.stream.name.eql?(@stream_entries.stream_entries.class::SERIALIZED_GLOBAL_STREAM_NAME)
80
-
81
- @events.read(specification)
71
+ @events.read(specification, @serializer)
82
72
  end
83
73
 
84
74
  def count(specification)
85
- raise ReservedInternalName if specification.stream.name.eql?(@stream_entries.stream_entries.class::SERIALIZED_GLOBAL_STREAM_NAME)
86
-
87
75
  @events.count(specification)
88
76
  end
89
77
 
90
- def update_messages(messages)
91
- # Validate event IDs
92
- @events
93
- .find_nonexistent_pks(messages.map(&:event_id))
94
- .each { |id| raise EventNotFound, id }
78
+ def update_messages(records)
79
+ validate_event_ids(records.map(&:event_id))
95
80
 
96
- unit_of_work do |changesets|
97
- changesets << @events.update_changeset(messages)
81
+ @unit_of_work.call do |changesets|
82
+ serialized_records = records.map { |record| record.serialize(@serializer) }
83
+ changesets << @events.update_changeset(serialized_records)
98
84
  end
99
85
  end
100
86
 
101
87
  def streams_of(event_id)
102
- @stream_entries.streams_of(event_id)
103
- .map { |name| Stream.new(name) }
88
+ @stream_entries
89
+ .streams_of(event_id)
90
+ .map { |name| Stream.new(name) }
91
+ end
92
+
93
+ private
94
+
95
+ def validate_event_ids(event_ids)
96
+ @events
97
+ .find_nonexistent_pks(event_ids)
98
+ .each { |id| raise EventNotFound, id }
99
+ end
100
+
101
+ def handle_unique_violation
102
+ yield
103
+ rescue ::ROM::SQL::UniqueConstraintError, Sequel::UniqueConstraintViolation => doh
104
+ raise ::RubyEventStore::EventDuplicatedInStream if IndexViolationDetector.new.detect(doh.message)
105
+ raise ::RubyEventStore::WrongExpectedEventVersion
104
106
  end
105
107
  end
106
108
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ module ROM
5
+ class IndexViolationDetector
6
+ MYSQL5_PKEY_ERROR = "for key 'index_event_store_events_on_event_id'".freeze
7
+ MYSQL8_PKEY_ERROR = "for key 'event_store_events.index_event_store_events_on_event_id'".freeze
8
+ POSTGRES_PKEY_ERROR = "Key (event_id)".freeze
9
+ SQLITE3_PKEY_ERROR = "event_store_events.event_id".freeze
10
+
11
+ MYSQL5_INDEX_ERROR = "for key 'index_event_store_events_in_streams_on_stream_and_event_id'".freeze
12
+ MYSQL8_INDEX_ERROR = "for key 'event_store_events_in_streams.index_event_store_events_in_streams_on_stream_and_event_id'".freeze
13
+ POSTGRES_INDEX_ERROR = "Key (stream, event_id)".freeze
14
+ SQLITE3_INDEX_ERROR = "event_store_events_in_streams.stream, event_store_events_in_streams.event_id".freeze
15
+
16
+ def detect(message)
17
+ message.include?(MYSQL5_PKEY_ERROR) ||
18
+ message.include?(MYSQL8_PKEY_ERROR) ||
19
+ message.include?(POSTGRES_PKEY_ERROR) ||
20
+ message.include?(SQLITE3_PKEY_ERROR) ||
21
+
22
+ message.include?(MYSQL5_INDEX_ERROR) ||
23
+ message.include?(MYSQL8_INDEX_ERROR) ||
24
+ message.include?(POSTGRES_INDEX_ERROR) ||
25
+ message.include?(SQLITE3_INDEX_ERROR)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rom/transformer'
4
-
5
3
  module RubyEventStore
6
4
  module ROM
7
5
  module Mappers
@@ -10,8 +8,10 @@ module RubyEventStore
10
8
  register_as :event_to_serialized_record
11
9
 
12
10
  map_array do
13
- rename_keys id: :event_id
14
- accept_keys %i[event_id data metadata event_type]
11
+ map_value :created_at, ->(time) { time.iso8601(TIMESTAMP_PRECISION) }
12
+ map_value :valid_at, ->(time) { time.iso8601(TIMESTAMP_PRECISION) }
13
+ rename_keys created_at: :timestamp
14
+ accept_keys %i[event_id data metadata event_type timestamp valid_at]
15
15
  constructor_inject RubyEventStore::SerializedRecord
16
16
  end
17
17
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rom/transformer'
4
-
5
3
  module RubyEventStore
6
4
  module ROM
7
5
  module Mappers
@@ -10,8 +8,11 @@ module RubyEventStore
10
8
  register_as :stream_entry_to_serialized_record
11
9
 
12
10
  map_array do
13
- unwrap :event, %i[data metadata event_type]
14
- accept_keys %i[event_id data metadata event_type]
11
+ unwrap :event, %i[event_id data metadata event_type created_at valid_at]
12
+ map_value :created_at, ->(time) { time.iso8601(TIMESTAMP_PRECISION) }
13
+ map_value :valid_at, ->(time) { time.iso8601(TIMESTAMP_PRECISION) }
14
+ rename_keys created_at: :timestamp
15
+ accept_keys %i[event_id data metadata event_type timestamp valid_at]
15
16
  constructor_inject RubyEventStore::SerializedRecord
16
17
  end
17
18
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rom/sql/rake_task"
4
+ require "ruby_event_store/rom"
5
+ load "ruby_event_store/rom/tasks/migration_tasks.rake"
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ module ROM
5
+ module Relations
6
+ class Events < ::ROM::Relation[:sql]
7
+ schema(:event_store_events, as: :events, infer: true) do
8
+ attribute :event_id, ::ROM::Types::String
9
+ attribute :data, RubyEventStore::ROM::Types::RecordSerializer,
10
+ read: RubyEventStore::ROM::Types::RecordDeserializer
11
+ attribute :metadata, RubyEventStore::ROM::Types::RecordSerializer,
12
+ read: RubyEventStore::ROM::Types::RecordDeserializer
13
+ attribute :created_at, RubyEventStore::ROM::Types::DateTime
14
+ attribute :valid_at, RubyEventStore::ROM::Types::DateTime
15
+
16
+ primary_key :event_id
17
+ end
18
+
19
+ alias take limit
20
+
21
+ def create_changeset(tuples)
22
+ events.changeset(Changesets::CreateEvents, tuples)
23
+ end
24
+
25
+ def update_changeset(tuples)
26
+ events.changeset(Changesets::UpdateEvents, tuples)
27
+ end
28
+
29
+ def by_event_id(event_id)
30
+ where(event_id: event_id)
31
+ end
32
+
33
+ def by_event_type(types)
34
+ where(event_type: types)
35
+ end
36
+
37
+ def newer_than(time)
38
+ where { |r| r.events[:created_at] > time.localtime }
39
+ end
40
+
41
+ def newer_than_or_equal(time)
42
+ where { |r| r.events[:created_at] >= time.localtime }
43
+ end
44
+
45
+ def older_than(time)
46
+ where { |r| r.events[:created_at] < time.localtime }
47
+ end
48
+
49
+ def older_than_or_equal(time)
50
+ where { |r| r.events[:created_at] <= time.localtime }
51
+ end
52
+
53
+ DIRECTION_MAP = {
54
+ forward: %i[asc > <],
55
+ backward: %i[desc < >]
56
+ }.freeze
57
+
58
+ def ordered(direction, offset_entry_id = nil, stop_entry_id = nil, time_sort_by = nil)
59
+ order, operator_offset, operator_stop = DIRECTION_MAP[direction]
60
+
61
+ raise ArgumentError, "Direction must be :forward or :backward" if order.nil?
62
+
63
+ event_order_columns = [:id]
64
+
65
+ case time_sort_by
66
+ when :as_at
67
+ event_order_columns.unshift :created_at
68
+ when :as_of
69
+ event_order_columns.unshift :valid_at
70
+ end
71
+
72
+ query = self
73
+ query = query.where { id.public_send(operator_offset, offset_entry_id) } if offset_entry_id
74
+ query = query.where { id.public_send(operator_stop, stop_entry_id) } if stop_entry_id
75
+
76
+ query.order(*event_order_columns.map { |c| events[c].public_send(order) })
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ module ROM
5
+ module Relations
6
+ class StreamEntries < ::ROM::Relation[:sql]
7
+ schema(:event_store_events_in_streams, as: :stream_entries, infer: true) do
8
+ attribute :created_at, RubyEventStore::ROM::Types::DateTime
9
+
10
+ associations do
11
+ belongs_to :events, as: :event, foreign_key: :event_id
12
+ end
13
+ end
14
+
15
+ alias take limit
16
+
17
+ def create_changeset(tuples)
18
+ changeset(ROM::Changesets::CreateStreamEntries, tuples)
19
+ end
20
+
21
+ def by_stream(stream)
22
+ where(stream: stream.name)
23
+ end
24
+
25
+ def by_event_id(event_id)
26
+ where(event_id: event_id)
27
+ end
28
+
29
+ def by_event_type(types)
30
+ join(:events).where(event_type: types)
31
+ end
32
+
33
+ def by_stream_and_event_id(stream, event_id)
34
+ where(stream: stream.name, event_id: event_id).one!
35
+ end
36
+
37
+ def max_position(stream)
38
+ by_stream(stream).select(:position).order(Sequel.desc(:position)).first
39
+ end
40
+
41
+ def newer_than(time)
42
+ join(:events).where { |r| r.events[:created_at] > time.localtime }
43
+ end
44
+
45
+ def newer_than_or_equal(time)
46
+ join(:events).where { |r| r.events[:created_at] >= time.localtime }
47
+ end
48
+
49
+ def older_than(time)
50
+ join(:events).where { |r| r.events[:created_at] < time.localtime }
51
+ end
52
+
53
+ def older_than_or_equal(time)
54
+ join(:events).where { |r| r.events[:created_at] <= time.localtime }
55
+ end
56
+
57
+ DIRECTION_MAP = {
58
+ forward: %i[asc > <],
59
+ backward: %i[desc < >]
60
+ }.freeze
61
+
62
+ def ordered(direction, stream, offset_entry_id = nil, stop_entry_id = nil, time_sort_by = nil)
63
+ order, operator_offset, operator_stop = DIRECTION_MAP[direction]
64
+
65
+ raise ArgumentError, "Direction must be :forward or :backward" if order.nil?
66
+
67
+ event_order_columns = []
68
+ stream_order_columns = %i[id]
69
+
70
+ case time_sort_by
71
+ when :as_at
72
+ event_order_columns.unshift :created_at
73
+ when :as_of
74
+ event_order_columns.unshift :valid_at
75
+ end
76
+
77
+ query = by_stream(stream)
78
+ query = query.where { id.public_send(operator_offset, offset_entry_id) } if offset_entry_id
79
+ query = query.where { id.public_send(operator_stop, stop_entry_id) } if stop_entry_id
80
+
81
+ if event_order_columns.empty?
82
+ query.order { |r| stream_order_columns.map { |c| r[:stream_entries][c].public_send(order) } }
83
+ else
84
+ query.join(:events).order { |r| event_order_columns.map { |c| r.events[c].public_send(order) } }
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end