我有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
控制器:
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
问题在于,当我向PUT
发送http://localhost:3000/users/1
请求时,请使用以下json:
{"user":{"project_memberships_attributes":[{"project_id": 1}]}
user.update user_params
使用相同的user_id
和project_id
创建2个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
来销毁和更新已存在的记录,可以正常工作。
答案 0 :(得分:1)
您需要采取的第一步是确保数据库级别的唯一性:
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
工作原理的误解造成的:
对于没有id密钥的每个哈希,新记录将是 实例化,除非哈希还包含
_destroy
密钥 评估为真。
如果您没有适当的唯一性验证,{"user":{"project_memberships_attributes":[{"project_id": 1}]}
将始终创建新记录。