<commit_before>CRUD Events
===========

Ramses supports Nefertari CRUD events. The following documentation describes how to define and connect them.

Writing Processors
------------------

You can write custom functions inside your ``__init__.py`` file, then add the ``@registry.add`` decorator before the functions that you'd like to turn into CRUD event subscribers. Ramses CRUD event subscribers has the same API as Nefertari CRUD event subscribers. Check Nefertari CRUD Events doc for more details on events API.

E.g.

.. code-block:: python

    @registry.add
    def lowercase(event):
        """ This processor lowercases the value of a field """
        value = (event.field.new_value or '').lower().strip()
        event.set_field_value(value)


Connecting event subscribers
----------------------------

When you define event subscribers in your ``__init__.py`` as described above, you can apply them on either a per-model or a per-field basis.

Subscribers are defined using the ``_event_handlers`` property. This property is an object, keys of which are called "event tags" and values are lists of subscriber names. Event tags are constructed of two parts: ``<type>_<action>`` whereby:

**type**
    Is either ``before`` or ``after``, depending on when subscriber should run - before view method call or after respectively.
**action**
    Exact name of Nefertari view method that processes the request (action)

Complete list of actions:
    * **index** - Collection GET
    * **create** - Collection POST
    * **update_many** - Collection PATCH/PUT
    * **delete_many** - Collection DELETE
    * **collection_options** - Collection OPTIONS
    * **show** - Item GET
    * **update** - Item PATCH
    * **replace** - Item PUT
    * **delete** - Item DELETE
    * **item_options** - Item OPTIONS

And one special action:
    * **set** - triggers on either of the following actions: ``create``, ``update``, ``replace`` and ``update_many``

E.g. The following example connects the ``lowercase`` subscriber to the ``before_set`` event and the subscriber will run before any of the following requests are processed:
    * Collection POST
    * Item PATCH
    * Item PUT
    * Collection PATCH
    * Collection PUT

.. code-block:: json

    "_event_handlers": {
        "before_set": ["lowercase"]
    }


We will use the following subscriber to demonstrate how to connect subscribers to events. This subscriber logs request.body.

.. code-block:: python

    import logging
    log = logging.getLogger(__name__)

    @registry.add
    def log_request(event):
        log.debug(event.request.body)


Using before/after events
-------------------------

``before`` events should be used to:
    * Transform input
    * Perform validation
    * Apply changes to object that is being affected by the request using the ``event.set_field_value`` method

``after`` events should be used to:
    * Change DB objects which are not affected by the request
    * Perform notifications/logging


Per-model subscribers
---------------------

To register subscribers on a per-model basis, you can define the ``_event_handlers`` property at the root of your model's JSON schema. For example, if we have a JSON schema for the model ``User`` and we want to log all collection GET requests to the ``User`` model after they were processed (using the ``log_request`` subscriber), we can register the subscriber in the JSON schema like this:

.. code-block:: json

    {
        "type": "object",
        "title": "User schema",
        "$schema": "http://json-schema.org/draft-04/schema",
        "_event_handlers": {
            "after_index": ["log_request"]
        },
        ...
    }


Per-field subscribers
---------------------

To register subscribers on a per-field basis, you can define the ``_event_handlers`` property inside the fields of your JSON schema (same level as ``_db_settings``).

E.g. if our model ``User`` has a field ``username`` we might want to make sure that ``username`` is not a reserved name. If ``username`` is a reserved name, we want to raise an exception to interrupt the request.

.. code-block:: python

    @registry.add
    def check_username(event):
        reserved = ('admin', 'cat', 'system')
        username = event.field.new_value
        if username in reserved:
            raise ValueError('Reserved username: {}'.format(username))


The following JSON schema registers ``before_set`` on the field ``User.username``. When connected this way, the ``check_username`` subscriber will only be executed if the request has the field ``username`` passed to it:

.. code-block:: json

    {
        "type": "object",
        "title": "User schema",
        "$schema": "http://json-schema.org/draft-04/schema",
        "properties": {
            "username": {
                "_db_settings": {...},
                "_event_handlers": {
                    "before_set": ["check_username"]
                }
            }
        }
        ...
    }


Other Things You Can Do
-----------------------

You can update another field's value, for example, increment a counter:

.. code-block:: python

    @registry.add
    def increment_count(event):
        counter = event.instance.counter
        incremented = counter + 1
        event.set_field_value(incremented, 'counter')


You can transform the value of a field, for example, encrypt a password before saving it:

.. code-block:: python

    @registry.add
    def encrypt(event):
        import cryptacular.bcrypt
        crypt = cryptacular.bcrypt.BCRYPTPasswordManager()
        password = event.field.new_value

        if password and not crypt.match(password):
            encrypted = str(crypt.encode(password))
            event.set_field_value(encrypted)


You can update other collections (or filtered collections), for example, mark sub-tasks as completed whenever a task is completed:

.. code-block:: python

    @registry.add
    def mark_subtasks_completed(event):

        from nefertari import engine
        completed = event.field.new_value
        instance = event.instance

        if completed:
            subtask_model = engine.get_document_cls('Subtask')
            subtasks = subtask_model.get_collection(task_id=instance.id)
            subtask_model._update_many(subtasks, {'completed': True})


You can perform more complex queries using ElasticSearch:

.. code-block:: python

    @registry.add
    def mark_subtasks_after_2015_completed(event):

        from nefertari import engine
        from nefertari.elasticsearch import ES
        completed = event.field.new_value
        instance = event.instance

        if completed:
            subtask_model = engine.get_document_cls('Subtask')
            es_query = 'task_id:{} AND created_at:[2015 TO *]'.format(instance.id)
            subtasks_es = ES(subtask_model.__name__).get_collection(_raw_terms=es_query)
            subtasks_db = subtask_model.filter_objects(subtasks_es)
            subtask_model._update_many(subtasks_db, {'completed': True})
<commit_msg>Fix docs<commit_after>CRUD Events
===========

Ramses supports Nefertari CRUD events. The following documentation describes how to define and connect them.

Writing Processors
------------------

You can write custom functions inside your ``__init__.py`` file, then add the ``@registry.add`` decorator before the functions that you'd like to turn into CRUD event subscribers. Ramses CRUD event subscribers has the same API as Nefertari CRUD event subscribers. Check Nefertari CRUD Events doc for more details on events API.

E.g.

.. code-block:: python

    @registry.add
    def lowercase(event):
        """ This processor lowercases the value of a field """
        value = (event.field.new_value or '').lower().strip()
        event.set_field_value(value)


Connecting event subscribers
----------------------------

When you define event subscribers in your ``__init__.py`` as described above, you can apply them on either a per-model or a per-field basis.

Subscribers are defined using the ``_event_handlers`` property. This property is an object, keys of which are called "event tags" and values are lists of subscriber names. Event tags are constructed of two parts: ``<type>_<action>`` whereby:

**type**
    Is either ``before`` or ``after``, depending on when subscriber should run - before view method call or after respectively.
**action**
    Exact name of Nefertari view method that processes the request (action)

Complete list of actions:
    * **index** - Collection GET
    * **create** - Collection POST
    * **update_many** - Collection PATCH/PUT
    * **delete_many** - Collection DELETE
    * **collection_options** - Collection OPTIONS
    * **show** - Item GET
    * **update** - Item PATCH
    * **replace** - Item PUT
    * **delete** - Item DELETE
    * **item_options** - Item OPTIONS

And one special action:
    * **set** - triggers on either of the following actions: ``create``, ``update``, ``replace`` and ``update_many``

E.g. The following example connects the ``lowercase`` subscriber to the ``before_set`` event and the subscriber will run before any of the following requests are processed:
    * Collection POST
    * Item PATCH
    * Item PUT
    * Collection PATCH
    * Collection PUT

.. code-block:: json

    "_event_handlers": {
        "before_set": ["lowercase"]
    }


We will use the following subscriber to demonstrate how to connect subscribers to events. This subscriber logs request.body.

.. code-block:: python

    import logging
    log = logging.getLogger(__name__)

    @registry.add
    def log_request(event):
        log.debug(event.view.request.body)


Using before/after events
-------------------------

``before`` events should be used to:
    * Transform input
    * Perform validation
    * Apply changes to object that is being affected by the request using the ``event.set_field_value`` method

``after`` events should be used to:
    * Change DB objects which are not affected by the request
    * Perform notifications/logging


Per-model subscribers
---------------------

To register subscribers on a per-model basis, you can define the ``_event_handlers`` property at the root of your model's JSON schema. For example, if we have a JSON schema for the model ``User`` and we want to log all collection GET requests to the ``User`` model after they were processed (using the ``log_request`` subscriber), we can register the subscriber in the JSON schema like this:

.. code-block:: json

    {
        "type": "object",
        "title": "User schema",
        "$schema": "http://json-schema.org/draft-04/schema",
        "_event_handlers": {
            "after_index": ["log_request"]
        },
        ...
    }


Per-field subscribers
---------------------

To register subscribers on a per-field basis, you can define the ``_event_handlers`` property inside the fields of your JSON schema (same level as ``_db_settings``).

E.g. if our model ``User`` has a field ``username`` we might want to make sure that ``username`` is not a reserved name. If ``username`` is a reserved name, we want to raise an exception to interrupt the request.

.. code-block:: python

    @registry.add
    def check_username(event):
        reserved = ('admin', 'cat', 'system')
        username = event.field.new_value
        if username in reserved:
            raise ValueError('Reserved username: {}'.format(username))


The following JSON schema registers ``before_set`` on the field ``User.username``. When connected this way, the ``check_username`` subscriber will only be executed if the request has the field ``username`` passed to it:

.. code-block:: json

    {
        "type": "object",
        "title": "User schema",
        "$schema": "http://json-schema.org/draft-04/schema",
        "properties": {
            "username": {
                "_db_settings": {...},
                "_event_handlers": {
                    "before_set": ["check_username"]
                }
            }
        }
        ...
    }


Other Things You Can Do
-----------------------

You can update another field's value, for example, increment a counter:

.. code-block:: python

    @registry.add
    def increment_count(event):
        counter = event.instance.counter
        incremented = counter + 1
        event.set_field_value(incremented, 'counter')


You can transform the value of a field, for example, encrypt a password before saving it:

.. code-block:: python

    @registry.add
    def encrypt(event):
        import cryptacular.bcrypt
        crypt = cryptacular.bcrypt.BCRYPTPasswordManager()
        password = event.field.new_value

        if password and not crypt.match(password):
            encrypted = str(crypt.encode(password))
            event.set_field_value(encrypted)


You can update other collections (or filtered collections), for example, mark sub-tasks as completed whenever a task is completed:

.. code-block:: python

    @registry.add
    def mark_subtasks_completed(event):

        from nefertari import engine
        completed = event.field.new_value
        instance = event.instance

        if completed:
            subtask_model = engine.get_document_cls('Subtask')
            subtasks = subtask_model.get_collection(task_id=instance.id)
            subtask_model._update_many(subtasks, {'completed': True})


You can perform more complex queries using ElasticSearch:

.. code-block:: python

    @registry.add
    def mark_subtasks_after_2015_completed(event):

        from nefertari import engine
        from nefertari.elasticsearch import ES
        completed = event.field.new_value
        instance = event.instance

        if completed:
            subtask_model = engine.get_document_cls('Subtask')
            es_query = 'task_id:{} AND created_at:[2015 TO *]'.format(instance.id)
            subtasks_es = ES(subtask_model.__name__).get_collection(_raw_terms=es_query)
            subtasks_db = subtask_model.filter_objects(subtasks_es)
            subtask_model._update_many(subtasks_db, {'completed': True})

{'fraction_non_alphanumeric': 0.1154775440591245, 'fraction_numerical': 0.001847640704945992, 'mean_word_length': 2.8189959294436906, 'pattern_counts': {'":': 30, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6058973', 'n_tokens_mistral': 3833, 'n_tokens_neox': 3641, 'n_words': 1399}