Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: up down logs build test run
.PHONY: up down logs build test run migrate
up:
@docker-compose -f docker-compose.yml up -d
down:
Expand All @@ -10,4 +10,6 @@ build:
test:
@rails test
run:
@rails server
@rails server
migrate:
@rails db:migrate
32 changes: 32 additions & 0 deletions app/models/session.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
class Session < ApplicationRecord
belongs_to :user

scope :active, -> { where(revoked_at: nil).where("expires_at > ?", Time.current) }

SESSION_TTL = 2.hours

validates :token, presence: true, uniqueness: true
validates :expires_at, presence: true

before_validation :set_expiration, on: :create

def revoked?
revoked_at.present?
end

def expired?
expires_at < Time.current
end

def active?
!revoked? && !expired?
end

def set_expiration
self.expires_at ||= SESSION_TTL.from_now
end

def generate_token
self.token ||= SecureRandom.hex(32)
end
end
16 changes: 16 additions & 0 deletions db/migrate/20260112020109_create_sessions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class CreateSessions < ActiveRecord::Migration[8.1]
def change
create_table :sessions do |t|
t.references :user, null: false, foreign_key: true

t.string :token, null: false
t.datetime :expires_at, null: false
t.datetime :revoked_at

t.timestamps
end

add_index :sessions, :token, unique: true
add_index :sessions, :expires_at
end
end
16 changes: 15 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions test/fixtures/sessions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html

alice_session:
user: alice
token: <%= SecureRandom.hex(32) %>
expires_at: <%= 30.days.from_now %>
71 changes: 71 additions & 0 deletions test/models/session_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
require "test_helper"

class SessionTest < ActiveSupport::TestCase
def setup
@user = users(:alice)
end

def test_fixture_session_is_active
session = sessions(:alice_session)

assert session.active?
assert_equal @user, session.user
end

def test_token_is_generated_on_create
session = Session.create!(user: @user, token: SecureRandom.hex(32))

assert session.token.present?
end

def test_token_is_unique
existing = Session.create!(user: @user, token: SecureRandom.hex(32))

duplicate = Session.new(
user: @user,
token: existing.token,
expires_at: 30.days.from_now
)

refute duplicate.valid?
assert_includes duplicate.errors[:token], "has already been taken"
end

def test_expired_session
session = Session.new(
user: @user,
expires_at: 1.hour.ago
)

assert session.expired?
refute session.active?
end

def test_revoked_session
session = Session.new(
user: @user,
revoked_at: Time.current
)

assert session.revoked?
refute session.active?
end

def test_active_scope_excludes_revoked_and_expired_sessions
active = Session.create!(user: @user, token: SecureRandom.hex(32))
expired = Session.create!(
user: @user,
token: SecureRandom.hex(32),
expires_at: 1.hour.ago
)
revoked = Session.create!(
user: @user,
token: SecureRandom.hex(32),
revoked_at: Time.current
)

assert_includes Session.active, active
refute_includes Session.active, expired
refute_includes Session.active, revoked
end
end
2 changes: 1 addition & 1 deletion test/test_helper.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
ENV["RAILS_ENV"] ||= "test"
ActiveRecord::Migration.maintain_test_schema!
require_relative "../config/environment"
ActiveRecord::Migration.maintain_test_schema!
require "rails/test_help"
require "minitest/reporters"
Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
Expand Down