ぴよログ

↓に移転したのでこっちは更新されません、多分。

ActiveRecordの日付カラムでレコードを絞り込める by_star gem

移転しました →

久々に便利なの見つけた。有名だったりするのかな?

radar/by_star

by_starはモデルの絞り込みに使えるgemで、ActiveRecordとMongoidで使える。

ある期間内のレコードだけを表示したり集計を取ったりというときに使える。自分でも大したコードにはならないんだけど汎用的なものなのでこのgemを使うのが良いでしょう。

githubのREADMEを見れば一目瞭然なのだけど、一応紹介しておく。

Post.by_year(2013)                           # all posts in 2013
Post.before(Date.today)                      # all posts for before today
Post.yesterday                               # all posts in 2013
Post.between_times(Time.zone.now - 3.hours,  # all posts in last 3 hours
                   Time.zone.now)
@post.next                                   # next post after a given post

カラムを指定しない場合はcreated_atが使われる。自分で定義したカラムを使いたいなら次のようにすればOK。

Post.by_year(2013, field: :hogehoge_at) # hogehoge_at という Date/DateTimeなカラムを使用

こういう絞り込みでよくあるのは○年○月のデータ一覧みたいなのだと思うのでそのやりだけ簡単に書いておく。

Post.by_year # 今年のpost
Post.by_month # 今月のpost
Post.by_month(4) # 今年の4月
Post.by_month(4, year: 2012) # 2014年の4月

自前カラムを使うならscopeにしとくと便利だと思う。

class Post < ActiveRecord::Base
  scope :by_year_month, ->(y, m) {
    by_month(m, year: y, field: :hogehoge_at)
  }
end

実装を見てみると最終的にはbetween_times_queryていうメソッドに行き着くっぽかった。

def between_times_query(start, finish, options={})
  start_field = by_star_start_field(options)
  end_field = by_star_end_field(options)
  scope = by_star_scope(options)
  scope = if options[:strict] || start_field == end_field
            scope.where("#{start_field} >= ? AND #{end_field} <= ?", start, finish)
          else
            scope.where("#{end_field} > ? AND #{start_field} < ?", start, finish)
          end
  scope = scope.order(options[:order]) if options[:order]
  scope
end

単機能をいい感じに切り出せてて良さげです。この発想は見習うとこありそう。