我想改变的has_many关联行为防止
考虑这个基本的数据模型
class Skill < ActiveRecord::Base
has_many :users, through: :skills_users
has_many :skills_users
end
class User < ActiveRecord::Base
has_many :skills, through: :skills_users, validate: true
has_many :skills_users
end
class SkillsUser < ActiveRecord::Base
belongs_to :user
belongs_to :skill
validates :user, :skill, presence: true
end
增加一个新技能,我们可以很容易地做到这一点:
john = User.create(name: 'John Doe')
tidy = Skill.create(name: 'Tidy')
john.skills << tidy
,但如果你这样做两次,我们得到一个重复的技能该用户
的可能性,以防止,它是将
john.skills << tidy unless john.skills.include?(tidy)
前检查但这是颇有意味...
我们也改变ActiveRecord::Associations::CollectionProxy#<<行为像
module InvalidModelIgnoredSilently
def <<(*records)
super(records.to_a.keep_if { |r| !!include?(r) })
end
end
ActiveRecord::Associations::CollectionProxy.send :prepend, InvalidModelIgnoredSilently
迫使CollectionProxy
到忽略透明地添加重复记录。
但我对此并不满意。
我们可以在SkillsUser
class SkillsUser < ActiveRecord::Base
belongs_to :user
belongs_to :skill
validates :user, :skill, presence: true
validates :user, uniqueness: { scope: :skill }
end
但在这种情况下,增加两次额外添加验证验证会提出了ActiveRecord::RecordInvalid
和我们再次加
前检查或作出CollectionProxy
一个丑陋的黑客
module InvalidModelIgnoredSilently
def <<(*records)
super(valid_records(records))
end
private
def valid_records(records)
records.with_object([]).each do |record, _valid_records|
begin
proxy_association.dup.concat(record)
_valid_records << record
rescue ActiveRecord::RecordInvalid
end
end
end
end
ActiveRecord::Associations::CollectionProxy.send :prepend, InvalidModelIgnoredSilently
但我仍然不满意。
要我上CollectionProxy理想,也许缺少的方法是:
john.skills.push(tidy)
=> false
和
john.skills.push!(tidy)
=> ActiveRecord::RecordInvalid
任何想法如何,我可以做的很好?
- 编辑 -
一种方法,我发现避免抛出异常被抛出异常!
class User < ActiveRecord::Base
has_many :skills, through: :skills_users, before_add: :check_presence
has_many :skills_users
private
def check_presence(skill)
raise ActiveRecord::Rollback if skills.include?(skill)
end
end
是不是基于验证,既不是一个通用的解决方案,但可以帮助...
你试过'has_many:技能, - > {uniq},通过::skills_users'? – Matt
只有当你删除验证'验证:用户,唯一性:{范围::技能}' –
主要问题是这隐藏了真相....记录被记录在数据库中,只是没有通过这个特殊的DISTINCT范围给出,不是我想要的。 –