Class: Lyra::DualView
- Inherits:
-
Object
- Object
- Lyra::DualView
- Defined in:
- lib/lyra/dual_view.rb
Overview
Provides dual view comparison between CRUD state and Event-sourced state
Instance Attribute Summary collapse
-
#model_class ⇒ Object
readonly
Returns the value of attribute model_class.
-
#model_id ⇒ Object
readonly
Returns the value of attribute model_id.
Class Method Summary collapse
Instance Method Summary collapse
-
#audit_trail ⇒ Object
Get audit trail from events.
-
#calculate_differences ⇒ Object
Calculate differences between CRUD and Event-sourced states.
-
#compare ⇒ Object
Get both CRUD and Event-sourced views.
-
#crud_state ⇒ Object
CRUD view - current database state.
- #event_operation(event) ⇒ Object
-
#event_sourced_state ⇒ Object
Event-sourced view - state rebuilt from events.
-
#event_timestamp(event) ⇒ Object
Helper methods to extract data from events (works with both Lyra::Event and RubyEventStore::Event).
-
#initialize(model_class, model_id) ⇒ DualView
constructor
A new instance of DualView.
Constructor Details
#initialize(model_class, model_id) ⇒ DualView
Returns a new instance of DualView.
6 7 8 9 |
# File 'lib/lyra/dual_view.rb', line 6 def initialize(model_class, model_id) @model_class = model_class @model_id = model_id end |
Instance Attribute Details
#model_class ⇒ Object (readonly)
Returns the value of attribute model_class.
4 5 6 |
# File 'lib/lyra/dual_view.rb', line 4 def model_class @model_class end |
#model_id ⇒ Object (readonly)
Returns the value of attribute model_id.
4 5 6 |
# File 'lib/lyra/dual_view.rb', line 4 def model_id @model_id end |
Class Method Details
.compare_all(model_class) ⇒ Object
185 186 187 188 189 |
# File 'lib/lyra/dual_view.rb', line 185 def compare_all(model_class) model_class.find_each.map do |record| new(model_class, record.id).compare end end |
.find_discrepancies(model_class) ⇒ Object
191 192 193 194 195 |
# File 'lib/lyra/dual_view.rb', line 191 def find_discrepancies(model_class) compare_all(model_class).select do |comparison| comparison[:differences] != { no_differences: true } end end |
Instance Method Details
#audit_trail ⇒ Object
Get audit trail from events
119 120 121 |
# File 'lib/lyra/dual_view.rb', line 119 def audit_trail AuditProjection.audit_trail(model_class, model_id) end |
#calculate_differences ⇒ Object
Calculate differences between CRUD and Event-sourced states
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/lyra/dual_view.rb', line 83 def calculate_differences crud = crud_state es = event_sourced_state return { exists_mismatch: true } if crud[:exists] != es[:exists] return { no_differences: true } if !crud[:exists] && !es[:exists] crud_attrs = crud[:attributes] || {} es_attrs = es[:state] || {} differences = {} # Build lookup tables with both string and symbol keys crud_lookup = build_key_lookup(crud_attrs) es_lookup = build_key_lookup(es_attrs) # Compare union of all keys from both sources, excluding DB-managed timestamps ignored_keys = %i[created_at updated_at] all_keys = (crud_lookup.keys + es_lookup.keys).uniq - ignored_keys all_keys.each do |key| crud_val = crud_lookup[key] es_val = es_lookup[key] unless values_equal?(crud_val, es_val) differences[key] = { crud: crud_val, event_sourced: es_val } end end differences.empty? ? { no_differences: true } : differences end |
#compare ⇒ Object
Get both CRUD and Event-sourced views
12 13 14 15 16 17 18 19 20 21 22 23 24 |
# File 'lib/lyra/dual_view.rb', line 12 def compare { crud_view: crud_state, event_sourced_view: event_sourced_state, differences: calculate_differences, metadata: { model_class: model_class.name, model_id: model_id, timestamp: Time.current, mode: Lyra.config.mode } } end |
#crud_state ⇒ Object
CRUD view - current database state
27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/lyra/dual_view.rb', line 27 def crud_state record = model_class.find_by(id: model_id) return { exists: false } unless record { exists: true, attributes: record.attributes, timestamps: { created_at: record.created_at, updated_at: record.updated_at } } end |
#event_operation(event) ⇒ Object
76 77 78 79 80 |
# File 'lib/lyra/dual_view.rb', line 76 def event_operation(event) return event.operation if event.respond_to?(:operation) op = event.data[:operation] || event.data["operation"] op.is_a?(String) ? op.to_sym : op end |
#event_sourced_state ⇒ Object
Event-sourced view - state rebuilt from events
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/lyra/dual_view.rb', line 43 def event_sourced_state stream_name = "#{model_class.name}$#{model_id}" begin events = Lyra.config.event_store.read.stream(stream_name).to_a rescue => e return { exists: false, error: e., events_count: 0 } end return { exists: false, events_count: 0 } if events.empty? state = StateProjection.new.rebuild_from_events(events) { exists: true, state: state, events_count: events.count, first_event_at: (events.first), last_event_at: (events.last), events_summary: events.map { |e| { type: e.class.name, operation: event_operation(e), timestamp: (e) } } } end |
#event_timestamp(event) ⇒ Object
Helper methods to extract data from events (works with both Lyra::Event and RubyEventStore::Event)
71 72 73 74 |
# File 'lib/lyra/dual_view.rb', line 71 def (event) return event. if event.respond_to?(:timestamp) && event. event.data[:timestamp] || event.data["timestamp"] || event.[:timestamp] end |