Plugins
*******


Using plugins
=============

   from sqlalchemy.continuum.plugins import PropertyModTrackerPlugin


   versioning_manager.plugins.append(PropertyModTrackerPlugin())


   versioning_manager.plugins  # <PluginCollection [...]>

   # You can also remove plugin

   del versioning_manager.plugins[0]


Activity
========

The ActivityPlugin is the most powerful plugin for tracking changes of
individual entities. If you use ActivityPlugin you probably don't need
to use TransactionChanges nor TransactionMeta plugins.

You can initalize the ActivityPlugin by adding it to versioning
manager.

   activity_plugin = ActivityPlugin()

   make_versioned(plugins=[activity_plugin])

ActivityPlugin uses single database table for tracking activities.
This table follows the data structure in activity stream
specification, but it comes with a nice twist:

   +----------------+-------------+-------------------------------------------------+
   | Column         | Type        | Description                                     |
   +================+=============+=================================================+
   | id             | BigInteger  | The primary key of the activity                 |
   +----------------+-------------+-------------------------------------------------+
   | verb           | Unicode     | Verb defines the action of the activity         |
   +----------------+-------------+-------------------------------------------------+
   | data           | JSON        | Additional data for the activity in JSON format |
   +----------------+-------------+-------------------------------------------------+
   | transaction_id | BigInteger  | The transaction this activity was associated    |
   |                |             | with                                            |
   +----------------+-------------+-------------------------------------------------+
   | object_id      | BigInteger  | The primary key of the object. Object can be    |
   |                |             | any entity which has an integer as primary key. |
   +----------------+-------------+-------------------------------------------------+
   | object_type    | Unicode     | The type of the object (class name as string)   |
   +----------------+-------------+-------------------------------------------------+
   | object_tx_id   | BigInteger  | The last transaction_id associated with the     |
   |                |             | object. This is used for efficiently fetching   |
   |                |             | the object version associated with this         |
   |                |             | activity.                                       |
   +----------------+-------------+-------------------------------------------------+
   | target_id      | BigInteger  | The primary key of the target. Target can be    |
   |                |             | any entity which has an integer as primary key. |
   +----------------+-------------+-------------------------------------------------+
   | target_type    | Unicode     | The of the target (class name as string)        |
   +----------------+-------------+-------------------------------------------------+
   | target_tx_id   | BigInteger  | The last transaction_id associated with the     |
   |                |             | target.                                         |
   +----------------+-------------+-------------------------------------------------+

Each Activity has relationships to actor, object and target but it
also holds information about the associated transaction and about the
last associated transactions with the target and object. This allows
each activity to also have object_version and target_version
relationships for introspecting what those objects and targets were in
given point in time. All these relationship properties use generic
relationships of the SQLAlchemy-Utils package.


Limitations
-----------

Currently all changes to parent models must be flushed or committed
before creating activities. This is due to a fact that there is still
no dependency processors for generic relationships. So when you create
activities and assign objects / targets for those please remember to
flush the session before creating an activity:

   article = Article(name=u'Some article')
   session.add(article)
   session.flush()  # <- IMPORTANT!
   first_activity = Activity(verb=u'create', object=article)
   session.add(first_activity)
   session.commit()

Targets and objects of given activity must have an integer primary key
column id.


Create activities
-----------------

Once your models have been configured you can get the Activity model
from the ActivityPlugin class with activity_cls property:

   Activity = activity_plugin.activity_cls

Now let's say we have model called Article and Category. Each Article
has one Category. Activities should be created along with the changes
you make on these models.

   article = Article(name=u'Some article')
   session.add(article)
   session.flush()
   first_activity = Activity(verb=u'create', object=article)
   session.add(first_activity)
   session.commit()

Current transaction gets automatically assigned to activity object:

   first_activity.transaction  # Transaction object


Update activities
-----------------

The object property of the Activity object holds the current object
and the object_version holds the object version at the time when the
activity was created.

   article.name = u'Some article updated!'
   session.flush()
   second_activity = Activity(verb=u'update', object=article)
   session.add(second_activity)
   session.commit()

   second_activity.object.name  # u'Some article updated!'
   first_activity.object.name  # u'Some article updated!'

   first_activity.object_version.name  # u'Some article'


Delete activities
-----------------

The version properties are especially useful for delete activities.
Once the activity is fetched from the database the object is no longer
available ( since its deleted), hence the only way we could show some
information about the object the user deleted is by accessing the
object_version property.

   session.delete(article)
   session.flush()
   third_activity = Activity(verb=u'delete', object=article)
   session.add(third_activity)
   session.commit()

   third_activity.object_version.name  # u'Some article updated!'


Local version histories using targets
-------------------------------------

The target property of the Activity model offers a way of tracking
changes of given related object. In the example below we create a new
activity when adding a category for article and then mark the article
as the target of this activity.

   session.add(Category(name=u'Fist category', article=article))
   session.flush()
   activity = Activity(
       verb=u'create',
       object=category,
       target=article
   )
   session.add(activity)
   session.commit()

Now if we wanted to find all the changes that affected given article
we could do so by searching through all the activities where either
the object or target is the given article.

   import sqlalchemy as sa


   activities = session.query(Activity).filter(
       sa.or_(
           Activity.object == article,
           Activity.target == article
       )
   )


Flask
=====

FlaskPlugin offers way of integrating Flask framework with SQLAlchemy-
Continuum. Flask-Plugin adds two columns for Transaction model, namely
*user_id* and *remote_addr*.

These columns are automatically populated when transaction object is
created. The *remote_addr* column is populated with the value of the
remote address that made current request. The *user_id* column is
populated with the id of the current_user object.

   from sqlalchemy_continuum.plugins import FlaskPlugin
   from sqlalchemy_continuum import make_versioned


   make_versioned(plugins=[FlaskPlugin()])


PropertyModTracker
==================

The PropertyModTrackerPlugin offers a way of efficiently tracking
individual property modifications. With PropertyModTrackerPlugin you
can make efficient queries such as:

Find all versions of model X where user updated the property A or
property B.

Find all versions of model X where user didn't update property A.

PropertyModTrackerPlugin adds separate modified tracking column for
each versioned column. So for example if you have versioned model
called Article with columns *name* and *content*, this plugin would
add two additional boolean columns *name_mod* and *content_mod* for
the version model. When user commits transactions the plugin
automatically updates these boolean columns.


TransactionChanges
==================

TransactionChanges provides way of keeping track efficiently which
declarative models were changed in given transaction. This can be
useful when transactions need to be queried afterwards for problems
such as:

1. Find all transactions which affected *User* model.

2. Find all transactions which didn't affect models *Entity* and
   *Event*.

The plugin works in two ways. On class instrumentation phase this
plugin creates a special transaction model called
*TransactionChanges*. This model is associated with table called
*transaction_changes*, which has only only two fields: transaction_id
and entity_name. If for example transaction consisted of saving 5 new
User entities and 1 Article entity, two new rows would be inserted
into transaction_changes table.

+------------------+-------------------+
| transaction_id   | entity_name       |
+------------------+-------------------+
| 233678           | User              |
+------------------+-------------------+
| 233678           | Article           |
+------------------+-------------------+


TransactionMeta
===============

TransactionMetaPlugin offers a way of saving key-value data for
transations. You can use the plugin in same way as other plugins:

   meta_plugin = TransactionMetaPlugin()

   versioning_manager.plugins.add(meta_plugin)

TransactionMetaPlugin creates a simple model called TransactionMeta.
This class has three columns: transaction_id, key and value.
TransactionMeta plugin also creates an association proxy between
TransactionMeta and Transaction classes for easy dictionary based
access of key-value pairs.

You can easily 'tag' transactions with certain key value pairs by
giving these keys and values to the meta property of Transaction
class.

   from sqlalchemy_continuum import versioning_manager


   article = Article()
   session.add(article)

   uow = versioning_manager.unit_of_work(session)
   tx = uow.create_transaction(session)
   tx.meta = {u'some_key': u'some value'}
   session.commit()

   TransactionMeta = meta_plugin.model_class
   Transaction = versioning_manager.transaction_cls

   # find all transactions with 'article' tags
   query = (
       session.query(Transaction)
       .join(Transaction.meta_relation)
       .filter(
           db.and_(
               TransactionMeta.key == 'some_key',
               TransactionMeta.value == 'some value'
           )
       )
   )
