数日に渡る調査と試行錯誤の末、自分が期待する動作の実装方法がようやく分かったので、ここで共有します。
課題
-
userのequipmentを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
validatesのlengthを使うとシンプルに書けるのですが、これだと課題に上げた処理をした場合にvalidation errorが出ます。
実現方法
対象となるassociationのlengthを呼ぶ前に
.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
以上です。
誰かのお役に立てれば幸いです。
もっとスマートな方法がありましたら、教えて頂けると嬉しいです。
参考