Skip to content

Commit

Permalink
Introduce StateStore and bring back MemoryStore for state
Browse files Browse the repository at this point in the history
  • Loading branch information
hopsoft committed May 20, 2024
1 parent 82dba15 commit a2b7f4c
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 23 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</h1>
<p align="center">
<a href="http://blog.codinghorror.com/the-best-code-is-no-code-at-all/">
<img alt="Lines of Code" src="https://img.shields.io/badge/loc-1634-47d299.svg" />
<img alt="Lines of Code" src="https://img.shields.io/badge/loc-1673-47d299.svg" />
</a>
<a href="https://codeclimate.com/github/hopsoft/turbo_boost-commands/maintainability">
<img src="https://api.codeclimate.com/v1/badges/fe1162a742fe83a4fdfd/maintainability" />
Expand Down
5 changes: 3 additions & 2 deletions lib/turbo_boost/commands/middlewares/entry_middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ def modify?(request)
# "startedAt" => 1708213193567, # Time the command was invoked
# "elementCache" => {...}, # Cache of ALL tracked element attributes (optimistic changes)
# "state" => { # State ... TODO: HOPSOFT
# "signed" => "", # - ...
# "unsigned" => {...} # - ...
# "page" => {...}, # - transient page state (element attributes, etc.)
# "signed" => "", # - signed state used for the last server render (untampered)
# "unsigned" => {...} # - state with optimistic changes from the client
# },
# "driver" => "frame", # Driver used to invoke the command
# "frameId" => "...", # TurboFrame id (if applicable)
Expand Down
32 changes: 13 additions & 19 deletions lib/turbo_boost/commands/state.rb
Original file line number Diff line number Diff line change
@@ -1,44 +1,42 @@
# frozen_string_literal: true

require_relative "state_store"

class TurboBoost::Commands::State
def initialize(payload = {})
payload = payload.to_h
@store ||= (payload[:unsigned] || {}).with_indifferent_access
payload = payload.to_h.with_indifferent_access

store[:_now] ||= {}
store[:_page] = payload[:page] || {}
store[:_signed] = if payload[:signed].present?
URI::UID.from_sgid(payload[:signed], for: self.class.name)&.decode
end
store[:_signed] ||= {}
@store = HashWithIndifferentAccess.new
@store[:_now] = {}.with_indifferent_access
@store[:_page] = payload.fetch(:page, {}).with_indifferent_access
@store[:_unsigned] = payload.fetch(:unsigned, {}).with_indifferent_access
@store[:_signed] = TurboBoost::Commands::StateStore.new(payload.fetch(:signed, {}))
end

def current
signed.merge now
signed.to_h.merge now
end

delegate_missing_to :current

def now
store[:_now]
@store[:_now]
end

def page
store[:_page]
@store[:_page]
end

def signed
store[:_signed]
@store[:_signed]
end

def cache_key
"TurboBoost::Commands::State/#{Digest::SHA2.base64digest(current.to_s)}"
end

def to_json
uid = URI::UID.build(signed, include_blank: false)
sgid = uid.to_sgid_param(for: self.class.name, expires_in: 2.day)
{signed: sgid, unsigned: signed}.to_json(camelize: false)
{signed: signed.to_sgid_param, unsigned: signed.to_h}.to_json(camelize: false)
end

def tag_options(options)
Expand Down Expand Up @@ -80,8 +78,4 @@ def tag_options(options)

options
end

private

attr_reader :store
end
58 changes: 58 additions & 0 deletions lib/turbo_boost/commands/state_store.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

class TurboBoost::Commands::StateStore < ActiveSupport::Cache::MemoryStore
include Enumerable

SGID_PURPOSE = name.dup.freeze

def initialize(payload = {})
super(expires_in: 1.day, size: 16.kilobytes)

begin
payload = URI::UID.from_sgid(payload, for: SGID_PURPOSE)&.decode if payload.is_a?(String)
payload ||= URI::UID.from_gid(payload, for: SGID_PURPOSE)&.decode if payload.is_a?(String)
rescue => error
Rails.logger.error "Failed to decode URI::UID when creating a TurboBoost::Commands::StateStore! #{error.message}"
payload = {}
end

merge! payload
end

alias_method :[], :read
alias_method :[]=, :write

def to_h
(@data || {}).each_with_object({}) do |(key, entry), memo|
memo[key] = entry.value
end
end

delegate :each, :dig, to: :to_h

def merge!(other = {})
other.to_h.each { |key, val| write key, val }
self
end

def to_uid
cleanup
URI::UID.build to_h, include_blank: false
end

def to_gid
to_uid.to_gid
end

def to_gid_param
to_gid.to_param
end

def to_sgid
to_uid.to_sgid for: SGID_PURPOSE, expires_in: 1.day
end

def to_sgid_param
to_sgid.to_param
end
end
2 changes: 1 addition & 1 deletion test/dummy/config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
config.cache_store = :null_store

# Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = false
config.action_dispatch.show_exceptions = :none

# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false
Expand Down
2 changes: 2 additions & 0 deletions test/dummy/db/schema.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
Expand Down

0 comments on commit a2b7f4c

Please sign in to comment.