В моем приложении есть клиенты и контакты. Клиенты могут иметь много контактов, а контакты могут иметь много клиентов. Они связаны через таблицу CustomerContact. Дело в том, что в базе данных может быть только один уникальный контакт на основе их адреса электронной почты.
Что, я хочу создать форму, в которой пользователь может ввести информацию о клиенте и столько контактов, сколько он хочет. Я выполняю это, используя коконовый камень . Теперь дело в том, что здесь нет «выбрать существующий контакт», есть только текстовые поля, и идея заключается в том, что при отправке формы система может увидеть, существует ли уже контакт в системе через существующий адрес электронной почты, а не только назначить клиента на контакт, но и обновить существующую контактную информацию. Если это новый контакт, он просто вставит новый контакт в базу данных и назначит контакт клиенту.
Теперь, очевидно, это совершенно за пределами обычного способа ведения дел. Обычно у вас есть какой-то поиск и затем выбирается существующий контакт, но это не то, что хочет босс.
Вот проблема, с которой я сталкиваюсь
Rails всегда выдавал сообщение об ошибке «Адрес электронной почты уже занят», когда я вводил адрес электронной почты для контакта, существующего в базе данных. «Не проблема, я думал», для существующих контактов, я буду искать в базе данных и создавать атрибут id для контакта, манипулируя параметрами в контроллере. Поэтому я написал код ниже:
# convert the params to a Hash so we can manuipulate them
myparams = customer_params.to_h
# if we have contacts we want to assign to the customer
if myparams['contacts_attributes']
# loop through each contact they entered
myparams['contacts_attributes'].each do |k,v|
# see if the contact exists in the database and isn't being destroyed
if Contact.exists?(email: v['email']) && v['_destroy'] != '1'
# grab the contact information
contact = Contact.where(email: v['email']).first
# just a double check that we got the contact from the database
if contact
# create an id attribute for the contact
myparams['contacts_attributes'][k]['id'] = contact.id
end
end
end
end
"Красивые !!!" или так я думал. Когда я попытался сохранить контакт, меня встретила следующая ошибка:
Couldn't find Contact with ID=117 for Customer with ID=
Очевидно, что при передаче параметра методу Customer#new
Rails выполняет поиск в таблице CustomerContact, чтобы попытаться получить доступ к таблице контактов, чтобы получить информацию для контакта. Однако, поскольку это новый Заказчик, эта связь еще не установлена, поскольку Заказчик не был создан.
Хорошо ... Итак, у меня появилась идея
Что если я удалил существующие контакты из contact_attributes
и сразу создал ассоциацию customer_contacts
!!! Итак, где myparams['customer_contacts_attributes'] = []
вступает в игру:
# convert the params to a Hash so we can manuipulate them
myparams = customer_params.to_h
# so we can create a record on the customer_contacts association
# directly if the contact already exits
myparams['customer_contacts_attributes'] = []
# if we have contacts we want to assign to the customer
if myparams['contacts_attributes']
# loop through each contact they entered
myparams['contacts_attributes'].each do |k,v|
# see if the contact exists in the database and isn't being destroyed
if Contact.exists?(email: v['email']) && v['_destroy'] != '1'
# grab the contact information
contact = Contact.where(email: v['email']).first
# just a double check that we got the contact from the database
if contact
# removed the contact
myparams['contacts_attributes'].delete(k)
# create the `customer_contact` association directly
myparams['customer_contacts_attributes'].push({'user_id': contact.id})
end
end
end
end
И ... ЭТО РАБОТАЛО !!!! Ну кое что. Клиент сохраняется, контакты, которые уже существуют в базе данных, назначаются, а новые контакты создаются и назначаются ... так в чем же тогда проблема ??? Хорошо ... если проверка не удалась по какой-либо причине и страница перерисовывается, существующий контакт, введенный пользователем, исчезает из формы.
Моя просьба о помощи
Так что у меня есть кое-что, что работает, но мне действительно нужна помощь. Мне нужно, чтобы в случае сбоя проверки контакт снова отображался в форме. Кроме того, очевидно, что это не лучший способ сделать это. Я надеюсь, что кто-то уже делал что-то подобное в рельсах, и у него есть лучший способ сделать это.
Ниже приведены настройки моей ассоциации:
class CustomerContact < ApplicationRecord
belongs_to :customer
belongs_to :contact
end
class Customer < ApplicationRecord
has_many :customer_contacts
has_many :contacts, through: :customer_contacts
accepts_nested_attributes_for :customer_contacts
accepts_nested_attributes_for :contacts, allow_destroy: true
end
class Contact < ApplicationRecord
has_many :customer_contacts
has_many :customers, through: :customer_contacts
end
Ниже приведена настройка раздела контактов, обратите внимание, что я использую это как в новых, так и в редактируемых действиях:
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Department</th>
<th>Manager</th>
<th>
<%= link_to_add_association f, :contacts, class: 'btn btn-primary', partial: 'contact_fields', data: {
association_insertion_node: '.contact_fields', association_insertion_method: :append
} do %>
<i class="fas fa-plus"></i>
<% end %>
</th>
</tr>
</thead>
<tbody class="contact_fields">
<%= f.fields_for :contacts do |contact| %>
<%= render 'projects/contact_fields', f: contact %>
<% end %>
</tbody>
</table>
contact_fields
частичное ниже:
<tr class="nested-fields">
<td>
<%= f.text_field :fullname, class: 'form-control invoke-contacts-search contact-fullname' %>
</td>
<td>
<%= f.text_field :email, class: 'form-control invoke-contacts-search contact-email' %>
</td>
<td>
<%= f.text_field :phone, class: 'form-control contact-phone' %>
</td>
<td>
<%= f.text_field :department, class: 'form-control contact-department' %>
</td>
<td>
<%= f.text_field :manager, class: 'form-control contact-manager' %>
</td>
<td>
<%= link_to_remove_association f, class: 'btn btn-danger' do %>
<i class="fas fa-trash-alt"></i>
<% end %>
</td>
</tr>
Вот контроллер new и create action
def new
@customer = Customer.new
4.times { @customer.contacts.build }
end
def create
# convert the params to a Hash so we can manuipulate them
myparams = customer_params.to_h
# so we can create a record on the customer_contacts association
# directly if the contact already exits
myparams['customer_contacts_attributes'] = []
# if we have contacts we want to assign to the customer
if myparams['contacts_attributes']
# loop through each contact they entered
myparams['contacts_attributes'].each do |k,v|
# see if the contact exists in the database and isn't being destroyed
if Contact.exists?(email: v['email']) && v['_destroy'] != '1'
# grab the contact information
contact = Contact.where(email: v['email']).first
# just a double check that we got the contact from the database
if contact
# removed the contact
myparams['contacts_attributes'].delete(k)
# create the `customer_contact` association directly
myparams['customer_contacts_attributes'].push({'user_id': contact.id})
end
end
end
end
@customer = Customers.new(myparams)
respond_to do |format|
if @customer.save
format.html { redirect_to edit_customer_path(@customer), success: 'Customer was successfully created.' }
else
format.html { render :new }
end
end
end
Вот действия по редактированию и обновлению контроллеров
def edit
@customer = Customer.find(params[:id])
end
def update
myparams = customer_params.to_h
if myparams['contacts_attributes']
myparams['contacts_attributes'].each do |k,v|
if Contacts.exists?(email: v['email']) && v['_destroy'] != '1'
contact = Contact.where(email: v['email']).first
if contact
myparams['contacts_attributes'][k]['id'] = contact.id
CustomerContact.find_or_create_by(project_id: @customer.id, user_id: contact.id)
end
end
end
end
@customer.assign_attributes(myparams)
respond_to do |format|
if @customer.save
format.html { redirect_to edit_customer_path(@customer), success: 'Customer was successfully updated.' }
else
format.html { render :edit }
end
end
end