ruby_event_store-rom 1.2.1 → 2.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.
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: 0fbad65ea5711e53b4647d47f04c6e22f3ef894d0754ab87e7b046b1db92c03c
4
- data.tar.gz: 07ac32244a81e624dbc01bcfec01f4a4eabbf1d6699b260c5b7d5917ba59448e
3
+ metadata.gz: 48f28d42768890a56d1b3b7656acdc5ac79c3cfe268292eccfbebc629a039c3d
4
+ data.tar.gz: ac9401345cbbfe5181822e94f87af9d0dfdabfa85b961665d66fd49f0a47a589
5
5
  SHA512:
6
- metadata.gz: 12f8616fdbe35f69b5c55030d21e9263cf1587896162a4570a28869a4ac464deb1f6707a971d3f9a656a2fddba4352adc2041e65596555b922b42f5b62aa6d20
7
- data.tar.gz: d8548c345aa648ee1ed2215131b21053e20b70eb3412884e4be0e23dd00c685b463206d5db542bcd1d3ed2d09ad090d0a3ab71499a5802beef2323d627edf5f1
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