суббота, 29 декабря 2012 г.

Deploy Capistrano + Mailman + RVM + Monit (for FreeBSD) - ВЫСТРАДАНО

you should use gem 'capistrano/rvm'

 

met be set(:rvm_ruby_string){ '1.9.3@mygemset' } somewhere

and here is recipe for capistrano

namespace :mailman do
desc "Mailman::Start"
task :start, :roles => [:app] do
run "cd #{current_path}; RAILS_ENV=production script/mailman_daemon start"
end

desc "Mailman::Stop"
task :stop, :roles => [:app] do
run "cd #{current_path}; RAILS_ENV=production script/mailman_daemon stop"
end
desc "Mailman::Status"
task :status, :roles => [:app] do
run "cd #{current_path}; RAILS_ENV=production script/mailman_daemon status"
end

desc "Mailman::Restart"
task :restart, :roles => [:app] do
mailman.stop
mailman.start
end
end
before :deploy, "mailman:stop"
after :deploy, "mailman:start"

in order to run monit you should use this config for user and group nexus

check process mailman_server with pidfile /www/itservice/current/script/mailman_server.pid
start program = "/usr/local/rvm/bin/rvm-shell '1.9.3@mygemset' -c 'cd /www/itservice/current/; RAILS_ENV=production /www/itservice/current/script/mailman_daemon start'"
as uid nexus and gid nexus
stop program = "/usr/local/rvm/bin/rvm-shell '1.9.3@mygemset' -c 'cd /www/itservice/current/; RAILS_ENV=production /www/itservice/current/script/mailman_daemon stop'"
as uid nexus and gid nexus
~

И что бы дополнить сагу тестировщика Rspec, отличная презентация

По факту – коротенький how to, как раз как я люблю http://kerryb.github.com/iprug-rspec-presentation/

Тестирование Rspec: лучшие практики

Увлекся я тут тестированием rspec не на шутку, вот, нашел хороший сайт

Прикольный форматтер Fuubar, рекомендую подключить к тестированию rspec как --format fuubar к запуску спеков

воскресенье, 23 декабря 2012 г.

Запуск Unicorn+RVM на FreeBSD

cd /www/itservice
rvm wrapper 1.9.3@itservice itservice unicorn

создает враппер совместимый с rc.d

Пришлось поправить ссылку на git:

sudo ln -nfs /usr/local/bin/git /usr/bin/git

как описано здесь:

http://ficial.wordpress.com/2011/07/13/phusion-passenger-fixing-no-such-file-or-directory-git-ls-files/

 

в итоге в rc.d

#
# Add the following lines to /etc/rc.conf to enable unicorn:
# unicorn_enable (bool): Set to "NO" by default.
# Set it to "YES" to enable unicorn
# unicorn_profiles (str): Set to "" by default.
# Define your profiles here.
# unicorn_flags (str): Set to "" by default.
# Extra flags passed to start command.

. /etc/rc.subr

name="unicorn"
rcvar=`set_rcvar`

pidfile="/www/itservice/shared/pids/unicorn.pid"

unicorn_flags="-c /www/itservice/current/config/unicorn/colo.rb -E production -D"
start_cmd="unicorn_start"

command="/usr/local/rvm/bin/itservice_unicorn"
#pidprefix="/var/run/unicorn"

[ -z "$unicorn_enable" ] && unicorn_enable="NO"

load_rc_config $name
unicorn_start() {
echo "Starting ${name}"
${command} ${unicorn_flags}
}

extra_commands="reload"
sig_reload="USR2"

run_rc_command "$1"

 

и в config/unicorn/colo.rb

deploy_to = "/www/itservice"
app_path = "#{deploy_to}/current"
shared_path = "#{deploy_to}/shared"
pid_file = "#{shared_path}/pids/unicorn.pid"
socket_file= "#{shared_path}/pids/unicorn.sock"
rails_root = "#{deploy_to}/current"
# Set unicorn options
worker_processes 1
preload_app true
timeout 180
listen "127.0.0.1:9000"
# listen socket_file

# Spawn unicorn master worker for user apps (group: apps)
user 'nexus', 'nexus'

# Fill path to your app
working_directory app_path

# Should be 'production' by default, otherwise use other env
rails_env = ENV['RAILS_ENV'] || 'production'

# Log everything to one file
stderr_path "log/unicorn.log"
stdout_path "log/unicorn.log"

# Set master PID location
pid "#{shared_path}/pids/unicorn.pid"

before_fork do |server, worker|
ActiveRecord::Base.connection.disconnect!

old_pid = "#{server.config[:pid]}.oldbin"
if File.exists?(old_pid) && server.pid != old_pid
begin
Process.kill("QUIT", File.read(old_pid).to_i)
rescue Errno::ENOENT, Errno::ESRCH
# someone else did our job for us
end
end
end

after_fork do |server, worker|
ActiveRecord::Base.establish_connection
end

суббота, 22 декабря 2012 г.

На этой неделе я постиг тестирование RSpec

Лично для меня, довольно тертого разработчика, процесс внедрения TDD был непростым и местами тернистым.

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

Теоретически процесс TDD должен выглядеть так:

1. Написали падающий тест, прогнали rspec, убедились что тест не проходит (красный)

2. Написали кусок кода, прогнали rspec, убедились что тест проходит (зеленый)

3. Отрефакторили, убедились что все хорошо и тесты не падают.

4. Отправили код в продакшн

Тестирование довольно медленно запускается само по себе, поэтому люди делают ухищрения: ставят spork, который держит инстанс приложения в памяти и экономит время при каждом запуске рельс. Уменьшение времени тестов на время запуска (у меня секунд 7-10) гарантировано. 

Далее. То, что раньше делал autotest, сейчас модно делать с guard. Суть в том, что бы мониторить изменения файлов и запускать только те тесты, которых это изменение касалось. Довольно удобно, только не всегда ловит эти самые изменения. Так, например, изменение в factory не приведет к тестам, для этого я лишний раз дергаю измененный файл спека модели и, вроде как, поехало.

Раньше тестовые наборы данных хранились в YAML файлах и назывались фикстурами (fixtures).  Каждый тест загружал фикстуры из файлов в базу, и, хотя, их можно было загружать выборочно, такая процедура была довольно медленной и ресурсоемкой.  Ребята из  thoughtbot решили данную проблему тем, что фикстуры стали генерить программно и назвали фабриками (factories). Фабрики действительно ощутимо быстрее и позволяют без проблем создавать связанные ассоциативные инстансы, изменяемые наборы данных (sequence), например, разные emailы пользователей и другие прикольные штуки. Сейчас все еще модно использовать FactoryGirl.

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

например: http://stackoverflow.com/questions/8303491/how-do-i-make-sure-the-helpers-and-models-reload-in-rspec-when-im-using-spork

=================================================

Само тестирование

Сейчас модно использовать RSpec, поскольку он лаконичнее и описательнее чем Test::Unit, который идет в комплекте с рельсами.  Тесты в rspec называются спеками (от слова спецификация). Вроде само тестирование рельс сейчас тоже переехало на rspec, не знаю. Самый писк моды – приблизиться к человеческому языку, так что бы тесты были как бы метаописаниями, как бы объясняли на примерах как должен работать код. Чем меньше сам текст теста и чем он выразительнее, тем лучше. Мне в этом плане был очень близок cucumber (огурец). Сейчас rspec научился собой заменять cucumber чуть менее чем полностью.

Как сейчас водится, люди тестируют в трех  направлениях.

1. Тесты моделей, или юнит-тесты

Здесь мы проверяем корректность методов в моделях, корректность валидаций.

validate_presence_of(:model) не идет в комплекте бойца с rspec, поэтому приходится ставить и подключать shoulda

gem "shoulda-matchers"

и далее в spec_helper.rb

require 'shoulda-matchers'

2. Тесты контроллеров, или как их еще называют, функциональные

Проверяются вещи, что бы create действительно создавал объект или рендерил 'new'  и такое прочее

 

типовой тест будет включать в себя вызов контроллера

post :create, mymodel: param1

и проверку результатов. Например в assigns идет то, что должно присвоиться  в методе контроллера

assigns(:mymodel){ should == MyModel.last }

проверяем, что отрендерится темплейт new. Это очевидно, но не интуитивно.

response.should render_template('new')

 

3. Интеграционные тесты, bdd истории

Это больше парафия Cucumber, однако, rspec в базе создает request/ папку, куда и помещаются интеграционные тесты

Мне очень нравится подход Cucumber тем, что происходит осмысление "зачем нужна эта фича" при каждом взгляде на тест, поскольку там собственно и написано "зачем". Но мы же знаем, что фичу можно сделать разными способами, главное, чтобы "зачем" осталось тем же самым.

Так вот, в базе интеграционный тест прогоняет цепочку действий при помощи эмуляции браузера. 

Чаще всего что бы работала такая хрень подключаются gem capybara или webrat, которые собственно и делают эмуляцию браузера

Тестирование обычно выглядит так:

- заполнить поле 1 данными 1

- нажать княпу сабмит

- проверить, что на странице появились записи с данными 1

примерно такой код

visit services_path

page.should have_content("Сервис антивирусных")

visit и have_content нам достается из гема капибары.

 

Немного про то, что и как тестировать. Следует проверять пограничные случаи, например, если код должен создавать юзера, то проверьте это двумя тестами:

1. Юзер создается корректно, проверяем что в базке запись появилась.

2. Что мы действительно получаем ошибку, если юзер не создается корректно.

понедельник, 17 декабря 2012 г.

Пачка багов при траблшутинге Capistrano+RVM+Unicorn+Apache2-Passenger на FreeBSD

Итак, при очередном деплое на FreeBSD было решено добиться автоматизированной взаимности от Capistrano в неожиданной конфигурации.

 

Для того, что бы использовать разные версии руби (1.9.3 например) мне пришлось использовать вместо удобного Passenger неудобный Unicorn.

Для отладки использован multistage в виде строки 

require 'capistrano/ext/multistage'

в deploy.rb

и в виде конфигов в config/deploy/xxxxxx.rb для каждого случая (у меня staging, aws, colo)

 

баг возник неожиданно, капистрано не распознавал deploy_to, что решил stackoverflow http://stackoverflow.com/questions/8213376/capistrano-multistage-deploying-to-wrong-directory

 

каждый файл должен содержать 

set(:deploy_to){ "/var/www/#{application}" }

set(:releases_path)     { File.join(deploy_to, version_dir) }

set(:shared_path)       { File.join(deploy_to, shared_dir) }

set(:current_path)      { File.join(deploy_to, current_dir) }

set(:release_path)      { File.join(releases_path, release_name) }

а конструкция 

set :deploy_to, "/var/www/#{application}" попросту не работала :(

======================================================================

Запуск на colo (FreeBSD) закончился удачно, но авторизация OAuth слетала с известной SSL багой, которая вылечилась скачиванием ca-bundle.crt в config приложения, и с указанием на нее в config/initializers/device.rb

config.omniauth :google_oauth2, "xxxxx", "yyyyyyyyyy", { :access_type => "offline", :approval_prompt => "", :client_options => { :ssl => { :ca_file => "#{Rails.root}/config/ca-bundle.crt" }}}

======================================================================

 

к Апачу unicorn подключили как балансер по http, через unix socket Unicorn с апачем не работает

что описано здесь http://stackoverflow.com/questions/5780886/proxy-apache-load-balancers-to-a-unix-socket-instead-of-a-port

<VirtualHost *:80>

        ServerName itservice

        DocumentRoot /www/itservice/current/public

<Proxy balancer://unicornservers>

   BalancerMember http://127.0.0.1:9000

</Proxy>

ProxyRequests Off

ProxyPass /assets/ !

ProxyPass / balancer://unicornservers/

ProxyPassReverse / balancer://unicornservers/

ProxyPreserveHost on

</VirtualHost>

=====================================================================
Столкнулся с тем, что Capistrano даже в другом namespace таски с названием upload и download не работают
я хотел db:upload и db:download, однако херъ. Получил забавные баги.
 
переименование в upload_production и download_production помогло
=====================================================================
RVM в системном виде бывает "не может переключить"  на другой ruby, это лечится
в  .bash_profile
 
[[ -s "/usr/local/rvm/scripts/rvm" ]] && . "/usr/local/rvm/scripts/rvm"
========================================================================
Passenger запускался автоматически апачем что было волшебно и ненавязчиво, Unicorn подобного не умеет и для него приходится руцями делать скрипты запуска для каждого сайта отдельно. Как в старые добрые допассажировские времена.
И запускать их при старте.
 
Для этого люди не поленились написать этот GIST
 
Тот еще способ.
set(:releases_path){File.join(deploy_to, version_dir)}set(:shared_path){File.join(deploy_to, shared_dir)}set(:current_path){File.join(deploy_to, current_dir)}set(:release_path){File.join(releases_path, release_name)}

четверг, 13 декабря 2012 г.

Ей FreeBSD, давай до свидания!

При всей моей любви к FreeBSD все у нее не слава богу. Не ориентируется на нее майнстрим, поэтому ничего на ней не тестируется и не работает искаропки.

Пример тому http://code.google.com/p/rubyenterpriseedition/issues/detail?id=61 установка ree-head тот что 2012.02

Просто тупо из-за небольшого известного бага с библиотеками не инсталлится. 

А порты? Все, перехожу или на убунту lts или на центос. При чем первый мне нравится больше благодаря богатому внутреннему миру apt-get.

rake seed

посеять данные с первичными связями не такая уж и простая задача

дядя Бейтс тут рассказал про правильные способы: http://railscasts.com/episodes/179-seed-data?view=asciicast

среда, 12 декабря 2012 г.

Следует попробовать, как завещал дядя Бейтс, уникорн

Если деплоить unicorn+nginx, то можно поставить nginx из портов и просто аплоадить конфиги. С другой стороны Леня Шевцов написал cuoco, что может еще более облегчить жизнь.
 

Простой способ сделать Capistrano multistage

Простой, как двери, рецепт успеха:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
desc "Run tasks in production enviroment."
task :production do
  # Production nodes 
  role :app"192.168.1.30"
  role :app"192.168.2.30"
  role :db,  "192.168.1.30":primary => true
end 

desc "Run tasks in staging enviroment."
task :staging do
  # Staging nodes 
  role :web"192.168.1.60"
  role :app"192.168.2.60"
  role :db,  "192.168.1.60":primary=>true
end

 

1
2
3
4
5
cap staging deploy:check
cap staging deploy
# or 
cap production deploy:check
cap production deploy

http://www.pastbedti.me/2009/01/handling-a-staging-environment-with-capistrano-rails/

Деплой Rails приложения Phusion Passenger для Apache и для Nginx

Несколько полезностей было найдено в направлении деплоя рейл приложений на типовой хостинг:

1. Деплой Rails на rvm + passenger + nginx

  - установить rvm, в rvm установить нужный ruby, установить gem passenger, затем passenger-install-module-nginx вытянет сорцы, вкомпилит модуль и установит (чаще всего в /opt/nginx) nginx. Запускать прийдется руками, добавлять скрипты в init.d. Из apt-get не рекомендуют ничего из выше перечисленного ставить.

2. Деплой на Rails rvm + passenger + apache2

 - установить rvm, нужный руби, установить gem passenger

 - apt-get install apache2

 - passender-install-module-apache установит просто модуль к уже готовому апачу, что, конечно, приятнее.

 

Для корректного использования RVM при деплое прийдется юзать в capistrano gem

Gemfile

gem 'capistrano_rvm'

 

bundle install, как водится

после чего в deploy.rb добавить

require "rvm/capistrano" (без этого не работает http://stackoverflow.com/questions/5541721/capistrano-bundle-not-found-error-during-deployment)

и в принципе все.

 

Без этого хостинг с RVM работать нормально не будет.

Страшно полезным оказался гем "capistrano_database_yml" который деплоит на хостинг конфиг базы из "config/database.example.yml"

http://www.simonecarletti.com/blog/2009/06/capistrano-and-database-yml/ он же https://github.com/amfranz/capistrano_database_yml

 

Еще какой-то туториал, но тут все ручное http://it-giki.com/post/467.html

понедельник, 10 декабря 2012 г.

Что бы работал :git => "github/mymodule" в capistrano:deploy необходимо

указать в deploy.rb

ssh_options[:forward_agent] = true
default_run_options[:pty] = true

это будет пробрасывать в удаленный шелл ваши локальные ключи

и первый раз из шелла на удаленном сервере сделать

ssh git@github.com для того что бы github.com был добавлен в known hosts


вторник, 18 сентября 2012 г.

Замечательный пример acts_as_taggable_on и rails3-jquery-autocomplete

рулит и бибикает применение двух гемов для функционала "теги с автокомплитом"
 

http://stackoverflow.com/questions/4486745/rails-autocomplete-tags-separated-by-commas-using-regex

понедельник, 27 августа 2012 г.

вместо time_ago_in_words рекомендую JS решение

Гораздо более прикольное решение вместо time_ago_in_words на основе JQuery
 

https://github.com/jgraichen/rails-timeago

понедельник, 7 мая 2012 г.

воскресенье, 8 апреля 2012 г.

понедельник, 19 марта 2012 г.

git with colors

Что бы расцветить вывод git достаточно выполнить следующее

 

git config --global color.branch auto
git config --global color.diff auto
git config --global color.interactive auto
git config --global color.status auto

 

среда, 14 марта 2012 г.

прикольная генерация ЧПУ

http://rubydoc.info/github/norman/friendly_id/master/frames

 

class Post < ActiveRecord::Base
extend FriendlyId  friendly_id :title_or_slug, :use => [:slugged, :history]
scope :published, where(:published=>true) default_scope :order=>"id DESC" validates :title, :presence => true
def should_generate_new_friendly_id?    new_record? || slug == ""  end
def title_or_slug slug = nil if slug == "" slug ? slug : title end def published_class published ? "published" : "unpublished" end  # def normalize_friendly_id(text)  #   text.to_slug.normalize! :transliterations => :russian  # endend

пятница, 9 марта 2012 г.

синтаксический сахар у order_by

Оказывается, можно написать и так:

Post.order("created_at desc")

среда, 7 марта 2012 г.

хранить файлы на S3

вот так, до смешного просто

 

http://doganberktas.com/2010/09/14/amazon-s3-and-paperclip-rails-3/

воскресенье, 26 февраля 2012 г.

Проблемы с gemspec типа Illformed requirement Invalid gemspec

Если мы получаем проблемы с Illformed requirement Invalid gemspec при операциях с bundle,

то у меня получилось руками (в vim) пройтись по спекам

specifications/rails-3.2.0.gemspec

 

и выполнить замену YAML:Syck

%s/#<YAML::Syck::DefaultKey:0x.*>/=/

 

само шаманство и причина описаны здесь: http://blog.rubygems.org/2011/08/31/shaving-the-yaml-yak.html

четверг, 23 февраля 2012 г.

фичулька

Page.all.drop(1).each пробежаться по всем страницам кроме первой

вторник, 14 февраля 2012 г.

Трансфер баз данных push/pull

http://adam.heroku.com/past/2009/2/11/taps_for_easy_database_transfers/

Крайне удобно, если необходимо трансферить базу данных из одного места в другое просто http запросами

$ taps server mysql://root@localhost/mydb tmpuser tmppas
$ mysqladmin create mydb
$ taps pull mysql://root@localhost/mydb http://tmpuser:tmppass@slicehost-box:5000 
Receiving schema from remote taps server
Receiving data from remote taps server
4 tables, 1,064 records
widgets:   20% |====              | Time: 00:00:00