Railsでブログアプリに月別アーカイブを導入

作成中のブログアプリケーションに月別アーカイブの機能を実装しました。

f:id:naito-coding0322:20181018221146p:plain
こんな感じのやつです。

前提

Rails: 5.2.1

リレーションは User has_one Blog, Blog has_many Articles です。

はじめにメソッドを用意

まずブログ記事の :created_at を元に月別に集計する必要があるので、blog.rbにdivide_monthlyメソッドを作成します。

def divide_monthly
    return self.articles.group("strftime('%Y%m', articles.created_at)")
                                 .order("strftime('%Y%m', articles.created_at) desc")
                                 .count
end

この状態だと

DEPRECATION WARNING: Dangerous query method (method whose arguments are used as raw SQL) called with non-attribute argument(s): "strftime('%Y%m', articles.created_at) desc". Non-attribute arguments will be disallowed in Rails 6.0. This method should not be called with user-provided values, such as request parameters or model attributes. Known-safe values can be passed by wrapping them in Arel.sql().

という警告が出ます。
詳しくは参考リンクの通りですが、とりあえずArel.sql()で囲めば大丈夫です。

  def divide_monthly
    return self.articles.group("strftime('%Y%m', articles.created_at)")
                                 .order(Arel.sql("strftime('%Y%m', articles.created_at) desc"))
                                 .count
  end


コントローラとルーティング

次に、コントローラーです。blogsコントローラにarchivesアクションを作成しましょう。

そして、blogsコントローラのshowアクションとarchivesアクション(月別アーカイブを表示するアクション)に以下を記述します

@archives = @blog.divide_monthly

archivesアクションを作成したのでルーティングも追加します。

get  '/blogs/:id/archives/:yyyymm', to: 'blogs#archives', as: :blog_archive

ヘルパーメソッド作成

そして、viewで「2018年10月(8)」のような表示をするために
application_helper.rbにヘルパーメソッドを作成します。

def ymconv(yyyymm,cnt)
    yyyy = yyyymm[0,4]
    mm = yyyymm[4,2]
    return yyyy + "" + mm + "月 (" + cnt + ")"
 end


ビューを編集

blogs/archives.html.erbを作成して、blogs/show.html.erbの内容をコピペします。

そしてサイドバー部分に

<h4 class="font-italic">過去ログ</h4>
<ul style="list-style:none;">
       <% @archives.each do |yyyymm, count| %>
            <li><%= ymconv(yyyymm, count.to_s) %></li>
       <% end %>
</ul>

これで、まだリンクにはなりませんが月別の記事数が表示されるようになります。

archivesアクション

さて、先程arichivesアクション用にルーティングを追加しました。
URLは

'/blogs/:id/archives/:yyyymm'

です。つまり

/blogs/2/archives/201809 だったら
IDが2のブログの、2018年9月に作成された記事一覧ページが表示されるようになります。

ブログのサイドバーの表示は先程作ったので、あとはリンクを付けるのと、archivesビューで月別の記事一覧を表示する部分を編集します。

Blogsコントローラー#archivesアクションに追記

  def archives  
    @blog = Blog.find(params[:id])
    @yyyymm = params[:yyyymm]
    @articles = @blog.articles.where("strftime('%Y%m', articles.created_at) = '"+@yyyymm+"'").paginate(:page => params[:page], :per_page => 5).order('created_at DESC')
    @archives = @blog.make_archive
  end

@articlesには、@blogのブログ記事の作成年月(yyyymm)がURLの年月(@yyyymm)と一致するものが代入されます。


アーカイブ記事の表示部分を作る

月別の記事一覧を表示する部分を作成するため、archives.html.erbを編集します。
ちょっと私のだとごちゃごちゃしてるんですが、要は

<% @articles.each do |article| %> 
 <%= article.title %>
 <%= article.text %>
<% end %>

@articlesに入っているその月の記事たちをeachで回していけばOKです。


リンクに変更する

最後に、show.html.erbとarchives.html.erbを以下のように変更します。

<h4 class="font-italic">過去ログ</h4>
<ul style="list-style:none;">
       <% @archives.each do |yyyymm, count| %>
            <li><%= link_to ymconv(yyyymm, count.to_s), blog_archive_path(@blog, yyyymm) %></li>
       <% end %>
</ul>

f:id:naito-coding0322:20181018221108p:plain これで月別アーカイブを実装することが出来ました。



(おまけ)◯月の記事一覧 という表示をする

blogs_helper.rbにヘルパーメソッド作成

def ymconvn(yyyymm)
   yyyy = yyyymm[0,4]
   mm = yyyymm[4,2]
   return yyyy + "" + mm + ""
end


archives.html.erb で

<%= ymconvn(@yyyymm) %>の記事一覧

と書けばOKです。


参考リンク

strftime - リファレンス - - Railsドキュメント

Rails 5.2でActive Recordのorder/pluckに追加される非推奨警告 - koicの日記

Ruby on Railsを触ってみる ⑨月別アーカイブ | 大都会で働く新人SEの日記