同时创建两个依赖模型

时间:2014-07-26 08:26:01

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

我试图建模一个系统,每个用户可能有很多电子邮件(至少一个)。

遵循良好的规范化规则,我创建了两个迁移(为简洁起见,删除了一些字段):

create_table :users do |t|
end

create_table :user_emails do |t|
  t.integer :user_id, null: false
  t.string :email, null: false
end

add_index :user_emails, :email, :unique => true
add_foreign_key :user_emails, :users, dependent: :delete

以及以下导轨型号:

class User < ActiveRecord::Base
  has_many :emails, class_name: 'UserEmail', dependent: :destroy

  def self.find_by_email(email)
    UserEmail.find_by(email: email).try(:user)
  end

  validate do
    if emails.count < 1
      errors.add(:emails, "is empty")
    end
  end
end

class UserEmail < ActiveRecord::Base
  belongs_to :user

  validates_presence_of :user_id, :email
  validates_uniqueness_of :email
end

现在我无法创造任何这些。无法创建用户,因为它至少需要UserEmail。同时,由于需要user_id,因此无法事先创建UserEmail。

我相信我已尝试过任何我能想到的@user.emails.build@user.emails << e的组合。

如何在不放弃数据一致性的情况下解决这个非常简单的问题(一个是保存而另一个不是)?

P.s。:我认为可能放宽验证并使用交易可以一致地解决问题。但是我从来没有在rails中使用过交易,所以非常感谢任何帮助。

由于

2 个答案:

答案 0 :(得分:2)

validates_presence_of:user_id仅检查可能不是有效用户的任何有效整数。

但是如果您使用validates_presence_of :uservalidates_presence_of :emails,他们会检查用户和电子邮件关联是否有效而不是空白。

此外,当您尝试创建用户和关联的电子邮件时,您可以在应用程序级别上执行以下代码。

 user = User.new(name: 'user name')
 user.emails.build(email_address: 'email address')  #build(email: '') for your case.

然后,您可以使用

保存到数据库
 user.save!

以下是我测试的代码。

class User < ActiveRecord::Base
  has_many :emails, class_name: 'UserEmail', dependent: :destroy
  validates_presence_of :emails, :message => 'User should have at least one email address.'
end

class UserEmail < ActiveRecord::Base
  belongs_to :user
  validates_presence_of :user
  validates_presence_of :email_address
end


[40] pry(main)> u = User.create!(name: 'doh')
   (0.1ms)  begin transaction
   (0.0ms)  rollback transaction
ActiveRecord::RecordInvalid: Validation failed: Emails User should have at least one email address.

[1] pry(main)> u = User.new(name: 'nice')
=> #<User id: nil, name: "nice", created_at: nil, updated_at: nil>
[2] pry(main)> u.emails.build(email_address: 'hello@world.blah')
=> #<UserEmail id: nil, user_id: nil, email_address: "hello@world.blah", created_at: nil, updated_at: nil>
[3] pry(main)> u.save!
   (0.1ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "users" ("created_at", "name", "updated_at") VALUES (?, ?, ?)  [["created_at", "2014-07-26 10:14:52.184245"], ["name", "nice"], ["updated_at", "2014-07-26 10:14:52.184245"]]
  SQL (0.2ms)  INSERT INTO "user_emails" ("created_at", "email_address", "updated_at", "user_id") VALUES (?, ?, ?, ?)  [["created_at", "2014-07-26 10:14:52.189757"], ["email_address", "hello@world.blah"], ["updated_at", "2014-07-26 10:14:52.189757"], ["user_id", 5]]
   (0.7ms)  commit transaction
=> true
[4] pry(main)> u.emails.count
   (0.2ms)  SELECT COUNT(*) FROM "user_emails"  WHERE "user_emails"."user_id" = ?  [["user_id", 5]]
=> 1
[5] pry(main)> u.id
=> 5
[6] pry(main)> u.name
=> "nice"
[7] pry(main)> u.emails
=> [#<UserEmail id: 4, user_id: 5, email_address: "hello@world.blah", created_at: "2014-07-26 10:14:52", updated_at: "2014-07-26 10:14:52">]
[8] pry(main)> u
=> #<User id: 5, name: "nice", created_at: "2014-07-26 10:14:52", updated_at: "2014-07-26 10:14:52">


[19] pry(main)> UserEmail.create!(email_address: 'test@test.org')
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
ActiveRecord::RecordInvalid: Validation failed: User can't be blank

希望这有帮助。

答案 1 :(得分:0)

我刚刚意识到我在存储层上添加NOT NULL约束就足够了。我可以删除:user_id状态验证并具有相同的一致性行为,但现在可以正常工作。

然而,从导轨POW来看,这看起来非常黑客。任何更好的解决方案仍然非常感激......