Кто не любит закаты? Из всех сайтов, которые вы видели о закате, вы чувствуете, что ни один не делает это правильно. Вы решаете сделать лучший сайт проклятых закатов в Интернете. Есть куча фотографий заката. Можем ли мы использовать общедоступные изображения, чтобы наполнить этот новый удивительный сайт? Мы создадим сайт, пока юристы выяснят последний вопрос.
Дети все еще говорят об API?
Кто-то может подумать: «Могу поспорить, у Flickr есть отличные фотографии заката». Да, действительно. Мы выберем эту группу . Внизу страницы вы увидите канал RSS. Можем ли мы прочитать этот канал и показать изображения и отдать должное нужным людям?
Да, да, мы можем.
Давайте использовать наших друзей Синатра для нашего сайта и стойки / тест для тестирования.
Установите Синатру и стойку / тестируйте
gem install sinatra
gem install rack-test
Мы создадим feed_aggregator и тестовую папку внутри него. Пока мы занимаемся этим, создайте пустой основной и тестовый файл тоже.
$ mkdir feed_aggregator
$ mkdir feed_aggregator/test
$ touch feed_aggregator/test/feed_aggregator_test.rb
Возможно, наш первый тест — убедиться, что приложение запускается. В тестовом файле давайте добавим
require '../main'
require 'test/unit'
require 'rack/test'
ENV['RACK_ENV'] = 'test'
class FeedAggregatorTest < Test::Unit::TestCase
include Rack::Test::Methods
def app
Sinatra::Application
end
def test_it_says_feed_aggregator
get '/'
assert last_response.ok?
assert_equal 'Feed Aggregator', last_response.body
end
end
Запустите тест.
test$ ruby feed_aggregator_test.rb
<internal:lib/rubygems/custom_require>:29:in `require': no such file to load -- ../main (LoadError)
from <internal:lib/rubygems/custom_require>:29:in `require'
from feed_aggregator_test.rb:1:in `<main>'
Да! Мы провалили тест. Там написано, что основного файла нет. Идите и сделайте этот файл.
feed_aggregator $ touch main.rb
test$ ruby feed_aggregator_test.rb
Loaded suite feed_aggregator_test
Started
E
Finished in 0.001054 seconds.
1) Error:
test_it_says_feed_aggregator(FeedAggregatorTest):
NameError: uninitialized constant FeedAggregatorTest::Sinatra
feed_aggregator_test.rb:11:in `app'
/Users/johnivanoff/.rvm/gems/ruby-1.9.2-p180@feed_aggregator/gems/rack-test-0.6.2/lib/rack/test/methods.rb:31:in `build_rack_mock_session'
/Users/johnivanoff/.rvm/gems/ruby-1.9.2-p180@feed_aggregator/gems/rack-test-0.6.2/lib/rack/test/methods.rb:27:in `rack_mock_session'
/Users/johnivanoff/.rvm/gems/ruby-1.9.2-p180@feed_aggregator/gems/rack-test-0.6.2/lib/rack/test/methods.rb:42:in `build_rack_test_session'
/Users/johnivanoff/.rvm/gems/ruby-1.9.2-p180@feed_aggregator/gems/rack-test-0.6.2/lib/rack/test/methods.rb:38:in `rack_test_session'
/Users/johnivanoff/.rvm/gems/ruby-1.9.2-p180@feed_aggregator/gems/rack-test-0.6.2/lib/rack/test/methods.rb:46:in `current_session'
feed_aggregator_test.rb:15:in `test_it_says_feed_aggregator'
1 tests, 0 assertions, 0 failures, 1 errors, 0 skips
Давайте добавим код для приложения с надписью «Агрегатор каналов».
require 'sinatra'
get '/' do
"Feed Aggregator"
end
Как вы думаете, тест пройдет сейчас? Давай выясним.
test$ ruby feed_aggregator_test.rb
Loaded suite feed_aggregator_test
Started
.
Finished in 0.063198 seconds.
1 tests, 2 assertions, 0 failures, 0 errors, 0 skips
Тест проходит.
Теперь давай возьмем этот канал. Поскольку мы разрабатываем тесты, было бы неплохо иметь фиксированный канал RSS. Идите вперед и создайте для них каталог и пустой XML-файл.
test$ mkdir fixtures
test$ touch fixtures/feed.xml
Отлично. Давайте скопируем источник канала в файл feed.xml . Если мы проведем тестирование в режиме реального времени, записи могут измениться и сделать его очень разочаровывающим во время тестирования. Кому это нужно?
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:media="http://search.yahoo.com/mrss/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:creativeCommons="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html"
xmlns:flickr="urn:flickr:user" >
<channel>
<title>Flickr's Best Sunsets Pool</title>
<link>http://www.flickr.com/groups/flickrsbestsunsets/pool/</link>
... Content elided ...
(Примечание. Весь XML-файл можно найти в этом разделе .
ВОТ ЭТО ДА! С чего начать? Почему бы нам не поискать ссылки, чтобы человек мог кликнуть на оригинальную фотографию на Flickr?
Если вы посмотрите на XML, то увидите, что каждая картинка находится в разделе товара. Ссылочный тег перенесет нас на страницу фотографии. Исходя из этого, нам нужно просмотреть канал, найти элементы и найти ссылку в этом элементе.
Это звучит как потрясающий тест. Запишите это.
def test_find_the_link
feed = File.read('fixtures/feed.xml')
items = parse feed
item = items.first
link = 'http://www.flickr.com/photos/mattcaustin/8205498382/in/pool-1373979@N22'
assert_equal item[:link], link
end
Где я взял текст для переменной ссылки? Я скопировал ссылку с первого пункта в приспособлении. Скоро вы увидите, как это используется.
Как я уже говорил, мы загрузим и проанализируем прибор. Код будет искать в первом элементе, чтобы найти узел ссылки, взять его текст, сравнить его с нашей переменной ссылки. Это должно соответствовать.
Давайте запустим тест. Я надеюсь, что это не удастся.
test$ ruby feed_aggregator_test.rb
Run options:
# Running tests:
E.
Finished tests in 0.025705s, 77.8059 tests/s, 77.8059 assertions/s.
1) Error:
test_find_the_link(FeedAggregatorTest):
NoMethodError: undefined method `parse' for #<FeedAggregatorTest:0x007ff7412ac390>
feed_aggregator_test.rb:24:in `test_find_the_link'
2 tests, 2 assertions, 0 failures, 1 errors, 0 skips
Нет метода для разбора
Идите дальше и создайте этот метод в файле main.rb.
require 'sinatra'
def parse
end
get '/' do
"Feed Aggregator"
end
Повторный тест.
test$ ruby feed_aggregator_test.rb
Run options:
# Running tests:
E.
Finished tests in 0.251661s, 7.9472 tests/s, 7.9472 assertions/s.
1) Error:
test_find_the_link(FeedAggregatorTest):
ArgumentError: wrong number of arguments (1 for 0)
/Users/john/Dropbox/feed_aggregator/main.rb:4:in `parse'
feed_aggregator_test.rb:23:in `test_find_the_link'
2 tests, 2 assertions, 0 failures, 1 errors, 0 skips
Мы не передавали никаких аргументов. Это нормально, так как мы хотели решить только последнюю ошибку. Что вы делаете, чтобы сделать этот пропуск? Вернуться в файл main.rb
require 'sinatra'
def parse feed
end
get '/' do
"Feed Aggregator"
end
Вы думаете, что избавитесь от ошибки аргументов ? Перезапустите тест и посмотрите.
test$ ruby feed_aggregator_test.rb
Run options:
# Running tests:
E.
Finished tests in 0.027235s, 73.4349 tests/s, 73.4349 assertions/s.
1) Error:
test_find_the_link(FeedAggregatorTest):
NoMethodError: undefined method `first' for nil:NilClass
feed_aggregator_test.rb:24:in `test_find_the_link'
2 tests, 2 assertions, 0 failures, 1 errors, 0 skips
Это действительно так. Теперь нам нужен «первый» метод.
Для того, чтобы добраться до первого, нам нужно будет просмотреть XML, чтобы получить первую ссылку.
Теперь, как можно проанализировать XML, чтобы найти эту ссылку? Мне нравится (Нокогири) [http://nokogiri.org]. У вас установлен этот драгоценный камень? Давайте проверим. Ваш вывод может выглядеть иначе, чем мой.
test$ gem list --local -d noko
*** LOCAL GEMS ***
Видимо, нет. Если вы этого не сделаете, идти вперед и установить его.
test$ sudo gem install nokogiri
Кроме того, увидев в терминале, что он успешно установлен, как вы можете проверить, установлен ли он?
test$ gem list --local -d noko
*** LOCAL GEMS ***
nokogiri (1.5.5)
Authors: Aaron Patterson, Mike Dalessio, Yoko Harada, Tim Elliott
Rubyforge: http://rubyforge.org/projects/nokogiri
Homepage: http://nokogiri.org
Installed at: /Users/john/.rvm/gems/ruby-1.9.3-p194@feed_aggregator
Nokogiri (鋸) is an HTML, XML, SAX, and Reader parser
Где были мы? Разбор XML-документа. Вам нужно загрузить корм в Нокогири. Затем вы можете просмотреть каждый элемент, получить ссылку и сохранить их в хеше. Не забудьте вернуть предметы.
require 'sinatra'
require 'nokogiri'
def parse feed
doc = Nokogiri::XML feed
doc.search('item').map do |doc_item|
item = {}
item[:link] = doc_item.at('link').text
item
end
end
get '/' do
"Feed Aggregator"
end
У вас есть хорошее чувство по этому поводу? Идите вперед и запустите тест. Вы не забыли включить nokogiri в main.rb?
test$ ruby feed_aggregator_test.rb
Loaded suite feed_aggregator_test
Started
..
Finished in 0.112374 seconds.
2 tests, 3 assertions, 0 failures, 0 errors, 0 skips
Snoopy Dance Можем ли мы сказать это?
Нам нужно получить миниатюру дальше. Где это в приспособлении?
Ты нашел это? Это атрибут в теге. Идите вперед и напишите тест для этого. Это довольно близко к первому.
def test_find_the_thumbnail_image
feed = File.read('fixtures/feed.xml')
items = parse feed
item = items.first
thumbnail = 'http://farm9.staticflickr.com/8488/8205498382_4e5ed09a62_s.jpg'
assert_equal item[:thumbnail], thumbnail
end
Запустите тест.
test$ ruby feed_aggregator_test.rb
Run options:
# Running tests:
.F..
Finished tests in 0.053503s, 74.7622 tests/s, 93.4527 assertions/s.
1) Failure:
test_find_the_thumbnail_image(FeedAggregatorTest) [feed_aggregator_test.rb:42]:
<nil> expected but was
<"http://farm9.staticflickr.com/8488/8205498382_4e5ed09a62_s.jpg">.
4 tests, 5 assertions, 1 failures, 0 errors, 0 skips
Это было ожидаемо. Давайте подумаем об этом. Нам нужно значение атрибута media:thumbnail
Как насчет этого?
require 'sinatra'
require 'nokogiri'
def parse feed
doc = Nokogiri::XML feed
doc.search('item').map do |doc_item|
item = {}
item[:link] = doc_item.at('link').text
item[:thumbnail] = doc_item.at('media:thumbnail').attr('url').value
item
end
end
get '/' do
"Feed Aggregator"
end
Это имеет смысл, поскольку URL-адрес, который нам нужен, является атрибутом этого узла. Идите и попробуйте.
test john$ ruby feed_aggregator_test.rb
Run options:
# Running tests:
EE.
Finished tests in 0.031776s, 94.4109 tests/s, 62.9406 assertions/s.
1) Error:
test_find_the_link(FeedAggregatorTest):
NoMethodError: undefined method `attr' for nil:NilClass
/Users/john/Dropbox/feed_aggregator/main.rb:10:in `block in parse'
/Users/john/.rvm/gems/ruby-1.9.3-p194@feed_aggregator/gems/nokogiri-1.5.5/lib/nokogiri/xml/node_set.rb:239:in `block in each'
/Users/john/.rvm/gems/ruby-1.9.3-p194@feed_aggregator/gems/nokogiri-1.5.5/lib/nokogiri/xml/node_set.rb:238:in `upto'
/Users/john/.rvm/gems/ruby-1.9.3-p194@feed_aggregator/gems/nokogiri-1.5.5/lib/nokogiri/xml/node_set.rb:238:in `each'
/Users/john/Dropbox/feed_aggregator/main.rb:7:in `map'
/Users/john/Dropbox/feed_aggregator/main.rb:7:in `parse'
feed_aggregator_test.rb:23:in `test_find_the_link'
2) Error:
test_find_the_thumbnail_image(FeedAggregatorTest):
NoMethodError: undefined method `attr' for nil:NilClass
/Users/john/Dropbox/feed_aggregator/main.rb:10:in `block in parse'
/Users/john/.rvm/gems/ruby-1.9.3-p194@feed_aggregator/gems/nokogiri-1.5.5/lib/nokogiri/xml/node_set.rb:239:in `block in each'
/Users/john/.rvm/gems/ruby-1.9.3-p194@feed_aggregator/gems/nokogiri-1.5.5/lib/nokogiri/xml/node_set.rb:238:in `upto'
/Users/john/.rvm/gems/ruby-1.9.3-p194@feed_aggregator/gems/nokogiri-1.5.5/lib/nokogiri/xml/node_set.rb:238:in `each'
/Users/john/Dropbox/feed_aggregator/main.rb:7:in `map'
/Users/john/Dropbox/feed_aggregator/main.rb:7:in `parse'
feed_aggregator_test.rb:31:in `test_find_the_thumbnail_image'
3 tests, 2 assertions, 0 failures, 2 errors, 0 skips
Ну, это не удалось. undefined метод ‘attr’ для nil: NilClass Не удается найти узел <media:thumbnail>
Если вы посмотрите на верхнюю часть RSS-канала, то увидите, что используется пространство имен мультимедиа. Подробнее о пространствах имен.
Оказывается, с Nokogiri, вы можете просто использовать символ трубы, чтобы указать поиск пространства имен. идти вперед и поменять местами двоеточие и заменить его трубкой в строке миниатюр.
require 'sinatra'
require 'nokogiri'
def parse feed
doc = Nokogiri::XML feed
doc.search('item').map do |doc_item|
item = {}
item[:link] = doc_item.at('link').text
item[:thumbnail] = doc_item.at('media|thumbnail').attr('url')
item
end
end
get '/' do
"Feed Aggregator"
end
Дай попробовать. перезапустить тесты
test$ ruby feed_aggregator_test.rb
Run options:
# Running tests:
...
Finished tests in 0.045266s, 66.2749 tests/s, 88.3665 assertions/s.
3 tests, 4 assertions, 0 failures, 0 errors, 0 skips
Потрясающие. Возможно, нам следует использовать заголовок картинки тоже. Идите вперед и напишите тест для этого. Я буду ждать.
Законченный? Вот что я сделал.
def test_find_the_title
feed = File.read('fixtures/feed.xml')
items = parse feed
item = items.first
title = 'An Evening at Shell Beach'
assert_equal item[:title], title
end
Опять же, мы используем заголовок из первого пункта нашего прибора. Запустите тест.
test$ ruby feed_aggregator_test.rb
Run options:
# Running tests:
.F.
Finished tests in 0.078211s, 38.3578 tests/s, 51.1437 assertions/s.
1) Failure:
test_find_the_title(FeedAggregatorTest) [feed_aggregator_test.rb:34]:
<nil> expected but was
<"An Evening at Shell Beach">.
3 tests, 4 assertions, 1 failures, 0 errors, 0 skips
Нам нужно искать название. Как бы вы добавили это в метод разбора?
require 'sinatra'
require 'nokogiri'
def parse feed
doc = Nokogiri::XML feed
doc.search('item').map do |doc_item|
item = {}
item[:link] = doc_item.at('link').text
item[:thumbnail] = doc_item.at('media|thumbnail').attr('url')
item[:title] = doc_item.at('title').text
item
end
end
get '/' do
"Feed Aggregator"
end
Запустите тест.
test$ ruby feed_aggregator_test.rb
Run options:
# Running tests:
....
Finished tests in 0.062725s, 63.7704 tests/s, 79.7130 assertions/s.
4 tests, 5 assertions, 0 failures, 0 errors, 0 skips
Круто, но давайте посмотрим что-нибудь на веб-странице. Мы хотим результаты в браузере.
Для простоты я буду использовать erb для создания веб-страницы.
require 'sinatra'
require 'nokogiri'
def parse feed
doc = Nokogiri::XML feed
doc.search('item').map do |doc_item|
item = {}
item[:link] = doc_item.at('link').text
item[:thumbnail] = doc_item.at('media|thumbnail').attr('url')
item[:title] = doc_item.at('title').text
item
end
end
get '/' do
erb :index
end
__END__
@@index
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="user-scalable=yes, width=device-width" />
<title>Lovely Sunsets</title>
</head>
<body>
<h1>Feed Aggregator</h1>
</body>
</html>
Так как мы внесли некоторые изменения, мы должны повторить тесты.
test$ ruby feed_aggregator_test.rb
Run options:
# Running tests:
...F
Finished tests in 0.251257s, 15.9200 tests/s, 19.8999 assertions/s.
1) Failure:
test_it_says_feed_aggregator(FeedAggregatorTest) [feed_aggregator_test.rb:18]:
<"Feed Aggregator"> expected but was
<"<!DOCTYPE html>n<html>n <head>n <meta charset="UTF-8">n <meta name="viewport" content="user-scalable=yes, width=device-width" />n<title>Lovely Sunsets</title> n</head>n<body>n <h1>Feed Aggregator</h1>n</body>n</html>n">.
4 tests, 5 assertions, 1 failures, 0 errors, 0 skips
К сожалению. Идите и исправьте это.
def test_it_says_feed_aggregator
get '/'
assert last_response.ok?
assert_match 'Feed Aggregator', last_response.body
end
Мы следим за тем, чтобы «Агрегатор кормов» был на странице.
Теперь, когда тесты проходят, давайте двигаться дальше. Вы перезапустили тест, верно? Мы добавим URL фида, а затем проанализируем нашу информацию.
require 'sinatra'
require 'nokogiri'
feed = File.read('test/fixtures/feed.xml')
def parse feed
doc = Nokogiri::XML feed
doc.search('item').map do |doc_item|
item = {}
item[:link] = doc_item.at('link').text
item[:thumbnail] = doc_item.at('media|thumbnail').attr('url')
item[:title] = doc_item.at('title').text
item
end
end
get '/' do
@pictures = parse feed
erb :index
end
__END__
@@index
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="user-scalable=yes, width=device-width" />
<title>Lovely Sunsets</title>
</head>
<body>
<h1>Feed Aggregator</h1>
<dl>
<% @pictures.each do |picture| %>
<dt><a href="<%= picture[:link] %>"><%= picture[:title] %></a></dt>
<dd><img src="<%= picture[:thumbnail] %>" /></dd>
<% end %>
</dl>
</body>
</html>
Вы можете заметить, что я имею в виду тестовое устройство вместо RSS-канала. Опять же, я не хочу постоянно запрашивать их сервер во время разработки.
Идите вперед, запустите сервер и проверьте прекрасную работу в вашем браузере.
Все выглядит хорошо. Давайте использовать реальные данные. Как бы вы подключили это, чтобы получить канал от Flickr? Да, давайте добавим open-uri в файл main.rb и затем nokogiri откроет файл.
require 'sinatra'
require 'nokogiri'
require 'open-uri'
feed = 'http://api.flickr.com/services/feeds/groups_pool.gne?id=1373979@N22&lang=en-us&format=rss_200'
def parse feed
doc = Nokogiri::XML(open(feed))
doc.search('item').map do |doc_item|
item = {}
item[:link] = doc_item.at('link').text
item[:thumbnail] = doc_item.at('media|thumbnail').attr('url')
item[:title] = doc_item.at('title').text
item
end
end
get '/' do
@pictures = parse feed
erb :index
end
__END__
@@index
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="user-scalable=yes, width=device-width" />
<title>Lovely Sunsets</title>
</head>
<body>
<h1>Lovely Sunsets</h1>
<dl>
<% @pictures.each do |picture| %>
<dt><a href="<%= picture[:link] %>"><%= picture[:title] %></a></dt>
<dd><img src="<%= picture[:thumbnail] %>" /></dd>
<% end %>
</dl>
</body>
</html>
Запустите его и просмотрите в браузере http://127.0.0.1:4567/
Сладкий. Теперь вы можете добавить обработку ошибок, возможно, кеширование или несколько каналов.
Если вы хотите увидеть статью по одному из них, дайте нам знать.
Приветствия.