LoginSignup
8
9

More than 5 years have passed since last update.

Railsでnested_attributes_forとlengthに関するvalidationを共存させる

Last updated at Posted at 2015-02-10

数日に渡る調査と試行錯誤の末、自分が期待する動作の実装方法がようやく分かったので、ここで共有します。

課題

  • userequipmentを5個に制限したい。
  • equipmentを既に5個持っているuserに対して、「equipmentを1個削除」と「equipmentを1個追加」を同時に行いたい。
app/models/equipment.rb
class Equipment < ActiveRecord::Base
  belongs_to :user
end
app/models/user.rb
class User < ActiveRecord::Base
  has_many :equipments
  accepts_nested_attributes_for :equipments, allow_destroy: true
  validates :equipments, length: { maximum: 5 }
end

validateslengthを使うとシンプルに書けるのですが、これだと課題に上げた処理をした場合にvalidation errorが出ます。

実現方法

対象となるassociationlengthを呼ぶ前に
.reject(&:marked_for_destruction?)
で削除対象となるinstanceを除外します。

具体例1: lengthを再定義する

app/models/user.rb
class User < ActiveRecord::Base
  has_many :equipments do
    def length
      reject(&:marked_for_destruction?).length
    end
  end
  accepts_nested_attributes_for :equipments, allow_destroy: true
  validates :equipments, length: { maximum: 5 }
end

具体例2: validationしたい内容をmethodとして定義する

app/models/user.rb
class User < ActiveRecord::Base
  has_many :equipments
  accepts_nested_attributes_for :equipments, allow_destroy: true
  EQUIPMENTS_MAX_LENGTH = 5
  validate legth_of_equipments

  def length_of_equipments
    equipments_length = 0
    if equipments.present?
      # ↓ここのrejectが肝でした。
      equipments_length = equipments.reject(&:marked_for_destruction?).length
    end
    errors.add(:equipments, 'too many') if equipments_length > EQUIPMENTS_MAX_LENGTH
  end
end

以上です。
誰かのお役に立てれば幸いです。
もっとスマートな方法がありましたら、教えて頂けると嬉しいです。

参考

8
9
2

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
8
9