Persistant Sets of Structured Data
db-struct
is a Ruby gem that provides class DBStruct
, a class that is similar to Ruby’s built-in Struct
class but stores its data in a SQLite database. In addition, each subclass also provides access to the database via an interface that closely mimics a Ruby Hash, including support for enumeration.
It is currently at the “experimental toy” stage of development.
Installation
Simply install it via gem
:
gem install --prerelease db-struct
Note that you will also need to install SQLite3 separately. On stock Ruby, that’s:
gem install sqlite3
while with JRuby, it’s:
gem install jdbc-sqlite3
It uses Sequel to do the heavy lifting. This is installed as a dependency, but you’ll need to know how to open a database with it.
Overview
Let’s start with a simple example, a table of books. The first thing we need to do is create a database connection:
DB = Sequel.sqlite("books.sqlite3")
We can then define the structure and the underlying table:
Book = DBStruct.with(DB, :books) do
field :title, String
field :author, String
field :date, Time
field :edition, Integer
end
Book
is now a subclass of DBStruct
; creating it will also create a table named :books
if it doesn’t exist.
We can create an instance of Book
like this:
b1 = Book.new(title: "Diseases of the Dragon",
author: "Lady Sybil Ramkin",
date: Time.new(1989, 11, 1),
edition: 1)
The contents are immediately written to the database, but this object behaves more or less like a Ruby Struct
:
puts b1.title
b1. = "Lady Sybil Ramkin-Vimes"
puts b1.
Note that these are not part of a transaction. If you need that (and you probably will), you can the transaction
method:
Book.transaction {
puts b1.title
b1. = "Lady Sybil Ramkin-Vimes"
puts b1.
}
This starts a transaction, evaluates the block and commits. If there is an exception inside the block, the transaction is rolled back instead. Transactions can be safely nested. (DBStruct#transaction
is simply a thin wrapper around Sequel::Database#transaction
.)
The class method items
returns a DBStruct::BogoHash
, which behaves like a Hash mapping numeric row IDs to corresponding Book
objects:
puts Book.items[b1.rowid].title
The usual enumeration operations are also available:
Book.items.values.each{|book|
puts " #{book.title} by #{book.}"
}
first_editions = Book.items
.select{|id, book| book.edition == 1}
.map{|id, _| id}
You can also add a special field type called a group
. This can be used to subdivide the table:
Book = DBStruct.with(DB, :books) do
group :category, String
field :title, String
field :author, String
field :date, Time
field :edition, Integer
end
b1 = Book.new(category:"non-fiction",
title: "Diseases of the Dragon",
author: "Lady Sybil Ramkin",
date: Time.new(1989, 11, 1),
edition: 1)
A group
is just an ordinary field except that Books.items
will filter by them:
non_fiction = Book.items("non-fiction")
Multiple groups are allowed and nil
can be used as a wildcard when selection them.
Alternately, you can filter using a Sequel
where
clause:
dragon_books = Book.where(Sequel.like(:title, "%Dragon%"))
but if you need that often, you may well be better off dealing with Sequel
directly.