Shared rss feeds:

Servare Mentem - blog

Мой предыдущий блог

воскресенье, 31 октября 2010 г.

Phusion Passenger with 2 different ruby versions

Сегодня долго разбирался как же запустить 2 версии Phusion Passenger с 2-мя различными версиями ruby.
Началось всё с того, что мне потребовалось задеплоить приложение на 3-х рельсах на сервер под управлением Ubuntu Server 9.04, где уже был установлен Ruby 1.8.7, который использовался для работы Redmine. Причём версия Redmine была из trunk и активно эксплуатировалась для ведения проектов.
Я подключил к проекту capistrano, настроил его и проверил. Локально всё заработало. А вот при попытке задеплоить приложение на целевой сервер возникли проблемы. Во-первых, мне был нужен Ruby 1.9.2, который я тут же поставил. Во-вторых после деплоя (приложение поднялось) сдох Redmine.
После продолжительных разбирательств, я обнаружил, что виной тому гем i18n. Для 3-х рельсов требуется его версия не ниже 0.4.1, а вот Redmine с этой версией не работает, а работает аж только с 0.3.7. Танцы с бубном вокруг "gem, :version => 0.3.7", "Monkey-патчинга", и даже gem:extract почти ничего толкового не дали. Никак redmine не хотел работать с другой версией i18n (когда я пытался принудительно оставить только 0.3.7). Поэтому я, в конец разочаровавшийся, начал искать пути запуска сервера с 2-мя различными версиями ruby.
Сперва я осознал, что надо установить одновременно 2 версии ruby. Для этого я установил и настроил RVM по этому мануалу.
Redmine на сервере был запущен через Phusion-Passenger-плагин к Apache и я начал выяснять можно ли использовать 2 версии ruby одновременно. Оказалось - через apache это сделать нельзя. Однако, можно установить nginx-версию passenger и использовать её. Сперва я перевёл apache на использование версии Ruby из RVM. Для этого нужно отредактировать файл /etc/apache2/mods-enabled/passenger.load ($RVM нужно заменить путём к rvm в вашем энвайроменте - в моём случае это было /home/smecsia/.rvm).
 LoadModule passenger_module $RVM/gems/ruby-1.8.7-p302/gems/passenger-3.0.0/ext
/apache2/mod_passenger.so
А так же файл /etc/apache2/mods-enabled/passenger.conf
PassengerRoot $RVM/gems/ruby-1.8.7-p302/gems/passenger-3.0.0
PassengerRuby $RVM/wrappers/ruby-1.8.7-p302/ruby
Теперь нужно было установить nginx и passenger-модуль для него. Для этого (как и написано в официальном туториале по Passenger) надо выполнить команду:
passenger-install-nginx-module
После выполнения я быстро понял, что необходимо собрать nginx из исходников. И инсталлятор может сделать это за меня. Я попробовал это сделать, но вскоре стало очевидно, что этот вариант не очень удобен, поскольку запускать nginx пришлось бы вручную - или самому писать скрипт запуска его в качестве демона. И я начал искать варианты установки nginx+passenger из пакетов. Оказалось этот вариант существует:
deb http://apt.brightbox.net hardy main
apt-get install nginx-brightbox
Однако, тут я тоже наткнулся на проблему. Версия из пакетов была не свежей, и несовместимой с версией гема passenger который был у меня установлен. Тогда я решил собрать всё же nginx из исходников, но воспользоваться скриптом его запуска из пакета nginx-brightbox.
mkdir ~/dist/nginx && cd ~/dist/nginx
wget http://sysoev.ru/nginx/nginx-0.8.53.tar.gz
tar zxf nginx-0.8.53.tar.gz && cd nginx-0.8.53
passenger-install-nginx-module
Дальше я выбрал вариант указать исходники nginx вручную (вариант 2) и указал ~/dist/nginx/nginx-0.8.53, затем указал в качестве директории для последующей установки ~/opt/nginx. После компиляции я отредактировал файл ~/opt/nginx/conf/nginx.conf таким образом:
http {
    passenger_root $RVM/gems/ruby-1.9.2-p0/gems/passenger-3.0.0;
    passenger_ruby $RVM/wrappers/ruby-1.9.2-p0/ruby;
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       127.0.0.1:81;
        root /var/www/rails-webapps/myapp/current/public;
        passenger_enabled on;
    }
}
Так же, открыл файл /etc/init.d/nginx и исправил строку DAEMON=/usr/sbin/nginx на путь к собранной версии (~/opt/nginx/sbin/nginx).
Затем я запустил nginx:
sudo /etc/init.d/nginx start
После этого можно настроить прокси из Apache на nginx (на 81 порт) и таким образом получить почти прозрачную возможность использовать 2 версии ruby для своих rails-приложений.

пятница, 29 октября 2010 г.

RubyMine 3.0b, ruby1.9.2 и bundler 1.0.3

Поставил rubymine и столкнулся с проблемой при работе с 3 рельсами и установленным ruby 1.9.2. Рубимайн не хотел загружать список тасков и запускать генераторы, постоянно падал с uninitialized constant Object::Bundler, хотя из консоли всё запускалось прекрасно. Поиск в гугле ничего дельного не дал.
Я попробовал добавить в ~/.bashrc инициализацию переменных окружения GEM_PATH и GEM_HOME. Это тоже не помогло.
Тогда я попробовал прямо в начале Rakefile написать:
require 'rubygems'
gem 'bundler'
Тогда перезагрузка тасков начала падать с ошибкой Cannot find gem bundler. Я прописал в Rakefile вывод пути, откуда грузятся гемы. И с удивлением обнаружил, что Gem.path выдаёт неверные пути, а именно, он пытался загрузить гемы из /usr/lib/ruby/gems/1.9.2, тогда как гемы для ruby1.9.2 у меня установлены в /var/lib/ruby/gems/1.9.2. Я начал смотреть код метода path и вскоре попробовал вывести на консоль из Rakefile помимо Gem.path ещё и Gem.configuration.path. Каково было моё удивление, когда после добавления этой строчки, всё заработало как надо! До конца я с проблемой разобраться не успел - спать очень хотелось, но решение в данной ситуации стало очевидно. Добавление следующих строк в Rakefile и config/boot.rb спасло ситуацию:
require 'rubygems'
Gem.path
Gem.configuration.path
gem 'bundler'

пятница, 22 октября 2010 г.

Избранные цитаты из мира программирования

"Clean up after yourself when you code"
Martin Fowler

"Code reuse is the Holy Grail of Software Engineering"
Alan Perlis

"Bad code just happens, inevitably"
Steve Yegge

"Yet manual refactoring really is about cutting down a forest with an axe - we need refactoring tools to take a real chain saw to the problem"
Martin Fowler

"Smells aren't inherently bad on their own - they are often an indicator of a problem rather than the problem themselves"
Martin Fowler

" I think one of the most valuable rules is avoid duplication. "Once and only once" is the Extreme Programming phrase"
Martin Fowler

"A language that doesn’t affect the way you think about programming, is not worth knowing"
Douglas Crockford

"If something smells, it definitely needs to be checked out, but it may not actually need fixing or might have to just be tolerated"
Martin Fowler

"First we'll solve the "that works" part of the problem. Then we'll solve the "clean code" part"
Kent Beck

"It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures.”
Alan Perlis

"Beauty is the ultimate defence against complexity.”
David Gelernter

"Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live”
Martin Golding

Ярлыки: ,

Рефакторинг ruby

Bad code just happens, inevitably
На днях участвовал в семинаре по рефакторингу кода. Был одним из 4-х докладчиков и выступил, на мой взгляд, не очень удачно, хоть и готовился достаточно основательно. Думаю, виной тому тот факт, что теоретическую часть разобрали в выступлениях прямо передо мной, а всё остальное аудитории было не столь интересно.
Так или иначе, решил изложить суть своего выступления письменно, благо это у меня получается обычно куда лучше, а суть всё же какая-то была.
Итак, тема выступления - Рефакторинг. Все мы знаем, что повторное использование кода - это очень хорошо. Ещё бы, ведь писать дважды одно и то же нам, как правило, лень. К тому же программерское сообщество нас в этом знании полностью поддерживает "Don't repeat yourself", "Duplication is evil" - говорят нам книжки и pragmatic-программеры, "Once and only once" - говорят нам XP-программисты. Что ж, я, разумеется, склонен согласиться с устоявшимся мнением. К тому же, я всегда считал, что автоматизация любого явления в этом мире - это хорошо. Никому не интересен рутинный труд, заключающийся в копировании чего бы-то ни было, а тем более программного кода. Поэтому, лучше один раз написать метод, а потом использовать его хоть 100 раз (при этом, разумеется, во всём нужно знать меру). На этом простейшем принципе и базируется основной способ автоматизации при разработке ПО - реюзабилити кода. Таким образом, повторное использование кода - это один из самых полезных аспектов, предоставляемых хорошим дизайном приложения, недаром Алан Перлис назвал его Священным Граалем разработки ПО.
Я подхожу к мысли о том, что рефакторинг позволяет сделать код чище, проще и элегантнее. А простой и незаграмождённый код куда легче расширять, поддерживать и повторно использовать. Выгода рефакторинга неоспорима, если требуется внести какие-либо изменения в программную структуру.
Тем не менее, практики рефакторинга исходно предполагают, что вы имеете дело с несовершенным дизайном. Но ведь и создать идеальный дизайн практически невозможно. В любом, даже самом простейшем проекте, всегда будет код, который можно сделать лучше. Пределов совершенству не существует в принципе. Поэтому программисты, которые взяли в свои руки знамя рефакторинга всегда принимают на веру и его священный закон "Плохой код просто возникает, это неизбежно". И это действительно так.
Вспомните себя. Как вы пишете код? Большинство из вас допускает типичные ошибки при проектировании приложения. И в этом нет вашей вины. Ведь невозможно с самого начала заложить совершенную концепцию на все случаи жизни. А если требования к конечному продукту не зафиксированны жёстко, то ситуация усложняется во много раз. И это вполне нормально... Все мы допускаем ошибки проектирования. Чаще всего они связаны со следующими действиями:
  • Мы оптимизируем код преждевременно
  • Мы стремимся хранить как можно больше промежуточных значений, чтобы не вычислять их повторно
  • Мы боимся писать очень маленькие методы (ещё бы, ведь стек-то не бесконечный!)
  • И самое банальное: мы создаём жирные иерархии классов, потому что предполагаем их мнимое повторное использование для каких-то других целей

  • Рефакторинг наставляет вас на путь истинный. Он заставляет исправлять ошибки, допущенные ранее. При этом практики рефакторинга вовсе не утверждают, что код, который мы собираемся менять, написан с ошибкой. При создании он вполне мог себя оправдывать. А в условиях изменившихся требований, подобный дизайн стал неприемлем для дальнейшей модификации программной архитектуры. И поэтому его необходимо модифицировать таким образом, чтобы он соответствовал ей на текущий момент времени. Как бы пространно это ни звучало, существует специальное определение симптомов, которые могут сигнализировать нам о том, что код вероятно нуждается в рефакторинге. В программерских кругах это определение получило название "запахов" или "душков". Существуют специальные практики выявления кода с душком, которые чаще всего применяют для поддержания наиболее приемлемой архитектуры кода. (Подробнее о "запахах" можно узнать из книги Мартина Фаулера "Рефакторинг"). При этом определение проблемных мест в коде вручную - процесс сложный и долгий, требующий больших затрат аналитической составляющей нашего мыслительного центра. Поэтому для большинства современных технологий существуют специальные инструменты, облегчающие поиск "пахнущих" мест в коде. Как пишет Фаулер, "если что-то воняет, его, определённо, нужно тщательно проверить, а при необходимости, заменить".
    Я рассмотрел несколько инструментов, облегчающих поиск проблемных мест в коде на ruby. Тщательно я изучил только 3 инструмента, которые ставятся и запускаются одной командой (именно это меня в них и привлекло):
  • Flog - утилита, позволяющая определить наиболее мучительные для понимания места в коде. Она анализирует ABC-метрики (Assignments, Branches, Calls) и считает количество баллов, которое набирает тот или иной метод. Чем больше баллов, тем труднее для осознания код, тем труднее его будет модифицировать и поддерживать
  • Flay - инструмент, анализирующий структуру ruby-кода, который на настоящий момент времени просто выявляет дублирование.
  • Reek - тулза с говорящим названием, позволяет находить в коде несколько основных типов запахов.
  • Подробнее с этими инструментами можно познакомиться по ссылкам в конце статьи. Далее мой рассказ касается примера простого рефакторинга, который я сделал, основываясь на показаниях этих инструментов.

    Предположим, инструменты рефакторинга просигнализировали нам о проблемных местах в коде и мы выбрали метод с наибольшим количеством баллов:

    Из примера в принципе ясно, что метод получает какие-то данные из POST-запроса и возвращает результат их декодирования в зависимости от используемого формата. Наверное, не самый удачный пример, поскольку метод не очень длинный и, по большей части достаточно понятный. Однако, представим ситуацию когда нам понадобилось добавить поддержку ещё одного формата декодирования результата. Здесь, в общем, ленивому программисту, привыкшему автоматизировать свою работу, сразу становится ясно, что метод берёт на себя слишком много ответственности, и вердикт неоспорим -декодирование результата нужно вынести в отдельный метод. Так мы и поступим.

    Однако, этим мы ещё не решили проблему добавления нового метода декодирования - по прежнему, для его добавления нужно будет написать "уродливый" код с проверкой формата. Практики рефакторинга советуют заменить подобные проверки полиморфизмом и созданием иерархии полиморфных классов. Однако, поскольку мы работаем с ruby, где возможно всё, и создавать лишние классы нам лень, то лучше просто выделим класс декодирования и отдельные методы декодирования для каждого формата:

    В итоге исходный код получился более прозрачным для понимания, а добавить новый формат декодирования стало проще. К тому же, инструменты вычисления "пахнущих" мест скажут нам, что код стал существенно лучше. Все довольны, галактика спасена.
    Другой пример, которого я коснулся в своей презентации, направлен на уменьшение дублирования кода. Вероятно, программируя на ruby on rails все вы сталкивались с ситуацией, когда большая часть ваших контроллеров выполняет одну и ту же функцию, а именно функцию CRUD (Create Update Delete). При этом код создания, изменения и удаления данных редко отличается и содержит какую-то сложную логику. И тут вам, как истинным ленивым программистам, наверняка хотелось как-то скрасить ситуацию. Следующий пример как раз направлен на то, чтобы улучшить запах от бесконечного числа контроллеров, выполняющих функции CRUD.
    Теперь представим, что код, выполняющий создание модели выглядит следующим образом:

    Сперва выполняется поиск соответствий и присваивания между полями данных, которые были присланы с клиента и колонками таблицы модели. Затем создаётся экземпляр и происходит проверка корректности сохранения. В итоге возвращается результат - либо ошибка, либо сообщение о том, что новая запись была создана.
    Итак, мы захотели избавиться от дублирования. Первым шагом станет классическое выделение метода:

    В примере выше мы выделили сразу 2 метода (simple_create и set_column_values). В жизни рефакторинг лучше производить мелкими шагами и делать по одному действию за раз, при этом каждый раз выполняя тесты чтобы убедиться, что ничего не сломалось.
    В контроллере мы просто создаём метод create, где будем вызывать simple_create и передавать туда класс модели, которую хотим инициализировать. Данное изменение уже спасёт нас от кучи дублирующегося кода, однако, нам всё равно придётся создавать методы create во всех контроллерах. Подобного дублирования так же хочется избежать. Сделать это можно с помощью средств мета-программинга ruby и подмешивания методов в базовый класс контроллера:

    Как видно из кода, мы подмешали метод acts_as_simple_crud в класс контроллера. Вызов этого метода подмешает метод create в инстанс. В итоге, всё что нам останется сделать - это написать в контроллере:

    Всё в рамках мета-программирования ruby, предельно просто и понятно. То же самое можно произвести с любыми другими задачами, идея которых уже прошла стадию обобщения и кричит нам о том, что дублирование кода превращается в кошмар.

    Статья сырая и, разумеется, требует капитального рефакторинга (который я непременно проведу), но надеюсь, что основные мысли я довёл:
  • существование плохого кода неизбежно, но это не конец света
  • техники рефакторинга пригодятся любому, кто собирается повторно использовать свой (или чужой) код
  • при осуществлении рефакторинга необходимо использование специальных инструментов
  • если вы такой же ленивый программист как я (впрочем, как и большинство нормальных программистов), то рефакторинг - это методология, которая сохранит вам массу времени
  • рефакторинг - прекрасный способ учиться на ошибках, которые, возможно, когда-то ошибками и не считались

  • Оригинал презентации можно посмотреть здесь Использованные источники:
  • http://www.refactoring.com/
  • http://www.martinfowler.com
  • http://www.exciton.cs.rice.edu
  • http://everydayrails.com/2010/09/27/rails-refactoring-tools.html
  • http://www.rubyinside.com/3-tools-for-drying-your-ruby-code-1305.html
  • http://ruby.sadi.st
  • http://github.com/kevinrutherford/reek
  • http://www.redhillconsulting.com.au/products/simian/
  • Ярлыки: , , , ,

    -->