【问题标题】:Creating model and nested model (1:n) at once with ActiveRecord使用 ActiveRecord 一次创建模型和嵌套模型 (1:n)
【发布时间】:2016-10-03 13:54:22
【问题描述】:

我的 Rails5 应用程序有一个组织模型和一个用户模型(1:n 关系)。创建组织的工作流程还应包括创建组织的第一个用户。我认为这可以通过嵌套模型使用 ActiveRecord,但是创建操作失败并显示错误消息“用户组织必须存在”。

class Organization < ApplicationRecord
    has_many :users, dependent: :destroy
    accepts_nested_attributes_for :users
end

class User < ApplicationRecord
    belongs_to :organization
end

class OrganizationsController < ApplicationController
    def new
        @organization = Organization.new
        @organization.users.build
    end

    def create
        @organization = Organization.new(organization_params)
        if @organization.save
            redirect_to @organization
        else
          render 'new'
        end
    end

    def organization_params
        params.require(:organization).permit(:name, users_attributes: [:name, :email, :password, :password_confirmation])
    end
end

在视图中我使用&lt;%= f.fields_for :users do |user_form| %&gt; 助手。

这是我这边的错误,还是 ActiveRecord 根本不支持?在导轨指南中找不到任何关于它的信息。毕竟,这应该(理论上)是可能的:首先为组织做 INSERT,然后是用户的 INSERT(顺序很重要,要知道用户外键的组织 id)。

【问题讨论】:

  • 您是否记得将“accepts_nested_attributes_for :users”放入您的组织模型文件中?
  • 发生这种情况的原因是因为 Rails 5 在默认情况下自然强制执行引用完整性。这意味着组织必须在用户之前存在。 accepts_nested_attributes_for 似乎没有实现这个概念。 SO Post to help you around this。其他选择是创建一个新类来保存组织,然后从这个组织中“建立”一个用户(或者在你的控制器中做,虽然我不支持这个)
  • 试试belongs_to :organization, optional: true。这将使其具有与 Rails 4 中相同的行为。它不是很好,但它可以防止在用户的存在验证在组织被持久化之前启动的问题。
  • @engineersmnky 谢谢,这帮助我理解了这个问题。我将发布关于我如何解决问题的答案。

标签: ruby-on-rails ruby activerecord


【解决方案1】:

https://github.com/rails/rails/issues/18233 中所述,Rails5 需要完整性检查。因为我不喜欢像禁用完整性检查这样的虚无缥缈的解决方案,所以我从上面链接的问题中遵循了 DHH 的建议:

我喜欢通过常规 Ruby 对象进行聚合。例如,我们有一个 Signup 模型,它只是一个编排构建过程的 Ruby 对象。所以我会试一试!

我编写了一个名为 Signup 的 ruby​​ 类,它封装了组织和用户模型,并提供了类似于 ActiveRecord 模型的保存/创建接口。此外,通过包含 ActiveModel::Model,有用的东西可以免费进入类(属性哈希构造函数等,请参阅http://guides.rubyonrails.org/active_model_basics.html#model)。

# The Signup model encapsulates an organization and a user model.
# It's used in the signup process and helps persisting a new organization
# and a referenced user (the owner of the organization).
class Signup
  include ActiveModel::Model

  attr_accessor :organization_name, :user_name, :user_email, :user_password, :user_password_confirmation

  # A save method that acts like ActiveRecord's save method.
  def save
    @organization = build_organization

    return false unless @organization.save

    @user = build_user

    @user.save
  end

  # Checks validity of the model.
  def valid?
    @organization = build_organization
    @user = build_user

    @organization.valid? and @user.valid?
  end

  # A create method that acts like ActiveRecord's create method.
  # This builds the object from an attributes hash and saves it.
  def self.create(attributes = {})
    signup = Signup.new(attributes)
    signup.save
  end

  private

  # Build an organization object from the attributes.
  def build_organization
    @organization = Organization.new(name: @organization_name)
  end

  # Build a user object from the attributes. For integritiy reasons,
  # a organization object must already exist.
  def build_user
    @user = User.new(name: @user_name, email: @user_email, password: @user_password, password_confirmation: @user_password_confirmation, organization: @organization)
  end
end

特别感谢 @engineersmnky 为我指出相应的 github 问题。

【讨论】:

  • 这绝对是我会采用的解决方案。很少有开发人员在他们的 Rails 基础设施中使用 PORO。
【解决方案2】:

您正在寻找“关联回调”。一旦您将这些参数发送到您的组织模型,您就可以在该模型中访问它们。如果每次创建组织时都会分配一个新用户,您可以在组织模型中执行以下操作:

 has_many :users, dependent: :destroy, after_add: :create_orgs_first_user

 attr_accessor: :username #create virtual atts for all the user params and then assign them as if they were organizational attributes in the controller.  This means changing your `organization_params` method to not nest user attributes inside the array `users_attributes`


  def create_orgs_first_user
    User.create(name: self.username, organization_id: self.id,  etc.) #  You can probably do self.users.create(params here) but I didn't try it that way. 
  end

【讨论】:

    【解决方案3】:

    不应出现“用户组织必须存在”错误。 ActiveRecord 是“智能的”,因为它应该执行两个INSERTs。首先,它将模型保存在has_many 端,使其具有id,然后将模型保存在belongs_to 端,填充外键值。该问题实际上是由 5.1.1 之前的 Rails 5 版本中的 accepts_nested_attributes_for 中的错误引起的。请参阅https://github.com/rails/rails/issues/25198Trouble with accepts_nested_attributes_for in Rails 5.0.0.beta3, -api option

    解决方案是使用inverse_of: 选项,或者升级到Rails 5.1.1。

    您可以通过删除Organization 模型中的accepts_nested_attributes_for 来证明这是正确的,并在Rails 控制台中创建一个新的Organization 模型和一个新的User 模型,将它们关联起来(例如@987654333 @) 并尝试使用save(例如myorg.save)。你会发现它会按预期工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-01-26
      • 1970-01-01
      • 1970-01-01
      • 2018-05-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多