before_save :define_screenshot
def define_screenshot
return if file.queued_for_write.empty?
temp_name = get_temp_name
video_file = file.queued_for_write[:original].path
cmd = "ffmpeg -i #{video_file} -an -ss 00:00:10 -an -r 1 -vframes 1 -y -f mjpeg #{temp_name} >/dev/null 2>&1"
Rails.logger.info "Running cmd: #{cmd}"
system cmd
self.thumbnail = File.open(temp_name)
end
def get_temp_name
tempdir = Dir::tmpdir || '/tmp'
t = Time.now.strftime("%Y%m%d")
path = "clip#{t}-#{$$}-#{rand(0x100000000).to_s(36)}.jpg"
File.join(tempdir, path)
end
Простой способ сделать скриншот с видео при загрузке
использование Google Docs в приложении
require 'hpricot'
class Gdoc
def self.get id
text = Rails.cache.fetch("doc-#{id}", :expires_in => 15.minutes) do
url = URI.parse('http://docs.google.com')
res = Net::HTTP.start(url.host, url.port) do |http|
http.get("/Doc?id=#{id}")
end
text = res.body
doc = Hpricot(text)
body = (doc/'body')
body.search("#google-view-footer").remove
body.search("script").remove
body.inner_html.gsub('File?id=', 'http://docs.google.com/File?id=')
end
text
end
def self.index
docs = Rails.cache.fetch("docs", :expires_in => 15.minutes) do
raw = self.get GDOC_INDEX
doc = Hpricot(raw)
docs = []
doc.search('//a').each do |link|
docs << {:id => link['href'].gsub('View?docid=',''), :name => link.innerText, :path => link.innerText.downcase.gsub(' ','-')}
end
docs
end
docs
end
end
Комментарии, думаю, излишни.
Создание баннерной системы на rails часть 1.
Одним из таких способов является Rails Metal. Благодаря нему возможно обрабатывать запросы в обход всей машинерии, что дает огромное преимущество в скорости работы.
Для примера я решил создать приложение для баннерной сети. Приложение крайне просто и используется только как пример.
Вначале создадим новое приложение с помощью шаблона:
git :init
plugin 'restful-authentication', :git => 'git://github.com/technoweenie/restful-authentication.git', :submodule => true
plugin 'haml', :git => "git://github.com/nex3/haml.git", :submodule => true
plugin 'paperclip', :git => "git://github.com/thoughtbot/paperclip.git", :submodule => true
plugin 'acts_as_taggable_redux', :git => 'git://github.com/geemus/acts_as_taggable_redux.git', :submodule => true
plugin 'will_paginate', :git => 'git://github.com/mislav/will_paginate.git', :submodule => true
plugin 'role_requirement', :git => 'git://github.com/timcharper/role_requirement.git', :submodule => true
run "touch tmp/.gitignore log/.gitignore vendor/.gitignore"
run %{find . -type d -empty | grep -v "vendor" | grep -v ".git" | grep -v "tmp" | xargs -I xxx touch xxx/.gitignore}
file '.gitignore', <<-END
.DS_Store
log/*.log
tmp/**/*
config/database.yml
db/*.sqlite3
END
run "rm README"
run "rm public/index.html"
run "rm public/favicon.ico"
run "rm public/robots.txt"
generate("authenticated", "user session")
generate("roles", "Role User")
rake('db:create')
rake('db:sessions:create')
rake('acts_as_taggable:db:create')
initializer 'session_store.rb', <<-END
ActionController::Base.session = { :session_key => '_#{(1..6).map { |x| (65 + rand(26)).chr }.join}_session', :secret => '#{(1..40).map { |x| (65 + rand(26)).chr }.join}' }
ActionController::Base.session_store = :active_record_store
END
git :submodule => "init"
git :add => '.'
git :commit => "-a -m 'Initial commit'"
rails . -m ../template.txt
После этого можно создать обработчик запроса баннера (показ) и обработчик клика на баннер.
script/generate metal get_banner
Появившийся после этого файл app/metal/get_banner.rb выглядит так:
class GetBanner
def self.call(env)
if env["PATH_INFO"] =~ /^\/get_banner/
[200, {"Content-Type" => "text/html"}, "Hello, World!"]
else
[404, {"Content-Type" => "text/html"}, "Not Found"]
end
end
end
Теперь стоит подумать об организации базы данных для того, что-бы наше преимущество в скорости обработки запроса не съедалось базой данных.
У нас имеется набор объектов WebSite (площадок), набор рекламных проектов (Projects) и набор баннеров в рекламных проектах (Banners). При этом каждый проект имеет тэги и каждая площадка тоже имеет набор тэгов. Нам необходимо создать сущность которая будет звеном между сайтом и баннером. Для того-чтобы выбор баннеров был простым запросом к БД, например:
SELECT *, MD5(RAND()) as rand FROM sites_banners WHERE site_id=1 ORDER BY rand LIMIT 0,1
А так-же необходимо все показы и клики записывать в другую таблицу с дальнейшим обсчетом по деньгам.
Создадим базовые модели:
script/generate resource website url:string user_id:integer enabled:boolean
script/generate resource project user_id:integer name:string enabled:boolean
script/generate resource banner user_id:integer image_file_name:string image_content_type:string image_file_size:integer project_id:integer enabled:boolean
И отредактируем файлы миграции для установления значения enabled в true. (Мы заботимся о наших пользователях ;))
После этого наши модели будут выглядеть примерно так:
class Banner < ActiveRecord::Base
belongs_to :project
belongs_to :user
has_attached_file :image
validates_attachment_content_type :image, :content_type => ['image/jpeg', 'image/gif']
end
class Project < ActiveRecord::Base
has_many :banners
belongs_to :user
validates_presence_of :name
end
class Website < ActiveRecord::Base
belongs_to :user
validates_presence_of :url
validates_format_of :url, :with => /^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$/ix
end
Так-же необходимо добавить в модель юзера строки:
has_many :websites
has_many :projects
has_many :banners
Теперь перейдем к работе со статистикой, для этого создадим модель BannerView
script/generate model banner_view website_id:integer banner_id:integer request_ip:string referrer:string
Именно в нее и будет писаться вся статистика по просмотру баннеров. В дальнейшем будет производится обработка данных этой таблицы с целью извлечения из нее необходимых данных с удалением старых. Для ускорения работы модель не имеет ORM связей.
Теперь перейдем к нашему metal раздатчику баннеров. В этой части статьи будем использовать самый примитивный выбор баннера - случайный из всех
поэтому его код будет выглядеть так:
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)
class GetBanner
def self.call(env)
if env["PATH_INFO"] =~ /^\/get_banner\/(\d+)/
banner = Banner.find :first, :order => 'rand()'
unless banner.nil?
BannerView.create(
:website_id => $1,
:request_ip => env['REMOTE_ADDR'],
:banner_id => banner.id,
:referrer => env['HTTP_REFERER']
)
return [301, {"location" => banner.image.url}, []]
end
[404, {"Content-Type" => "text/html"}, ["Not availible"]]
else
[404, {"Content-Type" => "text/html"}, ["Not Found"]]
end
end
end
Эта операция делает 2 довольно быстрых SQL запроса.
В следующей статье я напишу биллинг и более "качественный" выбор баннера
Использование плагина active_merchant для работы с кредитными картами
Для начала нам необходимо установить сам плагин в приложение
script/plugin install http://activemerchant.googlecode.com/svn/trunk/active_merchant
После установки можно приступать к его настройке, а именно получению тестового аккаунта на Authorize.net и настройкой поведения при различных environment в rails.В файле environment.rb необходимо дописать следующий код:
unless RAILS_ENV == 'production'
ActiveMerchant::Billing::Base.mode = :test
end
Который сообщит приложению что надо использовать тестовый интерфейс если приложение не в production режиме.Для успешной транзакции необходимо проделать следующие проверки:
- проверить верность номера карты
- Авторизовать карту на authorize.net
- Списать средства
Проверка верности карты
creditcard = ActiveMerchant::Billing::CreditCard.new(
:number => params[:cc_number],
:month => params[:cc_month],
:year => params[:cc_year],
:first_name => params[:first_name],
:last_name => params[:last_name],
:type => params[:cc_type],
:verification_value => params[:cvv]
)
if creditcard.valid?
#Данные карты верны
end
При неудачной проверке ошибки сходны с ошибками ActiveRecordАвторизация карты на Authorize.net
options = {
:address => {},
:billing_address => {
:name => params[:full_name],
:address1 => params[:address],
:city => params[:city],
:state => params[:state],
:country => params[:country],
:zip => params[:zip],
:phone => params[:phone]
}
}
gateway = AuthorizeNetGateway.new(
:login => 'APILOGINLOGINLOGIN', #API Login ID
:password => 'APIPASSWORD' #API Password
)
response = gateway.authorize(1000, creditcard, options)
unless response.success?
@message = response.message.to_s
render :action => 'error'
end
Следует помнить, что суммы списываются в центах.
Списание средств
gateway.capture(1000, response.authorization)
После чего с карты будет списано 10 долларов.
Новые книги о rails 2.1

Недавно Carlos Brando Написал небольшую книгу в которой описаны нововведения в rails 2.1.
Объяснены такие вещи как sum метод в AR, "грязные" объекты, вычисления и многое другое. Cкачать английский вариант книги можно со странички автора
Разработка на mod_rails под Leopard
gem install passenger
Затем запускается конфигурация (как в статье об установке под Linux )
c отличием только в том, что необходимо вписать в конфигурационный файл apache строки вручную
LoadModule passenger_module /opt/local/lib/ruby/gems/1.8/gems/passenger-1.0.5/ext/apache2/mod_passenger.so
RailsSpawnServer /opt/local/lib/ruby/gems/1.8/gems/passenger-1.0.5/bin/passenger-spawn-server
RailsRuby /opt/local/bin/ruby
Создаем виртуальные хосты и запускаем apache.Все готово
Социальная сеть за 5 минут
Для установки необходимо установить Rails Engines
svn export http://svn.rails-engines.org/engines/branches/rb_2.0/ vendor/plugins/engines
И установить сам Community Engine
git clone --depth 1 git@github.com:bborn/communityengine.git vendor/plugins/community_engine
и сгенерировать миграцию script/generate plugin_migration
Всё, социальная сеть работает
Rails наступает на массовый рынок хостинга или mod_rails
Модуль называется mod_rails (passenger) . Для первой установки я выбрал виртуальную машину с установленном на ней Debian Etch
Для установки mod_rails необходимо поставить следующие пакеты:
apt-get install mysql-server ruby libmysql-ruby rdoc1.8 ri1.8 apache2-mpm-prefork ruby1.8-dev build-essential apache2-prefork-dev libapr1-dev libopenssl-ruby1.8 irb
После чего необходимо установить rubygems скачав его c RubyForge и выполнив ruby setp.rb
После чего можно поставить passenger.После установки можно приступать к конфигурации Apache, для этого надо собрать mod_rails коммандой
/usr/bin/passenger-install-apache2-module
И настроить его написав 2 файла:
userad-virual-www:~# cat /etc/apache2/mods-available/rails.load
LoadModule passenger_module /usr/lib/ruby/gems/1.8/gems/passenger-1.0.1/ext/apache2/mod_passenger.so
userad-virual-www:~# cat /etc/apache2/mods-available/rails.conf
<IfModule passenger_module>
RailsSpawnServer /usr/bin/passenger-spawn-server
RailsRuby /usr/bin/ruby1.8
</IfModule>
после чего необходимо подключить его в конфиг Apache
a2enmod rails
a2enmod rewrite
А затем правим дефолтный сайт (или создаем свой новый) для того что-бы его DocumentRoot указывал на public приложения.Перезапускаем apache и приложение запускается в production. Если есть необходимость изменить приложение без перезпуска apache то необходимо создать файл tmp/restart.txt в приложении.
Вызов PHP кода из rails приложения
Не спрашивайте почему мне этого захотелос. Ответ банальный - лень :)
Итак, в lib/ проекта кидаем файл php_bridge.rb следующего содержания
module PHPBridge
def self.send_to_php(file,req)
require 'uri'
req1 = []
req.env.each do |name,value|
req1.push("#{name}[=]#{value}")
end
params = URI.escape(req1.join("[|]"))
req1 = []
req.parameters.each do |name,value|
req1.push("#{name}[=]#{value}")
end
param = URI.escape(req1.join("[|]"))
`/usr/bin/php #{RAILS_ROOT}/wrappers/php-wrapper.php #{file} "#{params}" "#{param}"`
end
end
А в контроллер который должен вызвать необходимо написать что-то похожее на:
class TestController < ApplicationController
include PHPBridge
def index
@output = PHPBridge::send_to_php('test.php',request)
end
end
После этого при запросе вызовется файл wrappers/php-wrapper.php в который необходимо написать следующий код:
<?
$file = $argv[1];
$params = $argv[2];
$params = urldecode($params);
$params = explode("[|]",$params);
$params2 = array();
foreach($params as $value)
{
$tt = explode('[=]',$value);
$params2[$tt[0]] = $tt[1];
}
$_SERVER = $params2;
$params = $argv[3];
$params = urldecode($params);
$params = explode("[|]",$params);
$params2 = array();
foreach($params as $value)
{
$tt = explode('[=]',$value);
$params2[$tt[0]] = $tt[1];
}
$_GET = $params2;
$_POST = $params2;
require_once('php-code/' . $file);
?>
Вот и всё. Для теста я выводил переменные окружения. 