2016-03-18 8 views
1

Ich habe Rails API-Anwendung mit vielen zu vielen Beziehung zwischen Benutzern und Projekten obwohl Project_memberships Tabelle.Verschachtelte Attribute mit has_many durch Verknüpfung erstellen Objekt zweimal

Modelle:

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 

Das Problem ist, dass, wenn ich senden PUT Anfrage an http://localhost:3000/users/1 mit folgendem json:

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

user.update user_params erstellt 2 ProjectMembership-Datensätze mit denselben user_id und project_id.

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 

Btw zu zerstören und die Aktualisierung bereits Aufzeichnungen bestehenden durch id in verschachtelte Attribute angeben korrekt funktioniert.

+0

Als erstes würde ich prüfen, ob die 'config/routes.rb' richtig eingestellt sind, so dass dies die richtige Controller # Aktion ist, die aufgerufen wird. – jaysoifer

+0

@ JonathanSoifer Ich habe 'binding.pry' vor und nach' user.update' gesetzt und die Ausführung gestoppt, also sind die Routen korrekt. Und SQL-Abfragen, die ich am Ende hinzugefügt habe, wurden dazwischen aufgerufen. –

Antwort

1

Der erste Schritt müssen Sie nehmen ist Einzigartigkeit auf der Datenbank-Ebene zu gewährleisten:

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

Dies vermeidet Rennbedingungen, die auftreten würden, wenn wir auf Active allein verlassen.

(C) Thoughtbot

Von Thoughtbot: The Perils of Uniqueness Validations.

Sie dann eine Anwendungsebene Validierung hinzufügen möchten die hässlichen DB-Treiber Ausnahmen zu vermeiden, die auftreten, wenn Sie die Einschränkung verletzen:

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 

Sie können dann entfernen Sie die -> { uniq } Lambda auf Assoziationen, wie Sie das gemacht haben richtige Schritte, um Einzigartigkeit zu gewährleisten.

Der Rest Ihrer Fragen zu einem Missverständnis beruhen, wie accepts_nested_attributes_for Werke:

Für jeden Hash, der nicht eine ID-Taste wird ein neuer Datensatz instanziiert hat, es sei denn, auch der Hash ein _destroy enthält Schlüssel, der als wahr auswertet.

So {"user":{"project_memberships_attributes":[{"project_id": 1}]} wird immer einen neuen Datensatz erstellen, wenn Sie keine eindeutigen Eindeutigkeitsprüfungen haben.

+0

Wenn Sie eine vorhandene Datenbank mit Daten haben, müssen Sie alle Duplikate löschen, bevor Sie den Index hinzufügen. – max

+0

"wird immer eine neue Platte erstellen" Ja, ich weiß, das erwarte ich hier. Aber es schafft nicht eine einzige, sondern 2 neue Datensätze sofort und das ist das Problem. Wie gesagt, das Aktualisieren und Löschen (mit der angegebenen ID) funktioniert korrekt. Zum Teil über die Datenbank - danke, wirklich interessante Sachen, aber es repariert nicht die Ursache, nur seine Ergebnisse (blockiert zweite SQL-Anfrage auf DB-Ebene einfügen). Und ich muss die Eindeutigkeit in Zukunft sowieso entfernen, da ich möchte, dass der Benutzer mehrere Male Mitglied eines Projekts sein kann (in verschiedenen Zeiträumen). –