-
Notifications
You must be signed in to change notification settings - Fork 18
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.
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
endTo 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
endAt 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 (anArkenstone::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.
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
endNow, every hook defined for Widget and GoldenWidget will run.
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, envSince 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.

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.
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.