<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	>

<channel>
	<title>Alno's Blog: C++, Java and Rails</title>
	<link>http://blog.alno.name</link>
	<description>Заметки о разрaботке на C++, Java и Rails</description>

	<pubDate>Thu, 21 Jan 2010 11:03:31 GMT</pubDate>
	<generator>http://radiantcms.org/</generator>
	<language>ru</language>


	<atom:link href="http://blog.alno.name/feed/" rel="self" type="application/rss+xml" />

<item>
  <title>Разработка приложений с Akonadi: добавление задач в календарь</title>
  <link>http://blog.alno.name/ru/2010/01/creating-tasks-with-akonadi/</link>
  <comments>http://blog.alno.name/ru/2010/01/creating-tasks-with-akonadi/#comments</comments>
  <pubDate>Thu, 21 Jan 2010 11:03:31 GMT</pubDate>
  <dc:creator>Alno</dc:creator>
  <guid isPermaLink="true">http://blog.alno.name/ru/2010/01/creating-tasks-with-akonadi/</guid>
  <description><![CDATA[<p>Недавно, благодаря хорошей идее на <span class="caps">KDE</span> Brainstorm я создал <a href="http://alno.name/projects/events-plasma-runner/">плагин для <span class="caps">KDE</span> Plasma Runner, позволяющий быстро добавлять задачи и события</a> в календарь, исходный код которого <a href="http://github.com/alno/plasma-runner-events">доступен на GitHub</a>.</p>
<p>Сегодня я хотел бы поделиться опытом создания, а конкретно рассмотреть тему написания приложений, использующих Akonadi.</p>
<p>В качестве примера я рассмотрю простое консольное приложение, которое позволяет добавлять задачи в календарь. Почему консольное приложение? Во-первых, чтобы не отвлекаться на аспекты, не имеющие прямого отношения к Akonadi. Во-вторых, чтобы</p>]]></description>
  <content:encoded><![CDATA[
    <p>Недавно, благодаря хорошей идее на <span class="caps">KDE</span> Brainstorm я создал <a href="http://alno.name/projects/events-plasma-runner/">плагин для <span class="caps">KDE</span> Plasma Runner, позволяющий быстро добавлять задачи и события</a> в календарь, исходный код которого <a href="http://github.com/alno/plasma-runner-events">доступен на GitHub</a>.</p>
<p>Сегодня я хотел бы поделиться опытом создания, а конкретно рассмотреть тему написания приложений, использующих Akonadi.</p>
<p>В качестве примера я рассмотрю простое консольное приложение, которое позволяет добавлять задачи в календарь. Почему консольное приложение? Во-первых, чтобы не отвлекаться на аспекты, не имеющие прямого отношения к Akonadi. Во-вторых, чтобы</p><h2>Требования</h2>

<p>Я использую Ubuntu 9.10 Karmic, в нем для работы необходимо наличие следующих пакетов:</p>
<ul>
<li><b>kdelibs5-dev</b> - библиотеки KDE</li>
<li><b>kdepimlibs5-dev</b> - библиотеки PIM KDE</li>
<li><b>libboost-dev</b> - Boost</li>
</ul>

<p>Соответственно:</p>
<pre><code class="bash">
sudo aptitude install kdelibs5-dev kdepimlibs5-dev libboost-dev
</code></pre>

<h2>Каркас приложения</h2>

<p>Итак, приступим к созданию такого приложения. Назовем его, например, addtodo. Для начала в директории будущего приложения создадим файлы для исходников:</p>

<p><b>CMakeLists.txt</b>, файл для конфигурации и сборки:</p>
<pre><code class="bash">
PROJECT(add-todo)

find_package(KDE4 REQUIRED) # Находим модули KDE4
find_package(KdepimLibs REQUIRED) # Находим модули KDE PIM

include(KDE4Defaults)

add_definitions (${QT_DEFINITIONS} ${KDE4_DEFINITIONS})
include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDEPIMLIBS_INCLUDE_DIR} ${KDE4_INCLUDES})

set(CMAKE_CXX_FLAGS &quot;-fexceptions&quot;)

kde4_add_executable(add-todo add_todo.cpp) # Добавляем цель

target_link_libraries(add-todo ${KDE4_PLASMA_LIBS} ${KDE4_KDEUI_LIBS} ${KDE4_AKONADI_LIBS} ${KDEPIMLIBS_KCAL_LIBS}) # Добавляем соответствующие библиотеки
</code></pre>

<p><b>add_todo_app.h</b>, заголовочный файл:</p>
<pre><code class="cpp">
#ifndef ADD_TODO_H
#define ADD_TODO_H

#include &lt;QCoreApplication&gt;

#include &lt;KJob&gt;

class AddTodo : public QCoreApplication {
    Q_OBJECT
public:
    AddTodo( int argc, char ** argv );
public slots:
    void collectionsFetched( KJob * job ); // Будет вызван, когда мы получим список коллекций
    void todoCreated( KJob * job ); // Будет вызван, когда мы создадим задачу
};

#endif // ADD_TODO_H
</code></pre>

<p><b>add_todo_app.cpp</b>, здесь будет содержаться основной код:</p>
<pre><code class="cpp">
#include &quot;add_todo.h&quot;

#include &lt;QTextStream&gt;

static QTextStream out( stdout ); // Поток для вывода данных

AddTodo::AddTodo( int argc, char ** argv ) : QCoreApplication( argc, argv ) {
    out &lt;&lt; &quot;Application started&quot; &lt;&lt; endl;
}

void AddTodo::collectionsFetched( KJob * job ) {
}

void AddTodo::todoCreated( KJob * job ) {
}

int main( int argc, char ** argv ) {
    AddTodo app( argc, argv ); // Создаем экземпляр приложения
    
    return app.exec(); // И входим в цикл обработки сигналов
}
</code></pre>

<p>Теперь можно проверить, что наше пока еще ничего не делающее приложение корректно собирается:</p>
<pre><code class="bash">
mkdir build
cd build
cmake ..
make
</code></pre>

<p>Если мы запустим приложение, то оно напишет "Application started" и уйдет в бесконечный цикл ожидания сигналов. Пускай, теперь будем добавлять полезную работу.</p>

<h2>Получения списка коллекций через Akonadi</h2>

<p>Для того, чтобы создать задачу через Akonadi, необходимо сначала получить ссылку на <b>коллекцию</b> (<a rel="nofollow" href="http://api.kde.org/4.x-api/kdepimlibs-apidocs/akonadi/html/classAkonadi_1_1Collection.html">Akonadi::Collection</a>), в которой мы будем ее создавать. Для этого мы получим все коллекции и выберем ту, которая поддерживает подходящий тип элементов. Получение коллекции в Akonadi осуществляется путем создания задачи <a rel="nofollow" href="http://api.kde.org/4.x-api/kdepimlibs-apidocs/akonadi/html/classAkonadi_1_1CollectionFetchJob.html">Akonadi::CollectionFetchJob</a>.</p>

<p>В начало add_todo.cpp добавим инклуды, импорт пространства имен Akonadi и объявим одну константу, в которой будет записан MIME-тип для задачи. Он необходим нам для того, чтобы выбрать подходящую задачу.</p>
<pre><code class="cpp">
#include &lt;QStringList&gt;

#include &lt;Akonadi/Collection&gt;
#include &lt;Akonadi/CollectionFetchJob&gt;

using namespace Akonadi;

static QString todoMimeType( &quot;text/calendar&quot; ); // MIME-тип задачи
</code></pre>

<p>В конструктор приложения добавляем:</p>
<pre><code class="cpp">
    CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::FirstLevel, this ); // Создаем задачу
    
    connect( job, SIGNAL(result(KJob*)), this, SLOT(collectionsFetched(KJob*)) ); // Связываем сигнал и слот
</code></pre>

<p>В первой строке аргументы означают то, что мы получаем подколлекции корневой коллекции, причем только первого уровня.</p>

<p>В метод <b>collectionsFetched</b> добавляем код обработки и выбора нужной нам коллекции:</p>
<pre><code class="cpp">
    out &lt;&lt; &quot;Collections fetched&quot; &lt;&lt; endl;
    
    if ( job-&gt;error() ) {
        out &lt;&lt; &quot;Error occurred: &quot; &lt;&lt; job-&gt;errorText() &lt;&lt; endl;
        exit( -1 );
        return;
    }

    const CollectionFetchJob * fetchJob = qobject_cast&lt;CollectionFetchJob*&gt;( job ); // Приводим задачу к нужному типу
    
    const Collection * selectedCollection = 0; // Переменная для выбранной коллекции
    
    foreach( const Collection &amp; collection, fetchJob-&gt;collections() ) {
        out &lt;&lt; &quot;Name: &quot; &lt;&lt; collection.name(); // Печатаем имя коллекции для отладки
        
        if ( collection.contentMimeTypes().contains( todoMimeType ) ) { // Проверяем, принимает ли коллекция нужный тип данных
            selectedCollection = &amp;collection;
            break;
        }
    }
    
    if ( !selectedCollection ) { // Если не нашли подходящей коллекции, то печатаем ошибку и выходим
        out &lt;&lt; &quot;Error occurred: no valid collection found&quot;&lt;&lt; endl;
        exit( -1 );
        return;
    }
    
    // А здесь будем создавать задачу
</code></pre>

<h2>Создание задачи</h2>

<p>Теперь у нас есть коллекция, в которую можно наконец добавить задачу. Для этого, необходимо сделать три вещи:</p>

<ul>
<li>Создать объект <a rel="nofollow" href="http://api.kde.org/4.x-api/kdepimlibs-apidocs/kcal/html/classKCal_1_1Todo.html">KCal::Todo</a>, описывающий нашу задачу</li>
<li>Создать объект <a rel="nofollow" href="http://api.kde.org/4.x-api/kdepimlibs-apidocs/akonadi/html/classAkonadi_1_1Item.html">Akonadi::Item</a>, представляющий элемент данных в Akonadi</li>
<li>Создать задачу создания нового элемента</li>
<li>Обработать ее результат</li>
</ul>

<p>Итак, приступим. Сначала подключим заголовочные файлы:</p>
<pre><code class="cpp">
#include &lt;Akonadi/Item&gt;
#include &lt;Akonadi/ItemCreateJob&gt;

#include &lt;kcal/todo.h&gt;

#include &lt;boost/shared_ptr.hpp&gt;
</code></pre>

<p>Теперь напишем код для первых трех пунктов в конце метода <b>collectionsFetched</b>:</p>
<pre><code class="cpp">
    KDateTime dueDate = KDateTime::fromString( arguments()[2], &quot;%d.%m.%Y&quot; ); // Парсим дату

    if ( !dueDate.isValid() ) { // Проверяем, что дата распарсилась
        out &lt;&lt; &quot;Error occured: invalid date '&quot; &lt;&lt; arguments()[2] &lt;&lt; &quot;'&quot; &lt;&lt; endl;
        exit( -2 );
    }

    KCal::Todo::Ptr todo( new KCal::Todo() );
    todo-&gt;setSummary( arguments()[1] ); // Текст
    todo-&gt;setDtDue( dueDate ); // Дата
    todo-&gt;setPercentComplete( 0 ); // Пока не выполнена
    todo-&gt;setHasStartDate( false ); // Начальная дата не установлена
    todo-&gt;setHasDueDate( true ); // Установлена дата выполнения

    Item item( todoMimeType );
    item.setPayload&lt;KCal::Todo::Ptr&gt;( todo );

    ItemCreateJob * itemCreateJob = new ItemCreateJob( item, *selectedCollection, this ); // Создаем задачу

    connect( itemCreateJob, SIGNAL(result(KJob*)), this, SLOT(todoCreated(KJob*)) ); // Связываем сигнал и слот
</code></pre>

<p>А в метод <b>todoCreated</b> добавим проверку:</p>
<pre><code class="cpp">
    if ( job-&gt;error() ) {
        out &lt;&lt; &quot;Error occurred: &quot; &lt;&lt; job-&gt;errorText() &lt;&lt; endl;
        exit( -1 );
        return;
    }

    out &lt;&lt; &quot;TODO created&quot; &lt;&lt; endl;
    quit();
</code></pre>

<p>Также, неплохо добавить в начало <b>main</b> проверку количества аргументов:</p>
<pre><code class="cpp">
    if ( argc &lt; 3 ) { // Проверяем количество аргументов
        out &lt;&lt; &quot;Usage: add-todo [text] [date]&quot; &lt;&lt; endl;
        return -2;
    }
</code></pre>

<p>Все, теперь программа завершена, можно скомпилировать ее и запустить следующим образом:</p>

<pre><code class="bash">
./add-todo &quot;Something&quot; 21.01.2010
</code></pre>

<p>После выполнения у вас в календаре должна появиться новая задача. Теперь можно что-то улучшать, например, добавить возможность распознавания ссылок на даты вида "today", "tomorrow", поддержку времени, категорий и много чего интересного...</p>

<p>Полный код <a href="http://gist.github.com/282046">можно посмотреть по ссылке</a>. </p>

<p>Код примеров построен на основе плагина для Plasma Runner, который можно найти <a href="http://www.kde-apps.org/content/show.php?action=content&content=118854">здесь</a> и <a href="http://github.com/alno/plasma-runner-events">здесь</a>.</p>
      

      <div id="links">
        <h2>Ссылки</h2>
        <ul>
<li><a rel="nofollow" href="http://api.kde.org/4.x-api/kdepimlibs-apidocs/akonadi/html/index.html">Akonadi API (En)</a></li>
<li><a rel="nofollow" href="http://techbase.kde.org/Development/Tutorials/Akonadi/Application">Tutorial: Akonadi Application (En)</a></li>
<li><a rel="nofollow" href="http://techbase.kde.org/Development/Tutorials/Plasma/AbstractRunner">Tutorial: Runner (En)</a></li>
</ul>
      </div>
  ]]></content:encoded>
</item><item>
  <title>Организация взаимодействия Java-приложений с помощью JGroups</title>
  <link>http://blog.alno.name/ru/2009/11/jgroups/</link>
  <comments>http://blog.alno.name/ru/2009/11/jgroups/#comments</comments>
  <pubDate>Sun, 08 Nov 2009 19:31:18 GMT</pubDate>
  <dc:creator>Alno</dc:creator>
  <guid isPermaLink="true">http://blog.alno.name/ru/2009/11/jgroups/</guid>
  <description><![CDATA[<p>Сегодня я хочу рассказать о <a href="http://jgroups.org/">JGroups</a>. Это Java-библиотека для организации группового взаимодействия между различными процессами Java. Приложения, использующие JGroups могут:
<ul>
  <li>Создавать и уничтожать группы</li>
  <li>Присоединяться к группам и покидать их</li>
  <li>Получать оповещения о новых членах групп</li>
  <li>Отправлять сообщения конкретному процессу или всем процессам группы</li>
</ul>
Библиотека достаточно широко используется, в частности в сервере приложений JBoss, в кэше OSCache и в Grid-платформе Infinispan.
</p>

<p>Здесь я ограничусь начальной информацией и опишу создание простого группового чата на Java.</p>]]></description>
  <content:encoded><![CDATA[
    <p>Сегодня я хочу рассказать о <a href="http://jgroups.org/">JGroups</a>. Это Java-библиотека для организации группового взаимодействия между различными процессами Java. Приложения, использующие JGroups могут:
<ul>
  <li>Создавать и уничтожать группы</li>
  <li>Присоединяться к группам и покидать их</li>
  <li>Получать оповещения о новых членах групп</li>
  <li>Отправлять сообщения конкретному процессу или всем процессам группы</li>
</ul>
Библиотека достаточно широко используется, в частности в сервере приложений JBoss, в кэше OSCache и в Grid-платформе Infinispan.
</p>

<p>Здесь я ограничусь начальной информацией и опишу создание простого группового чата на Java.</p><h2>Начало работы</h2>

<p>Итак, для начала надо скачать JGroups. Сделать это можно здесь: <a href="http://sourceforge.net/projects/javagroups/files/">http://sourceforge.net/projects/javagroups/files/</a>. При написании этой статьи я использовал версию <b>2.6.13.GA</b>. Также, для работы требуется Apache Commons Logging, или что-то его заменяющее (например, jcl-over-slf4j). Скачать можно здесь: <a href="http://commons.apache.org/downloads/download_logging.cgi">http://commons.apache.org/downloads/download_logging.cgi</a>.</p>

<p>Если же Вы используете Maven, то можно добавить репозиторий JBoss:

<pre><code class="xml">
  &lt;repository&gt;
    &lt;id&gt;jboss&lt;/id&gt;
    &lt;name&gt;jboss&lt;/name&gt;
    &lt;url&gt;http://repository.jboss.com/maven2&lt;/url&gt;
    &lt;snapshots&gt;
      &lt;enabled&gt;false&lt;/enabled&gt;
    &lt;/snapshots&gt;
  &lt;/repository&gt;
</code></pre>

и добавить в зависимости:

<pre><code class="xml">
  &lt;dependency&gt;
    &lt;groupId&gt;jgroups&lt;/groupId&gt;
    &lt;artifactId&gt;jgroups&lt;/artifactId&gt;
    &lt;version&gt;2.6.13.GA&lt;/version&gt;
  &lt;/dependency&gt;

  &lt;dependency&gt;
    &lt;groupId&gt;commons-logging&lt;/groupId&gt;
    &lt;artifactId&gt;commons-logging&lt;/artifactId&gt;
    &lt;version&gt;1.1.1&lt;/version&gt;
  &lt;/dependency&gt;
</code></pre>
</p>

<p>
Возможно, необходимо настроить сетевой интерфейс для работы с мультикастом. В Linux, для этого необходимо добавить соответствующий роут:

<pre><code class="bash">route add -net 224.0.0.0 netmask 240.0.0.0 dev lo</code></pre>
</p>

<h2>Иницализация</h2>

<p>
Для того,чтобы создать канал необходимо создать объект класса <tt><a href="http://jgroups.org/javadoc/org/jgroups/JChannel.html">JChannel</a></tt>, передав ему в конструктор параметры конфигурации. В примере в конфигурации я указываю адрес, который должен использоваться для канала. Полный список опций можно посмотреть <a href="http://www.jgroups.org/manual/html/protlist.html">в документации</a>.

<pre><code class="java">JChannel channel = new JChannel( &quot;UDP(bind_addr=127.0.0.1)&quot; );</code></pre>

Теперь можно подключаться к кластеру вызовом <tt>JChannel#connect()</tt>. В качестве параметра в него передается имя группы, можете выбрать любое, которое Вам нравится.

<pre><code class="java">channel.connect( &quot;MyCluster&quot; );</code></pre>
</p>

<h2>Отправка сообщений</h2>

<p>
Сообщения в JGroups представлены классом <tt><a href="http://jgroups.org/javadoc/org/jgroups/Message.html">Message</a></tt>, который содержит адрес отправителя, адрес получателя и данные. Адреса представлены объектами класса <tt><a href="http://jgroups.org/javadoc/org/jgroups/Address.html">Address</a></tt>, а данные - это любые сериализуемые объекты или просто массив байт. Например, чтобы создать сообщение для всей группой, содержащее строку можно написать:

<pre><code class="java">new Message( null, null, &quot;Some content&quot; )</code></pre>

Здесь первый параметр - получатель, затем - отправитель, а затем - содержимое.
</p>

<p>
Чтобы отправить сообщение необходимо вызвать метод <b>JChannel#send</b> и передать ему сообщение в качестве параметра. Например:

<pre><code class="java">channel.send( new Message( null, null, &quot;Some content&quot; ) )</code></pre>
</p>

<h2>Обработка сообщений</h2>

<p>Теперь необходимо написать код, для обработки сообщений, приходящих приложению. Для этого необходимо вызвать метод <tt>JChannel#setReceiver</tt>, передав в качестве параметра объект, реализующий интерфейс <tt><a href="http://jgroups.org/javadoc/org/jgroups/Receiver.html">Receiver</a></tt>. Например, чтобы просто выводить все полученные сообщения на печать достаточно написать следующий код:

<pre><code class="java">
channel.setReceiver( new ReceiverAdapter(){

	@Override
	public void receive( Message m ) {
		System.out.println( m.getObject() );
	}

} );
</code></pre>

Здесь, чтобы уменьшить объем кода, объект наследуется от класса <tt>ReceiverAdapter</tt>, который предоставляет пустые реализации различных методов интерфейса <tt>Receiver</tt>. Как видно из примера, для обработки сообщения используется метод <tt>receive</tt>, в который сообщение передается в качестве параметра.
</p>

<h2>Простейший чат</h2>

<p>Теперь можно собрать простейший чат. Он будет рассылать группе строки, принятые с консоли и печатать полученные строки на консоль. Пример код:</p>

<pre><code class="java">
import java.io.BufferedReader;
import java.io.InputStreamReader;

import org.jgroups.JChannel;
import org.jgroups.Message;
import org.jgroups.ReceiverAdapter;

public class SimplestChat {

	public static void main( String[] args ) throws Exception {
		JChannel channel = new JChannel( &quot;UDP(bind_addr=127.0.0.1)&quot; );
		channel.setReceiver( new ReceiverAdapter() {

			@Override
			public void receive( Message m ) {
				System.out.println( m.getObject() );
			}

		} );
		channel.connect( &quot;MyCluster&quot; ); // Подключаемся к группе

		/**
		 * Цикл обработки команд с консоли
		 */
		BufferedReader in = new BufferedReader( new InputStreamReader( System.in ) );
		while ( true ) {

			String line = in.readLine();

			if ( line.equalsIgnoreCase( &quot;quit&quot; ) || line.equalsIgnoreCase( &quot;exit&quot; ) ) {
				break;
			}

			channel.send( new Message( null, null, line ) );
		}

		channel.close(); // Отключаемся от группы по завершению работы
	}

}
</code></pre>

<p>При запуске этот код должен выводить что-нибудь вроде:</p>

<pre>
  Nov 8, 2009 4:18:27 PM org.jgroups.JChannel init
  INFO: JGroups version: 2.6.13.GA
</pre>

<h2>Что дальше?</h2>

<p>
Здесь я рассмотрел только создание канала и отправку сообщений группе. В стороне остались достаточно много моментов: адресация, сообщения конкретному процессу и, главное, управление группой. Желающие ознакомиться с ними могут заглянуть в документацию JGroups: <a href="http://jgroups.org/ug.html">http://jgroups.org/ug.html</a>.
</p>
      

      <div id="links">
        <h2>Ссылки</h2>
        <ul>
	<li><a href="http://jgroups.org/" title="En">Сайт проекта</a></li>
	<li><a href="http://jgroups.org/ug.html" title="En">Руководство пользователя</a></li>
</ul>
      </div>
  ]]></content:encoded>
</item><item>
  <title>Irwi - Wiki в Rails-приложениях</title>
  <link>http://blog.alno.name/ru/2009/08/irwi-plugin/</link>
  <comments>http://blog.alno.name/ru/2009/08/irwi-plugin/#comments</comments>
  <pubDate>Fri, 28 Aug 2009 08:43:27 GMT</pubDate>
  <dc:creator>Alno</dc:creator>
  <guid isPermaLink="true">http://blog.alno.name/ru/2009/08/irwi-plugin/</guid>
  <description><![CDATA[<p>
Сегодня я хочу представить свой Rails-плагин, позволяющий добавить в приложение функциональность вики (не просто расширение моделей, а полноценную вики которая бы <b>сразу работала</b>)
</p>

<p>
Я обнаружил то, что для такой, казалось бы, стандартной задачи в Rails нет полноценного готового решения, которое бы легко интегрировалось с существующим приложением. В связи с этим был написан свой. 
</p>

<p>
Основными критериями при разработке были:
</p>
<ul>
  <li>Возможность быстрой интеграции в приложение.</li>
  <li>Хорошая расширяемость.</li>
  <li>Отсутствие чужого кода (в смысле кода плагина) в приложении, к чему часто приводит использование генераторов. В этом смысле я пытался равняться на Authlogic.</li>
</ul>

<p>
То, что получилось представляет из себя что-то среднее между генератором (что обеспечивает хорошую расширяемость и модифицируемость) и engine'ом (чтобы можно было легко обновлять его).
</p>

]]></description>
  <content:encoded><![CDATA[
    <p>
Сегодня я хочу представить свой Rails-плагин, позволяющий добавить в приложение функциональность вики (не просто расширение моделей, а полноценную вики которая бы <b>сразу работала</b>)
</p>

<p>
Я обнаружил то, что для такой, казалось бы, стандартной задачи в Rails нет полноценного готового решения, которое бы легко интегрировалось с существующим приложением. В связи с этим был написан свой. 
</p>

<p>
Основными критериями при разработке были:
</p>
<ul>
  <li>Возможность быстрой интеграции в приложение.</li>
  <li>Хорошая расширяемость.</li>
  <li>Отсутствие чужого кода (в смысле кода плагина) в приложении, к чему часто приводит использование генераторов. В этом смысле я пытался равняться на Authlogic.</li>
</ul>

<p>
То, что получилось представляет из себя что-то среднее между генератором (что обеспечивает хорошую расширяемость и модифицируемость) и engine'ом (чтобы можно было легко обновлять его).
</p>

<h2>Установка и использование</h2>

<p>
Для работы плагина (в частности истории страниц) необходимо наличие гема <tt>diff-lcs</tt>. Для форматирования по умолчанию используется <tt>RedCloth</tt>, однако можно выбрать и другой форматтер. Таким образом:
</p>

<pre><code class="bash">
gem install diff-lcs RedCloth
</code></pre>

<p>
Для установки плагина достаточно выполнить в директории Rails-приложения:
</p>

<pre><code class="bash">
script/plugin install git://github.com/alno/irwi
</code></pre>

<p>
После установки необходимо сгенерировать миграцию, моделиь и контроллер... <b>Стойте, это вовсе не значит, что у вас в приложении появится куча чужого кода, который непонятно как поддерживать!</b> Контроллер и модель содержат всего по одной строке вида <tt>acts_as_*</tt> и генерируется для того, чтобы в последствии Вам было бы проще расширять функциональность. Вся собственная функциональность вики реализуется в файлах плагина, что позволяет безболезненно обновлять ее.
</p>

<p>
Итак, чтобы сгенерировать необходимые файлы необходимо вызвать соответсвующий генератор:
</p>

<pre><code class="bash">
script/generate irwi_wiki
</code></pre>

<p>
После вызова генератора в приложение будут произведены следующие изменения:
</p>

<ul>
  <li>Добавлен WikiPageController и соответствующий хелпер для обработки страниц</li>
  <li>Добавлены модели WikiPage и WikiPageVersion для представления страниц</li>
  <li>Будет сгенерирована миграция, создающая таблицы в БД</li>
</ul>

<p>
Также в роуты будет добавлена следующая строка:
</p>

<pre><code class="ruby">
  map.wiki_root '/wiki'
</code></pre>

<p>
Как несложно догадаться, в ней указывается корень wiki в вашем приложении. Вы можете легко его изменить на тот, который Вам больше нравится.
</p>

<p>
Все, работающая вики в вашем приложении сгенерирована. В принципе, можно уже использовать ее, но наверное вам все-таки хотелось бы изменить некоторые ее аспекты под свое приложение, например, изменить шаблоны, используемые для отображения или привязать ее к своей системе пользователей и ограничить права на редактирование страниц. Об этом и пойдет речь далее. 
</p>

<h2>Изменение внешнего вида</h2>

<p>
Изменение внешнего вида можно производить двумя путями:
</p>

<ul>
  <li>Замена шаблонов</li>
  <li>Замена стилей в дефолтных шаблонах</li>
</ul>

<p>
Для замены какого-то шаблона (включая partial'ы) необходимо определить шаблон с соответствующим именем в директории видов для вашего контроллера. Ничего необычного, правда? Если вы не знаете, что же туда писать, можете посмотреть на дефолтные шаблоны в исходниках плагина (они там в app/views), благо они крайне простые.
</p>

<p>
Скорее всего, вы будете применять первый способ, но о первом все же стоит упомянуть. По умолчанию в каждый дефолтный шаблон добавляется CSS с описанием дефолтного стиля. Наверное, вам захочется его выкинуть (и подключить свой собственный в layout'е). Для этого достаточно переопределить в хелпере метод <tt>wiki_page_style</tt> своим, который возвращает пустую строку. Таким образом вы просто уберете стили из страниц.
</p>

<h2>Привязка к пользователям</h2>

<p>
Что необходимо для того, чтобы привязать вики к существующей системе пользователей в приложении?
</p>

<p>
Самый простой случай, если модель вашего пользователя называется User и у вас в контроллере определен метод current_user, который возвращает текущего пользователя. Согласитесь, это достаточно распространенный случай. В этом случае вики автоматически привяжется к пользователям и считать текущего пользователя автором изменений.
</p>

<p>
Единственная проблема - на всех страницах имя пользователя будет отображаться как-нибудь вроде <b>User123</b>. Наверное, это все-таки не то, что вы хотите. Чтобы исправить эту ситуацию достаточно определить метод <tt>wiki_user</tt> в классе WikiPagesHelper. Например, следующим образом:
</p>

<pre><code class="ruby">
module WikiPagesHelper
  include Irwi::Helpers::WikiPagesHelper

  def wiki_user( user )
    user ? link_to( user.login, user_path( user ) ) : &quot;Guest&quot;
  end

end
</code></pre>

<p>
Если же ваша модель называется как-то по-другому, придется добавить еще одну строчку при инициализации приложения:
</p>

<pre><code class="ruby">
Irwi.config.user_class_name = 'Account' # Разумеется, если ваша модель называется именно так
</code></pre>

<p>
Я рекомендую для этого создать отдельный initializer, чтобы потом туда же добавлять и прочие опции, которые вы, быть может захотите установить, но в принципе, вы вольны поступать как вам хочется.
</p>

<h2>Ограничение прав на выполнение операций</h2>

<p>
Скорее всего, Вам захочется добавить ограничение прав на просмотр или редактирование страниц вики (вообще говоря, я считаю, что это весьма неплохая идея). Например, как минимум, дать права на редактирование только зарегистрированным пользователям.
</p>

<p>
Для этого необходимо всего-навсего переопределить в контроллере три метода, осуществляющие проверку прав: <tt>show_allowed?</tt>, <tt>history_allowed?</tt> и <tt>edit_allowed?</tt>. В каждом из методов необходимо проверить имеет ли текущий пользователь права на совершение действия (просмотр, просмотр истории, редактирование) с текущей страницей (<tt>@page</tt>) и соответственно вернуть что-то, что расценивается как исттина или как ложь. В случае, если соответствующий метод возвращает ложь, то действие не совершается и в контроллере вызывается метод <tt>not_allowed</tt>, который по умолчанию рендерит текст об ошибке, но скорее всего вам захочется переопределить и его (например, чтобы редиректить пользователя на страницу логина).
</p>

<p>
Таким образом, примерный код контроллера может выглядеть следующим образом:
</p>

<pre><code class="ruby">
class WikiPagesController &lt; ApplicationController
  
  acts_as_wiki_pages_controller
  
  def show_allowed?
    true # Показывать всем
  end
  
  def history_allowed?
    true # И историю пусть все смотрят
  end
  
  def edit_allowed?
    current_user # А редактируют только те, кто залогинены
  end
  
  def not_allowed
    redirect_to login_path # Всех нарушителей редиректим на страницу логина
  end
  
end
</code></pre>

<h2>Итого</h2>

<p>
Я вкратце описал использование своего плагина для вики в Rails. Некоторые моменты, конечно, остались за границами этой статьи, но я опишу их позже. Приветствуются всякие замечания, предложения и идеи (через комменты, github, или любые мои контакты), а также патчи и форки (если кто-то хочет добавить что-то в плагин).
</p>
      <div id="related">

      <h2>Записи на схожие темы</h2>
        <ul>
	<li><a href="http://blog.alno.name/2009/07/rails-testing-tools/">Средства тестирования в Ruby on Rails</a></li>
	<li><a href="http://blog.alno.name/2008/05/periodic-tasks-in-ruby-on-rails/">Периодические задачи в Ruby on Rails</a></li>
	<li><a href="http://blog.alno.name/2008/04/errors-using-rails-with-fastcgi/">Ошибки при использовании Rails и FastCGI</a></li>
	<li><a href="http://blog.alno.name/2009/03/radiant-extensions-development/">Разработка расширений для Radiant</a></li>
	<li><a href="http://blog.alno.name/2009/05/typus/">Typus &#8211; админка в Rails-приложениях</a></li>
</ul>
      </div>

      
  ]]></content:encoded>
</item><item>
  <title>Средства тестирования в Ruby on Rails</title>
  <link>http://blog.alno.name/ru/2009/07/rails-testing-tools/</link>
  <comments>http://blog.alno.name/ru/2009/07/rails-testing-tools/#comments</comments>
  <pubDate>Thu, 23 Jul 2009 10:06:48 GMT</pubDate>
  <dc:creator>Alno</dc:creator>
  <guid isPermaLink="true">http://blog.alno.name/ru/2009/07/rails-testing-tools/</guid>
  <description><![CDATA[<p>Как известно, тестирование является одной из важнейших составляющих разработки приложений на основе Ruby on Rails. Поддержка тестирования была включена в Rails с самого начала, для этого использовалась библиотека Test::Unit. Однако, со временем, появилось много альтернативных средств для тестирования в Rails-приложениях.</p>
<p>В этой заметке я проведу небольшой обзор средств, используемых в настоящий момент для тестирования приложений на базе Ruby on Rails. Я не ставлю себе целью выбрать &#8220;лучшее&#8221; &#8211; я считаю, что этот выбор должен делать каждый сам, однако я попытаюсь показать различные варианты &#8211; чтобы было из чего выбирать.</p>]]></description>
  <content:encoded><![CDATA[
    <p>Как известно, тестирование является одной из важнейших составляющих разработки приложений на основе Ruby on Rails. Поддержка тестирования была включена в Rails с самого начала, для этого использовалась библиотека Test::Unit. Однако, со временем, появилось много альтернативных средств для тестирования в Rails-приложениях.</p>
<p>В этой заметке я проведу небольшой обзор средств, используемых в настоящий момент для тестирования приложений на базе Ruby on Rails. Я не ставлю себе целью выбрать &#8220;лучшее&#8221; &#8211; я считаю, что этот выбор должен делать каждый сам, однако я попытаюсь показать различные варианты &#8211; чтобы было из чего выбирать.</p><h2>Модульные и функциональные тесты</h2>

<p>Здесь я рассмотрю основные средства, используемые для модульного и функционального тестирования в Rails-приложениях. Напомню, что модульные тесты используются для тестирования логики приложения, которая расположена в моделях, а также для тестирования различных независимых участков приложения, например, хелперов (helpers).</p>

<h3>Test::Unit</h3>

<p>
<b>Test::Unit</b> является классическим средством тестирования в стиле <a href="http://en.wikipedia.org/wiki/XUnit">xUnit</a>. В Rails-приложениях именно он и используется по умолчанию. Набор тестов с его использованием описывается в виде класса, методы которого представляют различные тесты. В коде методов в необходимых точках добавляются проверки, представленные вызовами <tt>assert_*</tt>.
</p>

<p>Ниже приведен пример простейшего теста с использованием <b>Test::Unit</b>:</p>

<pre><code class="ruby">require 'test/unit'

class MyTest &lt; Test::Unit::TestCase
  # def setup
  # end

  # def teardown
  # end

  def test_fail
    assert(2 + 2 == 4, 'Assertion was false.')
  end
end
</code></pre>

<p>RDoc-документация для Test::Unit: <a href="http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit.html">http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit.html</a></p>

<h3>RSpec</h3>

<p><b>RSpec</b> - средство, предназначенное для спецификации поведения кода. Я не хочу вдаваться в отличия спецификации от тестирования (и BDD от TDD), просто приведу пример спецификации модели с использованием <b>RSpec</b>:</p>

<pre><code class="ruby">describe &quot;A new account&quot; do

  before do
    @account = Account.new
  end

  it &quot;should have a balance of $0&quot; do
    @account.balance.should eql(Money.new(0, :dollars))
  end

end
</code></pre>

<p>Как видно из примера, RSpec представляет специальный язык в рамках Ruby для описания спецификаций. Следствием этого является то, что хорошо написанные спецификации легко читаются, практически как текст на английском языке.</p>

<p>Одним из минусов RSpec является то, что для проверки спецификаций требуются создавать специальные Rake-задачи в приложении, в отличии от тестов на основе Test::Unit, задачи для которого включены в Rails.</p>

<p>Сайт проекта: <a href="http://rspec.info">http://rspec.info</a></p>
<p>RDoc-документация для RSpec: <a href="http://rspec.rubyforge.org/rspec/1.2.8/">http://rspec.rubyforge.org/rspec/1.2.8/</a></p>

<h3>Test/Spec</h3>

<p><b>Test/Spec</b> предназначен для описания спецификаций, аналогичных RSpec на базе Test::Unit, что позволяет использовать те же самые стандартные задачи, что и для Test::Unit, а также совмещать тесты и спецификации.</p>

<pre><code class="ruby">require 'test/spec'

describe &quot;Foo&quot; do
  it &quot;should bar&quot; do
    (2 + 3).should.equal 5
  end
end
</code></pre>

<p>Сайт проекта Test/Spec и RDoc-документация: <a href="http://test-spec.rubyforge.org/">http://test-spec.rubyforge.org/</a></p>

<h3>Shoulda</h3>

<p>Shoulda  - это средство, которое работает на базе Test::Unit и предоставляет некоторые дополнительные возможности, в частности специальный язык для описания конструкций в тестах и набор макросов для часто используемых проверок:</p>

<pre><code class="ruby">class UserTest &lt; Test::Unit::TestCase

  should_have_many :posts

  should_not_allow_values_for :email, &quot;blah&quot;, &quot;b lah&quot; 
  should_allow_values_for :email, &quot;a@b.com&quot;, &quot;asdf@asdf.com&quot; 
  should_ensure_length_in_range :email, 1..100
  should_ensure_value_in_range :age, 1..100
  should_protect_attributes :password

  context &quot;A User instance&quot; do
    setup do
      @user = User.find(:first)
    end

    should &quot;return its full name&quot; do
      assert_equal 'John Doe', @user.full_name
    end

    context &quot;with a profile&quot; do
      setup do
        @user.profile = Profile.find(:first)
      end

      should &quot;return true when sent #has_profile?&quot; do
        assert @user.has_profile?
      end
    end
  end
end
</code></pre>

<p><b>Update:</b> В настоящий момент Shoulda может быть использована и вместе с RSpec</p>

<p>Сайт проекта: <a href="http://www.thoughtbot.com/projects/shoulda/">http://www.thoughtbot.com/projects/shoulda/</a></p>

<h3>Remarkable</h3>

<p>Shoulda показала удобство использования макросов для распространенных задач в тестировании, однако она работала только над Test::Unit. <b>Remarkable</b> - это реализация набора макросов, аналогичных Shoulda для <b>RSpec</b>. Его использование позволяет описывать следующие спецификации:</p>

<pre><code class="ruby">describe Post do
  it { should belong_to(:user) }
  it { should have_many(:comments) }
  it { should have_and_belong_to_many(:tags) }

  it { should validate_presence_of(:body) }
  it { should validate_presence_of(:title) }
  it { should validate_uniqueness_of(:title, :allow_blank =&gt; true) }
end
</code></pre>

<p>Сайт проекта Remarkable и RDoc-документация: <a href="http://remarkable.rubyforge.org/">http://remarkable.rubyforge.org/</a></p>
<p>Блог проекта: <a href="http://www.nomedojogo.com/category/remarkable/">http://www.nomedojogo.com/category/remarkable/</a></p>
<p>Исходный код Remarkable: <a href="http://github.com/carlosbrando/remarkable/tree/master">http://github.com/carlosbrando/remarkable/tree/master</a></p>

<h2>Интеграционные тесты - Cucumber</h2>

<p>Cucumber, используемый для интеграционного тестирования приложений продолжает движение в сторону читабельности тестов - тестовые сценарии описываются буквально на естественном языке:</p>

<pre><code class="ruby">  Scenario: Add two numbers
    Given I have entered 13 into the calculator
    And I have entered 22 into the calculator
    When I press &quot;+&quot;
    Then the result should be 35 on the screen
</code></pre>

<p>И не только на английском:</p>

<pre><code class="ruby">  Сценарий: Сложение двух целых чисел
    Допустим я ввожу число 50
    И затем ввожу число 70
    Если я нажимаю &quot;+&quot;
    То результатом должно быть число 120
</code></pre>

<p>Написание сценариев в таком виде делает Cucumber просто незаменимым инструментом для тестирования Rails-приложений. Разумеется, некоторое количество кода писать все-таки приходится, для того чтобы описать что означают те или иные приложения.</p>

<h2>Заключение</h2>

<p>Здесь я описал некоторые средства, используемые для тестирования Rails-приложений. Я сознательно не затрагивал здесь различные библиотеки для создания mock-объектов или замены fixtures, возможно, в дальнейшем им можно посвятить отдельную заметку.</p>
      <div id="related">

      <h2>Записи на схожие темы</h2>
        <ul>
	<li><a href="http://blog.alno.name/2008/05/periodic-tasks-in-ruby-on-rails/">Периодические задачи в Ruby on Rails</a></li>
	<li><a href="http://blog.alno.name/2008/04/errors-using-rails-with-fastcgi/">Ошибки при использовании Rails и FastCGI</a></li>
	<li><a href="http://blog.alno.name/2009/03/radiant-extensions-development/">Разработка расширений для Radiant</a></li>
	<li><a href="http://blog.alno.name/2009/08/irwi-plugin/">Irwi &#8211; Wiki в Rails-приложениях</a></li>
</ul>
      </div>

      <div id="links">
        <h2>Ссылки</h2>
        <ul>
	<li><a href="http://guides.rubyonrails.org/testing.html" title="En">Тестирование в Rails</a></li>
	<li><a href="http://giantrobots.thoughtbot.com/2007/4/6/shoulda-coulda-woulda" title="En">Введение в Shoulda</a></li>
	<li><a href="http://habrahabr.ru/blogs/testing/52929/" title="Ru">Экстремальное программирование, знакомство с Behavior Driven Development и RSpec</a></li>
	<li><a href="http://habrahabr.ru/blogs/ruby/62958/" title="Ru"><span class="caps">BDD</span> с помощью Cucumber</a></li>
</ul>
      </div>
  ]]></content:encoded>
</item><item>
  <title>Навигация по сайту в пользовательском поиске Google</title>
  <link>http://blog.alno.name/ru/2009/07/google-cse-navigation/</link>
  <comments>http://blog.alno.name/ru/2009/07/google-cse-navigation/#comments</comments>
  <pubDate>Wed, 08 Jul 2009 21:29:27 GMT</pubDate>
  <dc:creator>Alno</dc:creator>
  <guid isPermaLink="true">http://blog.alno.name/ru/2009/07/google-cse-navigation/</guid>
  <description><![CDATA[<p>Некоторое время назад я прикрутил к своему блогу поиск Google на основе <a href="http://www.google.com/cse/" title="Custom Search Engine">пользовательского поиска</a>. После некоторого осмотра возможностей пользовательского поиска я обнаружил &#8220;уточнения&#8221;, позволящие создавать дополнительные ссылки в интерфейсе поиска для уточнения результатов поиска.</p>
<p>Поскольку я пишу и о <a href="http://blog.alno.name/tags/cpp">C++</a>, и о <a href="http://blog.alno.name/tags/java">Java</a>, и о <a href="http://blog.alno.name/tags/ruby">Ruby</a>, естественным было бы добавить туда уточнения вида &#8220;C++&#8221;, &#8220;Java&#8221; и &#8220;Ruby&#8221;, позволяющие включать в результаты поиска только страницы с заданными тэгами. Как это выглядит можно посмотреть на картинке внизу, или по после нажатия кнопки &#8220;Найти&#8221; в блоге справа вверху.</p>
<p>Здесь я опишу, как такое можно осуществить.</p>]]></description>
  <content:encoded><![CDATA[
    <p>Некоторое время назад я прикрутил к своему блогу поиск Google на основе <a href="http://www.google.com/cse/" title="Custom Search Engine">пользовательского поиска</a>. После некоторого осмотра возможностей пользовательского поиска я обнаружил &#8220;уточнения&#8221;, позволящие создавать дополнительные ссылки в интерфейсе поиска для уточнения результатов поиска.</p>
<p>Поскольку я пишу и о <a href="http://blog.alno.name/tags/cpp">C++</a>, и о <a href="http://blog.alno.name/tags/java">Java</a>, и о <a href="http://blog.alno.name/tags/ruby">Ruby</a>, естественным было бы добавить туда уточнения вида &#8220;C++&#8221;, &#8220;Java&#8221; и &#8220;Ruby&#8221;, позволяющие включать в результаты поиска только страницы с заданными тэгами. Как это выглядит можно посмотреть на картинке внизу, или по после нажатия кнопки &#8220;Найти&#8221; в блоге справа вверху.</p>
<p>Здесь я опишу, как такое можно осуществить.</p><h2>Настройка системы поиска</h2>

<p>Для начала, разумеется, необходимо создать систему пользовательского поиска. Это можно сделать на странице <a href="http://www.google.com/cse/">Custom Search Engine</a>. При создании системы в список сайтов необходимо, разумеется включить свой сайт (или сайты). </p>

<p>Затем, в настройках системы зайти в раздел Уточнения(Refinements). Там можно добавить уточнение, возникает форма вида (например, сначала добавим раздел Ruby):</p>

<p style="text-align: center;"><img src="/assets/19/google-cse-ruby.png"  alt='google-cse-ruby' /></p>

<p>Основная проблема состоит в том, что написать в строке слов, чтобы выбрать все страницы, содержащие тэг <b>Ruby</b>.</p>

<p>Пришлось поискать описание различных операторов расширенного поиска для Google. Наиболее подробное описание различных операторов поиска я нашел <a href=":http://www.googleguide.com/advanced_operators.html" rel="nofollow">здесь</a>. Однако, для решения задачи мне понадобились всего три:</p>

<ul>
<li><tt>|</tt> - альтернативные варианты (или).</li>
<li><tt>[</tt> и <tt>]</tt> - скобки для группировки элементов в запросах.</li>
<li><tt>*</tt> - оператор, который обозначает одно произвольное слово. Например в запросе <b>"Мама * раму"</b> вместо * может находится ровно одно слово, то есть по этому запросу найдется текст "мама мыла раму", но не "мама мыла большую раму" или "мама раму".</li>
</ul>

<p>Далее, в начале каждой записи после заголовка я вставил код, выводящий список всех тэгов записи, перед которым находилось слово "тэги". После этого пришлось подождать некоторое время, пока Google переиндексирует все страницы сайта =).

<p>После того, как все страницы переиндексированы, можно легко выделить все, у которых первым тэгом является Ruby с помощью запроса <b>"Тэги: Ruby"</b>

<p>Теперь необходимо учесть то, что тэг Ruby стоит не всегда на первом месте. Здесь-то и нужен оператор * - запрос для тэга на втором месте выглядит как <b>"Тэги: * Ruby"</b>. Аналогично для третьего, четвертого и далее.</p>

<p>Чтобы сделать запрос, который выбирает все страницы, содержащие тэг на любом из первых N мест можно задать следующую конструкцию: <b>["Тэги: Ruby"|"Тэги: * Ruby"|"Тэги: * * Ruby"|"Тэги: * * * Ruby"|"Тэги: * * * Ruby"]</b>. Тут N=5. Если Вы ставите больше тэгов, то запрос придется сделать несколько длинее.</b></p>

<p>Полученную строку запроса необходимо записать в "слова, добавляемые в поисковый запрос", а затем нажать "Сохранить". Повторив описанную процедуру для необходимых тэгов на страницу поиска можно получить следующую строку навигации:</p>

<p style="text-align: center;"><img src="/assets/20/google-cse-results.png"  alt='google-cse-results' /></p>

<h2>Форма поиска</h2>

<p>Для поиска Google предлагает AJAX-форму, однако мне больше понравился вариант стандартной формы:</p>

<pre><code class="html">
&lt;form action=&quot;http://www.google.com/cse&quot; id=&quot;cse-search-box&quot;&gt;
&lt;input name=&quot;cx&quot; value=&quot;012005588861949235995:unlj7xfsgyy&quot; type=&quot;hidden&quot;&gt;
&lt;input name=&quot;ie&quot; value=&quot;UTF-8&quot; type=&quot;hidden&quot;&gt;
&lt;input name=&quot;q&quot; size=&quot;30&quot; type=&quot;text&quot;&gt;
&lt;input name=&quot;sa&quot; value=&quot;Найти&quot; type=&quot;submit&quot;&gt;
&lt;/form&gt;
</code></pre>

<p><b>012005588861949235995:unlj7xfsgyy</b> - это идентификатор моей системы поиска. Соответственно, для Вашей системы он будет другой, найти его можно, например, в URL при редактировании системы поиска.</p>
      

      
  ]]></content:encoded>
</item><item>
  <title>Typus - админка в Rails-приложениях</title>
  <link>http://blog.alno.name/ru/2009/05/typus/</link>
  <comments>http://blog.alno.name/ru/2009/05/typus/#comments</comments>
  <pubDate>Tue, 26 May 2009 23:35:25 GMT</pubDate>
  <dc:creator>Alno</dc:creator>
  <guid isPermaLink="true">http://blog.alno.name/ru/2009/05/typus/</guid>
  <description><![CDATA[<p>Я не люблю писать одинаковый код много раз, тем более тривиальный. Я не люблю писать админки для Rails-приложений, потому что они состоят по большей части как раз из такого кода. И я не люблю генераторы, создающие кучу кода, который необходимо менять только в некоторых местах (поэтому для аутентификации я использую AuthLogic, а не restful_authentication).</p>
<p>И поэтому я был очень рад обнаружить замечательный проект: <a href="http://github.com/fesplugas/typus">Typus</a>. Это плагин для Rails, позволяющий значительно упростить процесс создания админки для приложений.</p>]]></description>
  <content:encoded><![CDATA[
    <p>Я не люблю писать одинаковый код много раз, тем более тривиальный. Я не люблю писать админки для Rails-приложений, потому что они состоят по большей части как раз из такого кода. И я не люблю генераторы, создающие кучу кода, который необходимо менять только в некоторых местах (поэтому для аутентификации я использую AuthLogic, а не restful_authentication).</p>
<p>И поэтому я был очень рад обнаружить замечательный проект: <a href="http://github.com/fesplugas/typus">Typus</a>. Это плагин для Rails, позволяющий значительно упростить процесс создания админки для приложений.</p><p>Из его особенностей:</p>
<ul>
	<li>Позволяет практически мгновенно создать простейшую админку для моделей (создание, удаление, редактирование).</li>
	<li>Автоматически поддерживаются связи между моделями.</li>
	<li>Админка является расширяемой.</li>
	<li>Не генерирует огромного объема кода, который потом никогда не расширяется. Только то, что необходимо &#8211; конфиграционный файл и классы констроллеров для расширения. Только классы, без кода системы администрирования в них!</li>
	<li>Поддерживается локализация админки (пнглийский, испанский, португальский и <strong>русский</strong>).</li>
	<li>Поддерживается экспорт данных в различные форматы.</li>
</ul>
<h2>Установка</h2>
<p>Typus может быть установлен двумя способами:</p>
<ol>
	<li>Как обычный плагин Rails, для этого в директории приложения необходимо вызвать:<br />
  <pre><code class="bash">$ script/plugin install git://github.com/fesplugas/typus.git</code></pre></li>
	<li>Через Ruby Gems, для этого в <tt>config/environment.rb</tt> необходимо добавить строку:<br />
  <pre><code class="ruby">config.gem 'typus'</code></pre></li>
</ol>
<p>После установки необходимо в директории приложения вызвать команды:<br />
<pre><code class="bash">$ script/generate typus
$ rake db:migrate
</code></pre></p>
<p>В результате будут сгенерированы файлы конфигурации админки, контроллеры админки для всех существующих моделей и пара миграций, обеспечивающих работу системы пользователей Typus.</p>
<p>Также, для работы админки необходимо убедиться, что в конце файла <tt>config/routes.rb</tt> присутствуют строки:<br />
<pre><code class="ruby">map.connect ':controller.:format'
map.connect ':controller/:action/:id.:format'
</code></pre></p>
<h2>Конфигурация</h2>
<p>После установки плагина создается несколько файлов, хранящих конфигурацию:</p>
<ol>
	<li><tt>config/initalizers/typus.rb</tt> &#8211; здесь хранятся общие настройки админки</li>
	<li><tt>config/typus/*.yml</tt> &#8211; здесь хранятся настройки для различных моделей</li>
	<li><tt>config/typus/*_roles.yml</tt> &#8211; здесь хранятся права доступа к моделям</li>
</ol>
<p>В первом файле задаются имя приложения, используемая локаль и т.п. Вот часть опций из него:<br />
<pre><code class="ruby">Typus::Configuration.options[:app_name]
Typus::Configuration.options[:config_folder]
Typus::Configuration.options[:email]
Typus::Configuration.options[:locales]
Typus::Configuration.options[:recover_password]
Typus::Configuration.options[:root]
Typus::Configuration.options[:ssl]
Typus::Configuration.options[:templates_folder]
Typus::Configuration.options[:user_class_name]
Typus::Configuration.options[:user_fk]
</code></pre></p>
<p>Например, чтобы задать русскую локаль необходимо в соответствующей опции этого файла выставить:<br />
<pre><code class="ruby">Typus::Configuration.options[:locales] = [ [ &quot;Русский&quot;, :ru ] ]</code></pre></p>
<p>В файлах настроек для моделей хранится описание того, как модели должны отображаться в админке, в частности:</p>
<ul>
	<li>Какие поля должны отображаться при каждом из действий;</li>
	<li>Какие связи должны редактироваться;</li>
	<li>По каким полям должна осуществляться фильтрация и поиск;</li>
	<li>В каком порядке должны выводится записи;</li>
	<li>И тому подобное&#8230;</li>
</ul>
<h2>Модификация админки</h2>
<p>Сгенерированная админка может быть легко модифицирована одним из следующих способов:</p>
<ul>
	<li>(банальный) Добавление новых контроллеров в админку;</li>
	<li>Добавление новых действий к существующим контроллерам. Действие описывается в сгенерированном контроллере, а его указание в конфигурационном файле админки позволяет привязать действие к интерфейсу администрирования;</li>
	<li>Переопределение видов;</li>
	<li>Добавление блоков в виды. В последнем случае нет необходимости переопределять весь интерфейс, достаточно толко создать партиалы, которые будут встроены в соответствующие участки интерфейса. Список возможных партиалов приведен ниже (где <span class="caps">RESOURCE</span> &#8211; имя редактируемой модели):<br />
<pre><code class="bash">views/admin/login/_{bottom|top}.html.erb
views/admin/dashboard/_{bottom|sidebar|top}.html.erb
views/admin/RESOURCE/_edit.html.erb
views/admin/RESOURCE/_edit_{bottom|sidebar|top}.html.erb
views/admin/RESOURCE/_index.html.erb
views/admin/RESOURCE/_index_{bottom|sidebar|top}.html.erb
views/admin/RESOURCE/_new.html.erb
views/admin/RESOURCE/_new_{bottom|sidebar|top}.html.erb
views/admin/RESOURCE/_show.html.erb
views/admin/RESOURCE/_show_{bottom|sidebar|top}.html.erb
</code></pre></li>
</ul>
<h2>Пример приложения</h2>
<p>На сайте проекта выложен специальный шаблон, позволяющий сгенерировать простое демонстрационное приложение. С его помощью, чтобы попробовать Typus достаточно выполнить в консоли:</p>
<pre><code class="bash">$ rails example.com -m http://intraducibles.com/projects/typus/demo.txt
$ cd example.com &amp;&amp; script/server
</code></pre><p>После этого можно заходить в админку по адресу <tt>http://127.0.0.1:3000/admin</tt>.</p>
      <div id="related">

      <h2>Записи на схожие темы</h2>
        <ul>
	<li><a href="http://blog.alno.name/2009/07/rails-testing-tools/">Средства тестирования в Ruby on Rails</a></li>
	<li><a href="http://blog.alno.name/2008/05/periodic-tasks-in-ruby-on-rails/">Периодические задачи в Ruby on Rails</a></li>
	<li><a href="http://blog.alno.name/2009/03/radiant-extensions-development/">Разработка расширений для Radiant</a></li>
	<li><a href="http://blog.alno.name/2009/08/irwi-plugin/">Irwi &#8211; Wiki в Rails-приложениях</a></li>
</ul>
      </div>

      <div id="links">
        <h2>Ссылки</h2>
        <ul>
	<li><a href="http://intraducibles.com/projects/typus">Официальная страница проекта</a></li>
	<li><a href="http://github.com/fesplugas/typus">Репозиторий на GitHub</a></li>
</ul>
      </div>
  ]]></content:encoded>
</item><item>
  <title>Использование Boost Pointer Container Library</title>
  <link>http://blog.alno.name/ru/2009/04/using-boost-ptr-containers/</link>
  <comments>http://blog.alno.name/ru/2009/04/using-boost-ptr-containers/#comments</comments>
  <pubDate>Thu, 09 Apr 2009 12:23:08 GMT</pubDate>
  <dc:creator>Alno</dc:creator>
  <guid isPermaLink="true">http://blog.alno.name/ru/2009/04/using-boost-ptr-containers/</guid>
  <description><![CDATA[<p>Как часто Вам приходилось хранить в стандартных STL контейнерах указатели на объекты? Я думаю, достаточно. Но хранение обычных указателей приводит к тому, что приходится тратить дополнительные усилия на то, чтобы проверять, что память освобождается при уничтожении контейнера.</p>

<p>Обычное решение этой проблемы - <a href="http://blog.alno.name/2008/05/using-boost-smart-pointers/">умные указатели</a> с подсчетом ссылок, обеспечивающие автоматическое освобождение памяти. Однако, достаточно часто встречаются случаи, когда их использование избыточно, например, когда объекты имеют всего одного владельца и подсчет ссылок, вообще говоря не нужен.</p>

<p>А судя по замерам, накладные расходы по производительности и памяти на подсчет ссылок достаточно велики: <a rel="nofollow" href="http://www.boost.org/doc/libs/1_38_0/libs/smart_ptr/smarttests.htm">Boost smart pointer timings</a>.</p>

<p>Что же делать в таких случаях? На помощь приходит очередная библиотека в составе <a href="http://blog.alno.name/tags/boost/">Boost</a> - Pointer Container Library. Она предоставляет основные контейнеры, схожие со стандартными, специально предназнченные для хранения указателей.</p>
]]></description>
  <content:encoded><![CDATA[
    <p>Как часто Вам приходилось хранить в стандартных STL контейнерах указатели на объекты? Я думаю, достаточно. Но хранение обычных указателей приводит к тому, что приходится тратить дополнительные усилия на то, чтобы проверять, что память освобождается при уничтожении контейнера.</p>

<p>Обычное решение этой проблемы - <a href="http://blog.alno.name/2008/05/using-boost-smart-pointers/">умные указатели</a> с подсчетом ссылок, обеспечивающие автоматическое освобождение памяти. Однако, достаточно часто встречаются случаи, когда их использование избыточно, например, когда объекты имеют всего одного владельца и подсчет ссылок, вообще говоря не нужен.</p>

<p>А судя по замерам, накладные расходы по производительности и памяти на подсчет ссылок достаточно велики: <a rel="nofollow" href="http://www.boost.org/doc/libs/1_38_0/libs/smart_ptr/smarttests.htm">Boost smart pointer timings</a>.</p>

<p>Что же делать в таких случаях? На помощь приходит очередная библиотека в составе <a href="http://blog.alno.name/tags/boost/">Boost</a> - Pointer Container Library. Она предоставляет основные контейнеры, схожие со стандартными, специально предназнченные для хранения указателей.</p>
<h2>Использование</h2>

<p>В примере мы будем использовать ptr_vector. Для него необходимо подключить один заголовочный файл:</p>

<pre><code class="cpp">
#include &lt;boost/ptr_container/ptr_vector.hpp&gt;
</code></pre>

<p>Рассмотрим простейший пример использования, приведенный в документации библиотеки. Предположим, что у нас имеется классическая иерархия классов:</p>

<pre><code class="cpp">
class animal
{
public:
    virtual      ~animal()   {}
    virtual void eat()       = 0;
    virtual int  age() const = 0;
    // ...
};

class mammal : public animal
{
    // ...
};

class bird : public animal
{
    // ...
};
</code></pre>

<p>Теперь, нам необходимо создать список животных. Для этого мы будем использовать контейнер <b>ptr_vector</b>:</p>

<pre><code class="cpp">
boost::ptr_vector&lt;animal&gt; the_animals;
</code></pre>

<p>Сразу обращаем внимание: при объявлении контейнера * не указывается. Чтобы добавить объект в контейнер, вызываем метод push_back, аналогично стандартному контейнеру <b>std::vector</b>:</p>

<pre><code class="cpp">
the_animals.push_back( new mammal(&quot;joe&quot;) );
the_animals.push_back( new bird(&quot;dodo&quot;) );
</code></pre>

<p>Теперь о доступе к элементам: <b>контейнер предоставляет доступ к элементам не по указателям, а по ссылкам</b>. То, есть необходимо писать следующим образом:</p>

<pre><code class="cpp">
the_animals[0].eat(); // А в случае std::vector&lt;animal*&gt; это было бы vec[0]-&gt;eat();

boost::ptr_vector&lt;animal&gt;::iterator  i = vec.begin();
i-&gt;eat(); // Опять же, для std::vector это было бы (*i)-&gt;eat(); Не знаю как Вас, но меня такая запись всегда раздражала.
</code></pre>

<p>Еще одно отличие: обработка нулевых указателей. Стандартный <b>vector</b> позволяет добавлять элементы - нулевые указатели. <b>ptr_vector</b> - не позволяет (по умолчанию). То есть, следующий код вызывает исключение:</p>

<pre><code class="cpp">
the_animals.insert( the_animals.begin(), 0 ); // Исключение
</code></pre>

<p>Однако, если Вам необходимо, Вы можете разрешить хранение нулевых указателей. Делается это следующим образом:</p>

<pre><code class="cpp">
boost::ptr_vector&lt; boost::nullable&lt;animal&gt; &gt; the_animals_nullable;

the_animals_nullable.insert( the_animals_nullable.begin(), 0 ); // Это уже корректно
</code></pre>

<h2>Клонирование и перемещение данных</h2>

<p>Крайне важное отличие таких контейнеров от стандартных в том, что они не могут быть скопированы напрямую! В чем причина: предполагается, что каждый указатель в любой момент времени находится в одном из таких контейнеров, который и освободит память при необходимости. Если же скопировать содержимое, то Вы получили бы указатель в двух контейнерах, что привело бы к последующим проблемам с их удалением.</p>

<p>Однако, не все так плохо. Вместо копирования можно осуществлять другие операции:</p>

<ul>
  <li>Во-первых, контейнеры можно клонировать, вызывая клонирование всех входящих в них элементов.</li>
  <li>Во-вторых, данные можно извлечь из контейнера. И затем, например, переместить в другой.</li>
</ul>

<p>Сначала о клонировании. Для того, чтобы содержимео контейнера могло быть клонировано, для всех его элементов должны быть определены две функции:</p>

<pre><code class="cpp">
namespace boost
{
    inline T* new_clone( const T&amp; t )
    {
        // Здесь клонируем объект...
    }

    void delete_clone( const T* t )
    {
        // Здесь удаляем объект...
    }
}
</code></pre>

<p>Если Ваш компилятор поддерживает Argument-Dependent Lookup, то их можно объявлять не в пространстве имен boost, а в том, где расположен обрабатываемый ими тип.</p>

<p>После того, как такие функции объявлены, можно вызывать метод <tt>clone</tt> контейнера:</p>

<pre><code class="cpp">
the_animal_clones = the_animals.clone();
</code></pre>

<p>Теперь об извлечении и перемещении. Контейнеры предоставляют набор методов, позволяющих извлечь указатель по итератору и переместить набор элементов в другой контейнер:</p>

<pre><code class="cpp">
boost::ptr_vector&lt;animal&gt;::auto_type the_animal = the_animals.release( the_animals.begin() ); // Извлекаем первый элемент
the_animal-&gt;eat();
</code></pre>

<p>При этом <tt>auto_type</tt> представляет из себя аналог <tt>std::auto_ptr</tt>.</p>

<p>Для перемещения  существуют два вариант метода <tt>transfer</tt></p>

<pre><code class="cpp">
another_zoo.transfer( another_zoo.end(), // Добавляем последним элементом
                      zoo.begin(),       // Первый элемент
                      zoo );             // Из этого контейнера

another_zoo.transfer( another_zoo.begin(), // Добавляем в конец
                      zoo.begin(),       // От первого
                      zoo.end(),         // До последнего
                      zoo );             // Из этого контейнера

</code></pre>

<h2>Алгоритмы</h2>

<p>К сожалению, с этими контейнерами нельзя использовать стандартные алгоритмы STL. Однако, некоторые основные реализованы в виде методов:</p>

<pre><code class="cpp">
boost::ptr_vector&lt;animal&gt; zoo;
...
zoo.sort();                               // Предполагаем, что описан 'bool operator&lt;( const animal&amp;, const animal&amp; )'
zoo.sort( std::less&lt;animal&gt;() );          // То же самое, обращаем внимание на отсутствие *
zoo.sort( zoo.begin(), zoo.begin() + 5 ); // Сортируем участок

zoo.unique();                             // Предполагаем, что описан 'bool operator==( const animal&amp;, const animal&amp; )'
zoo.unique( zoo.begin(), zoo.begin() + 5, my_comparison_predicate() ); 

zoo.erase_if( my_predicate() );
</code></pre>
      <div id="related">

      <h2>Записи на схожие темы</h2>
        <ul>
	<li><a href="http://blog.alno.name/2008/05/using-boost-smart-pointers/">Умные указатели в C++: boost::shared_ptr, boost::weak_ptr, boost::intrusive_ptr</a></li>
	<li><a href="http://blog.alno.name/2008/04/using-boost-multi-index/">Использование контейнеров boost::multi_index</a></li>
</ul>
      </div>

      <div id="links">
        <h2>Ссылки</h2>
        <ul>
	<li><a href="http://www.boost.org/doc/libs/1_38_0/libs/ptr_container/doc/ptr_container.html">Официальная документация [En]</a></li>
	<li><a href="http://dvinogradov.blogspot.com/2008/11/storing-dynamic-created-objects-in.html">Запись в Raider&#8217;s Programming Blog [Ru]</a></li>
	<li><a href="http://sindicollo.blogspot.com/2008/09/boost.html">Boost на русском [Ru]</a></li>
</ul>
      </div>
  ]]></content:encoded>
</item><item>
  <title>XML-RPC в Ruby</title>
  <link>http://blog.alno.name/ru/2009/03/xmlrpc-in-ruby/</link>
  <comments>http://blog.alno.name/ru/2009/03/xmlrpc-in-ruby/#comments</comments>
  <pubDate>Sat, 28 Mar 2009 00:20:34 GMT</pubDate>
  <dc:creator>Alno</dc:creator>
  <guid isPermaLink="true">http://blog.alno.name/ru/2009/03/xmlrpc-in-ruby/</guid>
  <description><![CDATA[<p>Как известно, <span class="caps">XML</span>-<span class="caps">RPC</span> является достаточно распространенным методом вызова удаленных процедур, основанным на <span class="caps">XML</span>. Однако, несмотря на то, что он значительно проще, чем, например, <span class="caps">SOAP</span>, его использование все еще иногда достаточно сложно: необходимо сериализовать объекты в специальное <span class="caps">XML</span>-представление, затем распарсить полученный релзультат.</p>
<p>Однако, на самом деле, не все так плохо!</p>
<p>В стандартной библиотеке <a href="http://blog.alno.name/tags/ruby">Ruby</a> существует замечательная поддержка <span class="caps">XML</span>-<span class="caps">RPC</span>, о которой я немного расскажу далее.</p>]]></description>
  <content:encoded><![CDATA[
    <p>Как известно, <span class="caps">XML</span>-<span class="caps">RPC</span> является достаточно распространенным методом вызова удаленных процедур, основанным на <span class="caps">XML</span>. Однако, несмотря на то, что он значительно проще, чем, например, <span class="caps">SOAP</span>, его использование все еще иногда достаточно сложно: необходимо сериализовать объекты в специальное <span class="caps">XML</span>-представление, затем распарсить полученный релзультат.</p>
<p>Однако, на самом деле, не все так плохо!</p>
<p>В стандартной библиотеке <a href="http://blog.alno.name/tags/ruby">Ruby</a> существует замечательная поддержка <span class="caps">XML</span>-<span class="caps">RPC</span>, о которой я немного расскажу далее.</p><p>Для создания простейшего клиента необходимо всего лишь подключить файл <tt>xmlrpc/client</tt>, создать объект клиента и вызывать функции RPC, как методы этого объекта. А возвращаемым значением метода является хэш выходных параметров.</p>

<pre><code class="ruby">
  require 'xmlrpc/client'
  require 'pp'

  client = XMLRPC::Client.new2(&quot;http://xmlrpc-c.sourceforge.net/api/sample.php&quot;)
  result = client.call(&quot;sample.sumAndDifference&quot;, 5, 3)
  pp result
</code></pre>

<p>Это пример с сайта официальной документации. По идее, <tt>result</tt> должен представлять из себя хэш, в котором будут содержаться возвращаемые значения. Однако, этот пример не работает (как минимум у меня). При вызове он выдает исключение:</p>

<pre><code class="ruby">/usr/lib/ruby/1.8/xmlrpc/client.rb:555:in `do_rpc': Wrong content-type (received 'text/html' but expected 'text/xml'):  (RuntimeError)</code></pre>

<p>Что же не так? Сервер, к которому обращается клиент почему-то возвращает в заголовке <tt>Content-Type</tt> значение <tt>text/html</tt>, хотя ожидается <tt>text/xml</tt>. Вообще говоря, это стоит считать багом настройки сервера, но если Вы осуществляете запросы к каким-то внешним сервисам, есть вероятность, что какой-то из них так же будет отсылать некорректный <tt>Content-Type</tt>.</p>

<p>Первое что мне пришло в голову - это отключить проверку в клиенте. Самый простой способ не требует никакой модификации кода библиотеки. Для этого перед использованием клиента необходимо поместить следующий код:</p>

<pre><code class="ruby">
  XMLRPC::Client.class_eval do
    def parse_content_type(a)
      ['text/xml']
    end
  end
</code></pre>

<p>То есть мы просто подменяем метод <tt>parse_content_type</tt> с тем, чтобы он всегда возвращал <tt>text/xml</tt>. Теперь можно спокойно делать запросы к различным сервисам и вышеприведенный пример выведет что-то вроде:</p>

<pre><code class="ruby">
{&quot;sum&quot;=&gt;8, &quot;difference&quot;=&gt;2}
</code></pre>

<p>Теперь можно использовать библиотеку для работы сразличными XML-RPC сервисами в сети.</p>

<h2>Ссылки</h2>

<ul>
  <li><a href="http://www.ruby-doc.org/stdlib/libdoc/xmlrpc/rdoc/index.html">Ruby XMLRPC Documentation (En)</a></li>
</ul>
      

      
  ]]></content:encoded>
</item><item>
  <title>Разработка расширений для Radiant</title>
  <link>http://blog.alno.name/ru/2009/03/radiant-extensions-development/</link>
  <comments>http://blog.alno.name/ru/2009/03/radiant-extensions-development/#comments</comments>
  <pubDate>Sun, 01 Mar 2009 14:37:02 GMT</pubDate>
  <dc:creator>Alno</dc:creator>
  <guid isPermaLink="true">http://blog.alno.name/ru/2009/03/radiant-extensions-development/</guid>
  <description><![CDATA[<p>Продолжая тему перевода блога на Radiant <span class="caps">CMS</span> и подсветки кода, сегодня я расскажу о создании расширений для него на примере простейшего, которое добавляет специальный тэг для подсветки кода.</p>
<p>Подсветка будет осуществляться с помощью CodeRay, о котором я уже писал в прошлой заметке.</p>
<p>Итак, создание простейшего расширения для Radiant <span class="caps">CMS</span>:</p>]]></description>
  <content:encoded><![CDATA[
    <p>Продолжая тему перевода блога на Radiant <span class="caps">CMS</span> и подсветки кода, сегодня я расскажу о создании расширений для него на примере простейшего, которое добавляет специальный тэг для подсветки кода.</p>
<p>Подсветка будет осуществляться с помощью CodeRay, о котором я уже писал в прошлой заметке.</p>
<p>Итак, создание простейшего расширения для Radiant <span class="caps">CMS</span>:</p><h2>Создание скелета расширения</h2>

<p>Для того, чтобы создать скелет расширения необходимо вызвать скрипт <tt>script/generate extension</tt> в директории приложения. Например, для создания расширения с именем <tt>code</tt>:

<pre><code class="bash">script/generate extension code</code></pre>

В результате, в директории <tt>vendor/extensions/code</tt> будут созданы файлы расширения, основным из которых является <tt>code_extension.rb</tt>. Его содержимое:

<pre><code class="ruby">
# Uncomment this if you reference any of your controllers in activate
# require_dependency 'application'

class CodeExtension &lt; Radiant::Extension
  version &quot;1.0&quot;
  description &quot;Describe your extension here&quot;
  url &quot;http://yourwebsite.com/code&quot;
  
  # define_routes do |map|
  #   map.namespace :admin, :member =&gt; { :remove =&gt; :get } do |admin|
  #     admin.resources :code
  #   end
  # end
  
  def activate
    # admin.tabs.add &quot;Code&quot;, &quot;/admin/code&quot;, :after =&gt; &quot;Layouts&quot;, :visibility =&gt; [:all]
  end
  
  def deactivate
    # admin.tabs.remove &quot;Code&quot;
  end
  
end
</code></pre>

По сути, сейчас мы уже имеем расширение, которое, правда, ничего не делает. Это необходимо исправить.
</p>

<h2>Простейшее расширение</h2>

<p>
В расширение мы добавим один тэг <tt>&lt;r:code&gt;</tt>, который позволяет подсветить код с использованием <a href="http://blog.alno.name/2009/02/ruby-code-highlighters#coderay">библиотеки CodeRay</a>. Для этого необходимо создать новый модуль, содержащий тэг. Например, <tt>code_tags.rb</tt> в директории <tt>lib</tt> расширения. Чтобы в модуле можно было объявлять тэги, в него необходимо включить <tt>Radiant::Taggable</tt>:

<pre><code class="ruby">
module CodeTags
  include Radiant::Taggable
end
</code></pre>

Теперь в модуль необходимо добавить тэг. Делается это следующим образом:

<pre><code class="ruby">
module CodeTags
  include Radiant::Taggable

  desc %{
    Some tag description here...
  }
  tag 'code' do |tag|
    tag.expand
  end

end
</code></pre>

Метод <tt>desc</tt> используется для задания описания для тэга (оно будет отображаться в списке тэгов при редактировании страницы). Метод <tt>tag</tt> объявляет тэг.
</p>

<p>
Теперь, чтобы подкчлють модуль с тэгами в файле <tt>code_extension.rb</tt> в метод activate необходимо добавить подключение нашего модуля к классу <tt>Page</tt>:
<pre><code class="ruby">Page.send :include, CodeTags</code></pre>
</p>

<p>
Таким образом, мы создали тэг <tt>r:code</tt>, который ничего не делает. Точнее, он просто выводит свое содержимое: <tt>tag.extend</tt> возвращает содержимое тэга. Теперь вспомним, как использовался <a href="http://blog.alno.name/2009/02/ruby-code-highlighters#coderay">CodeRay для подсветки кода</a>:

<pre><code class="ruby">CodeRay.scan( text, lang ).div( :line_numbers =&gt; :table, :css =&gt; :style, :style =&gt; :cycnus )</code></pre>

В тэге <tt>code</tt> вместо tag.expand вставим аналогичный код. Исходный код будет выглядеть следующим образом:
<pre><code class="ruby">
  tag 'code' do |tag|
    CodeRay.scan( tag.expand, 'ruby' ).div( :line_numbers =&gt; :table, :css =&gt; :style, :style =&gt; :cycnus )
  end
</code></pre>

А в начало файла <tt>code_tags.rb</tt> необходимо добавить строку для загрузки <b>CodeRay</b>:

<pre><code class="ruby">require 'coderay'</code></pre>

Теперь, в страницах можно использовать конструкции вида &lt;r:code&gt;...&lt;/r:code&gt;. Однако, хотелось бы иметь возможность указывать конкретный язык в аттрибутах тэга. Для доступа к аттрибутам из кода обработчика используется хэш <tt>tag.attr</tt>:

<pre><code class="ruby">
  tag 'code' do |tag|
    CodeRay.scan( tag.expand, tag.attr['lang'] || 'ruby' ).div( :line_numbers =&gt; :table, :css =&gt; :style, :style =&gt; :cycnus )
  end
</code></pre>

Все!
</p>

<p>
Простейшее расширение для подсветки кода в Radiant готово и его можно использовать в своем блоге. Более продвинутый вариант такого расширения можно найти <a href="http://github.com/alno/radiant-code-extension/tree" rel="nofollowe">у меня на GitHub</a>. Я добавил в него поддержку различных библиотек для подсветки кода (в частности, <a href="http://blog.alno.name/2009/02/code-highlighters-ruby/">тех, о которых писал в прошлый раз</a>).
</p>

<h2>Ссылки</h2>
<ul>
  <li><a href="http://wiki.radiantcms.org/Creating_Radiant_Extensions" rel="nofollow">Creating Radiant Extensions, Part 1 (En)</a></li>
  <li><a href="http://wiki.radiantcms.org/Creating_Radiant_Extensions_B" rel="nofollow">Creating Radiant Extensions, Part 2 (En)</a></li>
  <li><a href="http://ruby.inuse.ru/article/ustanovka-radiant-cms" rel="nofollow">Установка Radiant CMS (Ru)</a></li>
</ul>
      

      
  ]]></content:encoded>
</item><item>
  <title>Библиотеки подсветки кода для Ruby</title>
  <link>http://blog.alno.name/ru/2009/02/code-highlighters-ruby/</link>
  <comments>http://blog.alno.name/ru/2009/02/code-highlighters-ruby/#comments</comments>
  <pubDate>Wed, 18 Feb 2009 22:35:50 GMT</pubDate>
  <dc:creator>Alno</dc:creator>
  <guid isPermaLink="true">http://blog.alno.name/ru/2009/02/code-highlighters-ruby/</guid>
  <description><![CDATA[<p>В связи с переводом моего блога на Radiant у меня возник вопрос с подсветкой кода, который у меня встречается достаточно часто. Сейчас я использую для этого highlight.js, однако рассматриваю возможность использования серверных решений.</p>
<p>Так что, сегодня я расскажу о трех различных Ruby-библиотеках, используемых для подсветки синтаксиса:</p>
<ul>
	<li><a href="http://blog.alno.name/2009/02/code-highlighters-ruby/#syntax">Syntax</a></li>
	<li><a href="http://blog.alno.name/2009/02/code-highlighters-ruby/#ultraviolet">UltraViolet</a></li>
	<li><a href="http://blog.alno.name/2009/02/code-highlighters-ruby/#coderay">CodeRay</a></li>
</ul>
<p>Также, я провел замеры производительности, так что в конце вы можете найти <a href="http://blog.alno.name/2009/02/code-highlighters-ruby/#performance">сравнение библиотек по производительности</a>.</p>]]></description>
  <content:encoded><![CDATA[
    <p>В связи с переводом моего блога на Radiant у меня возник вопрос с подсветкой кода, который у меня встречается достаточно часто. Сейчас я использую для этого highlight.js, однако рассматриваю возможность использования серверных решений.</p>
<p>Так что, сегодня я расскажу о трех различных Ruby-библиотеках, используемых для подсветки синтаксиса:</p>
<ul>
	<li><a href="http://blog.alno.name/2009/02/code-highlighters-ruby/#syntax">Syntax</a></li>
	<li><a href="http://blog.alno.name/2009/02/code-highlighters-ruby/#ultraviolet">UltraViolet</a></li>
	<li><a href="http://blog.alno.name/2009/02/code-highlighters-ruby/#coderay">CodeRay</a></li>
</ul>
<p>Также, я провел замеры производительности, так что в конце вы можете найти <a href="http://blog.alno.name/2009/02/code-highlighters-ruby/#performance">сравнение библиотек по производительности</a>.</p><h2>Syntax<br />
<a name="syntax"></a></h2>
<p>
<p><strong>Страница проекта</strong>: <a href="http://syntax.rubyforge.org/" rel="nofollow">http://syntax.rubyforge.org/</a><br />
<strong>Поддерживаемые языки</strong>: Ruby, <span class="caps">XML</span>, <span class="caps">YAML</span><br />
<strong>Плюсы</strong>: Существует плагин для Rails и описание, как интегрировать его в Radiant.<br />
<strong>Минусы</strong>: Малое количество поддерживаемых языков и отсутствие готовых тем.</p>
</p>
<p>
<p>Установка:</p>
<pre><code class="bash">gem install syntax</code></pre>
<p>Простейший пример использования:<br />
<pre><code class="ruby">require 'rubygems'
require 'syntax/convertors/html'</p>
<p>convertor = Syntax::Convertors::<span class="caps">HTML</span>.for_syntax &#8220;ruby&#8221;<br />
html = convertor.convert( File.read( &#8220;program.rb&#8221; ) )</p>
<p>puts html<br />
</code></pre></p>
</p>
<p>
<p>Окромя возможности подсветки кода, библиотека предоставляет возможность просто разобрать текст на лексемы, с тем чтобы производить потом какую-то обработку:</p>
<pre><code class="ruby">require 'syntax'

tokenizer = Syntax.load "ruby"
tokenizer.tokenize( File.read( "file.rb" ) ) do |token|
  puts "group(#{token.group}, #{token.instruction}) lexeme(#{token})"
end
</code></pre>
</p>
<p>
<p>P.S: Буквально перед публикацией нашел <a href="http://rpheath.com/posts/356-github-theme-for-syntax-gem" rel="nofollow">тему</a> для библиотеки, осуществляющую подсветку как на GitHub.</p>
</p>
<h2>UltraViolet<br />
<a name="ultraviolet"></a></h2>
<p>
<p><strong>Страница проекта</strong>: <a href="http://ultraviolet.rubyforge.org/" rel="nofollow">http://ultraviolet.rubyforge.org/</a><br />
<strong>Поддерживаемые языки</strong>: C, C++, Ruby, Bibtex, Latex, Diff, <span class="caps">HTML</span>, <span class="caps">CSS</span> и многие другие<br />
<strong>Плюсы</strong>: Использует файлы синтаксиса TextMate для подсветки, что сразу дает большое количество поддерживаемых языков.<br />
<strong>Минусы</strong>: Документация на сайте достаточно скудная. При попытке копирования кода со страницы он копируется вместе с номерами строк (если они сгенерированы). Тянет за собой достаточно много зависимостей и медленней всех работает.<br /></p>
</p>
<p>
<p>Установка:</p>
<pre><code class="bash">gem install ultraviolet</code></pre>
<p>При установке у меня возникла одна проблема: Ultraviolet тянет за собой гем Oniguruma, который требует, чтобы в системе была установлена библиотека Oniguruma, причем вместе с заголовочными файлами. В Ubuntu ее можно установить из пакета:<br />
<pre><code class="bash">sudo apt-get install libonig2 libonig-dev</code></pre></p>
<p>Для остальных &#8211; неплохое руководство по установке <a href="http://snippets.aktagon.com/snippets/61-Installing-Ultraviolet-and-Oniguruma" rel="nofollow">приведено здесь</a>.</p>
<p>Пример использования:</p>
<pre><code class="ruby">require 'rubygems'
require 'uv'

result = Uv.parse( text, "xhtml", "ruby", true, "amy")
</code></pre>
<p>Передаваемые в функцию параметры:<br />
<ol><br />
  <li>text &#8211; исходный код</li><br />
  <li>&#8220;xhtml&#8221; &#8211; выходной синтаксис</li><br />
  <li>&#8220;ruby&#8221; &#8211; входной язык</li><br />
  <li>true &#8211; рендерить номера строк</li><br />
  <li>&#8220;amy&#8221; &#8211; тема</li></p>
</ol>
</p>
<h2>CodeRay<br />
<a name="coderay"></a></h2>
<p>
<p><strong>Страница проекта</strong>: <a href="http://coderay.rubychan.de/" rel="nofollow">http://coderay.rubychan.de/</a><br />
<strong>Поддерживаемые языки</strong>: Ruby, C, Delphi, <span class="caps">HTML</span>, <span class="caps">RHTML</span> (Rails), Nitro-<span class="caps">XHTML</span>, <span class="caps">CSS</span>, Diff, Java, JavaScript, <span class="caps">JSON</span>, <span class="caps">YAML</span><br />
<strong>Плюсы</strong>: Судя по моим замерам работает быстрее всех. Имеет достаточно большое число опций, позволяющих настроить вывод.<br />
<strong>Минусы</strong>: Встроенные темы выглядят немного бедновато, особенно применительно к C и Java.</p>
</p>
<p>
<p>Установка:<br />
<pre><code class="bash">gem install coderay</code></pre></p>
<p>Пример использования:<br />
<pre><code class="ruby">require 'rubygems'
require 'coderay'</p>
<p>tokens = CodeRay.scan( text, :ruby )<br />
print tokens.div( :line_numbers =&gt; :table, :css =&gt; :class, :style =&gt; :cycnus )<br />
</code></pre></p>
<p>Методы преобразования (div,html,..) принимают хэш аргументов:<br />
<ul><br />
  <li>:tab_width &#8211; ширина табуляции в проблеах. По умолчанию: 8</li><br />
  <li>:css &#8211; как включать стили: указывая класс или явный стиль (:class и :style). По умолчанию: :class)</li><br />
  <li>:wrap &#8211; обернуть результат в тэг :page, :div, :span или не оборачивать (nil)</li><br />
  <li>:line_numbers &#8211; как включать номера строк (:table, :inline, :list или nil)</li><br />
  <li>:line_number_start &#8211; с какого номера строки начинать</li><br />
  <li>:bold_every &#8211; выделять каждый n-й номер строки жирным. По умолчанию: 10</li></p>
</ul>
<p>CodeRay, также как и Syntax может использоваться для анализа исходного текста, поскольку полученный объект Tokens представляет из себя по сути список лексем с заданными типами.</p>
</p>
<h2>Сравнение производительности<br />
<a name="performance"></a></h2>
<p>
<p>Ниже приведены замеры производительности библиотек при обработке фргаментов кода на Ruby и Xml:<br /></p>
<pre>
                          user     system      total        real
ruby ultraviolet:    10.270000   0.600000  10.870000 ( 11.584088)
ruby coderay:         0.710000   0.030000   0.740000 (  0.923616)
ruby syntax:          2.030000   0.130000   2.160000 (  2.830979)
xml ultraviolet:      3.280000   0.190000   3.470000 (  3.793856)
xml coderay:          0.540000   0.000000   0.540000 (  0.701140)
xml syntax:           0.660000   0.060000   0.720000 (  0.854120)
</pre>
<p>А также график:</p></p>
<p style="texta-align:center;"><img src="/assets/14/rch-performance.png"  alt='ruby-code-highligters-performance' /></p>
<p>
<p>Можно увидеть, что самым шустрым оказался CodeRay, а UltraViolet самым медленным. Однако, как мне кажется, некоторая тормознутость UltraViolet окупается наличием огромного количества поддерживаемых языков и готовых тем.</p>
</p>
<h2>Ссылки</h2>
<ul>
 <li><a href="http://syntax.rubyforge.org/" rel="nofollow">Syntax (En)</a></li>
 <li><a href="http://ultraviolet.rubyforge.org/" rel="nofollow">Ultraviolet (En)</a></li>
 <li><a href="http://coderay.rubychan.de/" rel="nofollow">Coderay (En)</a></li>
 <li><a href="http://www.railslodge.com/ruby_gems/7-syntax" rel="nofollow">Syntax Gem (En)</a></li>
 <li><a href="http://obvio171.wordpress.com/2007/06/03/colorful-ruby-code-for-your-blog/" rel="nofollow">Colorful Ruby code for your blog (En)</a></li>
</ul>
      

      
  ]]></content:encoded>
</item>

</channel></rss>
