【发布时间】:2016-10-06 02:35:01
【问题描述】:
我想改变 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 :skills, -> { uniq }, through: :skills_users吗? -
只有在您删除验证
validates :user, uniqueness: { scope: :skill }时才有效 -
主要问题是这隐藏了真相......记录被记录在数据库中,只是没有通过这个特殊的 DISTINCT 范围给出,这不是我想要的。
-
您是否尝试过删除
validate: true并在用户上使用validates_associated :skills?
标签: ruby-on-rails ruby activerecord