18 ноября 2010

немного стиля Android виджетам!

Вчера коллега спросил, как бы поменять "эти ужасные зелёные ползунки у SeekBar'а в оболочке HTC Sense"? Сегодня я над этим немного поразмыслил, благо недавно как раз заглядывал в эту тему.
Начать, пожалуй, стоит с ознакомления со стилями Android'а. Увы, в редкой книге поднимается эта тема, так что можно почитать про стили в документации и обратить внимание на встроенные стили. Во встроенных есть, например, такой фрагмент:

<style name="Widget.SeekBar">
<item name="android:indeterminateOnly">false</item>
<item name="android:progressDrawable">@android:drawable/progress_horizontal</item>
<item name="android:indeterminateDrawable">@android:drawable/progress_horizontal</item>
<item name="android:minHeight">20dip</item>
<item name="android:maxHeight">20dip</item>
<item name="android:thumb">@android:drawable/seek_thumb</item>
<item name="android:thumbOffset">8px</item>
<item name="android:focusable">true</item>
</style>

В нём и задаётся стиль для SeekBar'а. Значит, точно так же мы можем его переопределить. Как показывает вскрытие, @android:drawable/progress_horizontal есть фигура, описанная в xml (исходник, за ленью, выковыривать не стал), сделаем свою:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke android:width="1px" android:color="#FFF" />
<corners android:radius="9dip"/>

<gradient
android:angle="270"
android:endColor="#CCE2001A"
android:startColor="#CCA10013"
android:type="linear"/>
</shape>

Красный градиент в белой рамке.
Создадим стиль для виджета:
    <style name="MySeekBar">
<item name="android:indeterminateOnly">false</item>
<item name="android:progressDrawable">@drawable/seekbar</item>
<item name="android:indeterminateDrawable">@drawable/seekbar</item>
<item name="android:minHeight">20dip</item>
<item name="android:maxHeight">20dip</item>
<item name="android:thumbOffset">8px</item>
<item name="android:focusable">true</item>
</style>

и применим его на живом экземпляре:
<SeekBar
style="@style/MySeekBar"
android:id="@+id/screenControlVolume"
android:layout_width="150dip"
android:layout_height="wrap_content"
android:paddingLeft="10dip"
android:paddingRight="10dip"
android:layout_marginBottom="10dip"/>

Итог мучений (внизу - стандартный, не HTC):

Ну и, надо полагать, очевидно, что можно расширить и углубить мысль и применять этот подход почти ко всему.
UPD:
Как говорил мой завкафедры, показывая пальцем на голову, "Вы никогда не сможете перестать работать". И уже улёгшись спать, я вспомнил, что полоска-то в моём SeekBar'е одинакового цвета что слева от указателя позиции, что справа. Непорядок, который я сегодня исправляю. Итак, всё-таки расковыряв исходник android-варианта фигуры этой полоски, переписал свою версию:
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<stroke android:width="1px" android:color="@color/widgetBorder" />
<corners android:radius="9dip"/>

<gradient
android:angle="270"
android:endColor="@color/widgetBorder"
android:startColor="@color/widgetBorder"
android:type="linear"/>
</shape>
</item>

<item android:id="@android:id/progress">
<clip>
<shape>
<stroke android:width="1px" android:color="@color/widgetBorder" />
<corners android:radius="9dip"/>

<gradient
android:angle="270"
android:endColor="@color/gradientWidgetEndColor"
android:startColor="@color/gradientWidgetStartColor"
android:type="linear"/>
</shape>
</clip>
</item>
</layer-list>

тут уже список слоёв с 2 (в оригинале 3, зачем третья можете подумать на досуге), в которых могут быть разные фигуры, в моём случае - всё те же скруглённые градиентные прямоугольники, верхний слой обрезан - это левая часть. Ну и вот как это выглядит в моём проекте:

14 ноября 2010

Ну и опять про браузеры. И андроид.

Просто для поднятия настроения, раз уж я тут картинки разбираю. Документация Google Android в Google Chrome и Opera (версии последниие). И не говорите мне после этого ничего, и не осуждайте выбор. На самом android'е, кстати, встроенный браузер тоже слажал немного, не показав нижнюю левую панель, Opera Mobile справилась (хотя и были неточности).

13 ноября 2010

Gradients in Android

Построение интерфейса Android'а через xml - это сплошные преимущества, куда не посмотри. Только привыкнуть и научиться надо (конечно, на это надо время, которого никогда нет, поэтому этот рабочий пост написан в выходной).

Вот пример - описав на xml радиальный градиент, используемый в качестве фона приложения, сократил размер выходного пакета почти в 3 раза за счёт того, что выкинул картинки с фоном. Осталось только с дизайнером сесть и цвета поправить.

Ну и чтоб два раза не вставать, вот так можно ескейпить код в HTML:
alias -g E='| ruby -rcgi -e "ARGF.each{|line| puts CGI.escapeHTML line}"'
это на zsh.

XML:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:centerX="0.5"
android:centerY="0.5"
android:endColor="#023F8F"
android:gradientRadius="300"
android:startColor="#1A96CA"
android:type="radial"
/>
</shape>


До и после:

24 октября 2010

Линковка в Mac OS X

Симптомы

ld: in lib_foo.a, archive has no table of contents
collect2: ld returned 1 exit status

Лечение
export COMMAND_MODE=unix2003

ещё можно почитать man compat и убедиться, что врут, негодяи:
if it is unset or set to something other than legacy or unix2003 it behaves as if it were set to unix2003

чего ради оно сломалось вдруг - непонятно, но раньше работало и без этих костылей.

22 октября 2010

уходи, iTunes, уходи!

Чтобы угомонить вконец обнаглевшего уродливого толстяка iTunes, возомнившего себя единственным на свете плеером и намертво присосавшегося к аппаратной кнопке Play/Pause моего макбука после обновления оного до сноулео, достаточно сделать так:

sudo chmod -x /Applications/iTunes.app/Contents/MacOS/iTunes

а для редких случаев, когда iTunes всё-таки нужен, сделать в Automator приложение с простым шелл-скриптом:
sudo chmod +x /Applications/iTunes.app/Contents/MacOS/iTunes
/Applications/iTunes.app/Contents/MacOS/iTunes &
sudo chmod -x /Applications/iTunes.app/Contents/MacOS/iTunes
Это я после очередного апдейта вспомнил, записываю чтоб не забыть.
UPD:
После того, как в очередной раз пришлось курощать iTunes, скрипт перестал работать, то есть работал, но iTunes продолжала сетовать на отсутствие лайона. Рабочий вариант ниже:
sudo chmod +x /Applications/iTunes.app/Contents/MacOS/iTunes
open -a iTunes &
sleep 5
sudo chmod -x /Applications/iTunes.app/Contents/MacOS/iTunes

20 октября 2010

Yet another Vim blog post (YAVBP)

За последние пару недель как-то много вокруг меня стало инфоповодов на тему Vim. Вот например прекрасная статья о том, почему его надо использовать (очень толково, просто, есть про настройку с описанием).
Так что я усиленно постился^Wпилил конфиг, запрягал Vim работать в паре с Elipse для работы над Android проектом (всё на удивление просто - последний Elipse, последний MacVim или GVim и Eclim), собрался с духом, засунул наконец все конфиги в git и отправил на GitHub. Конфигурация получилась лёгкой, из неё было выброшены все вековые наслоения конфигов, ненужных плагинов и прочей чуши. Ещё там есть маленький враппер для открывания файла в новой вкладке существующего Vim'а а не в новом экземпляре Vim'а. Так что полюбопытствуйте.
Впрочем, скорее всего, разработку под андроид вскоре можно будет отдать на откуп IntelliJ Idea X EAP, в которой есть и поддержка оной и плагин-эмулятор Vim'а. Попробовал, повертел - за час не возникло серьёзных претензий (да и вообще продукты JetBrains - это что-то, как и их поддержка), разве что не сразу понятно куда там коней^WSDK запрягать. Во всяком случае решение лучше Eclipse.

27 сентября 2010

Как заставить работать Force Close Dialog на себя

... или Stacktrace or GTFO.

Задача.
Итак, первое приложение для Android написано и его бета достигла девайсов пользователей. Задача - при получении исчерпывающего сообщения от пользователя "Ничего не работает" (ну ладно, ладно, более конкретного - "Вылетает") знать, где и что не работает. Вообще это должно быть первым, что будет написано в приложении. Далее будет показано, как гарантированно поймать исключение и послать отчёт о нём в Redmine.

Примечание: большая часть информации относится скорее к Java, чем к Андроиду, однако повод и место применения намекают.

Стратегия.
Приложение совершило критический промах и укусило себя в глаз. Частный и самый распространённый случай этого - исключение (есть и другие варианты, я сталкивался с перезагрузкой телефона), которое принято отлавливать и что-то делать по факту. Если оно не отловлено - пользователь увидит т.н. Force Close Dialog. Начиная с версии 2.2 ещё и предложение собрать данные об ошибке и поспамить ими девелопера, НО надо понимать, что
1. специфика некоторых ошибок привязана к определённым моделям девайсов, на которых вполне определённые версии Андроида, и это не 2.2
2. специфика некоторых рынков в популярности определённых моделей девайсов, см. выше
так что на этот способ не рассчитываем (как на основной).

Необработанное исключение нужно прежде всего отловить. Для этого в Java есть обработчик исключений потока, Thread.UncaughtExceptionHandler, реализация Андроида как раз и показывает диалог и пишет стектрейс в LogCat. С отловленным исключением нужно что-то сделать (узнать о самом факте его возникновения) и... в случае с UI Thread Андроида отпустить дальше - передать умолчальной реализации обработчика, потому что в противном случае, если исключение утаить, получим вместо одного диалога об ошибке другой (Application Not Responding). Кроме того, пользователю тоже особо ничего сказать не получится - UI Thread-то уже загнулся.



public class CrashReporter implements UncaughtExceptionHandler {
private UncaughtExceptionHandler defaultHandler;
public CrashReporter(UncaughtExceptionHandler defaultHandler)
{
this.defaultHandler = defaultHandler;
}

public void uncaughtException(Thread thread, Throwable e) {
Log.e("CrashReporter", "Caught " + e.toString() + " in "
+ thread.toString() + "\n" + Log.getStackTraceString(e));
// allow system to kill us
if(defaultHandler != null)
defaultHandler.uncaughtException(thread, e);
}
}



На основе этого бесполезного пока (потом будет интереснее) класса и будет строится обработка ошибок.

Тактика. Андроид.

Поскольку UI Thread в Андроиде один на всех, то получить глобальную обработку ошибок очень просто, в главной Activity достаточно при создании указать этот класс как обработчик:

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread.currentThread().setUncaughtExceptionHandler(
new CrashReporter(Thread.currentThread().getUncaughtExceptionHandler()));
}


Тактика. Продвинутые техники.
Однако не всё делается в UI потоке Андроида, тяжёлые вычисления требуется вынести за его пределы. В Андроиде есть AsyncTask, который мне не подошёл (да и вообще на мой взгляд подходит только для простейших вещей), я использую ThreadPoolExecutor. Чтобы создаваемые для него потоки имели нужный класс обработчика ошибок, придётся создать свою фабрику (да, вот он, Java way) и передавать её в конструктор ThreadPoolExecutor'а:


public class CrashReportingThreadFactory implements ThreadFactory{
public Thread newThread(Runnable r) {
Thread newThread = Executors.defaultThreadFactory().newThread(r);
newThread.setUncaughtExceptionHandler(
new CrashReporter(newThread.getUncaughtExceptionHandler()));
return newThread;
}
}


public class CrashExecutor extends ThreadPoolExecutor {
private CrashExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
new CrashReportingThreadFactory());
}


Но это полдела. Теперь хлопок другой ладонью - надо заставить Executor обрабатывать исключения, возникшие в задачах, в т.ч. отправленных на выполнение вызовом submit и/или обёрнутые в класс типа FutureTask (за объяснениями - в доки к ThreadPoolExecutor.afterExecute и литературу):



protected void afterExecute (Runnable r, Throwable t)
{
super.afterExecute(r, t);
// задача отправлена на выполнение через submit или изначально является Future
if (t == null && r instanceof Future) {
try {
// принудительный вызов исключения
((Future<?>) r).get();
} catch (CancellationException ce) {
// отмена задачи - нормальное событие
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
// прерывание потока извне - передача исключения выше
Thread.currentThread().interrupt(); // ignore/reset
}
}
if (t != null) {
Thread thread = Thread.currentThread();
thread.getUncaughtExceptionHandler().uncaughtException(thread, t);
}
}

Так задача гарантированно будет вызывать исключение (из-за вызова get()) а исключения от Runnable не будут проглатываться.

Тактика. Redmine.

Всё, исключение есть, пора настроить Redmine:
1. Создать роль типа Crash reporter, имеющую право только создавать issue
2. учётную запись с этой ролью
3. набор дополнительных полей для Issue - например номер билда, тип девайса, на котором произошло падение
4. добавить учётку репортера в проект
5. разрешить REST web service
6. дополнительно можно завести под это свой тип трекера, чтобы отделять автоматически созданные issue

теперь можно постить:


// post reports only from wild (end-user) devices
if(!ApplicationInfo.getInstance().isAppDebuggable()){
try {
StringBuilder postBody = new StringBuilder()
.append("<issue><project_id>20</project_id><subject>")
.append(e.toString())
.append("</subject><tracker_id>4</tracker_id><priority_id>10</priority_id><custom_field_values><1>")
.append(Uri.decode(Settings.deviceInfo))
.append("</1><2>")
.append(ApplicationInfo.getInstance().deviceuid)
.append("</2><3>")
.append(String.valueOf(ApplicationInfo.getInstance().getAppBuild()))
.append("</3></custom_field_values><description><![CDATA[")
.append(Log.getStackTraceString(e).replaceAll("]]>","]]>]]><![CDATA["))
.append("]]></description></issue>");
post("http://redmine.company.com/issues.xml", postBody.toString());
} catch (Exception e1) {/*silently die*/}
}

public static void post(String url, String postData) throws IOException {
HttpClient client = new DefaultHttpClient();
HttpPost request = new HttpPost(url);
request.addHeader(BasicScheme.authenticate(
new UsernamePasswordCredentials("crash", "password"), "latin1", false));
request.addHeader("Content-Type", "application/xml");
StringEntity se = new StringEntity(postData);
request.setEntity(se);
HttpResponse response = client.execute(request);
}


ApplicationInfo - собственный класс, который собирает основную инфу об устройстве. Упоминаемые в xml ID можно узнать если посмотреть на ссылки соответствующих вещей - трекера, дополнительных полей, проекта.

Можно собирать релизный билд. Главное - не забыть убрать логирование и возможность отладки (удивительно, как много приложений, в которыx это не сделано).

09 июля 2010

Shell hint - end task notification

Запустил долгую задачу, а когда кончится - непонятно, мониторинг активности консоли (как в Konsole или iTerm) тоже не помогает - процесс иногда обделывается выводом в STDERR, что делать?

^z
fg; say "Job done!"

Это для маков, но идея проста - отправить задачу в сон, вернуть обратно, по окончании отчитаться (линуксоиды могут прослушать ядро).

28 июня 2010

Старший onchange наблюдает за тобой

У dom элемента есть события. Среди них есть onchange. Если я делаю

var onchange = function() {/* do here something*/};

то не странно ли присобачивать эту функцию _ко всем контролам ввода_ на странице? Они вообще слышали про пространства имён? Или это умышленно так?

11 мая 2010

jqGrid custom date picker

Да, давно не писал, но это жизнь, которой мало, и работа, которой много. Вот по работе иногда приходится хлопать одной ладонью.

Так и на прошлой неделе - нужно было приделать к редактору модели в jqGrid свой элемент для редактирования дат, наподобие того, что генерят рельсы в ответ на select_date. Скудость документации и отсутствие толковых примеров и как следствие полдня экспериментов на пути к желаемому и стали причинами появления данного поста.

Для начала обратимся к истокам и объявим наше поле как custom, указав свои функции для создания элемента и извлечения значения из него.
{name:'v_birth', index:'v_birth',formoptions:{elmprefix:"* &nbsp"},edittype:'custom',editable:true,editoptions:{custom_element:dinput, custom_value:dvalue}}


Дальше немного JS магии.



Создаём, заполняем значениями с выбором одного из них, и возвращаем массивом три select'а и кнопку сброса.

Подразумевается, что в таблицу дата приходит в американском формате (mm-dd-yyyy), именно поэтому у меня поле называется v_birth - это виртуальный атрибут, в базе это естественно тип Date. О валидации таких атрибутов и их применении совместно с jqGrid стоит сказать, пожалуй, отдельно, своим постом.

Пользователь увидит три селекта и кнопку сброса их в начальное значение (--). Браузер - невалидный код (три селекта с одинаковым id), но промолчит. Функция извлечения значения благодаря этому увидит все три select'а, отмапит их в массив выбранных значений и склеит из него строку. Бэкенд - либо опять же дату в американском формате, либо пустую строку, если пользователь сбросил дату, либо неверную дату, и может сохранить, об NULL'ить или остановить валидацию на своё усмотрение.

И для завершения - ещё один штрих. Истоки.

...
.navGrid('#my_pager',
{edit:true,add:true,del:true,search:false,refresh:true},
{recreateForm:true, afterSubmit:function(r,data){return afterSubmit(r,data,'edit');}},
....
Обратите внимание на recreateForm:true. С этой опцией элемент будет создаваться каждый раз при открытии формы редактирования и устанавливать начальные значения из нужной модели, а не из первой, для которой он был вызван.
А теперь отмазки. JS код грязноват и неоптимален, но я иррационально не люблю JS. Кроме того, на самом деле я использую 2dcJqgrid Rails plugin, но уже сильно модифицированный (оригинал бы не позволил сделать описываемое), так что привожу JS код для создания таблицы.

Такие вот пироги. Надеюсь, юбилейная 200-я запись была вам полезна.

13 января 2010

Коммить это!

Очень короткое наблюдение. Вчера заметил, что мой мозг отказывается думать над продолжением работы, потому что предыдущий цельный кусок сделан, но не скоммичен.

21 ноября 2009

Док

Нашёл для себя идеальный вариант дока - не показывать ничего кроме запущенных программ (и запускать их через Namely). И пару папок с документами (ебуками) ибо для быстрого открытия документов индексаторы типа Spotlight ещё не годятся совсем (о, где же ты, krunner). Хотя, казалось бы, какого чёрта, на кой в системе тогда есть locate?
UPD: нашёл такую штуку как Butler. Как минимум fuzzy-find и запуск документов (да и вообще всего) он умеет, что уже покрывает мои потребности, а там столько всего...

16 ноября 2009

Preview, Opera, whatever and Dictionary.app

Недавно возмущался тем, что в самых нужных местах не работает поиск выделенного текста по хоткею с помощью стандартного Dictionary.app. Не работало это в частности в Preview (так и хочется опять пнуть Эппл, оба приложени их) и в Opera (тут стоит пнуть саму Оперу - интеграция с макосью у них так себе, более того, в контекстном меню пункт "Поискать в словаре" загружает словари Яндекса, нужны они сто лет с таким latency).
А сегодня внезапно нашёл решение, оно как всегда в стиле Эппл - то есть несколько сбоку (читай через задницу). В меню каждого приложения есть пункт Services -> Look Up in Dictionary, этим и воспользуемся, дальше понятно без слов:


13 ноября 2009

Гоню на макось.

Давно заметка валяется в черновиках, пора бы уже запостить, а то вдруг на сно-лепарда обновлюсь. Вкратце - тут я буду всяко гнать на макось. Это не значит, что я не вижу в ней ничего хорошего.

  1. Нет буфера выделения.
  2. Нельзя потаскать окно мышкой при зажатом Alt'е (ну вот тут коллега пишет костыль для этого)
  3. Зато на Alt + letter забиндены все возможные Deadletters - чувствуешь себя в mc инвалидом, пока не вспомнишь, что можно юзать Esc
  4. ФМ ортодоксальных толковых НЕТ. Кроме mc, ну вы поняли
  5. Нет централизованного пакетного менеджмента, а макпорты и финк - говнище, каждый по-своему. Есть ещё Homebrew, идея мне нравится, но тоже хрень пока - очень молод. В общем ничего лучше портежей ещё я не видел.
  6. Как следствие культура "всё с собой" для бандлов (думайте про пакеты со всеми зависимостями внутри. тысячи их [зависимостей] !)
  7. Как следствие культура "всё с собой" для ОС - приготовьтесь работать с почерствевшими версиями языков и библиотек. Хорошо, что я тут root, посносил всё старьё нафиг.
  8. Мало крутилок. Мало! А те, что есть, по умолчанию выставлены в идиотские положения
  9. Некоторые скучают по тайловым оконным менеджерам, да и вообще оконный менеджмент тут слабоват.
  10. Жестокие и беспощадные вариации на тему программ. iTunes на макбуке про (про!) стартует так медленно, что можно уснуть и без колыбельной на ночь
  11. А держать всё в памяти не позволяет то, что всё дико свопится. На домашнем вообще своп отключен, используется в лучшем случае гигабайт памяти. Тут же менее гигабайта занятым не видел даже после старта.
  12. Обновления. Приехало обновление - отвалилась нативная версия игры, которая до того работала как часы, не упав ни разу. И никакого вам отката - нефиг. Да даже в винде какие-то там обновления присутствовали в Установке и удалении программ, как бы намекая нам, что их можно удалить (на минутку, в винде икспи, вышедшей в каком году? давно в общем). Ну а недавно вышедший унылый леопард уже давно стал притчей во езицех, утерев нос даже майкрософту с их вистой.
  13. Каждое рабочее утро начинаем с упражнения "Настрой-ка снова второй монитор".
  14. Каждый приход домой - с упражнения "найди ручками и подключи нестандартно настроенный вайфай" (в связи с некоторыми событиями это упражнение заменилось другим, см. ниже)
  15. Накрылся недавно домашний сервер, стал интернет раздавать с макбука. Ежечасное упражнение "воткни обратно непонятно куда девшуюся галочку Раздавать инет". И после этого они ещё выпускают какой-то там макосикс сервер. Отпавший PPPoE вроде научился переподнимать, да и то не всегда.
  16. Всякие разные спорадические глюки, чего только стоят внезапно отрубившиеся клава и тачпад макбука при работяющих как ни в чём ни бывало внешних мышке и клаве. Всего не вспомнишь.

  17. Пилить под себя никто не отменял, не факт, что я потратил на это времени меньше, чем потратил бы на свежей генте или там убунте.


Помидоры оставьте при себе, без конструктива не входить.

JSON, YAML, whatever...schema

Не так давно прикрутил к проекту валидатор схемы JSON документа. По ссылке всякая документация и примеры.
Зачем оно надо?

  1. Для спецификации. Причём спека может быть написана хоть на YAML, хоть на JSON, хоть на чистом ruby - валидатор схемы создаётся из ruby-объекта. Так что перед тем как пистаь очередной протокол обмена чего-то с чем-то через JSON можно написать его спецификацию на том же JSON'е и избежать разночтений (свежа в памяти фраза коллеги - не, так [по разные стороны протокола] мы работать не будем, просто не будем друг друга слышать и будем писать несовместимых продюсеров и консюмеров).
  2. Для валидации, как ни странно. До валидатора у меня была хитроумная система проверки с привлечением всех хитрых штук динамического программирования в ruby. Сейчас нет. Защита от дурака, особенно полезная в случае если потом таким образом будет кому-то предоставлено API.

В общем, на мой взгляд, с какой стороны не подойди - исполняемая (не просто где-то записанная, а исполняемая) спецификация - полезная в хозяйстве вещь.

24 октября 2009

C/C++/Obj-C closures

Не так давно решил таки ознакомиться (никак не мигрировать, боже упаси) на представленный в MacOS X 10.6 GCD, почитал их введение в это дело (ровно одна строчка кода, всё остальное - маркетоидный буллщит), но одна фраза заставила на себя обратить внимание - основной компонент этого GCD - блоки - что-то подозрительно напоминали. И добрался до расшаренной коллегой статьи про реализацию блоков, и понял - ну точно, это ж самые что ни на есть closures, которым скоро 50 лет, их наконец-то добавят и в эти языки... Однако, всё сильнее и сильнее этот лагерь офункционаливается.
Со статьёй крайне рекомендую ознакомиться, а потом по ссылкам в конце. Особенно по первой, там чуть ли не дословно цитируют ruby stdlib и часть парадигм RoR и вообще рубишных фреймворков.
Однако в руби их использование гораздо более синтаксически-слаще...

12 октября 2009

Cache money warm-up

С недавнего времени использую в своём проекте Cache money. Вещь хорошая, но, конечно же, есть и недостатки, впрочем, это предмет для форка и багфиксов. Стоит также отметить, что решение может уже и существует среди форков, коих много.
Если у вас всё в порядке с памятью (RAM, I mean), то может возникнуть желание принудительно заполнить кеш по всем индексам всех моделей. Сниппет извлечён из моего Rakefile'а и адаптирован для использования в рельсах. Предупреждение: не тестировалось именно в рельсах и против составных индексов (хотя и предусматривает их использование).

27 августа 2009

Using DRb

Сегодня ночью понял, что вслед за книгой the Ruby way дал маху и имел лишний вызов DRb.start_service на клиенте. Если почитать документацию, то в комментариях сэмпл кода выясняется, что это требуется далеко не всегда и уже в доке к классу DRbServer выясняются детали. А ведь пока в сорец не залез - не понял. А это устранило единственный крупный bottleneck в приложении.
Вот и читай после этого полезные книги.

10 августа 2009

Camping 1.9, Passenger & ruby 1.9

Стремление найти себе на разные места приключений никогда не покидает таких как я, вот и захотелось завести текущий проект под ruby 1.9.1 и перевести под passenger.
Пара слов - по общему моему впечатлению, мир руби не готов ещё использовать 1.9.1. То одно, то другое не работает, приходится хачить уже написанное, вставлять проверки версии языка, что-то вообще не лечится (так, у меня не завёлся монгрел, даже после советов с isitruby19.com)
Начнём с самого ruby. Даже на InfoQ засветился скрипт для установки и переключения разных версий Ruby: ruby_swither.
Однако для моей цели пришлось внести в него поравки в секции для 1.9.1 (OMG, опять на одну маленькую строчку я убил полдня):

 export GEM_HOME=~/.ruby_versions/ruby-1.9.1-p129/lib/ruby/gems/1.9.1
Дальше всё привычно - ставим гемы, ставим camping:
gem install camping --source http://gems.judofyr.net
Сам camping придётся поправить, вот патчик:
--- /Users/phoenix/projects/camping/lib/camping.rb      2009-07-05 00:35:46.000000000 +0600
+++ /Users/phoenix/.ruby_versions/ruby-1.9.1-p129/lib/ruby/gems/1.9.1/gems/camping-1.9.316/lib/camping.rb       2009-08-09 18:07:48.000000000 +0600
@@ -3,12 +3,12 @@
 S=IO.read(__FILE__)rescue nil;P="<h1>Cam\ping Problem!</h1><h2>%s</h2>"
 U=Rack::Utils;Apps=[];class H<Hash
 def method_missing m,*a;m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m.to_s]:super end
-undef id,type;end;module Helpers;def R c,*g
+undef id,type if respond_to? :id;end;module Helpers;def R c,*g
 p,h=/\(.+?\)/,g.grep(Hash);g-=h;raise"bad route"unless u=c.urls.find{|x|
 break x if x.scan(p).size==g.size&&/^#{x}\/?$/=~(x=g.inject(x){|x,a|
 x.sub p,U.escape((a[a.class.primary_key]rescue a))})}
 h.any?? u+"?"+U.build_query(h[0]):u end;def / p
-p[0]==?/?@root+p:p end;def URL c='/',*a;c=R(c, *a) if c.respond_to?:urls
+p[0]==?/?@root+p : p end;def URL c='/',*a;c=R(c, *a) if c.respond_to?:urls
 c=self/c;c=@request.url[/.{8,}?(?=\/)/]+c if c[0]==?/;URI c end
 end;module Base;attr_accessor:input,:cookies,:headers,:body,:status,:root
 M=proc{|_,o,n|o.merge(n,&M)}
В прошлый раз я дал маху, заявив, что новые версии camping под Passenger не идут - исправляюсь. config.ru:

ENV['GEM_HOME'] = '/Users/phoenix/.ruby_versions/ruby-1.9.1-p129/lib/ruby/gems/1.9.1'
require 'rubygems'
require 'camping'
$: << ::File.expand_path(::File.dirname(__FILE__))
require 'bin/app'

App.create if App.respond_to? :create

run App

Удачного полёта!

07 августа 2009

Делегирование методов контроллеров в Camping

Потребовалось мне недавно написать обёртку ко всем существующим ныне в приложении контроллерам, дабы их можно было вызывать через единую точку входа. Именно обёртку, поскольку всё к чертям переписывать не хотелось принципиально да и старый механизм работы мне бы самому пригодился. После пятнично-вечернего воскуривания сорцов Camping'а я это сделал (<ненависть>сорцы кемпинга - это лютое изнасилование мозга. Пусть этому вроде как и есть оправдание - '4K full of gags pocket framework', но так писать а тем более читать код нельзя</ненависть>). Вот вам примерчик - пусть есть контроллер, который просто возвращает завёрнутые в JSON свои параметры, и есть потребность вызвать его из другого контроллера:

  class Controller
    def post(param1, param2)
      return "{\"#{param1}\":\"#{param2}\"}"
    end
  end

controller_post_responce = App.post(:Controller, 'param1', 'param2').body
При этом вызываемый контроллер унаследует все куки, хедеры, окружение и прочий мусор вызывающего, так что вызываемый контролер и не заметит подвоха и менять в нём ничего не придётся.