воскресенье, 14 августа 2011 г.

Кеширование в мультидоменном веб приложении

добавляем в контроллер

before_filter { |c|
   c.class.page_cache_directory = "#{RAILS_ROOT}/public/#{c.request.host}"
}

в логах сразу видим как пишется

Write fragment views/sub.domain.com/s/test (2.6ms)

и при повторном запросе

Read fragment views/sub.domain.com/s/test (0.9ms)

Итого: с 300мс до 10 мс рендеринг страницы.

среда, 3 августа 2011 г.

Получение в SproutCore нескольких массивов данных одним запросом к бакэнду.

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

Допустим, у нас есть приложение Blog, в котором есть модели Blog.Post и Blog.Quote. Пусть они выводятся сервером по адресу '/records.json' и в json выглядят вот так:
{posts: [{post1}, {post2}, {post3}], quotes: [{quote1}, {quote2}, {quote3}]}
Что бы уменьшить количество запросов к серверу и тем самым увеличить скорость получения данных, нам нужно немного изменить datasource приложения.
Шаг 1. Создать запрос:
Blog.QUERY_POSTS_AND_QUOTES = SC.Query.local([Blog.Post, Blog.Quote]);
В SC.Query.local можно передавать не только один тип записи, но и массив типов.
Шаг 2. Создать хэш-таблицу с ссылками на данные и типами записей:
Это шаг нужен что бы избежать огромного количества if в функции fetch()

Blog.Datasource = SC.DataSource.extend({
   urls: {
     posts_and_quotes: { url: '/records.json', records: ['posts', 'quotes'] }
   },
});

Так же нам нужно поправить шан запрос, установив ему атрибум id, соответствующий имени одного из элементов urls, в данном случае posts_and_quotes:
Blog.QUERY_POSTS_AND_QUOTES = SC.Query.local([Blog.Post,Blog.Quote]).set('id','posts_and_quotes');
Шаг 3. Пишем fetch():

fetch: function(store, query) {
   //Получаем id запроса и берём из urls ссылку для запроса в соответствии с этим id
   var id = query.get('id');
   //Если id не задан, значит запрос не должен обращаться к серверу
   if(!id) return NO;
   var url = this.urls[id]['url'],
     funcs = this.get('fetchDidComplete'),
     //Если нужна особая обработка какого нибудь запроса то в fetchDidComplete
     //нужно добавить функцию, имя которой совпадает с id (ещё одна хэш-таблица).
     //Если имя не указано, то по завершении запроса будет вызвана default функция

     func = funcs[id] || funcs['default'];
   SC.Request.getUrl(url).json().notify(this, func, store, query).send();
   return YES;
}



Шаг 4. Обрабатываем полученные данные:

fetchDidComplete: {
   'default': function(response, store, query) {
     if (SC.ok(response)) {
       //В зависимости от id берём нужные записи из полученного json
       var body = this.urls[query.get('id')]['records'];
       //Получаем тип записи
       var recordType = query.get('recordType');
       //Если тип не задан, значит их несколько и они хранятся в recordsTypes
       if (!recordType) {
       var recordTypes = query.get('recordTypes'); //Получаем типы записей
       jQuery.each(body, function(i) {
       //Загружаем в store записи разных типов. Порядок выдачи записей разных типов сервером
       //и порядок этих типов в запросе должны совпадать. Т.е.:
       //SC.Query.local[Scm.Product, Scm.Taxon] и {products: [{product1}, {product2}], taxons: [{taxon1}, {taxon2}]}

       store.loadRecords(recordTypes[i], response.get('body')[body[i]]); });
     }
     else {
       store.loadRecords(recordType, response.get('body'));
     }
     store.dataSourceDidFetchQuery(query);
     } else store.dataSourceDidErrorQuery(query, response);
   }
}


Шаг 5. Используем новый запрос в приложении:
var data = Blog.store.find(Blog.QUERY_POSTS_AND_QUOTES);
Теперь, что бы получить отдельно posts, а отдельно quotes используем scoped queries:

var posts = data.find(SC.Query.local(Blog.Post);
var quotes = data.find(SC.Query.local(Blog.Quote);

Ну и теперь можно заполнить ArrayController'ы приложения полученными данными:

Blog.postsController.set('content', posts);
Blog.quotesController.set('content', quotes);

Полностью datasource будет выглядеть так:
Blog.Datasource = SC.DataSource.extend({
urls: {
   posts_and_quotes: { url: '/records.json', records: ['posts', 'quotes'] }
},
fetch: function(store, query) {
   //Получаем id запроса и берём из urls ссылку для запроса в соответствии с этим id
   var id = query.get('id');
   //Если id не задан, значит запрос не должен обращаться к серверу
   if(!id) return NO;
   var url = this.urls[id]['url'],
     funcs = this.get('fetchDidComplete'),
     //Если нужна особая обработка какого нибудь запроса то в fetchDidComplete
     //нужно добавить функцию, имя которой совпадает с id (ещё одна хэш-таблица).
     //Если имя не указано, то по завершении запроса будет вызвана default функция
     func = funcs[id] || funcs['default'];
   SC.Request.getUrl(url).json().notify(this, func, store, query).send();
   return YES;
},
fetchDidComplete: {
   'default': function(response, store, query) {
   if (SC.ok(response)) {
     //В зависимости от id берём нужные записи из полученного json
     var body = this.urls[query.get('id')]['records'];
     //Получаем тип записи
     var recordType = query.get('recordType');
     //Если тип не задан, значит их несколько и они хранятся в recordsTypes
     if (!recordType) {
       var recordTypes = query.get('recordTypes'); //Получаем типы записей
       jQuery.each(body, function(i) {
       //Загружаем в store записи разных типов. Порядок выдачи записей разных типов сервером
       //и порядок этих типов в запросе должны совпадать. Т.е.:
       //SC.Query.local[Scm.Product, Scm.Taxon] и {products: [{product1}, {product2}], taxons: [{taxon1}, {taxon2}]}
       store.loadRecords(recordTypes[i], response.get('body')[body[i]]); });
     }
     else {
       store.loadRecords(recordType, response.get('body'));
     }
     store.dataSourceDidFetchQuery(query);
     } else store.dataSourceDidErrorQuery(query, response);
     }
   },
});

вторник, 2 августа 2011 г.

Tradefast & Sproutcore 1.6: Создание и использование тем

Tradefast & Sproutcore 1.6: Создание и использование тем
В этом небольшом руководстве описывается процесс создания и последующего использования своей темы для sproutcore-приложения, импользующего handlebars в качестве шаблонизатора.

В самом простом (и наиболее часто используемом) случае, тема - это набор css-стилей. Помимо этого темы могут заменять DOM, сгенерированный через views приложения.

Создадим новую тему командой sc-gen theme и назовём её tradefast-red:

sc-gen theme tradefast-red
~ Created directory at themes/tradefast_red
~ Created directory at themes/tradefast_red/resources
~ Created file at themes/tradefast_red/resources/theme_styles.css
~ Created file at themes/tradefast_red/theme.js
~ Created file at themes/tradefast_red/Buildfile
Your theme is now ready to use!


Sproutcore добавил в /themes (если этой папки нет, то она так же будет сгенерирована) папку /tradefast_red, в которой хранятся все необходимые для темы файлы. В main.js определяется новая тема и добавляется в приложение:

TradefastRed = SC.AceTheme.create({
name: 'tradefast-red'
});
SC.Theme.addTheme(TradefastRed);


По-умолчанию новая тема создаётся как под-тема темы Ace, которая не нужна, если приложение использует handlebars. Поэтому лучше удалить ‘Ace’ из определения темы:
TradefastRed = SC.Theme.create

Так же стоит убрать ace из tradefast_red/Buildfile:
Было: config :tradefast_red, :css_theme => 'ace.tradefast-red'
Стало: config :tradefast_red, :css_theme => 'tradefast-red'

В папке /resources находятся стили вашей темы, по-умолчанию это файл theme_style.css, но вы можете добавлять сколько угодно css-файлов. В этой же папке стоит хранить все изображения, связанные с темой.

Что бы использовать тему в приложении, нужно указать её в Buildfile самого приложения, который лежит на одном уровне с папками /themes и /apps:
config :all, :required => :sproutcore', :theme => "tradefast_red"
Обратите внимание, что имя темы совпадает с именем папки с темой в /themes/. Не перепутайте с именем, указанном в main.js. :theme => “tradefast-red” не сработает.

Остаётся только перезапустить сервер sc-server и посмотреть на новую тему в действии.

Внимание: не рекомендуется при использовании тем создавать css внутри самого приложения, так как эти стили определяются до темы и переписывают все правила, заданные в вашей теме. Вместо того что бы портить css кучей !important, лучше хранить полный css в каждой теме отдельно. Такой подход так же позволит переопределять макет приложения без лишних телодвижений.

четверг, 14 июля 2011 г.

latest xen 4 and libvirt.

For connect libvirt to xen4 on fedora15 require install latest version of libvirt.
Today it is
rpm -Uvh http://kojipkgs.fedoraproject.org/packages/libvirt/0.9.3/2.fc16/x86_64/libvirt-0.9.3-2.fc16.x86_64.rpm http://kojipkgs.fedoraproject.org/packages/libvirt/0.9.3/2.fc16/x86_64/libvirt-python-0.9.3-2.fc16.x86_64.rpm http://kojipkgs.fedoraproject.org/packages/libvirt/0.9.3/2.fc16/x86_64/libvirt-client-0.9.3-2.fc16.x86_64.rpm http://kojipkgs.fedoraproject.org/packages/parted/3.0/2.fc16/x86_64/parted-3.0-2.fc16.x86_64.rpm http://kojipkgs.fedoraproject.org/packages/netcf/0.1.8/1.fc16/x86_64/netcf-libs-0.1.8-1.fc16.x86_64.rpm

But after tests: kvm is faster simpler and better.

воскресенье, 10 июля 2011 г.

capybara get html

For get html
find("ul.random_quotes").native.to_html
for get elements count
find("ul.random_quotes").native.search("li").size

четверг, 7 июля 2011 г.

запускаем тесты на jenkins

Приложение без иксов может работать в фреймбуфере.
Для рельсы есть готовый гем headless, добавляем его в Gemfile
gem "headless"

В features/support/env.rb добавляем проверку на то как запускать тесты - в иксах или Xvfb:
if ENV['HEADLESS'] == 'true'
require 'headless'
headless = Headless.new
headless.start
at_exit do
headless.destroy
end
end

ну а фреймбуфер ставим в зависимости от дистрибутива.Для федоры
yum install xorg-x11-server-Xvfb

теперь перед запуском в окружение стоит добавить export HEADLESS='true'

пятница, 1 июля 2011 г.

spree amazon api

Hello
released new spree ext. for export products from amazon.
https://github.com/pronix/spree_amazon_api

Spree <-> Amazon
Requirements

Spree >= 0.60.0
Installation

Add to Gemfile:

gem "spree_amazon_api", :git => "git@github.com:pronix/spree_amazon_api.git"

run task:

rake spree_amazon_api:install

run migrate: (add amazon_id to product table)

rake db:migrate

Root taxons define in file: db/amazon_categories.yml
Configure Amazon access:

Setting amazon options in amazon.yml file( Rails.root/config).
Configure example:

development:
:configure: # acces options
:aWS_access_key_id: 0XQXXC6YV2C85DX1BF02
:aWS_secret_key: fwLOn0Y/IUXEM8Hk49o7QJV+ryOscbhXRb6CmA5l
:response_group: 'Large'
:country: 'us' # region
:query: # search options
:q: "%{q}" # %{q} replace on user keywords
:options:
:search_index: 'Books'
:response_group: 'Large, Accessories'
:sort: "salesrank" # default sort

production:
:configure:
:aWS_access_key_id: 0XQXXC6YV2C85DX1BF02
:aWS_secret_key: fwLOn0Y/IUXEM8Hk49o7QJV+ryOscbhXRb6CmA5l
:response_group: 'Large'
:country: 'us'
:query:
:q: "%{q}"
:options:
:search_index: 'Books'
:response_group: 'Large, Accessories'
:sort: "salesrank"

Search options

To customize the search, set search format in param :q(Rails.root/config/amazon.yml)

For instance: if you set :q with "%{q} made in Vermont" then user query "tools" will be replaced with "tools made in Vermont"

четверг, 7 апреля 2011 г.

spree+middleware

Enable middleware from ext. for spree
in class Engine insert that

initializer "sc_middleware" do |app|
app.middleware.use ScMiddleware
end

воскресенье, 27 марта 2011 г.

switch postgresql from standby to normal mode.

After configure hot standby on postgresql 9.0, i configure heartbeat for switch slave server to master.
require add to recovery.conf this line
trigger_file = '/var/lib/pgsql/9.0/data/MASTER'

And after
touch /var/lib/pgsql/9.0/data/MASTER
and service postgresql-9.0 restart
server work as read/write server.

for return to standby require sync db files between master and slave
and
mv /var/lib/pgsql/9.0/data/recovery.done /var/lib/pgsql/9.0/data/recovery.conf
and again restart postgresql server

четверг, 3 марта 2011 г.

postgresql tablespace selinux

I need store separate databases in different path.
require define this space - it is called tablespace in postgresql.
For create tablespace require set selinux context to path.
example: i create new path /ooo/target
chcon -u system_u -r object_r -t var_lib_t /ooo
chcon -u system_u -r object_r -t postgresql_db_t /ooo/target

Complete for create table space:
CREATE TABLESPACE new_target_space LOCATION '/ooo/target';

четверг, 6 января 2011 г.

hudson utf8 encoding

For test we use hudson, but it show russian letters not correct.
Require set java option
-Dfile.encoding=UTF8
and restart hudson
for fedora and centos
HUDSON_JAVA_OPTIONS="-Djava.awt.headless=true -Dfile.encoding=UTF8"
in /etc/sysconfig/hudson