triAGENS/guacamole — pull request #78 by railsbros-dirk (2014-06-19 09:48:51+00:00)
Title: Implement external Callbacks

Implementing callbacks within the collection caused [all kinds of trouble](https://github.com/triAGENS/guacamole/pull/16). Thus we decided to re-think the whole approach and came up with _external callbacks_: Write the callback logic in dedicated class instead of the model or the collection.

The following code snippets outline this approach:

Imagine you want to hash the password of a model before saving it. Traditionally you would write this functionality into the model itself. With external callbacks you would create a dedicated class to handle this:

```ruby
class HashPasswordCallbacks
include Guacamole::Callbacks

# To what models these callbacks will be applied
around :user

before_create :hash_password

def hash_password
model.hashed_password = Digest::SHA1.hexdigest(model.password)
end
end
```

Everything else will be handled by Guacamole itself. This approach has several advantages:

* We won't pollute the `Collection` class with some crazy callback logic
* We can share callbacks between different models
* We can better test the callbacks in isolation

The first is especially true because we will just use `ActiveModel::Callbacks` (the code is just an outline):

```ruby
module Guacamole::Callbacks
extend ActiveModel::Callbacks

define_model_callbacks :create, :update, :save, :validate, :destroy

attr_reader :model

def initialize(model)
@model = model
end
end
```

Triggering the callbacks from will happen most of the time in the `Collection` and eventually look like this:

```ruby
class UsersCollection
def create(user)
callbacks = callbacks_for(user)
callbacks.run_callbacks :save do
callbacks.run_callbacks :create do
# perform create
end
end
end
end
```

Of course this must happen in the `valid?` method in the model as well. But since the callback doesn't know anything about the collection, this is perfectly fine.

Not shown in this outline is a place where all the callback classes are registered to remove the need for implement lookup and configuration logic in both the `Model` and the `Collection`. This implies all callbacks needs to be loaded in advance, since the registration happens in the callback class.

Additionally we must provide a `NoopCallback` in the case that there is no callback for the given model.

#### Todos

- [ ] Implement `Guacamole::Callback`
- [ ] Implement a callback registry and make sure pre-loading the callbacks still works with auto-reloading in development mode
- [ ] Trigger callbacks in the `Collection`
- [ ] Trigger callbacks in `Model#valid?`
- [ ] Implement `NoopCallback`
- [ ] Allow multiple callback classes to be reference one model
- [ ] Refactor the timestamp addition to callbacks