我的 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_id
和 project_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 会发生的竞争条件。
来自 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/