Ruby｜it がブロックの中で呼び出せるけど RSpec で使えるの？

# はじめに

> it がブロックの中で呼び出せるけど RSpec で使えるの？

はい、使えます。RSpec でよく見る `it '...' {} ` はメソッドです。Ruby 3.4 でブロック内で使えるようになった `it` はメソッド `it` とはそれぞれ干渉しません。

```ruby
describe 'Iterate' do
  describe "doubles" do
    it "doubles each element of the array" do
      doubles = [1, 2, 3, 4, 5].map { it * 2 }
      expect(doubles).to eq [2, 4, 6, 8, 10]
    end
  end
end
```

# RSpec のコミュニティの反応は？

RSpec コミュニティからのコメントもありメソッド `it` との競合が起きるのでは？と議論が交わされていました。全てを理解しているわけではないのですが、RSpec の `it` は引数ありで呼び出しているため、ブロック内の `it` と区別することができるので問題ない認識です。ただ、

> With the specification in my proposal, existing code seems to break if and only if you call a method #it without an argument. But it seems pretty rare (reminder: a block given to an RSpec test case is also an argument). It almost feels like people are too afraid of compatibility problems that barely exist or have not really thought about options to address them.

cf. https://bugs.ruby-lang.org/issues/18980

issue にもある通り、メソッド `it` を引数なしで呼び出したときに問題が起きそうです。検証してみたのですが正しく RSpec のメソッド `it` を認識していました :thinking: 

```ruby
require "debug"

describe 'Iterate' do
  describe "doubles" do
    binding.break
  end
end
```

```
❯ bundle exec rspec spec/sample_spec.rb
[1, 7] in ~/Development/private/itruby/spec/sample_spec.rb
     1| require "debug"
     2| 
     3| describe 'Iterate' do
     4|   describe "doubles" do
=>   5|     binding.break
     6|   end
     7| end
=>#0    block in <top (required)> (2 levels) at ~/Development/private/itruby/spec/sample_spec.rb:5
  #1    [C] Module#module_exec at ~/Development/private/itruby/vendor/bundle/ruby/3.4.0/gems/rspec-core-3.13.2/lib/rspec/core/example_group.rb:398
  # and 36 frames (use `bt' command for all frames)
(ruby) m = method(:it)
#<Method: RSpec::ExampleGroups::Iterate::Doubles(RSpec::Core::ExampleGroup).it(*all_args, &block) /Users/guppy0356/Development/private/itruby/vendor/bundle/ruby/3.4.0/gems/rspec-core-3.13.2/lib/rspec/core/example_group.rb:146>
(ruby) m.owner
#<Class:RSpec::Core::ExampleGroup>
(rdbg) 
```

# 他に面白い話題は？

ローカル変数 `it` あがるとローカル変数を優先します。ブロックの中で変数を参照したときブロック外にローカル変数があればそれを参照するのはごく自然なように見えます。RSpec の中でローカル変数 `it` を使うことはないと思いますが :rolling_eyes: 

```ruby
describe 'Iterate' do
  describe "doubles" do
    it "doubles each element of the array" do
      it = 10
      doubles = [1, 2, 3, 4, 5].map { it * 2 }
      expect(doubles).to eq [2, 4, 6, 8, 10]
    end
  end
end
```

:::note warn
```
Failures:

  1) Iterate doubles doubles each element of the array
     Failure/Error: expect(doubles).to eq [2, 4, 6, 8, 10]
     
       expected: [2, 4, 6, 8, 10]
            got: [20, 20, 20, 20, 20]
     
       (compared using ==)
     # ./spec/sample_spec.rb:8:in 'block (3 levels) in <top (required)>'
```
:::

ブロック引数がある場合は `it` を参照することは出来ず Syntax Error となります。

```ruby
describe 'Iterate' do
  describe "doubles" do
    it "doubles each element of the array" do
      doubles = [1, 2, 3, 4, 5].map { |item| p it }
      expect(doubles).to eq [2, 4, 6, 8, 10]
    end
  end
end
```

:::note warn
```
Failure/Error: __send__(method, file)

SyntaxError:
  --> /Users/guppy0356/Development/private/itruby/spec/sample_spec.rb

  `it` is not allowed when an ordinary parameter is defined

     3  describe 'Iterate' do
     4    describe "doubles" do
     5      it "doubles each element of the array" do
  >  6        doubles = [1, 2, 3, 4, 5].map { |item| p it }
  >  7        expect(doubles).to eq [2, 4, 6, 8, 10]
     8      end
     9    end
    10  end
```
:::