`find_by_xyz("")`と`find_by(xyz: "")`

## tl;dr

* `find_by_...`は非推奨になったわけではない
* 実際は`method_missing`でキャッチして`find_by`呼んでるだけ
* `find_by`がちょっと速い
  - 2回目以降はメソッド定義済みになるのでそこまで差は開かないような気もするんだけど…
  - メソッドコールが1段多いせい？

## `find_by_...`
ActiveRecordでレコードを1件取り出したいとき，`find_by_...`と`find_by`という2種類のメソッドが利用できる．
この`find_by_...`，てっきりdeprecatedになったもんだと思っていたのですが，Rails 4.2時点でまだ生きていた．

> ### Rails 4.0でも引き続き使えるfinderメソッド
> 
> ```ruby
> User.find_by_email('foo@example.com')
>
> User.find_by_email_and_name('foo@example.com', 'Foo Bar')
> ```
> <cite>[Rails 4で非推奨になった/なっていないfinderメソッドを整理する - Qiita][jnchito]</cite>

 じゃあ`find_by`とどっち使ってもいいのか，雑に検証してみる．

## 仮説
`find_by_...`は実装に際し，`method_missing`やそれに類するようなメタプログラミングを利用していると考えられる．
ならば`find_by`がパフォーマンスに優れているような気がする．

## Benchmark
環境は[Cloud9](https://c9.io/)上，Ruby 2.2.1p85，activerecord 4.2.4，DBはsqlite．
ローカルマシンでやってないことに深い意味は無いです．

```
$ ruby -v
ruby 2.2.1p85 (2015-02-26 revision 49769) [x86_64-linux]
```

スクリプトは以下（pryで実行）．適当に書いたので不適切ならごめんなさい．

```ruby
N = 100000

Benchmark.bm 24 do |r|
  r.report 'find_by(email: "")' do  
    N.times { User.find_by(email: email) }
  end

  r.report 'find_by_email("")' do
    N.times { User.find_by_email(email) }
  end
end

# =>                                user     system      total        real
# => find_by(email: "")        18.910000   3.970000  22.880000 ( 22.908217)
# => find_by_email("")         20.070000   4.300000  24.370000 ( 24.944700)
```

予想通り`find_by`がちょっと早い．

（ちなみに`users` tableにはindexを貼っていない．わすれてた．）

## Implementation of `find_by_...`
[10/23 JST 00:00時点で最新のcommit](https://github.com/rails/rails/tree/279012c76cb54ddc73a0f36888179b2ac90de54e)から．

[activerecord/lib/active_record/dynamic_matchers.rb#L14-L23](https://github.com/rails/rails/blob/279012c76cb54ddc73a0f36888179b2ac90de54e/activerecord/lib/active_record/dynamic_matchers.rb#L14-L23)

```ruby
module ActiveRecord
  module DynamicMatchers #:nodoc:

    # ...

    def method_missing(name, *arguments, &block)
      match = Method.match(self, name)

      if match && match.valid?
        match.define
        send(name, *arguments, &block)
      else
        super
      end
    end

    # ...

  end
end
```

`ActiveRecord::DynamicMatchers.method_missing`で実装されている．予想通り，むしろ当たり前のように（？）`method_missing`．

`Method.match(self, name)`が怪しい．少し下に`Method` classがある．

[activerecord/lib/active_record/dynamic_matchers.rb#L31-L38](https://github.com/rails/rails/blob/279012c76cb54ddc73a0f36888179b2ac90de54e/activerecord/lib/active_record/dynamic_matchers.rb#L31-L38)

```ruby
module ActiveRecord
  module DynamicMatchers #:nodoc:

    # ...

    class Method
      @matchers = []

      class << self
        attr_reader :matchers

        def match(model, name)
          klass = matchers.find { |k| name =~ k.pattern }
          klass.new(model, name) if klass
        end

        def pattern
          @pattern ||= /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/
        end

        def prefix
          raise NotImplementedError
        end

        def suffix
          ''
        end
      end

      # ...

    end # end of `Method` class
  end
end
```

`Match.prefix`と`Match.suffix`でメソッド名を探索してる．さらに下を見る．

[activerecord/lib/active_record/dynamic_matchers.rb#L93-L119](https://github.com/rails/rails/blob/279012c76cb54ddc73a0f36888179b2ac90de54e/activerecord/lib/active_record/dynamic_matchers.rb#L93-L119)

```ruby
module ActiveRecord
  module DynamicMatchers #:nodoc:

    # ...

    class FindBy < Method
      Method.matchers << self

      def self.prefix
        "find_by"
      end

      def finder
        "find_by"
      end
    end

    class FindByBang < Method
      Method.matchers << self

      def self.prefix
        "find_by"
      end

      def self.suffix
        "!"
      end

      def finder
        "find_by!"
      end
    end
  enc
end
```

`Method`を継承した`FindBy`というクラスがある．そこで`prefx`と`suffix`を決めてる．

ところで，`ActiveRecord::DynamicMatchers.method_missing`では`match`したあとに`Method#define`を読んでいる．

```ruby
module ActiveRecord
  module DynamicMatchers #:nodoc:

    # ...

    class Method

      # ...

      def define
        model.class_eval <<-CODE, __FILE__, __LINE__ + 1
          def self.#{name}(#{signature})
            #{body}
          end
        CODE
      end

      # ...

    end
  end
end
```

そういうことです．

実際に実装されるメソッドの実体（`Method#body`）を見てみる．

[activerecord/lib/active_record/dynamic_matchers.rb#L72-L86](https://github.com/rails/rails/blob/279012c76cb54ddc73a0f36888179b2ac90de54e/activerecord/lib/active_record/dynamic_matchers.rb#L72-L86)

```ruby
module ActiveRecord
  module DynamicMatchers #:nodoc:

    # ...

    class Method

      # ...

      def body
        "#{finder}(#{attributes_hash})"
      end

      def signature
        attribute_names.map { |name| "_#{name}" }.join(', ')
      end

      def attributes_hash
        "{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(',') + "}"
      end

      # ...

    end
  end
end
```

`find_by_hoge("")`から`find_by(hoge: "")`を呼んでるだけですね．
（`attribute_names`は`name.match(self.class.pattern)[1].split('_and_')`: つまり，メソッド名をいい感じにバラしただけ）

## Implementation of `find_by`
同様に[10/23 JST 00:00時点で最新のcommit](https://github.com/rails/rails/tree/279012c76cb54ddc73a0f36888179b2ac90de54e)から．

[activerecord/lib/active_record/core.rb#L174-L201](https://github.com/rails/rails/blob/279012c76cb54ddc73a0f36888179b2ac90de54e/activerecord/lib/active_record/core.rb#L174-L201)

```ruby
module ActiveRecord
  module Core
    module ClassMethods

      # ...

      def find_by(*args) # :nodoc:
        return super if scope_attributes? || !(Hash === args.first) || reflect_on_all_aggregations.any?

        hash = args.first

        return super if hash.values.any? { |v|
          v.nil? || Array === v || Hash === v || Relation === v
        }

        # We can't cache Post.find_by(author: david) ...yet
        return super unless hash.keys.all? { |k| columns_hash.has_key?(k.to_s) }

        keys = hash.keys

        statement = cached_find_by_statement(keys) { |params|
          wheres = keys.each_with_object({}) { |param, o|
            o[param] = params.bind
          }
          where(wheres).limit(1)
        }
        begin
          statement.execute(hash.values, self, connection).first
        rescue TypeError => e
          raise ActiveRecord::StatementInvalid.new(e.message, e)
        rescue RangeError
          nil
         end
       end

       # ...

    end
  end
end
```

## References
* [Rails 4で非推奨になった/なっていないfinderメソッドを整理する - Qiita][jnchito]


[jnchito]: http://qiita.com/jnchito/items/2b7d64851665071ed6e6