Railsのユニーク制約をshoulda-matchersを使ってテストしてみました。結果として、このマッチャーは複数カラムをまたいだ制約の時は使わない方がいい気がしました。1つのカラムでのユニーク制約も、複数カラムのユニーク制約も同じ扱いになっているようです。
ちなみに、undefined method
と表示される場合はshoulda-matchersのバージョンを2.8に落とすと直ります。3.0の不具合みたいです。
# 【環境】
# ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]
# Rails 4.2.3
# rspec 3.3.2
# shoulda-matchers 2.8.0
# db/schema.rb
ActiveRecord::Schema.define(version: 20151012132542) do
create_table "people", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.string "sex"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "people", ["first_name", "last_name", "sex"], name: "index_people_on_first_name_and_last_name_and_sex", unique: true
end
# app/model/person.rb
class Person < ActiveRecord::Base
# 下記3つ、どの書き方でもバリデートは成功。
# 今回は、一番上を使用してテストを書き進めていきます。
validates :first_name, uniqueness: { scope: [:last_name, :sex] }
#validates :last_name, uniqueness: { scope: [:first_name, :sex] }
#validates :sex, uniqueness: { scope: [:first_name, :last_name] }
# scopeに渡すカラムが1つの場合は配列にはしません
#validates :first_name, uniqueness: { scope: :last_name }
end
# spec/models/person_spec.rb
require 'rails_helper'
RSpec.describe Person, type: :model do
it { is_expected.to validate_uniqueness_of(:first_name) } # true
it { is_expected.to validate_uniqueness_of(:first_name).scoped_to(:last_name) } # true
it { is_expected.to validate_uniqueness_of(:first_name).scoped_to(:sex) } # true
it { is_expected.to validate_uniqueness_of(:first_name).scoped_to(:last_name, :sex) } # true
# 下記のようなエラーがでる
# Expected errors to include "has already been taken" when sex is set to "a", got no errors
it { is_expected.to validate_uniqueness_of(:last_name) } # false
it { is_expected.to validate_uniqueness_of(:last_name).scoped_to(:first_name, :sex) } # false
it { is_expected.to validate_uniqueness_of(:sex) } # false
end