首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Rails:在两个外键上都进行存在验证的has_one和belongs_to

Rails:在两个外键上都进行存在验证的has_one和belongs_to
EN

Stack Overflow用户
提问于 2012-11-03 05:16:54
回答 4查看 8.3K关注 0票数 10

我有一个包含users表和user_profiles表的应用程序。user has_one user profile和user profile belongs_to a user。

我希望确保关联场景始终为真,因此我对两个外键的存在进行了验证。问题是我遇到了一个“先有鸡还是先有蛋”的情况。当我创建一个用户时,它不工作,因为用户配置文件还不存在;当我创建一个用户配置文件时,它也不工作,因为用户还不存在。因此,我需要在创建用户时创建用户配置文件。更复杂的是,当我创建客户端时,我还在after_create回调中创建了一个用户。说到这里(或读/写),下面是一些代码:

代码语言:javascript
复制
class User < ActiveRecord::Base
    has_one :user_profile
    validates :user_profile_id, presence: true
end

class UserProfile < ActiveRecord::Base
  belongs_to :user
  validates :user_id, presence: true
end

class Client < ActiveRecord::Base
  after_create :create_client_user

  private

  def create_client_user
    User.create!(
      email: "admin@example.com",
      password: "admin",
      password_confirmation: "admin",
      client_id: self.id
      # I need to create a user profile dynamically here
    )
  end
end

可以做我想做的事吗?

更新

我尝试了解决方案@cdesrosiers建议,但我不能让我的规格通过。我主要有三个错误。首先,让我向您展示更新后的模型:

代码语言:javascript
复制
class User < ActiveRecord::Base
  has_one :user_profile, inverse_of: :user
  before_create { build_user_profile }

  validates :user_profile, presence: true

  def client=(client)
    self.client_id = client.id
  end

  def client
    current_database = Apartment::Database.current_database
    Apartment::Database.switch
    client = Client.find(self.client_id)
    Apartment::Database.switch(current_database)
    client
  end
end

class UserProfile < ActiveRecord::Base
  belongs_to :user

  validates :user, presence: true
end

class Client < ActiveRecord::Base
  attr_accessible :domain, :name

  after_create :create_client_database
  after_create :create_client_user
  after_destroy :drop_client_database

  # Create the client database (Apartment) for multi-tenancy
  def create_client_database
    Apartment::Database.create(self.domain)
  end

  # Create an admin user for the client
  def create_client_user
    Apartment::Database.switch(self.domain)

    User.create!(
      email: "admin@example.com",
      password: "admin",
      password_confirmation: "admin",
      client: self
    )

    # Switch back to the public schema
    Apartment::Database.switch
  end

  def drop_client_database
    Apartment::Database.drop(self.domain)
  end
end

我正在使用FactoryGirl创建工厂,这是我的工厂文件:

代码语言:javascript
复制
FactoryGirl.define do
  factory :client do
    sequence(:domain) { |n| "client#{n}" }
    name              Faker::Company.name
  end

  factory :user do
    sequence(:email)      { |n| "user#{n}@example.com"}
    password              "password"
    password_confirmation "password"
    client
    #user_profile
  end

  factory :credentials, class: User do
    email       "user@example.com"
    password    "password"
  end

  factory :user_profile do
    forename       Faker::Name.first_name
    surname        Faker::Name.last_name
    birthday       (5..90).to_a.sample.years.ago
    #user
  end
end

如果我分别取消注释user和user profile工厂中的user_profileuser关联,就会得到一个WARNING: out of shared memory

现在,当我创建其中一个工厂时,我得到了这三个错误中的一个:

代码语言:javascript
复制
Failure/Error: @user = create(:user)
     ActiveRecord::RecordInvalid:
       Validation failed: User profile A user profile is required
     # ./app/models/client.rb:41:in `create_client_user'
     # ./spec/controllers/users_controller_spec.rb:150:in `block (4 levels) in <top (required)>'

Failure/Error: create(:user_profile).should respond_to :surname
    ActiveRecord::RecordInvalid:
      Validation failed: User A user is required
    # ./spec/models/user_profile_spec.rb:29:in `block (4 levels) in <top (required)>'

Failure/Error: let(:client) { create(:client) }
     ActiveRecord::RecordInvalid:
       Validation failed: User profile A user profile is required
     # ./app/models/client.rb:41:in `create_client_user'
     # ./spec/controllers/sessions_controller_spec.rb:4:in `block (2 levels) in <top (required)>'
     # ./spec/controllers/sessions_controller_spec.rb:7:in `block (2 levels) in <top (required)>'

因此,我假设用户模型中的更改不起作用。还要注意,我从users表中删除了user_profile_id

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2012-11-03 06:10:21

当模型A has_one模型B时,这意味着B将外键存储到A中,就像模型C中的has_many模型D将外键存储到C中一样。has_one关系只是表达了您的愿望,即只允许B中的一条记录将特定的外键保存到A中。鉴于此,您应该从users模式中去掉user_profile_id,因为它没有被使用。仅使用来自UserProfileuser_id

您仍然可以使用User检查是否存在UserProfile,但请改用validates_presence_of :user_profile。这将检查用户对象是否具有关联的user_profile对象。

您的UserProfile对象不应该直接检查用户,因为在创建新的用户-用户_配置文件对时,这个id还不存在。取而代之的是使用validates_presence_of :user,它将在保存UserProfile之前检查它是否具有关联的User对象。然后在User中编写has_one :user_profile, :inverse_of => :user,这会让UserProfile知道它的User对象的存在,甚至在持久化和分配id之前也是如此。

最后,您可以在User中包含一个before_create块,以便在创建新用户时构建相关的UserProfile。(我相信)它将在构建新的user_profile之后运行验证,所以这些应该会通过。

总而言之,

代码语言:javascript
复制
class User < ActiveRecord::Base
    has_one :user_profile, :inverse_of => :user
    validates_presence_of :user_profile

    before_create { build_user_profile }
end

class UserProfile < ActiveRecord::Base
  belongs_to :user
  validates_presence_of :user
end

更新

我搞错了验证回调的顺序。验证在调用before_create回调之前运行,这意味着User甚至在构建UserProfile之前就会检查它是否存在。

一种解决方案是问问自己,从单独的用户和user_profile模型中获得了什么价值。考虑到它们是如此紧密地联系在一起,以至于一个不能离开另一个而存在,那么将它们组合到一个模型中是否有意义(也许还可以简化许多代码)?

另一方面,如果您真的发现拥有两个单独的模型是有价值的,那么也许您不应该使用验证来维护它们的相互存在。在我看来,模型验证通常应该用来让用户知道他们提交的数据有他们需要修复的错误。但是,他们无法修复user对象中缺少user_profile的问题。因此,也许更好的解决方案是让user对象构建一个user_profile (如果没有)。与其只是抱怨user_profile不存在,不如更进一步,只需构建它。两端都不需要验证。

代码语言:javascript
复制
class User < ActiveRecord::Base
  has_one :user_profile

  before_save { build_user_profile unless user_profile }
end

class UserProfile < ActiveRecord::Base
  belongs_to :user
end
票数 9
EN

Stack Overflow用户

发布于 2012-11-03 05:32:30

您无法验证user_profile_id是否存在,因为它不存在。has_one的意思是,另一个模型有一个外键引用。

我通常通过在创建被引用的模型时有条件地创建带有外键引用的模型来确保您所追求的行为。在您的示例中,这将为用户创建一个配置文件after_create,如下所示:

代码语言:javascript
复制
class User < ActiveRecord::Base
  ...
  after_create :create_profile
  private
  def create_profile
    self.user_profile.create
  end
end
票数 0
EN

Stack Overflow用户

发布于 2012-11-03 05:33:18

这个rail转换会创建嵌套的表单(同时创建user/user_profile )。http://railscasts.com/episodes/196-nested-model-form-part-1你需要做一些修改,因为它涵盖了一个has_many,但是你应该能够弄清楚它。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/13203159

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档