Rails Tutorialの第14章にある、拡張機能を作る件の続きです。
今回はRSSフィード機能を作ります。
##要件の確認
チュートリアルでは、
ユーザーごとのマイクロポストをRSSフィードする機能を実装してください。次にステータスフィードをRSSフィードする機能も実装し、余裕があればフィードに認証スキームも追加してアクセスを制限してみてください。
と要件が書いてあります。
RSSは利用したことはありますが、どういう仕組みなのかはあまりよく分かっていません。
作った人がいるかネットで調べてみます。
feed.rss.builderという標準で搭載されている Builder 機能があるようです。
次にフィードに認証スキームですが、ログオンしていないとフォローできないと言う意味と捉えました。
実例があるのかネットで調べます。
Twitterが認証しているかネットで調べます。Twitter自体はRSSを配信しておらず、別のサイトからRSSを取得しているようです。facebookも昔はオフィシャルの機能としてRSSを提供していたが、打ち切ったとのことした。
参考:認証ありのサイトでRSSを取得するには
http://ユーザ名:パスワード@RSSフィードのURL
でできるとかなり古いネットの記事で見つけましたが、URLに記載するというのはパスワード漏洩のリスクが高そうです。
調べた結果から要件をまとめました。
1.自分自身のポストを、RSSフィードで出力する機能
2.RSSフィードを登録するアイコンとリンクをホームに追加
3.ステータスフィード、つまりフォローしている人も含むポストを、RSSフィードで出力する機能
4.RSSに認証をかけるのは難しそうなので今回はやらない
仕様を設計
要件から具体的な機能に落とし込みます。
1.自分自身のポストを、RSSフィードで出力する機能
urlのxxx/rssにアクセスすると、RSSファイルが返される。
2.RSSフィードを登録するアイコンとリンクをホームに追加
ホームにリンクを追加する。
3.ステータスフィード、つまりフォローしている人も含むポストを、RSSフィードで出力する機能
urlのxxx/rssにアクセスすると、RSSファイルが返される。
##自分自身のポストをRSS出力:コントローラーの追加
トピックブランチを作ります。
ubuntu:~/environment/sample_app (master) $ git checkout -b rss
http://miner.hatenablog.com/entry/2017/07/20/142532
を参考にします。
ルーティングを追加します。
get :rss, to: 'rss#index', defaults: { format: :rss }
コントローラーを生成します。
ubuntu:~/environment/sample_app (rss) $ rails generate controller Rsss
create app/controllers/rsss_controller.rb
create app/views/rsss
create test/controllers/rsss_controller_test.rb
create app/helpers/rsss_helper.rb
create app/assets/javascripts/rsss.coffee
create app/assets/stylesheets/rsss.scss
コントローラーに、userのshowを参考にして、追記します。
class RsssController < ApplicationController
layout false
def index
@user = User.find(params[:id])
@microposts = @user.microposts.limit(10)
respond_to do |format|
format.rss
format.atom
end
end
end
##ビューを作成
ビューを作成します。
app/views/rsss/index.rss.builderファイルを作成します。
cache 'feed_cache_key', expires_in: 30.minutes do
xml.instruct! :xml, :version => "1.0"
xml.rss :version => "2.0" do
xml.channel do
xml.title "Rails Tutorial sample site"
xml.description "Rails Tutorial sample site"
xml.link root_url
@microposts.each do |b|
xml.item do
xml.title b.title
xml.description strip_tags(b.rich_text_body.to_s.gsub(/\r\n|\r|\n|\s|\t/, "")).truncate(120)
xml.image image_url(url_for(b.image))
xml.pubDate b.time.to_s(:rfc822)
xml.link blog_url(b)
xml.guid blog_url(b)
end
end
end
end
end
https://pgmg-rails.com/blogs/24
を参考にします。
cloud9の構文チェックの色がつかなくなりました。拡張子なのか原因は分かりませんでした。
xml.title b.content
xml.description b.content
xml.image image_url(url_for(b.picture)) if b.picture?
xml.pubDate b.updated_at.to_s(:rfc822)
##RSSが出力されるか確認
RSSが出力されるかを確認してみます。
rails serverで、/rssにアクセスします。
https://XXXXXXXXXXXXXXX.amazonaws.com/rss
エラーが出力されました。
ActionController::RoutingError (uninitialized constant RssController):
routesを確認します。
buntu:~/environment/sample_app (rss) $ rails routes
Prefix Verb URI Pattern Controller#Action
root GET / static_pages#home
...
rss GET /rss(.:format) rss#index {:format=>:rss}
複数形とファイル名が合っていないのが問題かとあたりをつけました。
routesのstatic_pages#homeにはファイル名がstatic_pages_controller.erbが対応しています。
なのでrss#indexにはファイル名がrss_controller.erbが対応するのではと考えました。
だとするとroutesがrsss#indexであるべきなので、修正します。
get :rss, to: 'rsss#index', defaults: { format: :rss }
違うエラーが出力されました。
ActiveRecord::RecordNotFound (Couldn't find User with 'id'=):
app/controllers/rsss_controller.rb:5:in `index'
先のエラーは解消されコントローラーに進みました。
ログインしていないので、userのidがないためです。
試しにログインしてからアクセスしてみましたが、同じエラーでした。
原因として、パラメーターからidがなぜ取れないのか、
そもそもRSSを取得するときにはログインしていないので、user#showを参考にします。
GET /users/:id(.:format) users#show
になっています。urlでxxx/users/1 にアクセスするとidに1が渡されてきます。
routesに/:idを追加すればよいのではと考え、修正します。
ネットでroutesの書き方を調べます。
https://www.sejuku.net/blog/13078
get 'rss/:id', to: 'rsss#index', defaults: { format: :rss }
ubuntu:~/environment/sample_app (rss) $ rails routes
Prefix Verb URI Pattern Controller#Action
root GET / static_pages#home
GET /rss/:id(.:format) rsss#index {:format=>:rss}
違うエラーになりました。
ActionView::Template::Error (undefined method `blog_url' for #<#<Class:0x000056130c6fb330>:0x000056130c6f9648>
Did you mean? login_url):
例として参考にした記事のそのままだと動かない点だと分かりました。
linkとguidには何が表示されるべきなのか調べます。
xml.link blog_url(b)
xml.guid blog_url(b)
参考にした記事はブログの例であり、各記事のURLを指定していました。
対応するものとして、マイクロポストごとのURLはないので、ユーザーのURLを出すことにします。pathとURLの違いがあいまいだったのでネットで確認しました。
https://qiita.com/higeaaa/items/df8feaa5b6f12e13fb6f
xml.link user_url(b.user)
xml.guid user_url(b.user)
修正してurlにアクセスしたところ、開くか保存するかのダイアログが表示されました。
保存したファイルをメモ帳で開いたところ、xmlが出力されていました。
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>Rails Tutorial sample site</title>
<description>Rails Tutorial sample site</description>
<link>https://xxxxxxxxxxxxxxxxxxxxx.amazonaws.com/</link>
<item>
<title>@Elvis1 reply test</title>
<description>@Elvis1 reply test</description>
<pubDate>Wed, 11 Nov 2020 23:27:05 +0000</pubDate>
<link>https://xxxxxxxxxxxxxxxxxxxxx.amazonaws.com/users/1</link>
<guid>https://xxxxxxxxxxxxxxxxxxxxx.amazonaws.com/users/1</guid>
</item>
<item>
<title>Est et placeat voluptates et alias.</title>
<description>Est et placeat voluptates et alias.</description>
<pubDate>Wed, 11 Nov 2020 23:27:04 +0000</pubDate>
<link>https://xxxxxxxxxxxxxxxxxxxxx.amazonaws.com/users/1</link>
<guid>https://xxxxxxxxxxxxxxxxxxxxx.amazonaws.com/users/1</guid>
</item>
..
<item>
<title>Sint porro molestiae corporis quidem eligendi.</title>
<description>Sint porro molestiae corporis quidem eligendi.</description>
<pubDate>Wed, 11 Nov 2020 23:27:03 +0000</pubDate>
<link>https://xxxxxxxxxxxxxxxxxxxxx.amazonaws.com/users/1</link>
<guid>https://xxxxxxxxxxxxxxxxxxxxx.amazonaws.com/users/1</guid>
</item>
</channel>
</rss>
このURLをFeedlyに試しに追加してみたのですが、RSSとして認識されませんでした。
We were not able to find an RSS feed for https://xxxxxxx.amazonaws.com/rss/1.
Please contact the website and ask them if they offer a valid RSS feed.
RSSの問題なのかネットで調べましたがよく分かりませんでした。
https://pgmg-rails.com/blogs/24
を参考にコントローラーの書き方を変更してみます。
respond_to do |format|
format.html
format.rss { render :layout => false }
end
ファイルを開くダイアログが表示される動きは変わりませんでした。
ネットで調べましたが分からなかったので、いったんそのままにします。
##ステータスフィードを出力するように変更
今は自分がポストしたものを出力しているので、ステータスフィード、つまりフォローしている人も含むポストを、RSSフィードで出力する機能に変更します。
ステータスフィードを画面に表示しているところを参考にします。
ホーム画面でしたので、static_pagesコントローラーとあたりを付けます。
@feed_items = current_user.feed.paginate(page: params[:page])
を見つけ、feedメソッドで出力できると分かります。コンソールで試してみると、確かにマイクロポストが返ってきました。
コントローラーを変更します。
class RsssController < ApplicationController
def index
@user = User.find(params[:id])
@microposts = @user.feed.limit(10)
respond_to do |format|
format.html
format.rss { render :layout => false }
end
end
end
rails serverで動かしてみます。ファイルが出力されるので内容を確認します。
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>Rails Tutorial sample site</title>
<description>Rails Tutorial sample site</description>
<link>https://https://xxxxxxxxxxxxxxxxxxxxx.amazonaws.com.amazonaws.com/</link>
<item>
<title>@Elvis1 reply test</title>
<description>@Elvis1 reply test</description>
<pubDate>Wed, 11 Nov 2020 23:27:05 +0000</pubDate>
<link>https://https://xxxxxxxxxxxxxxxxxxxxx.amazonaws.com.amazonaws.com/users/1</link>
<guid>https://https://xxxxxxxxxxxxxxxxxxxxx.amazonaws.com.amazonaws.com/users/1</guid>
</item>
<item>
<title>Est et placeat voluptates et alias.</title>
<description>Est et placeat voluptates et alias.</description>
<pubDate>Wed, 11 Nov 2020 23:27:04 +0000</pubDate>
<link>https://https://xxxxxxxxxxxxxxxxxxxxx.amazonaws.com.amazonaws.com/users/6</link>
<guid>https://https://xxxxxxxxxxxxxxxxxxxxx.amazonaws.com.amazonaws.com/users/6</guid>
</item>
</channel>
</rss>
ホーム画面と突き合わせてみると、ポストの2つ目は他のユーザーがポストしたもので合っていました。
RSSでは誰がポストしたマイクロポストなのかが分からないということが分かりました。今回はそのままにして、今後の改善点としておきます。
##テストを作成
動くようになりましたので、テストを書きます。RSSの出力結果をどう扱えばよいのかが分からないのでネットで調べましたが見つかりませんでした。なので画面と同じように書くことにします。
require 'test_helper'
class RssTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
test "rss show" do
get rss_path(@user)
assert_template 'rsss/index'
end
end
エラーになりました。
NoMethodError: NoMethodError: undefined method `rss_path' for #<RssTest:0x000055fc3d47d0f8>
ubuntu:~/environment/sample_app (rss) $ rails routes
Prefix Verb URI Pattern Controller#Action
new_dm GET /dms/new(.:format) dms#new
dm DELETE /dms/:id(.:format) dms#destroy
GET /rss/:id(.:format) rsss#index {:format=>:rss}
rss_pathのルーティングには、Prefixのところに名前がなく空白ないので、名前をつけてみます。
https://www.sejuku.net/blog/13078
を参考にします。
get 'rss/:id', to: 'rsss#index', as: 'rss', defaults: { format: :rss }
ubuntu:~/environment/sample_app (rss) $ rails routes
Prefix Verb URI Pattern Controller#Action
dm DELETE /dms/:id(.:format) dms#destroy
rss GET /rss/:id(.:format) rsss#index {:format=>:rss}
GREENになりました。
contentが表示されているかをassert_matchで調べるように追加します。テストを実行したところGREENでした。
これで完成とします。
test "rss show" do
get rss_path(@user)
assert_template 'rsss/index'
@user.feed.limit(10).each do |micropost|
assert_match micropost.content, response.body
end
end
いつものようにherokuにアップします。
ubuntu:~/environment/sample_app (rss) $ git add -A
ubuntu:~/environment/sample_app (rss) $ git commit -m "Add RSS"
ubuntu:~/environment/sample_app (rss) $ git checkout master
ubuntu:~/environment/sample_app (master) $ git merge rss
ubuntu:~/environment/sample_app (master) $ git push
ubuntu:~/environment/sample_app (master) $ git push heroku
HerokuでRSSのURLにアクセスしてみました。ファイルを開くダイアログが表示される動きは変わらなかったです。
##所要時間
12/21から1/11までの実働13日間の8.0時間です。
ネットで調べるのに時間がかかりました。書いたりテストしたりの時間は、感覚的に半分もかかっていないです。
###参考にした記事のまとめ