【问题标题】:Prevent from raising ActiveRecord::RecordInvalid or adding twice on has_many association防止引发 ActiveRecord::RecordInvalid 或在 has_many 关联上添加两次
【发布时间】: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

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

【问题讨论】:

标签: ruby-on-rails ruby activerecord


【解决方案1】:

也许我不明白这个问题,但这是我要做的:

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

【讨论】:

  • 嗨,我已经在数据库中设置了一个约束,这是我关心的问题,因为没有验证我会获得 ActiveRecord::RecordNotUnique: PG::UniqueViolation: ERROR: duplicate key value violates unique constraint 所以第二部分是我尝试以比添加基本条件更好的方式做的事情喜欢john.skills &lt;&lt; tidy unless john.skills.include?(tidy)
  • 我不认为对于一次性问题来说,搞乱关联会更好或更易读。
【解决方案2】:

您能告诉我创建 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”,则无需验证。 希望对你有帮助。

【讨论】:

  • 不是我想要的。我想要has_many 关系。我知道 HBTM 的行为不同
猜你喜欢
  • 2016-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-07
  • 1970-01-01
相关资源
最近更新 更多