Moribus

Build Status

Moribus is a set of tools for managing complex graphs of ActiveRecord objects for which there are many inbound foreign keys, attributes and associations with high rates of change, and business demands for well-tracked change history.

AggregatedBehavior

AggregatedBehavior implements a pattern in which an object's identity is modeled apart from its attributes and outbound associations. This enables higher level of normalization of your data, since one set of properties may be shared among multiple objects. This set of properties - attributes and outbound associations - are modeled on an object called an "info object". And we say that this info object is aggregated by host object(s) and it acts (behaves) as aggregated. When an aggregated object is about to be saved, it looks up for an existing record with the same attributes in the database under the hood, and if it founds, it 'replaces' itself by that record. This allows you to work with attributes of your entity as if they are properties of an actual model and normalize your data at the same time.

Inbound foreign keys will always point at the same object in memory, and the object will never be stale, as it has no attributes of its own that are subject to change. This is useful for objects with many inbound foreign keys and high-traffic attributes/associations, such as statuses. Without this pattern it would be difficult to avoid many StaleObjectErrors.

TrackedBehavior

TrackedBehavior implements history tracking on the stack of objects representing the identity object's attributes and outbound associations. When a model behaves as a tracked behavior, it will never get actually updated. Instead, it will update it's own 'is_current' column to false and will be saved as a new record with new attribute values and 'is_current' column as 'true'. Thus, under the hood, new attributes will supersede old attributes, leaving old record as history one.

Macros, Associations and Combination

Despite the fact that Behaviors may be used by models on they're own, they main purpose is to be used within associations and in conjunction. The best way to demonstrate this is by example.

Lets assume we have a User entity with attributes that should be tracked and normalized. Those attributes may be, for example, :first_name, :last_name and :status as enumerated integer value. This makes entity may be represented with three models: User - as main model for interactions, tracked UserInfo (user_id, person_name_id, status) for tracking, and aggregated UserName (first_name, last_name) for name normalization. Class definitions for that models will look as follows:

  class User < ActiveRecord::Base
    has_one_current :user_info
    delegate_associated :user_name, :to => :user_info
  end

  class UserInfo < ActiveRecord::Base
    has_aggregated :person_name
    acts_as_tracked
  end

  class UserName < ActiveRecord::Base
    acts_as_aggregated
  end

Despite the fact that internal representation is more complicated now, top-level operations will look exactly the same:

  user = User.create(:first_name => 'John', :last_name => 'Smith', :status => 0)
  # This creates User(id: 1) record, PersonName(id: 1, first_name: 'John', last_name: 'Smith')
  # record and UserInfo(id: 1, user_id: 1, person_name_id: 1, status: 0, is_current: true)

  user.update_attributes(:status => 1)
  # This creates new UserInfo(id: 2, user_id: 1, person_name_id: 1, status: 1, is_current: true)
  # record, and changes UserInfo(id: 1) record's 'is_current' attribute to false.

  user.update_attributes(:first_name => 'Mike')
  # This creates new PersonName(id: 2, first_name: 'Mike', last_name: 'Smith') record and new
  # current UserInfo(id: 3, user_id: 1, person_name_id: 2, :status: 1, is_current: true)

  # Now, if we want to create another 'John Smith' user:
  user2 = User.create(:first_name => 'John', :last_name => 'Smith', :status => 5)
  # This creates User(id: 2) record and UserInfo(id: 4, user_id: 2, person_name_id: 1, status: 5, is_current: true)
  # record that reuses existing UserName information.

Run tests

rake spec

Credits

Copyright (c) 2013 TMX Credit.