Ruby on Railsでサイトマップを自作
Ruby on Railsでサイトマップを自作
Railsのwebアプリをデプロイした後、SEO的につくらないといけないのがsitemapです。
ネットのサイトマップジェネレーターなんかもありますが、ページ数が制限されていたり、毎回手動で設置しないといけない。
また、sitemap-generatorなるgemもあるんですが、URLが複雑になってくると対応できない(できるのかもしれないけど、とにかくややこしい!)という問題があります。
そこでRailsそのものを使ってサイトマップを自作する方法を紹介したいと思います。よく考えてみるとRailsは優秀なWEB APIで、sitemapで使うxmlファイルにも対応しているわけです。WEBページを作る要領でカスタマイズできますし、ページが追加/削除されると自動的にsitemapに反映されるのでとても便利。Railsに慣れた方々にはおすすめの方法です。
1. Routesの設定
config/routes.rbに下記の行を追加。ドメイン/sitemapにアクセスするとsitemapコントローラーのインデックスメソッドを呼び出します。
get '/sitemap' => 'sitemaps#index'
2.Sitemaps Controllerを作る
まずはsitemapのみを扱うコントローラーを作成します。
$ rails g controller Sitemaps
Sitemapsコントローラーではホスト名とActive Recordのデータを取得します。Productモデルを表示する例です。
app/controllers/sitemap_controller.rbを下記のように設定します。
class SitemapsController < ApplicationController def index @domain = "#{request.protocol}#{request.host}" @products = Product.all end end
3.app/views/sitemaps/index.xml.erbを作成
作るのはxmlファイルなので、html.erbではなく、xml.erbファイルを作ります。
@productを全て表示したいので、eachメソッドを使って全て取り出します。
他に追加したいURLがあれば、
<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc><%= @domain %></loc> </url> <url> <loc><%= @domain %>/index</loc> </url> <% @products.each do |product| %> <url> <loc><%= "#{@domain}/products/#{product.id}" %></loc> </url> <% end %> </urlset>
4.rails serverしてsitemapsを確認
Rails serverを起動して/sitemap.xmlを確認します。ローカル環境では
本番環境ではlocalhostの部分が、ホスト名に切り替わります。
5. google ウェブマスターツールで送信
ウェブマスターツールのクロール→サイトマップでサイトマップの追加/テストに行き、http://ホスト/sitemap.xmlと入力して送信します。
sitemap.xmlは動的に作られますので、次回以降はデータが更新されると勝手にサイトマップも更新され、googleも勝手に見に来てくれます。あら便利。
問題点
僕の環境(rails 5.0.6)では、sitemap/xmlで親モデル/子モデル/孫モデルのデータをうまく取得できませんでした。
URLが /親モデル/id/子モデル/id/孫モデル/idという形だったので、URLを書くにあたって親モデルのidは必須。ここでしばらく詰まってしまいました
少しややこしいですが、下記のような状況です。
- 親モデル: Product
- 子モデル:Post
- 孫モデル:Comment
があった場合、CommentのURLに使うPostやProductのidが取得できないのです。
対応策としては子モデルが持っている親モデルのidを使えば、子モデル(ここではPost)までは問題解決できます。(下記@posts)
しかし、これでは孫モデル(@comments)は解決できません。
<% @posts.each do |post| %> <url> <loc><%=@domain%>/products/<%= post.product_id %>/post/<%= post.id %></loc> </url> <% end %><% @comments.each do |comment| %> <url> <loc><%=@domain %>/products/<%= comment.post.product_id %>/post/<%= comment.post_id %>/comments/<%=comments.id %></loc> ←comment.post.product_id がエラー! </url> <% end %>
そこでINNER JOINを利用して@commentsでproduct_idを利用できるようにします。
@comments = Comment.joins(:post).all.select("comments.*, posts.product_id AS product_id") end
あとは@comments.eachの部分を以下のように書き換えるだけです。
<% @comments.each do |comment| %> <url> <loc><%=@domain %>/products/<%= comment.product_id %>/post/<%= comment.post_id %>/comments/<%=comments.id %></loc> </url> <% end %>