新手问题 has_many through 的 uniq 设定

redemption · 2015年03月09日 · 最后由 hmilym 回复于 2015年03月09日 · 2211 次阅读
class A < ActiveRecord::Base
    has_many :bs
    has_many :cs , through: :bs
end

class B < ActiveRecord::Base
    belongs_to :a
    has_many :cs
end

class C < ActiveRecord::Base
    belongs_to :b
end

三个 model 的关系如上所示,如果我现在想要保证 C 中的一个属性(比如 c.name)在所关联的 A 中是唯一的。这样的 uniq 怎么实现呢? 是应该去用 before_create 去实现吗? 比如

a = A.create
b = B.create
a.bs << b
b.cs << C.create(name: "test")  #通过
b.cs << C.create(name: "test")  #Error
# in class C
validates_uniqueness_of :name, scope: :b_id

楼上正解,另外希望你belongs_to :cs修正一下 validates :name, uniqueness: { scope: :b_id,message: "name should not repeat per B object" }

#3 楼 @huopo125 已经修正了 :)

应该要数据库增加唯一索引,光靠 ActiveRecord 的 validation 在并发出现的时候,会不靠谱的

#2 楼 @lyfi2003 这个也不是我所需要的,我要保证的时在一个 A 的 record(例如 a),他所关联的所有 C 的 record 的 name 属性是唯一的。这个只能保证一个 b 所关联的 c 的所有 name 属性的值是唯一的。 也就是下面这种代码的效果。如果采用你的这种验证方式的话,下面的代码是能够通过的

a = A.create
   b1 = B.create
   b2 = B.create
   a.bs << b1
   a.bs << b2
   b1.cs << C.create(name: "test")  #通过
   b2.cs << C.create(name: "test")  #Error

#5 楼 @huacnlee 并发会出问题吗?有相关链接能发给我看一下吗?

#8 楼 @redemption

类似的问题还在 find_or_create_by

Please note this method is not atomic, it runs first a SELECT, and if there are no results an INSERT is attempted. If there are other threads or processes there is a race condition between both calls and it could be the case that you end up with two similar records.

http://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-find_or_create_by

Using this validation method in conjunction with ActiveRecord::Validations#save does not guarantee the absence of duplicate record insertions, because uniqueness checks on the application level are inherently prone to race conditions.

http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of

#6 楼 @redemption

validate do
  if self.b.a.cs.where(name: self.name).first
    errors.add(:name, 'babala...')
  end
end

这种限制在并发的环境下可能出现限制失效的情况, 比如你开了多进程实例或者多线程.

#8 楼 @redemption

             User 1                 |               User 2
------------------------------------+--------------------------------------
# User 1 checks whether there's     |
# already a comment with the title  |
# 'My Post'. This is not the case.  |
SELECT * FROM comments              |
WHERE title = 'My Post'             |
                                    |
                                    | # User 2 does the same thing and also
                                    | # infers that their title is unique.
                                    | SELECT * FROM comments
                                    | WHERE title = 'My Post'
                                    |
# User 1 inserts their comment.     |
INSERT INTO comments                |
(title, content) VALUES             |
('My Post', 'hi!')                  |
                                    |
                                    | # User 2 does the same thing.
                                    | INSERT INTO comments
                                    | (title, content) VALUES
                                    | ('My Post', 'hello!')
                                    |
                                    | # ^^^^^^
                                    | # Boom! We now have a duplicate
                                    | # title!

这个例子形象的说明了一切

#9 楼 @huacnlee #10 楼 @lyfi2003 #11 楼 @libuchao 文档看到了,原来不是原子操作。谢谢

find_or_create_by.. 害死了不少人

需要 登录 后方可回复, 如果你还没有账号请 注册新账号