Components

Component System

The Component System in Adhearsion is now vastly improved over the previous Helpers (now deprecated) available through Adhearsion 0.7.7. The component system is designed to provide an easy interface to developing plug-ins for Adhearsion that may be easily shared among the Adhearsion community. You may see a community member discuss their implementation experience here.

Method-based

The most significant difference over the old component system is the removal of strict OOP. This was done for a few reasons:

Scoped methods

# components/my_component/my_component.rb

initialization do
  COMPONENTS.my_component["server"] = MyCoolSpecialServerClass.new
end

methods_for :dialplan do
  def countdown
    COMPONENTS.my_component["server"].introduce(first, second)
  end
end

methods_for :dialplan, :generators do
  def log_something

  end
end

methods_for :rpc do

  delegate :originate, :to => COMPONENTS.my_component[:server]

  def introduce(first, second)

  end
end

methods_for :events do

end
Methods defined in a scope will have full access to other methods in that scope. For example, methods_for(:dialplan) will can execute dialplan methods directly by doing play(), menu(), etc. The object in which they're executed is actually the Call.

Finding method scopes

Below are a list of currently supported method scopes but this list may change. To get the latest list for your particular installation, type rake method_scopes

Important notes and gotchas

When you define methods for multiple scopes, make sure all of your methods are available to those scopes.

Referencing methods outside of your scope.

Let's say you want your :dialplan method to also be shared by :rpc. In this scenario, consider two things:

Method conflicts

All recognized method conflicts will raise warnings when the Adhearsion application initializes. The actual precedence will actually be handled by Ruby itself.

Constants

When you define a constant in a component, it will be available in the root namespace. A constant in Ruby begins with a capital letter.

Because a constant in the root namespace is globally accessible, you can use the constant in any methods you define in your scopes.

# components/constants_are_cool/constants_are_cool.rb
MyClientObject = SpecialClientObject.connect "localhost"

methods_for :dialplan do
  def send_greeting
    MyClientObject.send_message :greet
  end
end
If you come from other programming languages and feel inclined to use a global variable, try to accomplish the global availability with a constant. They work virtually the same and are a bit more Thread-safe.

Instance/class variables

Do not use instance or class variables. An instance variable is a variable whose name begins with "@" and a class variable is one which begins with "@@". You shouldn't use them because the methods you define will be ran in many different objects and classes. If you must share state between method invocations, either pass it around in the function arguments, define root-level constants, or store it in your components' configuration. Always consider thread-safety when you're using any shared resources in separate Threads (see next section).

You're writing multi-threaded code!

Please keep in mind you may introduce a race condition into your application inadvertently due to resources being in awkward states. For example, this would generate a race condition:

methods_for(:events) do
  def redefine_foobar
    # You should never be *this* dynamic. This is just a contrived example.
    metaclass.remove_method(:foobar)
    meta_def(:foobar) { doing_something }
    foobar
  end
end
Because the redefine_foobar method can be called from different Threads, two threads may try to execute it and find that the "foobar" method doesn't exist when one tries to run it because another Thread has temporarily removed it. I way to solve this would be something like this:

FOOBAR_LOCK = Mutex.new

methods_for(:events) do
  def redefine_foobar
    # Synchronizing makes this code Thread-safe
    FOOBAR_LOCK.synchronize do
      # This Ruby is kinda complex. If you don't understand it, don't worry.
      metaclass.remove_method(:foobar)
      meta_def(:foobar) { doing_something }
      foobar
    end
  end
end
This will ensure only one Thread can execute foobar at a time. If you can avoid it, try no to share state or only share constants which are frozen().

Configuration

Every component has its own YAML configuration file named after the component which should be in the component's directory. If the component's name is "phone_hero", the configuration filename would be "phone_hero.yml".

Example configuration file

# This is a comment
company_name: Apple, Inc.
stock_symbol: APPL

Accessing configuration within your component

Because of the functional nature of the new component system, all functions must refer to their own configuration globally. The root-level constant COMPONENTS is available as syntax sugar. Below is an example

# kewlz0rz_component.rb

methods_for(:generators) do
  def public_company
    name   = COMPONENTS.kewlz0rz_component["company_name"]
    symbol = COMPONENTS.kewlz0rz_component["stock_symbol"]

    some_special_method_that_does_something name, symbol
  end
end
Warning: Do NOT load your configuration file manually. Your component may be installed via a gem or some other means and not kept in the same directory as your config file.

Reusing code via the :rpc scope

Writing methods in the :rpc scope can be a great way to have other processes reuse the code assets you developed for your Adhearsion application. If you wish to generate HTML documentation for the :rpc methods exposed in your Adhearsion application, type rake doc:rpc. This will create a doc-rpc folder of YARD-generated docs.

These methods can be invoked via DRb using Adhearsion's internal DRb support. Some components (such as the restful_rpc component) expose Adhearsion's internal RPC system to new protocols.

Directory structure

The directory structure is simple. Given the component name "killer_tofu", the directory structure for a newly created component would look like the following:

Note: a more sophisticated "killer_tofu" component may have the following structure:

Managing lifecycles of servers and objects

Because OOP is a totally valid and useful paradigm for software development, it's not compromised in the new component system, despite its scoped method-based paradigm. Also, you may want your component to start up new servers.

Don't forget to do shutdown your resources!

If you start a server, you should register a shutdown hook. For example

initialization do
  host, port = COMPONENTS.micromenus.values_at "host", "port"
  ::MICROMENUS_SERVER = MyServer.start(host, port)
  Events.register_callback("/shutdown") { ::MICROMENUS_SERVER.stop }
end

Using the logging system

The Adhearsion logging system is meant to be very easy for virtually all parts of the framework to log without much friction. Every log message is scoped in one of two ways:

Both of these are optional. Here is an example of omitting both of these.

ahn_log "Testing 123"
The example above uses the :info logging level at the root level namespace. The example below uses the :error logging level at the root level logging namespace:

ahn_log.error "Warning Will Robinson!"
Namespaces can be arbitrarily invoked on the ahn_log object. They'll be created the first time you use them.

ahn_log.kewlz0rz_stuff "Starting kewlz0rz stuff"
This example above uses the :info logging level in the kewlz0rz_stuff namespace.

ahn_log.kewlz0rz_stuff.error "Couldn't start kewlz0rz_stuff!"
This uses the :error logging level in the kewlz0rz_stuff namespace.

ahn_log.lolcats.icanhascheezburger.warn "TONIGHT WE NOM IN HELL!"
This shows arbitrarily nested namespaces.

Note: Doing ahn_log.error.kewlz0rz_stuff is invalid. If the logging level is specified at all, it must be the last method.

Unit Testing Components

The component system has a built-in testing framework which lets you provide quality control over these crucial aspects of your framework. It uses RSpec and Flexmock.

Example spec

Adhearsion has a built-in RSpec helper classes and modules which can make your life easier when testing your components. The best way to learn how to use it is by example. When you create an application, take a look at the components/disabled/restful_rpc/spec/restful_rpc_spec.rb file for an example of a well-tested component.

Component spec helpers

Must absorb all of ActiveRecord's testing stuff. Fixtures, etc. Are there any really awesome patterns I should use? fixture_scenarios?

Custom method scopes

What if you have a special and super-sophisticated component in which you want users to basically extend it with your own component system?

You must define a component_scope_name method in your method context so the components can do some scope-specific logic if necessary.

How does it work?

This new component system is a great example of Ruby's dynamism and power. Methods are completely tangible and manipulable. In this section, I'm going to dive into deep Ruby jargon to explain this system.

The methods_for() method instantiates a new anonymous Module by doing Module.new and then module_evals the block given to it in that module. It then gives Adhearsion's ComponentMethodScopeManager the Module and tells it which Symbols the particular Module is for.

Enabling/Disabling components

You can run the following command:

ahn enable component <ComponentName>
which will simply move your component to the components/disabled folder, creating it if it doesn't exist.

Splitting a Component .rb File

Sometimes you will want to breakdown your 'component.rb' file into pieces to make them more manageable. You may not simply split them up and do a require, but you must use the Component subsystem of Adhearsion to load them. So, for example, if you wanted to break up your component into files grouped by each type of 'methods_for' you could do something like this:

Components.component_manager.load_file File.expand_path(File.dirname(__FILE__) + "/lib/dialplan.rb")
Components.component_manager.load_file File.expand_path(File.dirname(__FILE__) + "/lib/events.rb")
Components.component_manager.load_file File.expand_path(File.dirname(__FILE__) + "/lib/global.rb")
Components.component_manager.load_file File.expand_path(File.dirname(__FILE__) + "/lib/rpc.rb")

Browse Space

- Pages
- News
- Labels
- Attachments
- Bookmarks
- Mail
- Advanced
- Activity

Explore Confluence

- Popular Labels
- Notation Guide

Your Account

Log In

or Sign Up  

Other Features

Add Content