在我的应用程序中,我有客户,并且有联系人。客户可以有很多联系人,而联系人可以有很多客户。它们通过CustomerContact表关联。事实是,数据库只能基于其电子邮件地址拥有一个唯一的联系人。
我想创建一个表单,用户可以在其中输入客户信息以及所需的尽可能多的联系人。我使用cocoon gem完成此操作。现在的问题是,没有“选择现有联系人”,只有文本字段,其想法是,在提交表单时,系统可以通过现有电子邮件地址查看系统中是否已存在联系人,而不是不仅将客户分配给该联系人,而且还会更新现有的联系信息。如果是新联系人,则只需将新联系人插入数据库并将该联系人分配给客户。
现在,这显然超出了正常的工作方式。您通常会进行某种查找,然后选择一个现有的联系人,但这不是老板想要的。
当我输入数据库中存在的联系人的电子邮件地址时,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表执行查找以尝试获取Contacts表,以便它可以获取Contact的信息。但是,由于这是新客户,因此尚未建立该关联,因为尚未创建该客户。
如果我从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>
这是新的控制器并创建动作
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
答案 0 :(得分:0)
所以我终于使它起作用了,但是为了使它起作用,我基本上不得不重新编写嵌套属性为您所做的一切。它不是很漂亮,但是效果很好。
还...查看this post,了解我如何在模板中进行联系人收集
def update
myparams = customer_params.to_h
contacts_valid = true
@customer.assign_attributes(myparams)
customer_valid = @customer.valid?
@contacts = []
contacts_to_delete = []
cparams = contact_params.to_h
cparams['contacts'].each do |k, v|
if v['id'].to_i > 0
if v['_destroy'].to_i == 1
contacts_to_delete << v['id'].to_i
else
contact = Contact.find(v['id'])
contact.assign_attributes(v.except('_destroy', 'id'))
end
else
if (!v['name'].blank? || !v['email'].blank?) && v['_destroy'].to_i != 1
unless v['email'].blank?
contact = Contact.where(email: v['email']).first
end
if contact
contact.assign_attributes(v.except('_destroy', 'id'))
else
contact = Contact.new(v.except('_destroy', 'id'))
end
end
end
if contact
unless contact.valid?
contacts_valid = false
# This adds the errors from the contact to the customer object
contact.errors.full_messages.each do |m|
@customer.errors.add(:base, m)
end
end
@contacts << contact
end
end
respond_to do |format|
if customer_valid && contacts_valid
@customer.save!
@contacts.each do |c|
c.save!
CustomerContact.find_or_create_by(customer_id: @customer.id, contact_id: c.id)
end
CustomerContact.where(customer_id: @customer.id, contact_id: contacts_to_delete).destroy_all
format.html { redirect_to edit_customer_path(@customer), success: 'Customer was successfully updated.' }
format.json { render :show, status: :ok, location: @customer }
else
format.html {
(6 - @contacts.count).times { @contacts << @customer.contacts.build }
render :edit
}
format.json { render json: @customer.errors, status: :unprocessable_entity }
end
end
end
def customer_params
params.require(:customer).permit(:company, :name, :line1, :line2, :city, :state, :zipcode, :country, :webaddress, :status,
contacts_attributes: [:fullname, :phone, :email, :department, :manager, :id, :_destroy],
notes_attributes: [:note]
)
end
def contact_params
params.permit(contacts: [:fullname, :email, :phone, :department, :manager, :id, :_destroy])
end