👌

[Feature #20498] obj.!exits? のような構文を追加する提案

2024/05/23に公開

[Feature #20498] Negated method calls

  • 次のように #exists? を否定する場合は呼び出し元の先頭に ! を追加する必要がある
must_create_user = !User.where(somelong: :condition, even_more: "thing").exists?
  • これの視認性が悪いので改善したいという内容のチケット
  • 現状だと上記のことを回避する場合は以下のような選択肢がある
    • 変数に代入する
    • unless を使う
    • .! をチェーンする
      • .exists?.!
    • 否定するメソッド名を定義する
  • チケットの起票者は以下のような構文を追加する提案をしている
# 判定メソッドの直前に ! を追加することで否定する
must_create_user = User.where(somelong: :condition, even_more: "thing").!exists?
# foo が nil のときには nil を返してほしいが
foo&.!empty?
  • ちなみに obj.!empty? は現状でも有効な Ruby の構文で以下のような解釈がされます
# obj.! メソッドが呼び出されて、その引数に empty? メソッドの結果が渡される
obj.!(empty?)
  • コメントでは .not.! を使うといいんじゃないか、と書かれていますが判定メソッドに引数がある場合はやはり視認性が悪いとも返信されています
puts A.new.exists?(with_friends: true, skip_administrator: true).!
puts A.new.exists?(with_friends: true, skip_administrator: true).not
  • その他、コメントででていた代替案
foo&.empty?&.!
foo&.non&.empty?
foo&.empty?&.not
foo&.!(&:empty?)
  • .not.empty?.!(&:empty?) は既存の機能で便利メソッド定義するような形ですねー
class MethodNegator < BasicObject
  def initialize(obj)
    @obj = obj
  end

  def method_missing(method, ...)
    @obj.public_send(method, ...)
  end

  def respond_to_missing?(include_all = false)
    @obj.respond_to?(include_all)
  end
end

module Kernel
  def non(sym=nil)
    if sym
      proc { |*args,**kwargs,&block| !sym.to_proc.call(*args,**kwargs,&block) }
    else
      MethodNegator.new(self)
    end
  end
end

p "".non.empty?
# => true
p ["", "a", "b"].select(&non(:empty?))
# => ["a", "b"]
  • 個人的にはこういう機能がほしいことは多いんだけど、構文的にちょっと過剰にならないかは気になる
  • 他の言語とかこういうケースはどうカバーしているんですかねー
  • ちなみに代替案としては以下のようにかけなくもないかなあ…
must_create_user = User.where(somelong: :condition, even_more: "thing").then { !_1.exists? }
GitHubで編集を提案

Discussion