我试图建模一个系统,每个用户可能有很多电子邮件(至少一个)。
遵循良好的规范化规则,我创建了两个迁移(为简洁起见,删除了一些字段):
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中使用过交易,所以非常感谢任何帮助。
由于
答案 0 :(得分:2)
validates_presence_of:user_id仅检查可能不是有效用户的任何有效整数。
但是如果您使用validates_presence_of :user
和validates_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来看,这看起来非常黑客。任何更好的解决方案仍然非常感激......