Generic Object Mapper
The Generic Object Mapper maps ruby objects to different storage engines and vice versa. The interface is designed to be small and try to avoid any unnecessary dependencies between GOM and your code. On the other side, the storage engine is plugged-in via an adapter interface. Currently, the following adapters are provided.
-
filesystem - github.com/phifty/gom-filesystem-adapter
-
couchdb - github.com/phifty/gom-couchdb-adapter
Configuration
At the beginning of your program the storage configuration should be done with the GOM::Storage.configure
command.
GOM::Storage.configure {
storage {
name :storage_name
adapter :filesystem
directory "/var/project-name/data"
}
}
Look at the adapter pages to see the adapter-specific configuration values.
How to use
Setup the system
First step after the configuration has been read is to setup the whole storage system. This can be done by
GOM::Storage.setup
The call should be done during the initialization of your application and triggers each storage adapter to do his own setup. To shutdown the system and trigger final clean ups simply call
GOM::Storage.teardown
Storing an object
To store an object just pass it to GOM::Storage.store
.
class Book
attr_accessor :author_name
attr_accessor :pages
end
book = Book.new
book. = "Mr. Storyteller"
book.pages = 1253
GOM::Storage.store book, :storage_name
The storage name doesn’t have to be specified. If it’s missing, the object’s previously use storage or the default storage is used.
There is no base class needed for your model class. GOM inspects your object, reads all the instance variables and passes the values to specified store adapter. The first time an object is stored, an id is generated and assigned to the object. This id an be determined by calling GOM::Object.id
.
book_id = GOM::Object.id book
# book_id => "storage_name:1234..."
Fetching an object
Once an object is stored, it can be easily brought back to life by using it’s id to fetch it from the storage.
book = GOM::Storage.fetch book_id
The storage name is encoded (prefixed) in the id and don’t have to be specified. The classname of the object was also saved during the storage and the fetch instantiate a new object using the constructor. If the constructor requires arguments, nil
will be passed for each of them. The internal state (the instance variables) will be overwritten anyway.
Removing an object
To remove an object from the storage, simply pass it to GOM::Storage.remove
.
GOM::Storage.remove book
It’s also possible to use just the id to remove the assigned object.
GOM::Storage.remove book_id
Relations
GOM does make a distinction between object properties and object relations. The properties are more atomic values that can be stored in a key/value-way and relations are links to more complex objects. Since in Ruby everything is an object, it’s necessary to mark the relations. This is done by the following way.
class Book
attr_accessor :author
end
class Author
attr_accessor :name
end
= Author.new
.name = "Mr. Storyteller"
book = Book.new
book. = GOM::Object.reference
The GOM::Object.reference
call creates a proxy to the referenced object, that passes every call to it. For example, the call
book..name
will return the instance variable @name
(“Mr. Storyteller”) from the author object.
Views
Views are a kind of prepared queries to the data store. They are initialized during the setup and provide collections of results at runtime. There are several kinds of views.
Class views
There views simply provides a collection of all object of a specified class. They are defined at the storage configuration.
GOM::Storage.configure {
storage {
name :storage_name
adapter :filesystem
directory "/var/project-name/data"
view {
name :users
type :class
model_class User
}
}
}
The example defines a class view for all objects of the class User
. The result can be fetched via…
users = GOM::Storage.collection :storage_name, :users
Collections can be handled like (read-only) ruby-arrays and the data will be fetched by the first read access to that array.
Map/Reduce views
These views are also defined in the storage configuration.
GOM::Storage.configure {
storage {
name :storage_name
adapter :couchdb
view {
name :active_user_count
type :map_reduce
map_function """
function(document) {
if (document['model_class'] == 'User' && document['active']) {
emit(document['_id'], 1);
}
}
"""
reduce_function """
function(keys, values, rereduce) {
return sum(values);
}
"""
}
}
}
The example defined a map/reduce view that results in a single row with the count of all active users. This row can be fetched by…
rows = GOM::Storage.collection :storage_name, :active_user_count
rows.first.value # => 123
If no reduce
method is given, GOM
will try to map the fetched data back to ruby-objects. The definition would be…
GOM::Storage.configure {
storage {
name :storage_name
adapter :couchdb
view {
name :active_users
type :map_reduce
map_function """
function(document) {
if (document['model_class'] == 'User') {
emit(document['_id'], null);
}
}
"""
}
}
}
…and the fetch is done by…
active_users = GOM::Storage.collection :storage_name, :active_users
Development
Development has been done test-driven and the code follows at most the Clean Code paradigms. Code smells has been removed by using the reek code smell detector.
This project is still experimental and under development. Any bug report and contribution is welcome!
Support
Apart from contribution, support via Flattr is welcome.