防止

2016-10-04 62 views
1

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

是不是基于验证,既不是一个通用的解决方案,但可以帮助...

+0

你试过'has_many:技能, - > {uniq},通过::skills_users'? – Matt

+0

只有当你删除验证'验证:用户,唯一性:{范围::技能}' –

+0

主要问题是这隐藏了真相....记录被记录在数据库中,只是没有通过这个特殊的DISTINCT范围给出,不是我想要的。 –

回答

0

也许我不理解的问题,但这里是我会怎么做:

  • 添加约束的DB水平,以确保数据是干净的,不管事情是如何实现的
  • 确保不会多次添加技能(在客户端上)
+0

嗨,我已经在数据库中的约束,是我的关注,因为没有验证,我获得'ActiveRecord :: RecordNotUnique: PG :: UniqueViolation:错误:重复的键值违反唯一约束,所以第二部分是我试图做的更好的方式,比添加一个基本条件,如'john.skills << tidy,除非john.skills.include?(tidy)' –

+0

我不认为与协会混淆是更好的或对于一次性问题更具可读性。 –

0

您可以向我展示创建SkillsUser表的迁移吗? 如果您向我展示您拥有的SkillsUser表的索引,效果会更好。 我通常使用has_and_belongs_to_many而不是has_many - through。 尝试,如果你有双指数“skills_users”添加此迁移

$ rails g migration add_id_to_skills_users id:primary_key 
# change the has_many - through TO has_and_belongs_to_many 

无需验证。 希望它可以帮助你。

+0

不是我想要的。我想'has_many'关系。我知道HBTM的行为不同 –