Moribus
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)
user.update_attributes(:status => 1)
user.update_attributes(:first_name => 'Mike')
user2 = User.create(:first_name => 'John', :last_name => 'Smith', :status => 5)
Run tests
rake spec
Credits
Copyright
Copyright (c) 2013 TMX Credit.