Джим Меггелен - Asterisk™: будущее телефонии Второе издание
• Если необходимо воспроизводить несколько файлов последовательно, play() принимает массив имен файлов. К счастью, в Ruby есть удобный способ создания массива строковых значений (String).
Конечно, это только простой пример, демонстрирующий лишь самые основы возможностей Adhearsion для создания диалплана.
Интеграция с базами данных
Несмотря на чрезвычайный успех в области веб-разработки для обслуживания динамического содержимого, интеграция с базами данных всегда была и остается недостаточно реализованной возможностью управления динамическими голосовыми приложениями в Asterisk. Большинство приложений Asterisk, выполняющих интеграцию с базами данных, делегируют реализацию сложных вопросов AGI-сценариям на
PHP или Perl, потому что extensions.conf или синтаксиса AEL просто недостаточно для решения задач такого уровня сложности. Adhearsion использует библиотеку интеграции с базами данных ActiveRecord, разработанную создателями инфраструктуры Ruby on Rails. Имея ActiveRecord, конечный пользователь изредка, если вообще делает это, пишет SQL-выражения. А разработчик осуществляет доступ к базе данных, как к любому другому объекту Ruby. Благодаря обеспечиваемым Ruby гибкости и динамичности, доступ к базе данных выглядит и ощущается довольно естественным. Кроме того, ActiveRe- cord устраняет различия между системами управления базами данных, делая реализацию доступа к базе данных универсальной. Не вдаваясь в детали ActiveRecord и более сложные варианты ее использования, рассмотрим следующую простую схему MySQL: CREATE TABLE groups (
'id' int(11) DEFAULT NULL auto_increment PRIMARY KEY, 'description' varchar(255) DEFAULT NULL, 'hourly_rate' decimal DEFAULT NULL
);
CREATE TABLE customers (
'id' int(11) DEFAULT NULL auto_increment PRIMARY KEY, 'name' varchar(255) DEFAULT NULL, 'phone_number' varchar(10) DEFAULT NULL, 'usage_this_month' int(11) DEFAULT 0, 'group_id' int(11) DEFAULT NULL
);
В реальности, конечно, о заказчике хранилось бы намного больше сведений и информация об использовании сервиса находилась бы в записи параметров вызова в базе данных, но такое упрощение позволяет более эффективно продемонстрировать основные моменты ActiveRecord. Чтобы подключить Adhearsion к этой базе данных, необходимо просто задать информацию для доступа к базе данных в конфигурационном файле YAML: adapter: mysql host: localhost database: adhearsion username: root password: pass
Так Adhearsion будет знать, как подключиться к базе данных, однако механизм доступа к информации в таблицах зависит от того, как смоделированы наши объекты ActiveRecord. Поскольку объект - это экземпляр класса, для каждой таблицы ставим в соответствие класс. С помощью методов надкласса определяем простые свойства и отношения в классе.
Вот два класса, которые могут использоваться с вышеупомянутыми таблицами:
class Customer < ActiveRecord::Base belongs_to :group
validates_presence_of :name, :phone_number validates_uniqueness_of :phone_number validates_associated :group def total_bill
self.group.hourly_rate * self.usage_this_month / 1.hour end
end
class Group < ActiveRecord::Base has_many :customers
validates_presence_of description, :hourly_rate
end
Уже из этого небольшого объема информации ActiveRecord может сделать множество логических выводов. При обработке данных классов ActiveRecord переводит их имена в нижний регистр, ставит во множественное число и принимает, что это - имена таблиц (customers и groups соответственно). Если применение такого соглашения нежелательно, автор может без труда переопределить его. Кроме того, во время интерпретации ActiveRecord на самом деле заглядывает в столбцы базы данных и делает доступными многие новые создаваемые динамически методы.
Методы belongs_to (принадлежит) и has_many (имеет много) в данном примере определяют отношения между Customers (клиенты) и Groups (группы). Опять обратите внимание, как ActiveRecord использует множественное число в строке has_many :customers для большей выразительности кода. В этом примере также можно увидеть несколько проверок достоверности - политик, которые будет применять ActiveRecord. При создании нового объекта Customer мы должны обеспечить для него как минимум name (имя) и phone_number (номер телефона). Задание двух телефонных номеров может привести к конфликту. У каждого Customer должна быть Group. У каждой группы должно быть description (описание) и hourly_rate (почасовая ставка). Это поможет разработчику избежать ошибок, а также нарушения целостности базы данных.
Также обратите внимание на метод total_bill (общий счет) класса Customer. Для любого объекта Customer, извлекаемого из базы данных, можно вызвать этот метод, который умножает значение hourly_rate для группы, к которой принадлежит Customer, на время пользования телефоном этого клиента (в секундах).
Вот несколько примеров, которые могут точнее продемонстрировать то, насколько удобно применять абстрактную объектную логику Ruby для работы с базами данных:
everyone = Customer.find :all
jay = Customer.find_by_name "Jay Phillips"
jay.phone_number # Выполняем выражение SELECT
jay.total_bill # Выполняем вычисления по нескольким выражениям SELECT
jay.group.customers.average :usage_this_month jay.group.destroy
jay.group = Group.create description => "New cool group!",
:hourly_rate => 1.23
jay.save
Интеграция с базой данных стала здесь намного более естественной, а диалпланы Asterisk выглядят более выразительными. Ниже представлен пример диалплана поставщика сервисов, который налагает ограничение на суммарную продолжительность исходящих звонков, используя информацию из базы данных. Постараемся сохранить простоту:
# Предположим, сервис VoIP предлагается клиентам,
# которые могут быть идентифицированы по их callerid.
service {
# Строка кода ниже реализует выражение SQL SELECT
# по отношению к нашей базе данных. Метод
# find_by_phone_number() был создан автоматически,
# потому что ActiveRecord обнаружила в базе данных
# столбец phone_number. Adhearsion создает для нас
# переменную callerid.
caller = Customer.find_by_phone_number callerid
usage = caller.usage_this_month if usage >= 100.hours
play "sorry-cant-let-you-do-that" else
play %w'to-hear-your-account-balance press-1
otherwise wait-moment' choice = wait_for_digit 3.seconds
p choice if choice == 1
charge = usage / 60.0 * caller.group.hourly_rate play %W"your-account will-reflect-charge-of
$#{charge} this month for #{usage / 60} minutes and #{usage % 60} seconds"
end
# Мы также можем записать значение свойства
# usage_this_month объекта caller. По завершении
# выполнения метода time новое значение для этого
# абонента будет внесено в базу данных. caller.usage_this_month += time do
# Засекаем время выполнения данного фрагмента кода. dial IAX/'main-trunk'/extension
end
caller.save
end
Надежная интеграция с базой данных, которую обеспечивает Adhear- sion, упрощает управление и разработку для офисной АТС. Хранящаяся централизованно информация позволяет Asterisk напрямую интегрироваться с другими сервисами, обеспечивая при этом более ценные сервисы, которые не могут быть реализованы в рамках традиционных технологий разработки в Asterisk.
Распространение и повторное использование кода
Приложение Adhearsion располагается в одной папке, поэтому полностью скопировать VoIP-приложение - не сложнее, чем архивировать файлы. Наверное, впервые в сообществе Asterisk разработчики могут запросто обмениваться друг с другом удачными приложениями и дорабатывать их. Кстати, весьма поощряется предоставление кода собственных приложений Adhearsion.
Кроме того, на локальном уровне расширения инфраструктуры Adhear- sion, называемые помощниками, могут использоваться повторно или создаваться самостоятельно. Помощниками могут быть как целые вспомогательные инфраструктуры, такие как Micromenus для интеграции с телефонными микроброузерами, так и обычный новый метод диал- плана, который возвращает выбираемые случайным образом цитаты Оскара Уайльда.
Ниже представлен простой помощник Adhearsion, написанный на Ruby. Он создает новый метод, который будет существовать во всей инфраструктуре, включая диалплан. В целях сохранения простоты метод загружает XML-документ по заданному URL HTTP и преобразует его в Ruby-объект Hash (тип ассоциативного массива Ruby): def remote_parse url
Hash.from_xml open(url).read
end
Заметьте, что вспомогательный файл может включать только эти три строки. При загрузке Adhearsion выполняет сценарий таким образом, что все описанные методы или классы становятся доступными во всей системе.
Для некоторых задач, в частности задач масштабирования Adhearsion, может потребоваться обеспечить в узких местах эффективность короля производительности: языка программирования С. Ниже представлен пример помощника Adhearsion, который возвращает факториал заданного числа:
int fast_factorial(int input) { int fact = 1, count = 1; while(count <= input) { fact *= count++;
}
return fact;
Опять же, приведенный здесь код может составлять все содержимое вспомогательного файла. В данном случае, поскольку код написан на С, файл должен называться factorial.alien.c. Это указывает Adhearsion запустить алгоритм для чтения файла, добавить стандартные заголовки разработки языков С и Ruby, скомпилировать файл, кэшировать общий объект, загрузить его в интерпретатор и затем создать для С-ме- тода оболочку на Ruby. Ниже представлен диалплан, который просто воспроизводит факториал шести, используя этот помощник на С:
fast_test {
num = fast_factorial 6 play num
}
Заметьте, что С-метод становится первоклассным методом на Ruby. Числовые объекты Ruby, преданные в метод, преобразуются в элементарный тип С int, а затем возвращаемое значение преобразуется обратно в числовой объект Ruby.