Rails: страница в странице

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

Совсем недавно пришлось сделать что-то подобное. Необходимо на странице отобразить еще несколько страниц со своего же сайта.

Вот что получилось сделать с помощью jquery


$.ajax({
url: "controller/action",
cache: false,
success: function(html){
$("#conatiner").append(html);
}
});


Контроллер же просто по вызову `action` отдает необходимый контент. Причем можно использовать любые параметры. Мне сама идея очень понравилась. Для подгрузки не основных модулей.
Долгое время парился с тем что не мог прологировать запросы которые отсылает ActiveResource. Оказалось все просто, надо добавить одну строчку в enviroment.rb:


ActiveResource::Base.logger = ActiveRecord::Base.logger
Последнее время появилось много обсуждений по поводу rspec, того что он вгрызается в core языка и фреймворка. На самом деле я сам сталкивался с проблемами, когда для того чтобы просто заставить работать rspec требовалось много часов тупого плутания по коду.

В ror2ru многие советуют shoulda, наконец решил его попробовать.

Почитав документации тут и тут написал несколько тестов:

Тестируемый код:

module KeywordsHelper
MIN_LENGHT = 3

def get_keywords(full_text)
words = FrequencyHash.new
full_text.split(/\W/).each do |word|
words.occur(word) if word.length >= MIN_LENGHT
end
words.frequent_keys(2)
end

end


Cам тест:


require 'test_helper'

class KeywordsTest < Test::Unit::TestCase
include KeywordsHelper

context "get_keywords" do
setup do
end

should "return keywords that occur more than 2 times" do
assert_equal ['lazy'], get_keywords("quick lazy frog jumps over the lazy dog")
assert_equal ['frog', 'lazy'], get_keywords("quick lazy frog jumps over the lazy frog")
end

should "not return keywords with lenght smaller than 3 symbols" do
assert_equal [], get_keywords("do do do do")
end

should "return keywords sorted by frequency" do
assert_equal ["dog", "fox"], get_keywords("fox fox dog dog fox dog dog")
end

should "return keywords sorted alphabetically" do
assert_equal ["dog", "fox"], get_keywords("fox fox dog dog fox dog")
end

end

end


В итоге получилось достаточно не плохо, в отличии от стандартных тестов, появились контексты, не надо использовать underscore, плюс масса полезных хелперов.

Все это дело безболезненно заработало вместе с rails 2.3.2

Теперь планирую поюзать shoulda для тестирования контроллеров и проинтегрять с mocha. Если все пройдет гладко, то можно пробовать вводить его для некоторых проектов.
Столкнулся с проблемой, когда при обновлении версий на production сервере код и миграции накатываются в разных транзакциях. В итоге приходит большое количество ошибок, так как модели еще не проапдейтились. Решил написать capistrano таск, который бы вводил сайт в режим "технические работы" и отдавал статическую страничку с чем-то типа "тех обслуживание сайта".

Выглядеть это должно так:


cap maintenance:start
cap maintenance:stop


Причем необходимо сделать зависимости, к примеру если происходит запуск cap deploy после определенного периода времени, автоматически включать статус maintenance, а также выключать его по прошествии определенного времени.

Как это все реализовать еще обсуждается.

Парсинг с помощью libxml

Наконец добрался и до lib-xml. Особых отличий в использовании я не увидел. Производительность на уровне nokogiri.

Вот их документация.

И небольшой пример:


xml = File.read(self.filename)
remove_unused_nodes(xml)

doc = XML::Document.string(xml)
# doc = XML::Document.file(self.filename) # Можно и так создавать документ

doc.find('//VisioDocument/Masters/Master').each do |s|
master = {}
master.store(:base_id, s['BaseID'])
master.store(:master_id, s['ID'])

self.masters << master
end


В целом документ размером в 5-10мб парсит меньше чем пол секунды, для моей задачи более чем приемлемо. На этом я думаю мои искания парсеров закончены :)

Парсинг средствами nokogiri

Возращаясь к теме парсинга файлов. Появилась необходимость распарсить файлы генерируемые Microsoft Visio. Файлы размера порядка 5-10Мб.

Изначально использовал Rexml и сразу стало понятно что при парсинге файлов больше 1мб парсер никуда не годиться. Файл объемом в 10мб он парсил две-три минуты, при этом съедая 400 и больще мб ОЗУ.

Далее попробовал hpricot, но и он не показал особых улучшений. Примерно все стало на 15-20% быстрее, но это все равно долго.

В итоге я добрался до nokogiri, вот он меня приятно порадовал. Файлы размером в 10мб парсились за неполные 2 секунды и памяти при это почти не съедал. Nokogiri для парсинга использует XPath соответственно код получается довольно наглядным и простым. Ниже пример:


xml = File.read(filename)
doc = Nokogiri::XML(xml)

self.page_height = doc.xpath('//VisioDocument/Pages/Page/PageSheet/PageProps')
.search('.//PageHeight').inner_html

doc.xpath('//VisioDocument/Pages/Page/Shapes/Shape').each do |s|
shape = {}
shape.store(:master_id, s.attributes['Master'].text) if s.attributes['Master']

if line_object?(s)
xform1d = s.at('.//XForm1D')
shape.store(:x, xform1d.at('.//BeginX').inner_html)
shape.store(:y, xform1d.at('.//BeginY').inner_html)
shape.store(:to_x, xform1d.at('.//EndX').inner_html)
shape.store(:to_y, xform1d.at('.//EndY').inner_html)

self.line_shapes << shape
end

end


Возможно разница в производительности связана с неправильными использованием библиoтек, но я старался делать все по оригинальной документации.

Парсинг XML файлов в Ruby

Утилитку для парсинга написал довольно давно, но на днях пришлось снова попользоваться, оказалось, что работает она вполне адекватно.

Работает она просто, весь xml файл, вместе с аттрибутами и вложенными тегами, конвертирует в хэш.


module XmlParser

def parse(xml_text, prefix)
items = []
doc = REXML::Document.new(xml_text)
REXML::XPath.each(doc, prefix) do |xml|
item = {}
xml.attributes.each do |k, value|
item.store(k.to_sym, value)
end
translate_children(xml, item)
items << item
end
return items
end

private
def translate_children(xml, item)
return if xml.children.empty?

xml.each_child do |child|
if child.kind_of? REXML::Element
sub_item = {}
if item.include?(child.name.to_sym)
if item[child.name.to_sym].is_a? Array
item.store(child.name.to_sym,
item[child.name.to_sym] << sub_item)
else
item.store(child.name.to_sym,
[item[child.name.to_sym], sub_item])
end
else
item.store(child.name.to_sym, sub_item)
end
if child.has_text?
sub_item[:text] = child.text.strip
end
child.attributes.each do |k, value|
sub_item.store(k.to_sym, value)
end
translate_children(child, sub_item)
end
end
end
end


Пример использования


include XmlParser
parse(xml_text, '/projects')