Version 2.0 introduces a simplified, class-based session architecture that replaces the SessionData module and SessionId(T) wrapper with a single Session::Base class. This is a breaking change that requires migration.
| Area | Old API | New API |
|---|---|---|
| Session definition | struct + include Session::SessionData |
class + < Session::Base |
| Configuration | config.provider = ... |
config.store = ... |
| Store creation | .provider suffix |
.new directly |
| Data access | session.data.property |
session.property |
| Global access | Session.provider |
Session.config.store |
| Type wrappers | SessionId(T) |
T directly |
| Callbacks | Session::SessionData param type |
Session::Base param type |
The SessionData module and struct-based sessions are replaced with class inheritance from Session::Base.
Before:
struct UserSession
include Session::SessionData
property user_id : Int64?
property username : String?
property roles : Array(String) = [] of String
def authenticated? : Bool
!user_id.nil?
end
endAfter:
class UserSession < Session::Base
property? authenticated : Bool = false
property user_id : Int64? = nil
property username : String? = nil
property roles : Array(String) = [] of String
endKey differences:
- Use
classinstead ofstruct - Inherit from
Session::Baseinstead of includingSession::SessionData Session::BaseincludesJSON::SerializableautomaticallySession::Baseprovidessession_id,created_at,expires_at,valid?,expired?,touch, andtime_until_expiry- Must implement abstract method
authenticated?(useproperty?for simple boolean) - All properties must have default values (for parameterless constructor)
The provider property is renamed to store, and store creation no longer uses the .provider suffix.
Before:
Session.configure do |config|
config.secret = ENV["SESSION_SECRET"]
config.provider = Session::MemoryStore(UserSession).provider
endAfter:
Session.configure do |config|
config.secret = ENV["SESSION_SECRET"]
config.store = Session::MemoryStore(UserSession).new
endStore creation changes for each backend:
# Cookie Store
# Before: Session::CookieStore(UserSession).provider
# After:
config.store = Session::CookieStore(UserSession).new
# Memory Store
# Before: Session::MemoryStore(UserSession).provider
# After:
config.store = Session::MemoryStore(UserSession).new
# Redis Store
# Before: Session::RedisStore(UserSession).provider(client: Redis.new)
# After:
config.store = Session::RedisStore(UserSession).new(client: Redis.new)
# Clustered Redis Store (unchanged)
config.store = Session::ClusteredRedisStore(UserSession).new(client: Redis.new)
# Redis Store with connection pool
# Before: Session::RedisStore(UserSession).with_pool(config)
# After (unchanged):
config.store = Session::RedisStore(UserSession).with_pool(config)The .data accessor is removed. Session properties are accessed directly on the session object.
Before:
session = store.create
session.data.user_id = 42
session.data.username = "alice"
puts session.data.authenticated?After:
session = store.create
session.user_id = 42
session.username = "alice"
puts session.authenticated?For store-level access:
# Before
provider.data.user_id
provider.data.username
# After
store.current_session.user_id
store.current_session.usernameBefore:
provider = Session.provider
session = provider.createAfter:
store = Session.config.store.not_nil!
session = store.createRemove SessionId(T) wrapper references — stores now work directly with T.
Before:
def handle_session(session : Session::SessionId(UserSession))
puts session.data.username
puts session.session_id
endAfter:
def handle_session(session : UserSession)
puts session.username
puts session.session_id
endCallback parameter types changed from SessionData to Base.
Before:
config.on_started = ->(sid : String, data : Session::SessionData) do
Log.info { "Session #{sid} started" }
endAfter:
config.on_started = ->(sid : String, session : Session::Base) do
Log.info { "Session #{sid} started" }
endBefore:
server = HTTP::Server.new([
Session::SessionHandler.new(Session.provider),
MyAppHandler.new,
])After:
store = Session.config.store.not_nil!
server = HTTP::Server.new([
Session::SessionHandler.new(store),
MyAppHandler.new,
])Before:
store.each_session { |s| puts s.data.username }
admins = store.find_by { |s| s.data.roles.includes?("admin") }
store.bulk_delete { |s| s.data.user_id == compromised_id }After:
store.each_session { |s| puts s.username }
admins = store.find_by { |s| s.roles.includes?("admin") }
store.bulk_delete { |s| s.user_id == compromised_id }- Change
structsession types toclassinheriting fromSession::Base - Replace
include Session::SessionDatawith< Session::Base - Add default values to all session properties
- Rename
config.providertoconfig.store - Remove
.providersuffix from store creation calls - Replace all
.data.propertywith.property - Replace
Session.providerwithSession.config.store.not_nil! - Update
SessionId(T)type annotations to justT - Update callback signatures from
SessionDatatoBase - Update HTTP handler initialization to use store directly
- Run
crystal specto verify all changes compile correctly