きまぐれweb/app開発日記

個人でWEBやアプリの開発してます。使用している言語・フレームワークはGo,Flutter,Rails,Vue,ionicです。これらの開発で発見したこと、思ったことを気まぐれに書いていきます。

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があれば、 <%= @domain %>/******という感じで書きます。

<?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を確認します。ローカル環境ではhttp://localhost/indexのようになっているはずです。

本番環境ではlocalhostの部分が、ホスト名に切り替わります。

5. google ウェブマスターツールで送信

ウェブマスターツールのクロール→サイトマップサイトマップの追加/テストに行き、http://ホスト/sitemap.xmlと入力して送信します。

sitemap.xmlは動的に作られますので、次回以降はデータが更新されると勝手にサイトマップも更新され、googleも勝手に見に来てくれます。あら便利。

問題点

僕の環境(rails 5.0.6)では、sitemap/xmlで親モデル/子モデル/孫モデルのデータをうまく取得できませんでした。

URLが /親モデル/id/子モデル/id/孫モデル/idという形だったので、URLを書くにあたって親モデルのidは必須。ここでしばらく詰まってしまいました

少しややこしいですが、下記のような状況です。

  1. 親モデル: Product
  2. 子モデル:Post
  3. 孫モデル: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 %>