PostgreSQLではstrftimeを使えない。困った

20日間ほどかけて取り組んできたブログサービスをHerokuにデプロイしたところ以下のエラーが。

ActiveRecord::StatementInvalid (PG::UndefinedFunction: ERROR:  function strftime(unknown, timestamp without time zone) does not exist
LINE 1: SELECT COUNT(*) AS count_all, strftime('%Y%m', articles.crea...
HINT:  No function matches the given name and argument types. You might need to add explicit type casts.

SELECT COUNT(*) AS count_all, strftime('%Y%m', articles.created_at) AS strftime_y_m_articles_created_at FROM "articles" WHERE "articles"."blog_id" = $1 AND "articles"."published" = $2 GROUP BY strftime('%Y%m', articles.created_at) ORDER BY strftime('%Y%m', articles.created_at) desc):

app/models/blog.rb:14:in `divide_monthly'

月別アーカイブの機能を実装するのにstrftimeを使ったんですが、これがどうやらPostgreSQLではサポートしてないらしい。
SQLiteはサポートしてるから、開発環境ではエラーにならなかったのか。

いろいろ調べて、異なるSQL同士の「方言」的な違いによる問題も起こるのでそもそも開発環境とデプロイ環境で違うDBを使うのは良くないらしいということが分かった。


エラーが出たメソッドはこちら

#blogの記事一覧を取得して月別に集計
  def divide_monthly
    return self.articles.published.group("strftime('%Y%m', articles.created_at)")
                                 .order(Arel.sql("strftime('%Y%m', articles.created_at) desc"))
                                 .count
  end

冒頭のエラーで検索してみると、同じエラーでハマった人がちらほら出てくる。

ruby on rails - Equivalent of strftime in Postgres - Stack Overflow ruby on rails - heroku error PG::Error: ERROR: function strftime(unknown, timestamp without time zone) does not exist - Stack Overflow

リンク先で解決策として出てくるextractrubyのメソッドではなくSQLで、与えた年月日からMONTHやYEARのみを抽出する。
しかし、YEAR_MONTHという感じで年月を抽出するのはできないらしい。
これを見るとできそうなんだけど……
MySQL Tryit Editor v1.0


諦めずに探していると素敵なgemを発見。

github.com

month_of_yearでのGROUP BYができる。

さっそく使おう

  #blogの記事一覧を取得して月別に集計
  def self.divide_monthly(blog)
    where({blog_id: blog.id}).group_by_month(:created_at, format: "%Y年%b", reverse: true, series: false).count

  end

このようになった。
「~月」は自動で入ってくるけど「~年」は入らないので手動で追加。
他にオプションで順番を降順に、重複をfalseに。

「2018年10月(3)」のような記述を作成していたapplication_helper.rbのymconvを以下のように変更。

  #月別の記事数を処理
  def ymconv(yyyymm,cnt)
    return yyyymm + " (" + cnt + ")"
  end

blogs_helper.rbに作ったymconvnは必要なくなった。

サイドバーの表記はこれで完了したんですが、月別の記事一覧を取得するのにもstrftimeを使ってたので変更。

これが

def self.archive_articles(blog, yyyymm)
    eager_load(:blog).where({blog_id: blog.id}).includes(:taggings).published.where("strftime('%Y%m', articles.created_at) = '"+yyyymm+"'")
end

こうなった

  #blogの月別アーカイブを取得
  def self.archive_articles(blog, yyyymm)
  yyyy = yyyymm[0,4].to_i
  mm =   yyyymm.delete("")[5,2].to_i
  end_of_date = Date.new(yyyy, mm, -1)
  date = Date.new(yyyy, mm, 1)
    
    eager_load(:blog).where({blog_id: blog.id}).includes(:taggings).published.where(created_at: date..end_of_date)
  end  

あんまり美しくないと思う……。
これは合ってるのか正直わからん。動きはする。


ということで一応、herokuへのデプロイが完了したんだけど、ちょっとデータのロードに時間がかかる気がする。
おそらくeager loadingを間違えてたり余計なことをしてたりすると思うので、重点的にいろいろ試してみる。