Rails’ ActiveRecord makes it quite easy to ad event callbacks when an object is inserted, updated or deleted:
class User < ActiveRecord::Base before_create :encrypt_password def encrypt_password do_some_encryption(self[:password]) end end
In SQLAlchemy we can do the same with a MapperExtension:
from sqlalchemy.orm.mapper import MapperExtension class EncryptField(MapperExtension): self.__init__(self, field_for_encryption): self.field = field_for_encryption def before_insert(self, mapper, connection, instance): encrypted_value = do_some_encryption(getattr(instance, self.field)) setattr(instance, self.field, encrypted_value)
When we declare our mapper, we pass an instance of this class to the extension argument. This can be a single MapperExtension instance, or list of instances(so we can chain extensions together):
users_mapper = mapper(User, users_table, extension = EncryptField('password'))
If you are using Elixir you have to use the using_mapper_options statement:
class User(Entity): has_field('password', String(50)) using_mapper_options(extension = EncryptField('password'))
The MapperExtension class provides various methods for you to override, for example before and after insert/update/delete and when the object is retrieved from the database.
OK, but what we really want is something like this:
class User(Entity): has_field('password', String(50)) before_insert('encrypt_password') def encrypt_password(self): self.password = do_some_encryption(self.password)
With Elixir we can do this with a Statement. Elixir infact is little more than Statements (DSLs) on top of SQLAlchemy. See http://cleverdevil.org/computing/52/ for an excellent introduction to Statements.
First, we need a MapperExtension subclass that handles the before_insert behaviour in a generic way:
from sqlalchemy.orm.mapper import MapperExtension class MapperExtensionProxy(MapperExtension): def before_insert(self, mapper, connection, instance): if has_attr(instance, '__before_insert__'): instance.before_insert()
This simply checks if our Entity subclass has the __before_insert__ method (underscores added to prevent possible name clashes) and calls that method. We could do exactly the same with other behaviours, such as after_insert, before_destroy, etc; see the MapperExtension docs for a list of methods and arguments.
Then, we need to create the Statement class. This is just a plain Python new-style class:
from elixir.statements import Statement class BeforeInsert(object): def __init__(self, entity, *callbacks): add_extension_proxy(entity, 'before_insert', callbacks) before_insert = Statement(BeforeInsert)
The add_extension_proxy function simply adds the appropriate behaviour to the Entity class, then adds the MapperExtensionProxy object to the list of extensions the Entity has (that way, we don’t override other extensions we might want to use).
So now we can do this:
class User(Entity): has_field('password', String(50)) before_insert('encrypt_password','email_new_user') before_delete('email_ex_user') def encrypt_password(self): self.password = do_some_encryption(self.password) def email_new_user(self): self.send_notification_mail('Welcome to the system') def email_ex_user(self): self.send_notificiation_mail('Your account is about to be destroyed, Bye!')
The full code for adding these events to Elixir can be found here.