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 UserCallbacks
  include Guacamole::Callbacks

  before_create :encrypt_password

  def encrypt_password
    object.encrypted_password = BCrypt::Password.create(object.password)
  end
end
```

Inside the model you only need to tell which callback class is responsible:

```ruby
class User
  include Guacamole::Model
  
  # This will register the `UserCallbacks` to be used for the `User` models
  callbacks :user_callbacks

  attribute :encrypted_password, String

  attr_accessor :password
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
 * ~~Eventually 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 `DefaultCallback` in the case that there is no callback for the given model.

#### Todos

 - [x] Implement `Guacamole::Callback`
 - [x] Implement a callback registry and
 - [x] Make sure pre-loading the callbacks still works with auto-reloading in development mode
 - [x] Add generator for callback classes
 - [x] Trigger callbacks in the `Collection`
 - [x] Trigger callbacks in `Model#valid?`
 - [x] Implement `DefaultCallback`
 - [x] Refactor the timestamp addition to callbacks
 - [x] Add some documentation
 - [x] Describe callbacks in README
Why shouldn't you hash passwords in examples?


On 30 June 2014 14:17, Sebastian Cohnen <notifications@github.com> wrote:

> In lib/rails/generators/guacamole/callbacks/templates/callbacks.rb.tt:
>
> > @@ -0,0 +1,14 @@
> > +<% module_namespacing do -%>
> > +class <%= class_name %>Callbacks
> > +  include Guacamole::Callbacks
> > +
> > +  # Add your callbacks
> > +  # before_create :hash_password
> > +
> > +  # In your methods you will have access to the model instance via the `object` method
> > +  # def hash_password
> > +  #   object.hashed_password = Digest::SHA1.hexdigest(object.password)
>
> You shot *NEVER* ever hash passwords – not even in examples!
>
> Instead, use something like bcrypt-ruby which is made for costly password
> encryption:
>
> object.hashed_password = BCrypt::Password.create(object.password)
>
> —
> Reply to this email directly or view it on GitHub
> <https://github.com/triAGENS/guacamole/pull/78/files#r14340359>.
>



-- 
Thanks
Ujjwal