【问题标题】:ActiveRecord uniqueness validation and orphaned has_many associations in Rails 4 using << syntaxRails 4 中使用 << 语法的 ActiveRecord 唯一性验证和孤立的 has_many 关联
【发布时间】:2016-02-02 04:11:53
【问题描述】:

我在简单的 has_many 关联上遇到了一个奇怪的 validates_uniqueness_of 问题。以下是模型的简化版本:

#profile.rb

class Profile < ActiveRecord::Base
  ...
  has_many :majors, -> { uniq }, :dependent => :destroy
  ...
end

#major.rb

class Major < ActiveRecord::Base
  belongs_to :profile
  validates_uniqueness_of :name, :scope => :profile
  ...
end

当我从 rails 控制台添加关联时出现奇怪的行为:

> Profile.first.majors << Major.create(:name => 'basketweaving')

第一个按预期添加。然后我再次运行它......

> Profile.first.majors << Major.create(:name => 'basketweaving')

第二个返回一个成功的结果,没有回滚或唯一性验证错误。我预计此插入的错误和回滚,我留下了下面显示的记录。请注意,第二条记录的 profile_id 为 nil,但由于某种原因,它仍在保存中。

[
[0] #<Major:0x007ff72bdbfce8> {
             :id => 20,
           :name => "Finance",
     :profile_id => 5,
    :school_code => nil,
     :created_at => Tue, 02 Feb 2016 03:48:56 UTC +00:00,
     :updated_at => Tue, 02 Feb 2016 03:48:56 UTC +00:00
},
[1] #<Major:0x007ff72bdbfba8> {
             :id => 21,
           :name => "Finance",
     :profile_id => nil,
    :school_code => nil,
     :created_at => Tue, 02 Feb 2016 03:48:58 UTC +00:00,
     :updated_at => Tue, 02 Feb 2016 03:48:58 UTC +00:00
}
]

第三次运行命令:

 > Profile.first.majors << Major.create(:name => 'basketweaving')

我得到了预期的回滚,但它似乎正在回滚,因为具有 :name => 'basketweaving' 和 nil profile_id 的 Major 是重复的。这似乎不直观,我最终得到了那些孤立的记录。有一个更好的方法吗?我是否应该这样做:

unless Profile.first.majors.where(:name => 'basketweaving').exists?
    Profile.first.majors << Major.create(:name => 'basketweaving')
end

【问题讨论】:

  • 每次推送后你是否保存Profile.first
  • 不,它没有被更改,尽管 应该 导致关联使用正确的 profile_id 自动保存
  • Major 是否验证 profile 的存在?
  • 不管怎样,你真正应该做的是:Profile.first.majors.create(:name =&gt; 'basketweaving')
  • 我同意。仍然不清楚为什么我会从另一种方法中得到奇怪的行为。

标签: ruby-on-rails ruby ruby-on-rails-4 activerecord rails-activerecord


【解决方案1】:

这一行:Profile.first.majors &lt;&lt; Major.create(:name =&gt; 'basketweaving')

...分两步执行:

  1. Major.create(:name =&gt; 'basketweaving')

这会创建一个新的Major 对象,但它还没有与配置文件关联。因此,profile_id 为零。

  1. Profile.first.majors &lt;&lt; (newly created Major object)

这将获取新创建的对象并尝试将其添加到第一个 Profile 的专业集合中。它通过更改Major 对象的profile_id 以匹配Profile 对象来实现。

第一次运行时,第 1 步和第 2 步都成功了。但这是第二次发生的情况:

  1. Major 对象是用nil profile_id 创建的
  2. 由于唯一性验证,它无法成功将Major 对象添加到集合中。已经有一个具有相同名称和profile_idMajor 对象。虽然没有回滚通知,但它确实返回 false 而不是返回集合。所以Major 对象的profile_id 仍然是nil

...第三次:

  1. Major 对象未创建,同样是因为唯一性验证。已经有一个具有该名称的 Major 对象,其中 profile_idnil

建议

由于这个怪癖,我建议像这样添加到集合中:Profile.first.majors.create(:name =&gt; 'basketweaving')。这样一来,您就在集合本身上调用了一个方法。

另外,如果您想确保 Major 对象永远不会有 nil profile_id,请在 Major 上设置存在验证:validates :profile, presence: true

【讨论】:

  • 这很有意义。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-08
  • 2013-02-03
相关资源
最近更新 更多