rails_simple_event_sourcing 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f612b51a40ff345ec42bb1fbc1aa60f7444278e4d7e8adca318fbf9ae3d6676e
4
- data.tar.gz: 4e62dd3f13548750d95309e729537e1681c2630f05a901702d2e4afe96f48971
3
+ metadata.gz: 3de0a8a9ff2169679636b2f07ecbeb3c665bfd6a7167f564029c343d508dbcc0
4
+ data.tar.gz: 136e1bb962428462dd0921cefa63746beefb6efab578f6c8a0e018709f10da1f
5
5
  SHA512:
6
- metadata.gz: fbd0a8385be3282c59d0c29908b65eb209f82a4a6978e9f614f72de8ebe5ed06b1c88daa4e6975cab94e3c5f4e9e9e3b00d8c977d7c46f64642e2c7eadde2c8e
7
- data.tar.gz: e2552009f821e2e71e0c72f274c5dd1056465c1f1cc991100320f08152be6db7df4f874e83439ad29408779cac02877b851e9ebb5204b6be6f03da76ada8d03c
6
+ metadata.gz: fcd653bc3201aeb4cffca05df6f9819e3b8d9ee2eab08e60e35988438321bf6f6b15fb7d65d0940483f9087877f5f451645fc26f7e1fb31fb36e61a898760aee
7
+ data.tar.gz: 85449f552f258d5d2ccd43f238389769dafdf885d93beeb7f1e4f47280f1bbc9ab5acb8fa435e94a3ca9f0602af52677812f1c8dcffa85857277b977081f0fb7
data/README.md CHANGED
@@ -88,6 +88,9 @@ RailsSimpleEventSourcing.configure do |config|
88
88
  # When true, falls back to convention-based handler resolution
89
89
  # When false, requires explicit registration of all handlers
90
90
  config.use_naming_convention_fallback = true
91
+
92
+ # Number of events displayed per page in the Events Viewer (defaults to 25)
93
+ config.events_per_page = 50
91
94
  end
92
95
  ```
93
96
 
@@ -545,11 +548,11 @@ Mount the engine in your `config/routes.rb`:
545
548
 
546
549
  ```ruby
547
550
  Rails.application.routes.draw do
548
- mount RailsSimpleEventSourcing::Engine, at: "/event-store"
551
+ mount RailsSimpleEventSourcing::Engine, at: "/events"
549
552
  end
550
553
  ```
551
554
 
552
- Then navigate to `/event-store` in your browser to access the viewer.
555
+ Then navigate to `/events` in your browser to access the viewer.
553
556
 
554
557
  **Password Protection:**
555
558
 
@@ -561,11 +564,11 @@ In production you will likely want to restrict access to the events viewer.
561
564
  Rails.application.routes.draw do
562
565
  mount Rack::Auth::Basic.new(
563
566
  RailsSimpleEventSourcing::Engine,
564
- "Event Sourcing"
567
+ "Events"
565
568
  ) { |username, password|
566
569
  ActiveSupport::SecurityUtils.secure_compare(username, "admin") &
567
570
  ActiveSupport::SecurityUtils.secure_compare(password, Rails.application.credentials.event_sourcing_password || "secret")
568
- }, at: "/event-sourcing"
571
+ }, at: "/events"
569
572
  end
570
573
  ```
571
574
 
@@ -574,7 +577,7 @@ end
574
577
  ```ruby
575
578
  Rails.application.routes.draw do
576
579
  authenticate :user, ->(user) { user.admin? } do
577
- mount RailsSimpleEventSourcing::Engine, at: "/event-store"
580
+ mount RailsSimpleEventSourcing::Engine, at: "/events"
578
581
  end
579
582
  end
580
583
  ```
@@ -587,16 +590,6 @@ end
587
590
  - **Filtering** - Filter events by event type or aggregate type using dropdown selectors
588
591
  - **Search** - Search by aggregate ID, or use `key:value` syntax to search within payload and metadata (e.g., `email:john@example.com`)
589
592
 
590
- **Configuration:**
591
-
592
- You can configure the number of events displayed per page (defaults to 25):
593
-
594
- ```ruby
595
- RailsSimpleEventSourcing.configure do |config|
596
- config.events_per_page = 50
597
- end
598
- ```
599
-
600
593
  ### Model Configuration
601
594
 
602
595
  Models that use event sourcing should include the `RailsSimpleEventSourcing::Events` module:
@@ -646,10 +639,6 @@ class Customer < ApplicationRecord
646
639
 
647
640
  scope :active, -> { where(deleted_at: nil) }
648
641
  scope :deleted, -> { where.not(deleted_at: nil) }
649
-
650
- def soft_delete
651
- update(deleted_at: Time.current)
652
- end
653
642
  end
654
643
 
655
644
  # Event
@@ -3,9 +3,9 @@
3
3
  module RailsSimpleEventSourcing
4
4
  class EventsController < ApplicationController
5
5
  def index
6
- load_filter_options
7
- scope = search_events
8
- paginate(scope)
6
+ @event_types = event_types
7
+ @aggregates = aggregates
8
+ paginate(search_events)
9
9
  end
10
10
 
11
11
  def show
@@ -16,15 +16,21 @@ module RailsSimpleEventSourcing
16
16
 
17
17
  private
18
18
 
19
- def load_filter_options
20
- @event_types = Event.distinct.pluck(:event_type).sort
21
- @aggregates = Event.where.not(eventable_type: nil).distinct.pluck(:eventable_type).sort
19
+ def event_types
20
+ return Event.descendants.map(&:name).sort if Rails.env.production?
21
+
22
+ Event.distinct.pluck(:event_type).sort
23
+ end
24
+
25
+ def aggregates
26
+ return Event.descendants.filter_map(&:aggregate_class).map(&:name).uniq.sort if Rails.env.production?
27
+
28
+ Event.where.not(eventable_type: nil).distinct.pluck(:eventable_type).sort
22
29
  end
23
30
 
24
31
  def search_events
25
- scope = Event.order(created_at: :desc)
26
32
  EventSearch.new(
27
- scope:,
33
+ scope: Event.all,
28
34
  event_type: params[:event_type],
29
35
  aggregate: params[:aggregate],
30
36
  query: params[:q]
@@ -34,8 +40,9 @@ module RailsSimpleEventSourcing
34
40
  def paginate(scope)
35
41
  @paginator = Paginator.new(
36
42
  scope:,
37
- page: params[:page],
38
- per_page: RailsSimpleEventSourcing.config.events_per_page
43
+ per_page: RailsSimpleEventSourcing.config.events_per_page,
44
+ cursor: params[:after] || params[:before],
45
+ direction: params[:before].present? ? :prev : :next
39
46
  )
40
47
  end
41
48
 
@@ -2,26 +2,5 @@
2
2
 
3
3
  module RailsSimpleEventSourcing
4
4
  module EventsHelper
5
- def pagination_window(current_page, total_pages, window: 2) # rubocop:disable Metrics/MethodLength
6
- return [1] if total_pages <= 1
7
-
8
- pages = []
9
- left = [current_page - window, 1].max
10
- right = [current_page + window, total_pages].min
11
-
12
- if left > 1
13
- pages << 1
14
- pages << :gap if left > 2
15
- end
16
-
17
- (left..right).each { |p| pages << p }
18
-
19
- if right < total_pages
20
- pages << :gap if right < total_pages - 1
21
- pages << total_pages
22
- end
23
-
24
- pages
25
- end
26
5
  end
27
6
  end
@@ -13,6 +13,10 @@ module RailsSimpleEventSourcing
13
13
  @write_access_enabled = true
14
14
  end
15
15
 
16
+ def disable_write_access!
17
+ @write_access_enabled = false
18
+ end
19
+
16
20
  private
17
21
 
18
22
  def write_access_enabled
@@ -14,10 +14,13 @@ module RailsSimpleEventSourcing
14
14
 
15
15
  before_validation :setup_for_create, on: :create
16
16
  before_save :persist_aggregate, if: :aggregate_defined?
17
+ after_create :disable_write_access!
17
18
 
18
19
  def apply(aggregate)
19
20
  payload.each do |key, value|
20
- aggregate.send("#{key}=", value) if aggregate.respond_to?("#{key}=")
21
+ raise "Unknown attribute '#{key}' on #{aggregate.class}" unless aggregate.respond_to?("#{key}=")
22
+
23
+ aggregate.send("#{key}=", value)
21
24
  end
22
25
  end
23
26
 
@@ -67,6 +70,7 @@ module RailsSimpleEventSourcing
67
70
 
68
71
  aggregate_repository.save!(@aggregate)
69
72
  self.aggregate_id = @aggregate.id
73
+ @aggregate.disable_write_access!
70
74
  end
71
75
 
72
76
  def aggregate_repository
@@ -1,23 +1,13 @@
1
- <% if @paginator.total_pages > 1 %>
1
+ <% if @paginator.prev? || @paginator.next? %>
2
2
  <nav class="pagination">
3
- <% if @paginator.current_page > 1 %>
4
- <%= link_to "Prev", events_path(page: @paginator.current_page - 1, q: params[:q], event_type: params[:event_type], aggregate: params[:aggregate]) %>
3
+ <% if @paginator.prev? %>
4
+ <%= link_to "Prev", events_path(before: @paginator.prev_cursor, q: params[:q], event_type: params[:event_type], aggregate: params[:aggregate]) %>
5
5
  <% else %>
6
6
  <span class="disabled">Prev</span>
7
7
  <% end %>
8
8
 
9
- <% pagination_window(@paginator.current_page, @paginator.total_pages).each do |page| %>
10
- <% if page == :gap %>
11
- <span class="gap">&hellip;</span>
12
- <% elsif page == @paginator.current_page %>
13
- <span class="current"><%= page %></span>
14
- <% else %>
15
- <%= link_to page, events_path(page: page, q: params[:q], event_type: params[:event_type], aggregate: params[:aggregate]) %>
16
- <% end %>
17
- <% end %>
18
-
19
- <% if @paginator.current_page < @paginator.total_pages %>
20
- <%= link_to "Next", events_path(page: @paginator.current_page + 1, q: params[:q], event_type: params[:event_type], aggregate: params[:aggregate]) %>
9
+ <% if @paginator.next? %>
10
+ <%= link_to "Next", events_path(after: @paginator.next_cursor, q: params[:q], event_type: params[:event_type], aggregate: params[:aggregate]) %>
21
11
  <% else %>
22
12
  <span class="disabled">Next</span>
23
13
  <% end %>
@@ -10,8 +10,7 @@
10
10
 
11
11
  <div class="card">
12
12
  <div class="info-bar">
13
- <span><%= @paginator.total_count %> event<%= @paginator.total_count == 1 ? "" : "s" %> found</span>
14
- <span>Page <%= @paginator.current_page %> of <%= @paginator.total_pages %></span>
13
+ <span>Events</span>
15
14
  </div>
16
15
 
17
16
  <table>
@@ -1,4 +1,4 @@
1
- <%= link_to "&larr; Back to events".html_safe, events_path(q: params[:q], page: params[:page]), class: "back-link" %>
1
+ <%= link_to "&larr; Back to events".html_safe, events_path(q: params[:q]), class: "back-link" %>
2
2
 
3
3
  <div class="card">
4
4
  <dl class="detail-grid">
data/config/routes.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  RailsSimpleEventSourcing::Engine.routes.draw do
4
- resources :events, only: [:index, :show]
5
- root to: "events#index"
4
+ root to: "events#index", as: :events
5
+ get ":id", to: "events#show", as: :event
6
6
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'concurrent'
3
4
  require_relative 'aggregate_repository'
4
5
  require_relative 'command_handler'
5
6
  require_relative 'command_handlers/base'
@@ -2,18 +2,68 @@
2
2
 
3
3
  module RailsSimpleEventSourcing
4
4
  class Paginator
5
- attr_reader :total_count, :total_pages, :current_page, :per_page
5
+ attr_reader :per_page
6
6
 
7
- def initialize(scope:, page:, per_page:)
7
+ def initialize(scope:, per_page:, cursor: nil, direction: :next)
8
8
  @scope = scope
9
9
  @per_page = per_page
10
- @total_count = scope.count
11
- @total_pages = [(@total_count.to_f / per_page).ceil, 1].max
12
- @current_page = (page.presence || 1).to_i.clamp(1, @total_pages)
10
+ @cursor = cursor&.to_i
11
+ @direction = direction
13
12
  end
14
13
 
15
14
  def records
16
- @scope.offset((@current_page - 1) * @per_page).limit(@per_page)
15
+ @records ||= fetch_records
16
+ end
17
+
18
+ def next_cursor
19
+ records.last&.id
20
+ end
21
+
22
+ def prev_cursor
23
+ records.first&.id
24
+ end
25
+
26
+ def next?
27
+ records
28
+ @has_next
29
+ end
30
+
31
+ def prev?
32
+ records
33
+ @has_prev
34
+ end
35
+
36
+ private
37
+
38
+ def fetch_records
39
+ if @cursor.nil?
40
+ fetch_first_page
41
+ elsif @direction == :prev
42
+ fetch_prev_page
43
+ else
44
+ fetch_next_page
45
+ end
46
+ end
47
+
48
+ def fetch_first_page
49
+ rows = @scope.order(id: :desc).limit(@per_page + 1).to_a
50
+ @has_prev = false
51
+ @has_next = rows.size > @per_page
52
+ rows.first(@per_page)
53
+ end
54
+
55
+ def fetch_next_page
56
+ rows = @scope.where(id: ...@cursor).order(id: :desc).limit(@per_page + 1).to_a
57
+ @has_prev = true
58
+ @has_next = rows.size > @per_page
59
+ rows.first(@per_page)
60
+ end
61
+
62
+ def fetch_prev_page
63
+ rows = @scope.where('id > ?', @cursor).order(id: :asc).limit(@per_page + 1).to_a
64
+ @has_next = true
65
+ @has_prev = rows.size > @per_page
66
+ rows.first(@per_page).reverse
17
67
  end
18
68
  end
19
69
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsSimpleEventSourcing
4
- VERSION = '1.0.6'
4
+ VERSION = '1.0.8'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_simple_event_sourcing
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.6
4
+ version: 1.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Damian Baćkowski
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-02-15 00:00:00.000000000 Z
11
+ date: 2026-02-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: 7.1.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: concurrent-ruby
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '1.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '1.1'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rubocop
43
57
  requirement: !ruby/object:Gem::Requirement