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.

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.author_name = "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 = 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

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.