ExposeDB
ExposeDB provides raw, read-only access to your data over an HTTP JSON API. It uses the excellent Sinatra and Sequel libraries for all of the real work.
WARNING: ExposeDB doesn't offer any secure options, and as such is expected to be used only in a high-trust network!
ExposeDB consists of both a client and server in one package. The server need not be used with the client: it simply provides a JSON endpoint. The client will wrap that JSON endpoint in a very simple Sequel-like API.
But Why?
Ideally, connecting straight to the source database would always be possible. Since it's not: why.
Server Usage
ExposeDB is in a very alpha stage of development, but seems to be doing a rudimentary job querying for data. Output options are currently limited to all fields in a table and is only exposed via JSON.
expose-db SEQUEL_DATABASE_URI
For example:
expose-db postgres://user:password@dbhost/my_db
Existing endpoints:
-
/
- Simple query interface -
/my_table
- Get all the records inmy_table
as JSON -
/my_table?q=ENCODED_QUERY&values[]=2&values[]=bob
- replace ?'s in ENCODED_QUERY with [2, 'bob']
Client Usage
ExposeDB client use isn't required to use the server, but provides a very simple
mapping layer built with Hashie. Use exposed_as
to define the server's
table name after inheriting from ExposeDB::Model
and setting your subclass's
base_uri
. You can find
and filter
your models, along with anything that
[HTTParty][httparty] provides.
Since the models are built from a Hashie::Mash
, you don't need to define
any fields or columns on the client side.
require 'expose_db/model'
class BaseModel < ExposeDB::Model
base_uri 'http://api.example.com/v1'
end
class Person < BaseModel
exposed_as 'people'
# Class is inferred from singular of relation name, and foreign key is assumed
# to be this_class_name_id. Singular inflections are very limited, so you may
# need to specify class_name here
has_many :tasks
end
class Task < BaseModel
exposed_as 'tasks'
# Assume the returned task has a person_id column and the class_name is Person
belongs_to :person
# Provie a wrapper for the underlying string from the ExposeDB server
def completed_at
ca = self[:completed_at]
ca ? Time.parse(ca) : nil
end
end
bob = Person.find(123)
#=> your person (by column `id`) or raise ExposeDB::RecordNotFound
bob.tasks
#=> find all tasks with person_id = 123
Person.find_by_id(123)
#=> your person (by column `id`) or nil
Person.filter('last_name = ? AND first_name LIKE ?',
"Saget", "B%")
#=> array of persons with last_name of Saget and first_name starting with B
Task.find(456).person.first_name
#=> Load task with `id` 456, then request Person with `id` (if task.person_id isn't nil)
# This task's person is cached on the task itself, but there is no identity map
# so all Task.find(456) calls will call the server again.
Implementing your own filter methods are easy:
class Task < BaseModel
exposed_as 'tasks'
# Assume the returned task has a person_id column and the class_name is Person
belongs_to :person
def self.completed_for_person(person)
filter('person_id = ? AND completed = 1', person.id)
end
def self.next_for_person(person)
# NOTE: Currently, this pulls back ALL the items maching the query and only
# then selects the first task
filter('person_id = ? AND completed = 0', person.id).first
end
end
TODO
- Tests
- Smarter relations
- Has one
- Offsets and limits
- Other missing common SQL (i.e. Sequel) functionality
- Improve configuration options
- HTTP Auth and security
- Alternate output formats/content types
- Primary keys other than
id
- Better documentation
Contributing
- Fork this repository
- Make your changes and submit a pull request
License
Copyright (c) 2012 Doug Mayer. Distributed under the MIT License.
See MIT-LICENSE
for further details.