RSpec  shoulda-matchersの使い方

## はじめに
RSpecにおいて`shoulda-matchers`というgemがあります。
とても便利で、RSpecを簡潔に書く事ができます。

## 環境
rails (7.0.4)
rspec-rails (6.0.1)
shoulda-matchers (5.3.0)

## shoulda-matchersとは
RSpecと互換性があり、RSpecのコードを簡潔に記述する事ができるgemです。
主にモデルのバリデーションで使用します。
参考：[Shoulda Matchers](https://matchers.shoulda.io/docs/v5.3.0/)

## 導入
Gemに追加
```ruby:Gemfile
  gem "shoulda-matchers"
```
インストール
```:ターミナル
bundle install
```
`spec/rails_helper.rb`に以下を記述（もしくはsupportファイル）
```ruby:spec/rails_helper.rb
RSpec.configure do |config|
#...省略
#...
end

#以下追記
Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :rails
  end
end
```

## 使い方(使用頻度の高いものを抜粋）
よく使うバリデーションや設定を書いていきます。
詳しく知りたい方は[こちらのREADME](https://matchers.shoulda.io/docs/v5.3.0/)をご覧ください。

### presence
```ruby:User.rb
class User < ApplicationRecord
    validates :email, presence: true
end
```
```ruby:spec/models/user_sepc.rb
require 'rails_helper'

RSpec.describe User, type: :model do
    it { is_expected.to validate_presence_of :name }
end
```
### uniqueness
```ruby:User.rb
class User < ApplicationRecord
    validates :name, uniqueness: true
end
```
```ruby:spec/models/user_sepc.rb
require 'rails_helper'

RSpec.describe User, type: :model do
    it { should validate_uniqueness_of(:email) }
end
```
※Deviseを使用していたり、DBに保存する前に`downcase`などで加工しているとエラーが発生します。

その場合は以下のようにします。  [参考](issuecomment-226301242)
```ruby:spec/models/user_sepc.rb
require 'rails_helper'

RSpec.describe User, type: :model do
    it { should validate_uniqueness_of(:email).ignoring_case_sensitivity  }
end
```

### length
```ruby:User.rb
class User < ApplicationRecord
    validates :name, length: { minimum: 3 }
    validates :name, length: { maximum: 6 }
    validates :password, length { in: 6..10 }
end
```
```ruby:spec/models/user_sepc.rb
require 'rails_helper'

RSpec.describe User, type: :model do
    it { is_expected.to validate_length_of(:name).is_at_least(3) }
    it { is_expected.to validate_length_of(:name).is_at_most(6) }
    it do
      is_expected.to validate_length_of(:password).
        is_at_least(6).
        is_at_most(10)
    end
end
```
### has_secure_password
```ruby:User.rb
class User < ApplicationRecord
    has_secure_password
    has_secure_password :reset_password
end
```
```ruby:spec/models/user_sepc.rb
require 'rails_helper'

RSpec.describe User, type: :model do
    it { is_expected.to have_secure_password }
    it { is_expected.to have_secure_password(:reset_password) }
end
```
### belongs_to
```ruby:Person.rb
class Person < ApplicationRecord
    belongs_to :organization
end
```
```ruby:spec/models/person_sepc.rb
require 'rails_helper'

RSpec.describe Person, type: :model do
    it { is_expected.to belong_to(:organization) }
end
```
### has_many
```ruby:User.rb
class User < ApplicationRecord
    has_many :shirts, -> { order('color') }
end
```
```ruby:spec/models/user_sepc.rb
require 'rails_helper'

RSpec.describe User, type: :model do
    it { is_expected.to have_many(:shirts).order('color') }
end
```
### dependent
```ruby:User.rb
class User < ApplicationRecord
    has_many :tweets, dependent: :destroy
end
```
```ruby:spec/models/user_sepc.rb
require 'rails_helper'

RSpec.describe User, type: :model do
    it { is_expected.to have_many(:tweets).dependent(:destroy) }
end
```
### through
```ruby:Person.rb
class Person < ApplicationRecord
    has_many :acquaintances, through: :friends
end
```
```ruby:spec/models/person_sepc.rb
require 'rails_helper'

RSpec.describe Person, type: :model do
    it { is_expected.to have_many(:acquaintances).through(:friends) }
end
```
### source
```ruby:Person.rb
class Person < ApplicationRecord
    has_many :job_offers, through: :friends, source: :opportunities
end
```
```ruby:spec/models/person_sepc.rb
require 'rails_helper'

RSpec.describe Person, type: :model do
    it do
      should have_many(:job_offers).
        through(:friends).
        source(:opportunities)
    end
end
```
### has_one
```ruby:Tweet.rb
class Tweet < ApplicationRecord
    has_one :image
end
```
```ruby:spec/models/tweet_sepc.rb
require 'rails_helper'

RSpec.describe Tweet, type: :model do
    it { is_expected.to have_one_attached(:image) }
end
```
### has_many
```ruby:Tweet.rb
class Tweet < ApplicationRecord
    has_one :images
end
```
```ruby:spec/models/tweet_sepc.rb
require 'rails_helper'

RSpec.describe Tweet, type: :model do
    it { is_expected.to have_many_attached(:images) }
end
```

## 参考サイト
[Shoulda Matchers](https://matchers.shoulda.io/docs/v5.3.0/)