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
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.
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
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.author_name = "Mr. Storyteller" book.pages = 1253 GOM::Storage.store book, :storage_name
The storage name doesn’t has to be specified. If it’s missing, the object’s previously used 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 the specified storage adapter. The first time an object is stored, an id is generated and assigned to the object. This id can be determined by calling GOM::Object.id
.
book_id = GOM::Object.id book # book_id => "storage_name:1234..."
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 doesn’t has to be specified. The classname of the object was also saved during the storage and the fetch
method instantiates 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.
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
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 = Author.new author.name = "Mr. Storyteller" book = Book.new book.author = GOM::Object.reference author
The GOM::Object.reference
call creates a proxy to the referenced object, that passes every call to it. For example, the call
book.author.name
will return the instance variable @name
(“Mr. Storyteller”) from the author object.
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.
There are views simply providing a collection of all objects 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.
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 defines 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 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!
Apart from contribution, support via Flattr is welcome.