假设我有一个人物模型,一个人有很多宠物,一只宠物与狗和猫有多态关系。请注意,一个人不能拥有他/她过敏的动物。
class Person < ActiveRecord::Base
has_many :pets, dependent: :destroy
validate :not_allergic_to_animal
def not_allergic_to_animal
if allergic_to_cats? && owns_a_cat?
errors.add(:pets, 'cannot have a cat if allergic to cats')
elsif allergic_to_dogs? && owns_a_dog?
errors.add(:pets, 'cannot have a dog if allergic to dogs')
end
end
def owns_a_cat?
pets.any? { |pet| pet.animal_type == Cat.name }
end
def owns_a_dog?
pets.any? { |pet| pet.animal_type == Dog.name }
end
end
class Pet < ActiveRecord::Base
belongs_to :user
belongs_to :animal, polymorphic: true
validates :animal_type, inclusion: { in: [Cat.name, Dog.name] }
end
class Cat < ActiveRecord::Base
end
class Dog < ActiveRecord::Base
end
现在让我们说人鲍勃已经有2只猫,一只名叫爱丽丝,另一只名叫阿黛尔。
bob = Person.where(name: 'Bob')
alice = Cat.where(name: 'Alice')
bob.pets.create!(animal: alice)
adele = Cat.where(name: 'Adele')
bob.pets.create!(animal: adele)
鲍勃喜欢爱丽丝,但阿黛尔是一只非常激进的猫,所以鲍勃不再想要了。他还想要一只狗。因此,他决定向他的终端做一个PUT请求。 3是杰克狗的身份,1是爱丽丝猫的身份。
PUT /me/ {
"pets": {
"dog_ids": [3],
"cat_ids": [1]
}
}
现在问题!
如何在内存中替换Bob的宠物,验证Bob确定他对任何宠物都不过敏,然后保存关联(不使用数据库事务)?
我知道可以使用#build
在内存中创建关联,但我不想附加到现有关联,但替换现有关联,所有在内存中< / strong>保存之前。因此,上述示例将在验证Person实例后进行2次数据库查询,一次用于为Jack狗创建Pet关联,另一次用于删除Adele对猫的关联,在验证之后。
我尝试操作ActiveRecord::Associations::CollectionProxy
,但在删除或添加元素时,它总是在数据库中提交。
请注意,我不想创建一个param验证器,只是验证id,其他接口可用于更新Person,验证需要在所有这些接口上保持一致并靠近模型。
修改
我也尝试将#build
与#mark_for_destruction
结合使用,但是替换将无效,现有的关联将在保存后重新创建。
答案 0 :(得分:1)
我刚刚带来的解决方案:
标记破坏的宠物animal_id
不在提供的ID中,构建不存在的动物,并跳过在验证中标记为销毁的动物。
class Person < ActiveRecord::Base
has_many :pets, dependent: :destroy
validate :not_allergic_to_animal
def not_allergic_to_animal
if allergic_to_cats? && owns_a_cat?
errors.add(:pets, 'cannot have a cat if allergic to cats')
elsif allergic_to_dogs? && owns_a_dog?
errors.add(:pets, 'cannot have a dog if allergic to dogs')
end
end
def owns_a_cat?
pets
.reject(&:marked_for_destruction?)
.any? { |pet| pet.animal_type == Cat.name }
end
def owns_a_dog?
pets
.reject(&:marked_for_destruction?)
.any? { |pet| pet.animal_type == Dog.name }
end
end
bob = Person.where(name: 'Bob')
alice = Cat.where(name: 'Alice')
bob.pets.create!(animal: alice)
adele = Cat.where(name: 'Adele')
bob.pets.create!(animal: adele)
jack = Dog.create
bob.pets.find_by(animal_type: Cat.name, animal_id: adele.id).mark_for_destruction
bob.pets.build(animal: jack)
bob.valid?
bob.save!
答案 1 :(得分:0)
您可以使用pets=
方法直接指定集合。例如,
# Find the first pet
p1 = bob.pets.find_by_animal_id(alice.id)
# Create the second pet, this won't save yet
jack = Dog.find(3)
p2 = Pet.new(animal: jack)
bob.pets = [p1, p2]