Files
simply_based/spec/redis_replacements_spec.rb
T
GregandClaude Opus 4.8 86299b8827 SimplyBased 0.1.0 — Couchbase-native ActiveModel ORM
A from-scratch fork of simply_couch's model layer that runs on Couchbase
natively: `include SimplyBased::Model` instead of `include SimplyCouch::Model`.

- SimplyBased::Store replaces the CouchDB layer: KV documents, N1QL/SQL++
  finders + lazily-created GSIs, CAS mapped onto _rev. No design documents or
  JavaScript map/reduce — the hand-written association views become N1QL
  (belongs_to/has_many/has_one scalar FK; habtm via ANY ... SATISFIES + array
  index, or KV multi-get on the storing side).
- Redis replacements on the same cluster: Counter (atomic increment/decrement),
  Cache (ActiveSupport::Cache::Store over KV+TTL, drop-in Rails.cache/ActiveJob),
  Lock (insert-if-absent + TTL + CAS-checked release).
- Runs the simply_couch test suite against live Couchbase: 378/432 applicable
  examples green (CouchDB-mechanic specs tagged :couchdb_only and excluded).
- Remaining: ancestry (tree) + embedded_in reverse-lookups (recursive/embedded
  N1QL) and a few pagination/lifecycle edges.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 13:58:47 -05:00

57 lines
1.9 KiB
Ruby

require 'spec_helper'
# Couchbase-backed replacements for common Redis uses: atomic counters, a Rails
# cache store, and a distributed lock — all on the same cluster as the models.
RSpec.describe 'Redis replacements' do
describe SimplyBased::Counter do
subject(:counter) { described_class.new }
it 'atomically increments, seeding at the initial value' do
expect(counter.increment('c:hits')).to eq(0) # created at initial 0
expect(counter.increment('c:hits')).to eq(1)
expect(counter.increment('c:hits', 5)).to eq(6)
expect(counter.get('c:hits')).to eq(6)
end
it 'decrements and resets' do
counter.increment('c:n', 0, initial: 10)
expect(counter.decrement('c:n', 3)).to eq(7)
expect(counter.reset('c:n', 0)).to eq(0)
expect(counter.get('c:n')).to eq(0)
end
end
describe SimplyBased::Cache do
subject(:cache) { described_class.new(namespace: 'test') }
it 'works as an ActiveSupport::Cache::Store (read/write/fetch/delete)' do
cache.write('k', { 'a' => 1 })
expect(cache.read('k')).to eq({ 'a' => 1 })
expect(cache.fetch('k2') { [1, 2, 3] }).to eq([1, 2, 3])
expect(cache.read('k2')).to eq([1, 2, 3])
cache.delete('k')
expect(cache.read('k')).to be_nil
end
end
describe SimplyBased::Lock do
subject(:lock) { described_class.new }
it 'grants the lock to one holder and blocks a second acquire' do
handle = lock.acquire('job:1', ttl: 30)
expect(handle).not_to be_nil
expect(lock.acquire('job:1', ttl: 30)).to be_nil # already held
lock.release('job:1', handle)
expect(lock.acquire('job:1', ttl: 30)).not_to be_nil # released → free
end
it 'runs a critical section with with_lock' do
ran = false
lock.with_lock('job:2') { ran = true }
expect(ran).to be(true)
# lock auto-released after the block → re-acquirable
expect(lock.acquire('job:2')).not_to be_nil
end
end
end