Как добавить много ко многим записи, имеющей дополнительный столбец
У меня есть следующие модели
пользователь
has_many :users_contacts
has_many :contacts, through: :users_contacts
accepts_nested_attributes_for :contacts, allow_destroy: true
контакт
has_many :users_contacts
has_many :users, through: :users_contacts
accepts_nested_attributes_for :users_contacts, allow_destroy: true
UsersContact
belongs_to :users
belongs_to :contacts
Я использую следующие строгие параметры
params.require(:user).permit(:id, :email,
contacts_attributes: [:id, :first_name, :last_name,
users_contacts_attributes: [:id, :contact_id, :user_id, :order]])
Проблема, с которой я сталкиваюсь, заключается в том, что когда я обновляю пользователей users_contacts_attributes
например, {contact_id: 5, order: 5} создает две записи, одна с order: nil
и другие с order: 5
Я получаю заказ в парам.
Я хочу следовать
Не хочу, чтобы дублированные записи создавались.
Сохраните запись в объединяющей таблице с дополнительным столбцом, т.е.
order
Мои параметры что-то вроде следующего
{"id"=>4,
"email"=>"abc@xyz.com",
"contacts_attributes"=>
[{"id"=>150,
"first_name"=>"Pqr",
"is_shareable"=>true,
"users_contacts_attributes"=>[{"id"=>87, "user_id"=>4, "contact_id"=>150, "order"=>100}]
},
{"first_name"=>"Def",
"is_shareable"=>true,
"users_contacts_attributes"=>[{"user_id"=>4, "order"=>101}]
}
]
}
1 ответ
Не видя ваших контроллеров или формы, трудно определить, что именно вызывает вашу проблему. В дополнение к комментарию от @surya выше, я заметил, что два belongs_to
заявления в вашем UsersContact
класс должен быть в единственном числе, а не во множественном числе (:user
, :contact
). Кроме того, не уверен, что вам нужно вложить users_contacts_attributes
в contacts_attributes
который затем делает его вложенным в два user
когда вы можете просто иметь отдельный accepts_nested_attributes_for :users_contacts
в User
учебный класс.
Кроме того, вам придется сравнить свой код с кодом, который я предоставил ниже, чтобы выяснить, в чем заключается ваша проблема.
Примечание: форма очень проста, и я изменил некоторые имена, чтобы более четко обозначать, когда используются единственное и множественное число и для удобства чтения.
модели
class User < ActiveRecord::Base
has_many :user_contact_pairs, inverse_of: :user
has_many :contacts, through: :user_contact_pairs
accepts_nested_attributes_for :contacts, allow_destroy: true
accepts_nested_attributes_for :user_contact_pairs, allow_destroy: true
end
class Contact < ActiveRecord::Base
has_many :user_contact_pairs, inverse_of: :contact
has_many :users, through: :user_contact_pairs
accepts_nested_attributes_for :user_contact_pairs, allow_destroy: true
end
class UserContactPair < ActiveRecord::Base
belongs_to :contact
belongs_to :user
end
контроллер
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
def index
@users = User.all
end
def show
end
def new
@user = User.new
end
def edit
@user.user_contact_pairs.build(user_id: @user.id)
end
def create
@user = User.new(user_params)
respond_to do |format|
if @user.save
format.html { redirect_to @user, notice: 'User was successfully created.' }
format.json { render :show, status: :created, location: @user }
else
format.html { render :new }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if @user.update(user_params)
format.html { redirect_to @user, notice: 'User was successfully updated.' }
format.json { render :show, status: :ok, location: @user }
else
format.html { render :edit }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
def destroy
@user.destroy
respond_to do |format|
format.html { redirect_to users_url, notice: 'User was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_user
@user = User.find(params[:id])
end
def user_params
params.require(:user).
permit(:email, :first_name, :last_name, :username,
contacts_attributes:
[:id, :first_name, :last_name],
user_contact_pairs_attributes:
[:id, :contact_id, :user_id, :order_number])
end
end
Просмотр (users/index.html.erb)
<p id="notice"><%= notice %></p>
<h1>Listing Users</h1>
<table>
<thead>
<tr>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<tr>
<td><%= 'Id' %></td>
<td><%= 'First Name' %></td>
<td><%= 'Last Name' %></td>
<td><%= 'Username' %></td>
<td></td>
<td></td>
<td></td>
</tr>
<% @users.each do |user| %>
<tr>
<td><%= user.id %></td>
<td><%= user.first_name %></td>
<td><%= user.last_name %></td>
<td><%= user.username %></td>
<td><%= link_to 'Show', user %></td>
<td><%= link_to 'Edit', edit_user_path(user) %></td>
<td><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New User', new_user_path %>
Просмотр (users/edit.html.erb)
<h1>Editing User</h1>
<%= render 'form' %>
<%= link_to 'Show', @user %> |
<%= link_to 'Back', users_path %>
Просмотр (users/_form.html.erb)
<%= form_for(@user) do |user_form| %>
<% if @user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% @user.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= user_form.label :email %>
<%= user_form.email_field :email %><br>
<%= user_form.label :first_name %>
<%= user_form.text_field :first_name %><br>
<%= user_form.label :last_name %>
<%= user_form.text_field :last_name %><br>
<%= user_form.label :username %>
<%= user_form.text_field :username %><br>
</div>
<h2>User Contact Pair</h2>
<%= user_form.fields_for :user_contact_pairs do |ucp_fields| %>
<div class="user-contact-pair">
<div class="field">
<%= ucp_fields.label :contact_id %>
<%= ucp_fields.number_field :contact_id %><br>
<%= ucp_fields.label :order_number %>
<%= ucp_fields.number_field :order_number %><br>
</div>
</div>
<% end %>
<div class="actions">
<%= user_form.submit %>
</div>
<% end %>
Миграции
class CreateUser < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :email, index: { unique: true }
t.string :first_name
t.string :last_name
t.string :username, index: { unique: true }
t.timestamps null: false
end
end
end
class CreateContact < ActiveRecord::Migration
def change
create_table :contacts do |t|
t.string :first_name
t.string :last_name
t.timestamps null: false
end
end
end
class CreateUserContactPair < ActiveRecord::Migration
def change
create_table :user_contact_pairs do |t|
t.integer :contact_id
t.integer :user_id
t.integer :order_number
t.timestamps null: false
end
end
end
Исходный файл БД
User.create(email: 'lhawkinsa@icio.us', first_name: 'Lisa', last_name: 'Hawkins', username: 'lhawkinsa')
User.create(email: 'htaylorb@imdb.com', first_name: 'Helen', last_name: 'Taylor', username: 'htaylorb')
User.create(email: 'gtaylorc@unblog.fr', first_name: 'Gregory', last_name: 'Taylor', username: 'gtaylorc')
User.create(email: 'hlaned@whitehouse.gov', first_name: 'Henry', last_name: 'Lane', username: 'hlaned')
User.create(email: 'hphillipse@howstuffworks.com', first_name: 'Harry', last_name: 'Phillips', username: 'hphillipse')
User.create(email: 'jgonzalesf@com.com', first_name: 'Jeffrey', last_name: 'Gonzales', username: 'jgonzalesf')
User.create(email: 'ljamesg@sfgate.com', first_name: 'Lori', last_name: 'James', username: 'ljamesg')
User.create(email: 'rhillh@gnu.org', first_name: 'Roger', last_name: 'Hill', username: 'rhillh')
User.create(email: 'rharveyi@tripadvisor.com', first_name: 'Raymond', last_name: 'Harvey', username: 'rharveyi')
User.create(email: 'sperryj@mit.edu', first_name: 'Stephen', last_name: 'Perry', username: 'sperryj')
Contact.create(first_name: 'Louis', last_name: 'Harris')
Contact.create(first_name: 'Fred', last_name: 'Adams')
Contact.create(first_name: 'David', last_name: 'Lane')
Contact.create(first_name: 'Kevin', last_name: 'Ryan')
Contact.create(first_name: 'Samuel', last_name: 'Jones')
Rails Console (после двух пользовательских обновлений через форму)
Running via Spring preloader in process 30656
Loading development environment (Rails 4.2.7.1)
2.3.3 :001 > UserContactPair.all
UserContactPair Load (0.7ms) SELECT "user_contact_pairs".* FROM "user_contact_pairs"
=> #<ActiveRecord::Relation [#<UserContactPair id: 1, contact_id: 1, user_id: 1, order_number: 1, created_at: "2017-02-14 09:05:31", updated_at: "2017-02-14 09:05:31">, #<UserContactPair id: 2, contact_id: 4, user_id: 1, order_number: 4, created_at: "2017-02-14 09:05:50", updated_at: "2017-02-14 09:05:50">]>
2.3.3 :002 >
ОБНОВЛЕНИЕ (в ответ на комментарий):
Вот обновленный код, который должен делать то, что вы просите. Вы должны добавить валидации, которые подтверждают наличие и уникальность, где это необходимо, в приведенном выше коде и в этом дополнительном коде. Вы многие также хотите отсортировать Contact
коллекция, используемая для заполнения формы.
(Примечание: из-за того факта, что использование вложенных атрибутов вызывает добавление цифровых ключей (например, ["0"]) в хэши параметров, которые варьируются в зависимости от количества связанных записей тех классов вложенных атрибутов, которые имеет объект, I добавили гем hashie в файл gem, который дает возможность находить глубоко вложенные ключи в хэше через .deep_find
, позволяя прямой доступ к нужным клавишам, минуя эти клавиши с различными номерами.)
Модели (добавлен метод в класс контактов для выпадающего списка выбора формы)
def last_name_first
self.last_name + ', ' + self.first_name
end
Контроллер (редактирование и обновление действий обновлено)
def edit
@contacts = Contact.all
@user.contacts.build
@user.user_contact_pairs.build
end
def update
@user = User.find(params[:id])
user_only_params = { email: params[:user][:email], first_name: params[:user][:first_name], last_name: params[:user][:last_name], username: params[:user][:username] }
contact_params = params[:user][:contacts_attributes]
user_contact_pair_params = params[:user][:user_contact_pairs_attributes]
contact_params.extend(Hashie::Extensions::DeepFind)
contact_first_name = contact_params.deep_find(:first_name)
contact_last_name = contact_params.deep_find(:last_name)
user_contact_pair_params.extend(Hashie::Extensions::DeepFind)
user_contact_pair_contact_id = user_contact_pair_params.deep_find(:contact_id)
user_contact_pair_order_number = user_contact_pair_params.deep_find(:order_number)
@user.assign_attributes(user_only_params)
if user_contact_pair_order_number != ''
if contact_first_name != '' && contact_last_name != ''
@contact = Contact.create(first_name: contact_first_name, last_name: contact_last_name)
@user.user_contact_pairs.new(contact_id: @contact.id, order_number: user_contact_pair_order_number.to_i)
elsif user_contact_pair_contact_id != ''
@user.user_contact_pairs.new(contact_id: user_contact_pair_contact_id.to_i, order_number: user_contact_pair_order_number.to_i)
end
end
respond_to do |format|
if @user.save
format.html { redirect_to @user, notice: 'User was successfully updated.' }
format.json { render :show, status: :ok, location: @user }
else
@contact.destroy if @contact.exists?
format.html { render :edit }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
Просмотр (users/_form.html.erb)
<%= form_for(@user) do |user_form| %>
<% if @user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% @user.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="user">
<h2 style="margin-bottom: 4px">User</h2>
<%= user_form.label :email %><br>
<%= user_form.email_field :email %><br><br>
<%= user_form.label :first_name %><br>
<%= user_form.text_field :first_name %><br><br>
<%= user_form.label :last_name %><br>
<%= user_form.text_field :last_name %><br><br>
<%= user_form.label :username %><br>
<%= user_form.text_field :username %><br><br>
</div>
<%= user_form.fields_for :user_contact_pairs do |ucp_fields| %>
<% if ucp_fields.object.new_record? %>
<h2 style="margin-bottom: 4px">Use Existing Contact</h2>
<%= ucp_fields.collection_select(:contact_id, @contacts, :id, :last_name_first, prompt: "Select...") %><br><br>
<% end %>
<% end %>
<%= user_form.fields_for :contacts do |contact_fields| %>
<% if contact_fields.object.new_record? %>
<h2 style="margin-bottom: 4px">Or Create New Contact</h2>
<%= contact_fields.label 'First Name' %><br>
<%= contact_fields.text_field :first_name %><br><br>
<%= contact_fields.label 'Last Name' %><br>
<%= contact_fields.text_field :last_name %><br><br>
<% end %>
<% end %>
<%= user_form.fields_for :user_contact_pairs do |ucp_fields| %>
<% if ucp_fields.object.new_record? %>
<h2 style="margin-bottom: 4px">Order</h2>
<%= ucp_fields.label 'Order Number' %><br>
<%= ucp_fields.number_field :order_number %><br><br>
<% end %>
<% end %>
<div class="actions">
<%= user_form.submit %>
</div>
<% end %>