Class: TurboReflex::StateManager

Inherits:
Object
  • Object
show all
Includes:
ActiveModel::Dirty
Defined in:
lib/turbo_reflex/state_manager.rb

Overview

Class used to hold ephemeral state related to the rendered UI.

Examples:

  • Sidebar open/closed state

  • Tree view open/closed state

  • Accordion collapsed/expanded state

  • Customized layout / presentation

  • Applied data filters

  • Number of data rows to display etc.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(runner) ⇒ StateManager

Returns a new instance of StateManager.



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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/turbo_reflex/state_manager.rb', line 44

def initialize(runner)
  @runner = runner

  begin
    @state = TurboReflex::State.new(cookie) # server state as stored in the cookie
  rescue => error
    Rails.logger.error "Failed to construct TurboReflex::State! #{error.message}"
    @state = TurboReflex::State.new
  end

  # State the server used to render the page last time
  cookie_state_hash = state.to_h

  # State managed by the server on the backend (redis cache etc.)
  # SEE: `TurboReflex::StateManager.state_override_block`
  server_state_hash = {}

  # State the client expects... related to optimistic UI updates
  # i.e. Changes made on the client before making this request
  header_state_hash = {}

  # Apply server state overrides (i.e. state stored in databases like Redis, Postgres, etc...)
  if TurboReflex.config.apply_server_state_overrides
    begin
      state_override_block = self.class.state_override_block(runner.controller)
      if state_override_block
        server_state_hash = runner.controller.instance_eval(&state_override_block).with_indifferent_access
        server_state_hash.each { |key, val| self[key] = val }
      end
    rescue => error
      Rails.logger.error "Failed to apply `state_override_block` configured in #{runner.controller.class.name} to TurboReflex::State! #{error.message}"
    end
  end

  # Apply client state overrides (i.e. optimistic state)
  # NOTE: Client state HTTP headers are only sent if/when state has changed on the client (only the changes are sent).
  #       This prevents race conditions (state mismatch) caused when frame and XHR requests emit immediately
  #       before the <meta id="turbo-reflex"> has been updated with the latest state from the server.
  if TurboReflex.config.apply_client_state_overrides
    begin
      header_state_hash = TurboReflex::State.deserialize_base64(header).with_indifferent_access
      header_state_hash.each { |key, val| self[key] = val }
    rescue => error
      Rails.logger.error "Failed to apply client state from HTTP headers to TurboReflex::State! #{error.message}"
    end
  end

  @cookie_data = cookie_state_hash
  @header_data = header_state_hash
  @server_data = server_state_hash
rescue => error
  Rails.logger.error "Failed to construct TurboReflex::State! #{error.message}"
ensure
  @state ||= TurboReflex::State.new
end

Instance Attribute Details

Returns the value of attribute cookie_data.



42
43
44
# File 'lib/turbo_reflex/state_manager.rb', line 42

def cookie_data
  @cookie_data
end

#header_dataObject (readonly)

Returns the value of attribute header_data.



42
43
44
# File 'lib/turbo_reflex/state_manager.rb', line 42

def header_data
  @header_data
end

#server_dataObject (readonly)

Returns the value of attribute server_data.



42
43
44
# File 'lib/turbo_reflex/state_manager.rb', line 42

def server_data
  @server_data
end

Class Method Details

.add_state_override_block(controller_name, block) ⇒ Object



25
26
27
# File 'lib/turbo_reflex/state_manager.rb', line 25

def add_state_override_block(controller_name, block)
  state_override_blocks[controller_name] = block
end

.state_override_block(controller) ⇒ Object



29
30
31
32
33
# File 'lib/turbo_reflex/state_manager.rb', line 29

def state_override_block(controller)
  return nil if state_override_blocks.blank?
  ancestor = controller.class.ancestors.find { |a| state_override_blocks[a.name] }
  state_override_blocks[ancestor.name]
end

.state_override_blocksObject



21
22
23
# File 'lib/turbo_reflex/state_manager.rb', line 21

def state_override_blocks
  @state_overrides ||= {}
end

Instance Method Details

#[](*keys, default: nil) ⇒ Object



102
103
104
# File 'lib/turbo_reflex/state_manager.rb', line 102

def [](*keys, default: nil)
  state.read(*keys, default: default)
end

#[]=(*keys, value) ⇒ Object



106
107
108
109
# File 'lib/turbo_reflex/state_manager.rb', line 106

def []=(*keys, value)
  state_will_change! if value != self[*keys]
  value.nil? ? state.delete(*keys) : state.write(*keys, value)
end

#clearObject



117
118
119
120
# File 'lib/turbo_reflex/state_manager.rb', line 117

def clear
  provisional_state.clear
  state.clear
end

#ordinal_payloadObject



128
129
130
131
132
133
# File 'lib/turbo_reflex/state_manager.rb', line 128

def ordinal_payload
  provisional_state.clear
  state.shrink!
  state.prune! max_bytesize: TurboReflex.config.max_cookie_size
  state.ordinal_payload
end

#payloadObject



122
123
124
125
126
# File 'lib/turbo_reflex/state_manager.rb', line 122

def payload
  provisional_state.clear
  state.shrink!
  state.payload
end

#provisional_stateObject Also known as: now



111
112
113
# File 'lib/turbo_reflex/state_manager.rb', line 111

def provisional_state
  @provisional_state ||= TurboReflex::ProvisionalState.new(self)
end


135
136
137
138
139
140
141
# File 'lib/turbo_reflex/state_manager.rb', line 135

def write_cookie
  return unless changed? || cookie.blank?
  cookies.signed["turbo_reflex.state"] = {value: ordinal_payload, path: "/", expires: 1.day.from_now}
  changes_applied
rescue => error
  Rails.logger.error "Failed to write the TurboReflex::State cookie! #{error.message}"
end