Skip to content

Commit

Permalink
Add paging mechanics
Browse files Browse the repository at this point in the history
  • Loading branch information
mattruggio committed Sep 6, 2022
1 parent 32d4cc6 commit 5d7749f
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 17 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#### 0.0.3 - Monday, Sept. 5th, 2022

* Paging mechanics added: Db#get now supports `skip` and `limit` optional keyword arguments.
#### 0.0.2 - Monday, Sept. 5th, 2022

* Initial proof-of-concept release.
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![Gem Version](https://badge.fury.io/rb/teton.svg)](https://badge.fury.io/rb/teton) [![Ruby Gem CI](https://github.com/mattruggio/teton/actions/workflows/rubygem.yml/badge.svg)](https://github.com/mattruggio/teton/actions/workflows/rubygem.yml) [![Maintainability](https://api.codeclimate.com/v1/badges/787a5d512223e85efd69/maintainability)](https://codeclimate.com/github/mattruggio/teton/maintainability) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

#### Hierarchical key-value object store interface
#### Hierarchical key-value object store

---

Expand All @@ -26,12 +26,16 @@ bundle add teton

The main API is made up of these instance methods:

Method | Description
------------------- | -----------
`Db#set(key, data)` | Set an entries data to the passed in values.
`Db#get(key)` | Get the entry if it exists or nil if it does not. If the key is a resource then it will always return an array.
`Db#del(key)` | Delete the key and all children of the key from the store.
`Db#count(key)` | The number of entries directly under a key if the key is a resource. If they key is an entry then 1 if the entry exists and 0 if it does not exist.
Method | Description
---------------------------------------| --------------------------------------------------------------
`Db#set(key, data = {})` | Set an entries data to the passed in values.
`Db#get(key, limit: nil, skip: nil)` | Get the entry if it exists or nil if it does not. If the key is a resource then it will always return an array.
`Db#del(key)` | Delete the key and all children of the key from the store.
`Db#count(key)` | The number of entries directly under a key if the key is a resource. If they key is an entry then 1 if the entry exists and 0 if it does not exist.

Note(s):

* limit and skip are optional and only apply to resource keys, not entry keys.

#### Setting Up Database

Expand Down
14 changes: 12 additions & 2 deletions lib/teton/db.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@ def set(key, data)
self
end

def get(key)
store.get(key(key))
def get(key, limit: nil, skip: nil)
store.get(
key(key),
limit: zero_floor_or_nil(limit),
skip: zero_floor_or_nil(skip)
)
end

def del(key)
Expand All @@ -44,6 +48,12 @@ def count(key)

private

def zero_floor_or_nil(value)
return unless value

value ? [value, 0].max : nil
end

def key(key)
key.is_a?(Key) ? key : Key.new(key, separator: separator)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/teton/entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def initialize(key, data: {}, created_at: Time.now.utc, updated_at: Time.now.utc
end

def [](data_key)
data[data_key]
data[data_key.to_s]
end

def to_s
Expand Down
16 changes: 11 additions & 5 deletions lib/teton/stores/memory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ def set(key, data)
self
end

def get(key)
def get(key, limit: nil, skip: nil)
store_pointer = traverse_to_last(key)

return unless store_pointer

if key.resource?
entries(key, store_pointer, key.last_part)
entries(key, store_pointer, key.last_part, limit: limit, skip: skip)
else
entry(key, store_pointer, key.last_part)
end
Expand Down Expand Up @@ -158,14 +158,20 @@ def entry(key, pointer, part)
)
end

def entries(key, pointer, part)
def entries(key, pointer, part, limit: nil, skip: nil)
pointer = pointer.dig(part, IDS_KEY)

return [] unless pointer

pointer.map do |inner_part, value|
start_index = skip || 0
end_index = limit ? (start_index + limit - 1) : -1
selected_keys = pointer.keys[start_index..end_index] || []

selected_keys.map do |selected_key|
value = pointer[selected_key]

Entry.new(
key.to_s(inner_part),
key.to_s(selected_key),
data: value[DATA_KEY],
created_at: value[META_KEY][CREATED_AT_KEY],
updated_at: value[META_KEY][UPDATED_AT_KEY]
Expand Down
2 changes: 1 addition & 1 deletion lib/teton/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Teton
VERSION = '0.0.2'
VERSION = '0.0.3'
end
61 changes: 61 additions & 0 deletions spec/teton_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
# rubocop:disable RSpec/MultipleExpectations
# rubocop:disable RSpec/ExampleLength
describe Teton do
# The other classes in this library need to be better unit-tested, but this at least gives
# the library a regression of the most common scenarios.
it 'passes full API integration test' do
# Stage data
db = Teton::Db.new
Expand Down Expand Up @@ -115,6 +117,65 @@

expect(bozo).to be_nil
end

describe 'Paging' do
subject(:db) { Teton::Db.new }

let(:practice_key) { 'practices/1' }
let(:patients_key) { "#{practice_key}/patients" }

before do
db.set(practice_key, name: 'The Happy Practice')

(1..20).each do |i|
patient_key = "#{patients_key}/#{i}"

db.set(patient_key, first: 'Dobby', middle: 'is', last: "Number#{i}")
end
end

it 'limits with no skip' do
patients = db.get(patients_key, limit: 3)

expected_lasts = %w[Number1 Number2 Number3]
actual_lasts = patients.map { |p| p[:last] }

expect(actual_lasts).to eq(expected_lasts)
end

it 'limits with skip' do
patients = db.get(patients_key, limit: 3, skip: 3)

expected_lasts = %w[Number4 Number5 Number6]
actual_lasts = patients.map { |p| p[:last] }

expect(actual_lasts).to eq(expected_lasts)
end

it 'skips with no limit' do
patients = db.get(patients_key, skip: 18)

expected_lasts = %w[Number19 Number20]
actual_lasts = patients.map { |p| p[:last] }

expect(actual_lasts).to eq(expected_lasts)
end

it 'limit too large just goes to the end' do
patients = db.get(patients_key, skip: 19, limit: 100)

expected_lasts = %w[Number20]
actual_lasts = patients.map { |p| p[:last] }

expect(actual_lasts).to eq(expected_lasts)
end

it 'skip too large returns nothing' do
patients = db.get(patients_key, skip: 25)

expect(patients).to be_empty
end
end
end
# rubocop:enable RSpec/ExampleLength
# rubocop:enable RSpec/MultipleExpectations
2 changes: 1 addition & 1 deletion teton.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ require './lib/teton/version'
Gem::Specification.new do |s|
s.name = 'teton'
s.version = Teton::VERSION
s.summary = 'Hierarchical key-value object store interface.'
s.summary = 'Hierarchical key-value object store.'

s.description = 'Store key-value pair objects in a discoverable hierarchy. Provides a pluggable interface for multiple back-ends.'

Expand Down

0 comments on commit 5d7749f

Please sign in to comment.