ruby-on-rails - 通过关联两次创建对象的 has_many 嵌套属性

标签 ruby-on-rails postgresql activerecord rails-api

我的 Rails API 应用程序通过 project_memberships 表在用户和项目之间建立了多对多关系。

模型:

class User < ActiveRecord::Base
  has_many :project_memberships, dependent: :destroy
  has_many :projects, -> { uniq }, through: :project_memberships

  accepts_nested_attributes_for :project_memberships, allow_destroy: true
end

class Project < ActiveRecord::Base
  has_many :project_memberships, dependent: :destroy
  has_many :users, -> { uniq }, through: :project_memberships
end

class ProjectMembership < ActiveRecord::Base
  belongs_to :user
  belongs_to :project

  validates :user, presence: true
  validates :project, presence: true
end

Controller :

class UsersController < ApplicationController
  expose(:user, attributes: :user_params)

  respond_to :json

  # removed unrelated actions

  def update
    user.update user_params
    respond_with user
  end

  private

  def user_params
    params.require(:user).permit(
      :first_name, :last_name,
      project_memberships_attributes: 
        [:id, :project_id, :membership_starts_at, :_destroy]
    )
  end
end

问题是,当我使用以下 json 向 http://localhost:3000/users/1 发送 PUT 请求时:

{"user":{"project_memberships_attributes":[{"project_id": 1}]}

user.update user_params 创建 2 个具有相同 user_idproject_id 的 ProjectMembership 记录。

  SQL (0.3ms)  INSERT INTO "project_memberships" ("project_id", "user_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["project_id", 1], ["user_id", 1], ["created_at", "2016-03-18 18:00:07.670012"], ["updated_at", "2016-03-18 18:00:07.670012"]]
  SQL (0.2ms)  INSERT INTO "project_memberships" ("project_id", "user_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["project_id", 1], ["user_id", 1], ["created_at", "2016-03-18 18:00:07.671644"], ["updated_at", "2016-03-18 18:00:07.671644"]]
  (1.0ms)  COMMIT

顺便说一句,通过在嵌套属性中指定 id 来销毁和更新已经存在的记录可以正常工作。

最佳答案

您需要采取的第一步是确保数据库级别的唯一性:

class AddUniquenessConstraintToProjectMemberships < ActiveRecord::Migration
  def change
    # There can be only one!
    add_index :project_memberships, [:user, :project], unique: true
  end
end

这避免了如果我们单独依赖 ActiveRecord 会发生的竞争条件。

(C) Thoughtbot

来自 Thoughtbot: The Perils of Uniqueness Validations .

然后您想要添加一个应用程序级别的验证,以避免在您违反约束时发生丑陋的数据库驱动程序异常:

class ProjectMembership < ActiveRecord::Base
  belongs_to :user
  belongs_to :project
  validates :user, presence: true
  validates :project, presence: true 
  validates_uniqueness_of :user_id, scope: :project_id
end

然后您可以删除关联中的 -> { uniq } lambda,因为您已采取适当的步骤来确保唯一性。

您的其他问题是由于误解了 accepts_nested_attributes_for 的工作原理:

For each hash that does not have an id key a new record will be instantiated, unless the hash also contains a _destroy key that evaluates to true.

因此,如果您没有适当的唯一性验证,{"user":{"project_memberships_attributes":[{"project_id": 1}]} 将始终创建一条新记录。

关于ruby-on-rails - 通过关联两次创建对象的 has_many 嵌套属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36092396/

相关文章:

ruby-on-rails - Rails 加载我的@items n*n 次

ruby-on-rails - 如何/何时/在哪里扩展 Rails 3 中的 Gem 类(通过 class_eval 和模块)?

ruby-on-rails - 干燥仅改变顺序的查询

Ubuntu 上的 PostgreSQL 日志配置

postgresql - 数据库连接的最大池大小 Keycloak 版本 11

mysql - 无法使用rails获取mysql结果中的信息

ruby - Rails validates_presence_of 正在检查保存的记录而不是编辑的记录

ruby-on-rails - 如何从两个不同的外键访问模型?

javascript - rails : Javascript not getting to heroku

php - PostgreSQL PHP - 使用第二个表的值作为 Excel/CSV 导出中第一个表的值的列标题和列描述