Low commit activity in last 3 years
Thread-local key-value state bag with scoped overrides via blocks, automatic restoration, and thread isolation for implicit context propagation.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

philiprehberger-state_bag

Tests Gem Version Last updated

Thread-local state bag for implicit context propagation

Requirements

  • Ruby >= 3.1

Installation

Add to your Gemfile:

gem "philiprehberger-state_bag"

Or install directly:

gem install philiprehberger-state_bag

Usage

require "philiprehberger/state_bag"

Philiprehberger::StateBag.set(:user_id, 42)
Philiprehberger::StateBag.get(:user_id)
# => 42

Default Values

Philiprehberger::StateBag.get(:missing, 'fallback')
# => "fallback"

Scoped Overrides

Philiprehberger::StateBag.set(:locale, 'en')

Philiprehberger::StateBag.with(locale: 'de') do
  Philiprehberger::StateBag.get(:locale)
  # => "de"
end

Philiprehberger::StateBag.get(:locale)
# => "en"

Fetch and Delete

require "philiprehberger/state_bag"

Philiprehberger::StateBag.set(:user, "Alice")
Philiprehberger::StateBag.fetch(:user)             # => "Alice"
Philiprehberger::StateBag.fetch(:missing, "default") # => "default"
Philiprehberger::StateBag.fetch(:missing) { |k| "no #{k}" } # => "no missing"

Philiprehberger::StateBag.delete(:user)  # => "Alice"
Philiprehberger::StateBag.key?(:user)    # => false

Inspection

Philiprehberger::StateBag.set(:a, 1)
Philiprehberger::StateBag.key?(:a)   # => true
Philiprehberger::StateBag.size       # => 1
Philiprehberger::StateBag.empty?     # => false
Philiprehberger::StateBag.keys       # => [:a]
Philiprehberger::StateBag.values     # => [1]
Philiprehberger::StateBag.to_h       # => {:a=>1}
Philiprehberger::StateBag.clear

Deep access

Philiprehberger::StateBag.set(:request, { id: "abc", user: { email: "x@y.com" } })
Philiprehberger::StateBag.dig(:request, :user, :email) # => "x@y.com"
Philiprehberger::StateBag.dig(:request, :missing)      # => nil

Bulk Operations

Philiprehberger::StateBag.merge(user_id: 42, locale: 'en', request_id: 'req-1')
# => {:user_id=>42, :locale=>"en", :request_id=>"req-1"}

Philiprehberger::StateBag.slice(:user_id, :locale)
# => {:user_id=>42, :locale=>"en"}

Philiprehberger::StateBag.each { |k, v| puts "#{k}=#{v}" }

Philiprehberger::StateBag.replace(user_id: 99)
Philiprehberger::StateBag.to_h
# => {:user_id=>99}

Update

Atomically read-modify-write a single key. The block receives the current value (or nil if the key is missing) and its return value is stored back.

Philiprehberger::StateBag.set(:counter, 0)
Philiprehberger::StateBag.update(:counter) { |v| v + 1 }
Philiprehberger::StateBag.get(:counter) # => 1

Philiprehberger::StateBag.update(:visited) { |v| (v || []) + ['/home'] }
Philiprehberger::StateBag.get(:visited)  # => ["/home"]

Snapshot & Restore

Philiprehberger::StateBag.set(:user_id, 42)
Philiprehberger::StateBag.set(:locale, 'en')

snapshot = Philiprehberger::StateBag.snapshot
# => {:user_id=>42, :locale=>"en"} (frozen)

Philiprehberger::StateBag.set(:user_id, 99)
Philiprehberger::StateBag.restore(snapshot)
Philiprehberger::StateBag.get(:user_id)
# => 42

API

Method Description
.set(key, val) Store a value in the thread-local state bag
.get(key, default = nil) Retrieve a value or return the default
.with(**overrides, &block) Execute block with temporary state, restoring after
.fetch(key, default = UNSET, &block) Retrieve a value; raises KeyError if missing with no default/block
.dig(*keys) Dig into nested hash-valued entries; returns nil on any miss
.delete(key) Remove a key and return its value
.clear Remove all entries from the state bag
.to_h Return a snapshot of the current state
.key?(key) Check if a key exists in the state bag
.size Number of entries in the state bag
.empty? True if the state bag has no entries
.keys Array of all keys in the state bag
.values Array of all values in the state bag
.merge(**entries) Bulk-set multiple keys; returns a snapshot
.replace(hash) Replace the entire state with the given hash; returns a snapshot
.slice(*keys) Return a hash containing only the given keys
.each(&block) Iterate key-value pairs; returns an Enumerator without a block
.update(key, &block) Atomic read-modify-write; yields current value (or nil), stores block result
.snapshot Return a frozen copy of the current state
.restore(snapshot) Replace the state with a copy of the given snapshot; raises ArgumentError for non-Hash

Development

bundle install
bundle exec rspec
bundle exec rubocop

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT