Overview

The name “Poro” is derived from “plain ol’ Ruby object”. Poro is a simple and lightweight persistence engine. Unlike most persistence engines, which require your persistent objects to be subclasses of a base model class, Poro aims to extend plain ol’ Ruby objects to be stored in any persist way you choose (e.g. SQL, MongoDB, Memcache), and even mix and match different stores between objects.

Additionally, Poro takes a hands-off philosophy by default, only minimally disturbing an object it persists. Of course, there is a mixin available to add model functionality to your object if you like, but how and when you do this is up to you.

While the packages available for managing individual kinds of repositories focus on a large breadth of functionality, Poro aims to give the simplest, lightest weight, common interface possible for these storage methods. Given the disparity in functionality available between different persistence stores (e.g. SLQ, key/value, documents), additional needs of the store are accomplished by working with the individual adapter package APIs themselves rather than through whatever inferior homogenized API Poro may try to provide.

Installation

At this time, Poro has only ever been tested on ruby 1.9.2. It is expected to work on any 1.9.x, but may require some effort to make work on 1.8.6 or later. By version 1.0 I hope to have 1.8.6 and 1.8.7 support, however the development of a production worthy gem in 1.9.2 takes the priority.

Basic usage only requires the installation of the gem:

gem install poro

However to utilize any meaningful persistence data store, the underlying gems for the desired persistence contexts are needed. The documentation of the desired Context class’ documentation should inform you of any necessary gems, though a useful error is thrown if you are missing a needed gem, so it is probably easier to just try.

If you wish to run the gem’s unit tests, you should also install rspec.

It is also worthwhile checking rake for meaningful tasks, using:

rake -T

Supported Data Stores

Currently, the following data stores are supported:

MongoDB

Install the gems mongo and bson_ext, and you should be good to go! Good automatic support embedded documents, including conversion to objects when it can figure out how to do so.

In-Memory Hash

This is really only for trial and testing purposes as it stores everything in RAM and is lost when the application dies.

The following data stores are currently planned for version 1.0:

SQL

Install the sequel gem and it should be good to go.

Memcache

Install instructions forthcoming. Will be useful for those working with web apps.

Architecture

Poro revolves around Contexts. Each class that must persist gets its own Context, and that Context manages the persistence of that object.

Contexts come in many flavors, depending on the data store that backs them. To create the data stores, the application must have a ContextFactory instance. There are different ContextFactories, depending on the needs of your application, but the base ContextFactory can be customized via a block.

In general, Poro is hands-off with the objects it persists, however there is one exception: In order for the ContextFactory to create a Context for an object, the object must be tagged as persistent by including Poro::Persistify.

If you wish to have model-like functionality to your objects, you may also include Poro::Modelify. This is not necessary for a Context to be used, but the convenience and familiarity of this paradigm makes this desirable functionality.

Getting Started

The following sample code sets up a basic context manager for the application, using an in-memory only testing store (which is just a hash):

require 'poro'

Poro::Context.factory = Poro::ContextFactories::SingleStore.instantiate(:hash)

class Foo
  include Poro::Persistify
  include Poro::Modelify
end

f = Foo.new
puts "f doesn't have an ID yet: #{f.id.inspect}"
f.save
puts "f now has an ID: #{f.id.inspect}"
g = Foo.fetch(f.id)
puts "g is a fetch of f and has the same ID as f: #{g.id.inspect}"
f.remove
puts "f no longer has an ID: #{f.id.inspect}"

Configuration

Each Context has its own configuration parameters, based on how its data store works. There are two ways in which to manage this configuration, depending on the needs of your application.

Inline

Many users, thanks to some popular Ruby ORMs, are most comfortable with model class inline configuration. Poro’s philosophy is to be hands off with objects in your code, however there is a convenience method included into your object when you mark it for persistence that makes inline configuration of the context easy:

class Person
  include Poro::Persistify
  configure_context do |context|
    context.primary_key = :some_attribute
  end
  include Poro::Modelify # if you want model methods.
end

The above configure method is really just a shortcut to the configure_for_class method on Context, which can be called instead.

External

The problem with inline configuration is that it does not abstract the persistence engine from the plain ol’ ruby objects. Poro provides a solution to this layering violation via a configuration block that is supplied during ContextManager initialization. This block may return the fully configured Context instance for each persistified class.

For example, the following generic code has the same result as Poro::Context.factory = Poro::ContextFactories::SingleStore.instantiate(:hash), which uses a specialized factory:

Poro::Context.factory = Poro::ContextManager.new do |klass|
  Poro::Contexts::HashContext.new(klass)
end

Of course, one normally would have a more complex block and/or utilize one of the specialized factories, but this example shows just how simple a factory nees to be.

Note that all Contexts are cached after creation, so the context configuration can be mutated by other methods (such as configure_for_class on Context), but developers are encouraged to choose one paradigm for their application and stick with it.

Contact

If you have any questions, comments, concerns, patches, or bugs, you can contact me via the github repository at:

github.com/paploo/poro

or directly via e-mail at:

[email protected]

Version History

0.1.5 - 2011-Feb-15

Bug Fixes.

  • MongoContext: Fixed a typo in the data_store set accessor.

0.1.4 - 2010-Nov-08

Bug Fixes.

  • MongoContext: Fixed broken fetch method.

  • HashContext: Fixed crash on find without a conditions hash.

  • Minor gemspec file change for compatibility with bundler.

0.1.3 - 2010-Oct-21

Callbacks and MongoContext Bug Fixes.

  • Added callbacks for common events.

  • MongoContext: You can actually remove records now.

  • MongoContext: Bignum encodes instead of throwing errors.

  • MongoContext: Made recognition of true/false/nil class during encoding more robust.

0.1.2 - 2010-Sep-30

Feature Additions.

  • Added a module namespace factory.

  • HashContext: Find one is faster when the conditions restrict on the primary key.

  • Many HashContext bugs fixed.

0.1.1 - 2010-Sep-24

Minor Additions and Bug Fixes.

  • MongoContext now can optionally encode Symbols as hashes or just leave them as strings.

  • MongoContext can have conversion to BSON::ObjectId turned off.

  • MongoContext can save Sets in various formats.

  • MongoContext handles namespaced models better.

  • Context doesn’t error when trying to find by id.

0.1.0 - 2010-Sep-18

Initial public release.

  • Major base functionality is complete, though is subject to big changes as it is used in the real world.

  • Only supports MongoDB and Hash Contexts.

  • No performance testing and optimization yet done.

  • The documentation is rough around the edges and may contain errors.

  • Spec tests are incomplete.

TODO List

The following are the primary TODO items, roughly in priority order:

  • YAML Connection Configuration:

    • Make a Util module that is able to use a rails-style YAML file–given by path–to get the elements needed for configuration of a SingleStore factory.

    • Modify SingleStore to use this file for configuration when appropriate.

  • Modelify: Break into modules for each piece of functionality.

  • Specs: Add specs for Context Find methods.

  • Specs: Add spec tests for Mongo Context.

  • Mongo Context: Split into modules in separate files.

  • Context: Split out modules into files.

  • Contexts: Add SQL Context.

  • Ruby: Verify support for ruby 1.9.0 and 1.9.1.

  • Ruby: Evaluate adding support for ruby 1.8.6 and 1.8.7.

License

The files contained in this repository are released under the commercially and GPL compatible “New BSD License”, given below:

License Text

Copyright (c) 2010, Jeffrey C. Reinecke
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the copyright holders nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL JEFFREY REINECKE BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Poro::Util::Inflector and its submodules are adapted from ActiveSupport, and its source is redistributed under the MIT license it was originally distributed under. The text of this copyright notice is supplied in poro/util/inflector.rb.