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
リンク先で解決策として出てくるextract
はrubyのメソッドではなくSQLで、与えた年月日からMONTHやYEARのみを抽出する。
しかし、YEAR_MONTH
という感じで年月を抽出するのはできないらしい。
これを見るとできそうなんだけど……
MySQL Tryit Editor v1.0
諦めずに探していると素敵なgemを発見。
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を間違えてたり余計なことをしてたりすると思うので、重点的にいろいろ試してみる。