23
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

DB保存前のhas_many内で値がユニークか判定をする

Posted at

テーブルに格納されていない値にバリデーションが行われない

ActiveRecord::Base#validatesのuniquenessオプションはテーブルに格納されている値でユニーク判定を行います。

そのためDBに保存前のモデルにはユニーク判定が行われません。

以下サンプルコードです。uriが同じ「a」ですがバリデーションエラーは起こりません。

class Page < ActiveRecord::Base
  belongs_to :site
  validates :uri, uniqueness: true, scope: {[:site_id, :uri]}
end

class Site < ActiveRecord::Base
  has_many :pages
end

site = Site.new
site.pages << [Page.new(uri: :a), Page.new(uri: :a)]
site.save! # ここでエラーにならない

テーブルに格納されていない値にもバリデーションが行われて欲しいですよね。

before_validation に値がユニークかの検証を追加する

これを解決するには親に当たるモデル、この文中の例ではSiteクラスで子に当たるpagesの値を確認します。

まずSite#pages内で値の重複を確認して、問題なければPageモデルのvalidatesでテーブルの値を確認する流れになります。

class Page < ActiveRecord::Base
  belongs_to :site
  validates :uri, uniqueness: true, scope: {[:site_id, :uri]}
end

class Site < ActiveRecord::Base
  has_many :pages
  before_validation :children_be_uniq

  def children_be_uniq
    resource_be_uniq(pages, :uri)
  end

  def resource_be_uniq(collection, attribute_name)
    success = true
    uniq = []
    collection.each{|m|
      if uniq.include?(m[attribute_name])
        error_message = I18n.t("errors.messages.taken", attribute: I18n.t("activerecord.attributes.#{m.class.to_s.underscore}.#{attribute_name}"))
        errors.add(:base, error_message)
        m.errors.add(attribute_name, error_message)
        success = false
      else
        uniq << m[attribute_name]
      end
    }
    success
  end
end

site = Site.new
site.pages << [Page.new(uri: :a), Page.new(uri: :a)]
site.save! # ここでエラーになる

きっちりとバリデーションエラーが発生して良い感じですね。

ActiveRecord::RecordInvalid: バリデーションに失敗しました。 URLはすでに存在します。

accepts_nested_attributes_forを使って複数のhas_manyを一括で更新する場合などでも利用できます。

class Site < ActiveRecord::Base
  has_many :pages
  accepts_nested_attributes_for :keywords, allow_destroy: true
  before_validation :children_be_uniq
  ...以下略
end

permit_attrs = [
 {pages_attributes: [:id, :uri, :_destroy]}
]
permit_params = params.require(Site).permit(permit_attrs)
site.update_attributes!(permit_params) # before_validationで値の確認が行われる

質問などあればコメント欄かTwitter宛にお願いします。

リファレンス情報

23
10
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?