防止引发ActiveRecord :: RecordInvalid或在has_many关联上添加两次

时间:2016-10-04 17:28:53

标签: ruby-on-rails ruby activerecord

我想改变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)

但这很有意思......

我们也可以像{/ p>一样改变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

不是基于验证,既不是通用解决方案,也可以帮助......

2 个答案:

答案 0 :(得分:0)

也许我不理解这个问题,但这就是我要做的事情:

  • 在数据库级别添加约束以确保数据是干净的,无论如何实现
  • 确保不会多次添加技能(在客户端上)

答案 1 :(得分:0)

您能告诉我创建SkillsUser表的迁移吗? 如果你向我展示你拥有的SkillsUser表的索引,那就更好了。 我通常使用has_and_belongs_to_many而不是has_many - through。 尝试添加此迁移

$ rails g migration add_id_to_skills_users id:primary_key
# change the has_many - through TO has_and_belongs_to_many
如果您有双重索引“skills_users”,则无需验证。 希望它可以帮助你。