Показаны сообщения с ярлыком camping. Показать все сообщения
Показаны сообщения с ярлыком camping. Показать все сообщения

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
При этом вызываемый контроллер унаследует все куки, хедеры, окружение и прочий мусор вызывающего, так что вызываемый контролер и не заметит подвоха и менять в нём ничего не придётся.

02 августа 2009

способы запуска Camping приложений

Недавно ради интереса посчитал - после моих экспериментов над Camping приложением его можно запустить 4 способами:
  • традиционным camping app.rb
  • напрямую app.rb или ruby app.rb
  • через Passenger
  • с использованием библиотеки Daemons
Первый не требует никаких дополнительных действий и использует все традиционные умолчания, этим и удобен.
Второй требует, чтобы приложение само себя запустило, примерно таким кодом:
if __FILE__ == $0 || !ENV['RACK_ENV'].nil?
  App::Models::Base.establish_connection :adapter => 'sqlite3', \
    :database => "#{File.expand_path(File.dirname(__FILE__))}/../app.db"
  App.create
end
if __FILE__ == $0
  require 'mongrel/camping'
  server = Mongrel::Camping::start(OPTIONS[:app_host], OPTIONS[:app_port], '/', App)
  trap("INT"){server.stop;exit}
  trap("KILL"){server.stop;exit}
  $logger.info "Apnp server running at http://#{OPTIONS[:app_host]}:#{OPTIONS[:app_port]}"
  server.acceptor.join
end
Первый условный блок общий для данного метода и запуска из-под Passenger. Полезно, если требуется запустить как-то нестандартно - с другим адаптером БД etc. Есть один неприятный момент - этот метод работает только с 1.5 версией Camping, так что если используется Git-версия(1.9) - то стоит или ставить обычным gem install camping или использовать другой способ запуска (судя по экспериментам, для 1.9 работает только первый).
Третий способ тоже требует создания AR подключения и запуска приложения. Запуск из-под Passenger обнаруживается по наличию переменной окружения RACK_ENV. Об использовании этой переменной немного ниже. Ну а config.ru выглядит так:
# vim:syntax=ruby
require 'rubygems'
require 'rack'
$: << File.expand_path(File.dirname(__FILE__))
require 'bin/app'
run Rack::Adapter::Camping.new(App)
Самый, на мой взгляд, удобный способ со многими преимуществами: легко перезапускать приложение (да, уже не автоматически, как в первом), ну и все возможности апача на руках. С автоматическим перезапуском поможет vim:
" restart Passenger app
au BufWritePost * silent !test -f 'tmp/restart.txt' && touch 'tmp/restart.txt'
Четвёртый способ заключается в написании небольшого враппера вокруг приложения, который запускает его вторым способом, но в фоне:
#!/usr/bin/env ruby
require 'rubygems'
require 'daemons'
Daemons.run( File.expand_path(File.dirname(__FILE__))+'/app.rb')
Однако на практике, имхо, это проблем создаёт больше, чем преимуществ, и ради демонизации приложения можно использовать того же пассажира.
Ну и напоследок об окружениях. Идея RAILS_ENV хороша, почему бы и тут не сделать так же.
DEVELOPMENT = ENV['RACK_ENV'].nil? || ENV['RACK_ENV'] == 'development' # some things are for development purposes only
$logger = ((ENV['RACK_ENV'] || defined? Daemons )? \
           # file-based logger for background execution and stdout for the rest
           Logger.new("#{File.expand_path(File.dirname(__FILE__))}/../log/app.log", 10, 1024000) : \
           Logger.new(STDOUT))
Так, у меня в development окружении поставлены в некоторых местах задержки для тестирования клиента этого приложения, а для всех фоновых способов запуска логи пишутся в файл вместо стандартного вывода. При желании, раз уж нам бесплатно досталась в руки RACK_ENV, то можно делать подключения к разным базам и писать логи в разные файлы в зависимости от неё.

14 июля 2009

Роутинг в Camping, Regexp'ы в Ruby

Если коротко - не следуйте документации в способе задания роутов для контроллеров. Документация советует так:

147   class AccountAck < R '/([\dabcdef]+)/'
А надо так:
147   class AccountAck < R %r!/([\dabcdef]+)/!
иначе может статься, что на более сложных роутах вы получите 404.
PS: если кто запамятовал, то %r!regexp! (где вместо ! может быть и другой символ) - это такой способ обрамить регексп, наряду с /regexp/

12 июля 2009

Github'но-тестировочное

Смотрел я тут скринкаст на тему ObjC, было там немного про тестирование, и понял я - дальше так нельзя - тестировать. Полез в гугль (хотя кхе, туда я за этим давно залезал, руки не доходили применить), тот меня выкинул сначала на одного из коммитеров в Camping с его тестсьюитом 'camping/test', а потом и на mosquito в репозитарии автора того самого скринкаста. Скажу сразу, москит отстал от жизни и не работает, зато в том репозитории нашлось много полезного: ruby-style категории над основными Cocoa классами, скрипты для правильной максимизации окна (очень пригождается с MacVim) и куча пищи для размышлений.

Заодно накидал Rake task для дампа фикстур из используемого кемпингом (и только кемпингом, забудем про Rails, аминь) sqlite:

 1 require 'rake'
 2
 3 require 'rubygems'
 4 require 'activerecord'
 5
 6 desc "Generate fixtures for all tables of current project"
 7 task :generate_fixtures do
 8   database = "#{ENV['HOME']}/.camping.db"
 9   ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => database)
10   project = File.basename(File.expand_path('.')).downcase
11   tables = `sqlite3 #{database} ".tables"`.split(' ').reject{ |t| t.include?("sessions") || t.include?("schema_infos")}.collect{|t| t if t.include? project}
12   tables.each do |table_name|
13     File.open("test/fixtures/#{table_name}.yml", 'w') do |file|
14       data = ActiveRecord::Base.connection.select_all("SELECT * FROM #{table_name}")
15       rows = {}
16
17       data.each do |record|
18         rows["#{table_name}_#{record['id']}"] = record
19       end
20
21       file.write rows.to_yaml
22     end
23   end
24 end

Там много 'длиннострочников', но понять можно. Дампит все таблицы используя в качестве имени проекта имя текущей директории.
Не могу не пнуть попутно юродивый svn - перед тестированием всего и вся разложил сорцы по полочкам, так svn положенный в другое место файл считает новым, в то время как гит даже частично патченный и перемещённый после этого файл понимает и считает именно перемещённым.
Думается, пора покрыть тестами, оформить и выкинуть на гитхаб в виде библиотечки часть одного интересного проекта, но это уже как руки дойдут.
Stay tuned.

04 июля 2009

ActionMailer с Gmail без Rails: Camping

В последнее время пишу прототип внутреннего сервиса на Camping. Попутно должен заметить, что это весьма хорошая штука от чокнутого _why для очень быстрого прототипирования - весь мой фронтэнд лежит в одном файле и занимает 150 строк.
И само собой, без отправки электронной почты не обошлось. А раз уж camping это такие очень маленькие рельсы - почему бы не встать на плечи ещё одного из гигантов мира рельс - ActionMailer. К условиям задачи добавилась и необходимость использования Google Apps for Domains.
Первым делом нужно научить Ruby <= 1.8.7 там авторизоваться:

sudo gem install openrain-action_mailer_tls -s http://gems.github.com

подробнее
и в корне проекта с camping приложением создаём mailer.rb

# for this example the folder structure should be as follow
#
# --+ camping_project_root
#   |-- mailer.rb (this file)
#   |--+ mailer
#      |-- new_account.erb
 
require 'rubygems'
require 'action_mailer'
require "smtp_tls"
 
class Mailer < ActionMailer::Base
  def new_account(bar)
    recipients recipient
    from      "noreply@company.com"
    subject   "New account confirmation"
    body      :foo => bar
  end
 
end
 
Mailer.template_root = File.dirname(__FILE__)
 
Mailer.smtp_settings = {
  :address  => "smtp.gmail.com",
  :domain => "company.com",
  :port  => 587,
  :user_name  => "noreply@company.com",
  :password  => "noway",
  :authentication => :plain,
  :enable_starttls_auto => true
}


И в camping приложении подключаем этот мэйлер.