block_given?

ジャングル ブロック ナイ アルノハ jump ダケ。CC-BY-SA 3.0

where.notについて

ActiveRecordでは次のように、引数無しでwhereを呼び出したあと、すぐさまwhereに渡すのと同じような条件をnotに渡すと、その条件に当てはまらないレコードを選択することが出来ます。

Post.where.not(author: author)
# SELECT `posts`.* FROM `posts` WHERE (`authors`.`id` != 1)

この機能はRails 4でリリースされました。Rails 4がリリースされてから1年以上経ちましたが、ようやくRails 4.x系を使いはじめました。 備忘録的に調べたことをまとめておきます。

はじまり、消えたwhere.likeとwhere.not_like

現在のwhere.notAPIJeremy Kemperさんのコメントの提案により、amatsudaさんにより実装されました。 このPull Requestでは同時にlikenot_likeも導入されましたが後にwhere.likeとwhere.not_likeはロールバックされました

このあたりはRuby on Your Rails 15p〜Commit.where.not(liked_by: ‘DHH’).reject!あたりに詳しくまとまっていて大変参考になりました。

where.likewhere.not_likeDHHのコメント1DHHのコメント2を見る限り、納得のいく理由や名前がないかぎり復活することはなさそうですね。

Squeelの場合

Squeelを使うとActiveRecordのクエリをよりRubyishに書けます。

そのメンテナーのErnie Millerさんのブログで、Squeelがwhere.notに対応したときの話が書かれています。(Ernie MillerさんはActiveRecord HackeryActiveRecordをハックするようなgemをいくつもメンテナンスしています)

ActiveRecord.where.not(:sane => true) - Ernie Miller

TL;DR - ActiveRecord: This is yak country.

タイトルを訳すなら「ActiveRecordの正気じゃないところ」まとめは「ActiveRecord: ここはヤクの国」つまり、ヤクの毛を刈り続ける大地といったところです。

Rails 4で無引数でwhereを呼び出した場合の動作が変更されたため動かなくなったSqueelのwhere { condition }が動くように対応したときの話を書いています。

# このブログ記事は大変参考になるのですが全部ヤクする時間がないので要約すると
# 次のようなシチュエーションで`Foo#do_something`を呼びたいという話です
module Foo
  def do_something
    puts 'Foo#do_something'
  end
end

module Bar
  def do_something
    super
    puts 'Bar#do_something'
  end
end

class Baz
  include Foo
  include Bar
end

# Fooのメソッドを呼びたいがそのままだとBar#do_somethingが呼ばれてしまう
Baz.new.do_something

# Foo#do_somethingを使いたいが、Fooが既にBarより前に差し込まれているので
# `extend`してもBarの位置を動かすことが出来ない
Baz.new.extend(Foo).do_something

# 別のモジュールで`include`してそのモジュールを差し込んでも動かすことが出来ない
module Pow
  include Foo
end
Baz.new.extend(Pow).do_something

# Fooモジュールから取り出したメソッドを
# `define_method`を使ってPowモジュール自身に持たせることで解決
module Pow
  include Foo
  define_method :do_something, Foo.instance_method(:do_something)
end
Baz.new.extend(Pow).do_something

記事中でwhere.notの対応のため、ActiveRecordwhere.notwhere_notに変更するプルリクエストを出したがcloseされたことに触れられています。

Remove WhereChain, and implement where_not. by ernie · Pull Request #9551 · rails/rails

そのプルリクエスト上のwhere.notの提案者Jeremy Kemperさんからのコメントを読むと以下のような理由で、where.notが消えることはなさそうです。

  • このwhere句の組み立て方のパターンは良い
  • notだけが入ったけど、将来のリリースで他の操作が入るかもしれない
  • サードパーティーのライブラリがlikeregexporをカバーするための足がかりとなる

まとめない

where.not便利。

参考

Ruby on Railsのドキュメント

where.notについて言及しているスライド

where.notについて言及している記事