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:
- When someone wishes to simply add a new dialplan method, it's unnecessary to encapsulate that into an object
- When forcing everyone to instantiate classes that only exist as a framework requirement, the chosen variables names to which these objects are assigned are meaningless and don't carry any additional meaning over the function name. For example: HelpCommand.help
- When exposing components to the world via distributed computing, custom objects are not really an option
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
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
- :dialplan
- :events
- :generators (note: this is not used yet.)
- :rpc - Remote Procedure call
- :global
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:
- The scopes of these methods will be very different. Methods may appear to be the same but actually be completely different.
- For example, if you have a method shared in both :dialplan and :rpc named :foo and it invokes the hangup method, it must .
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
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
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
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
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:
- components/killer_tofu/killer_tofu.yml
- components/killer_tofu/killer_tofu.rb
Note: a more sophisticated "killer_tofu" component may have the following structure:
- components/killer_tofu/killer_tofu.yml
- components/killer_tofu/killer_tofu.rb
- components/killer_tofu/tofu_eater.rb
- components/killer_tofu/spec/killer_tofu_spec.rb
- components/killer_tofu/spec/tofu_spec.rb
- components/killer_tofu/spec/lethality_spec.rb
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:
- Namespace
- Logging level
Both of these are optional. Here is an example of omitting both of these.
ahn_log "Testing 123"
ahn_log.error "Warning Will Robinson!"
ahn_log.kewlz0rz_stuff "Starting kewlz0rz stuff"
ahn_log.kewlz0rz_stuff.error "Couldn't start kewlz0rz_stuff!"
ahn_log.lolcats.icanhascheezburger.warn "TONIGHT WE NOM IN HELL!"
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?
- global
- should_log_with_message_matching(regexp)
- :dialplan
- call - Instance of a Call
- :events
- Causality Specs. Concurrency-based specs
- :generators
- should execute_shell_command()
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>
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")