Skip to content
Scott Williams edited this page Jun 12, 2014 · 3 revisions

Hooks

Hooks are allow you to call arbitrary code at various points in the object lifecycle within Arkenstone. For example, if you need to massage some property names before they are sent off to the url, you can do that with a hook. A hook must extend Arkenstone::Hook and then override the method you want to hook into.

Using Hooks

Let's say you want to send log information to the console at various parts of the Arkenstone lifecycle. First, subclass Arkenstone::Hook:

class LogHook < Arkenstone::Hook
    def before_request(env)
      console.log "BEFORE REQUEST"
      console.log env
    end

    def after_complete(response)
        console.log "AFTER RESPONSE"
        console.log response
    end

    def encode_attributes(attributes)
      console.log "ENCODING ATTRIBUTES"
      console.log attributes
    end

    def on_error(response)
      console.log "OH NOOOO"
      console.log attributes
    end
end

To attach it to an Arkenstone powered class, send an instance of that to the add_hook directive:

class Widget
  include Arkenstone::Document
  
  url 'http://example.com/widgets'
  
  add_hook LogHook.new
  
  attributes :name, :size, :sku
end

At runtime, you'll see all sorts of fun and exciting log messages fly by when you use those objects. Each of those methods on the hook object are called at certain times from Arkenstone.

  • before_request — Called before the request is sent to the web service. Passes in the request environment (an Arkenstone::Environment) as a parameter.
  • after_complete — Called after the request has been successfully completed. Passes in a Net::HTTPResponse as a parameter.
  • on_error — Called if the response returned an error. Passes in a Net::HTTPResponse as a parameter.
  • encode_attributes — Called when an object is going to be saved. Allows you to tweak attributes before they get to the request stage.

Inheritance

Let's assume you want to sublcass Widget. Hooks are are attached to the class, not the instance. This means that even if GoldenWidget extends Widget it will not have the same hooks. You can solve this by re-adding hooks to subclasses, but that's kind of annoying. There's a shortcut directive that'll walk up the inheritance chain and call every hook it finds when appropriate.

class GoldenWidget < Widget
  inherit_hooks
  
  attributes :number_of_karets
end

Now, every hook defined for Widget and GoldenWidget will run.

How do hooks work?

The add_hook directive is in lib/arkenstone/document.rb in the Arkenstone::Document::ClassMethods. It adds the provided hook to the class's arkenstone_hooks array.

inherit_hooks sets the class's arkenstone_inherit_hooks value too.

A hook is called using one of several class methods: Arkenstone::Hook.call_request_hooks, Arkenstone::Hook.call_response_hooks, or Arkenstone::Hook.call_error_hooks. Those are defined in lib/arkenstone/network/hook.rb. Each method takes a class object, and another parameter appropriate for the hook being called (a request object for request hooks, a response object for response hooks, etc). They all run the call_hook method and pass in the class object as well as a block that'll run the appropriate hook method.

For example, here's how the before_request hook is called in Arkenstone::Network::ClassMethods#send_request:

  env = Arkenstone::Environment.new url: url, verb: verb, body: data
  Arkenstone::Hook.call_request_hooks self, env

Since this method is within a module extended onto a class the self parameter is the class itself. All this module meta-programming can be confusing. Here's a diagram that demonstrates how these all connect to each other.

Diagram 1

call_request_hooks(klass, request)

klass is a the class object that we're going to find hooks on, in this case, Widget. Request is the Environment object that the hooks will modify.

It immediately runs call_hook and passes the klass, and constructs a Proc that'll ultimately call the before_request method on any hook sent to it.

call_hook(klass, enumerator)

Here's where the work happens. First an array is created. This'll hold any hook objects we find on the class (Widget) and its ancestors (if applicable).

If the klass has the arkenstone_inherit_hooks turned on, it'll start to walk up the inherit tree to look for hooks on each ancestor. If an ancestor responds to arkenstone_hooks, those are added to our hooks array.

If the klass does not have arkenstone_inherit_hooks, it'll just populate hooks with its own arkenstone_hooks.

Now that we have a list of hooks to run, the list will execute the supplied enumerator on each one. In our example, this means that before_request will run on each hook.

Clone this wiki locally