请不要将此标记为重复。我已经回顾了其他类似的问题,我的不同。
我遇到一个问题,其中一些输入其地址的用户设法创建重复的CartAddress记录。
我的数据库结构包括Cart模型和CartAddress模型。
Cart模型看起来像这样:
class Cart < ApplicationRecord
has_many :cart_addresses, -> { order(address_type: :asc) }, inverse_of: :cart, dependent: :destroy
accepts_nested_attributes_for :cart_addresses, allow_destroy: true
end
我的CartAddress模型如下所示:
class CartAddress < ApplicationRecord
belongs_to :cart
validates :first_name, presence: true
validates :last_name, presence: true
validates :email, presence: true
validates :telephone, presence: true
validates :address_line_one, presence: true
validates :city, presence: true
validates :country, presence: true
validates :state, presence: true
validates :zip_code, presence: true
validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
end
当用户加载购物车页面时:
def index
#bunch of init code here
#more here
@exists = true
if @cart.cart_addresses.count == 0
@exists = false
2.times { @cart.cart_addresses.build(country: "United States", state: "Alabama")}
end
end
然后当我创建/更新时:
def update_addresses
@countries = Country.all
@states = State.all
@cart = Cart.find(params[:cart][:id])
if params[:cart][:cart_addresses_attributes]["0"][:address_type] == "Billing" and params[:cart][:cart_addresses_attributes]["1"][:address_type] == "Shipping"
@cart.update(params.require(:cart).permit(cart_addresses_attributes: [:id, :address_type, :first_name, :last_name, :email, :telephone, :address_line_one, :address_line_two, :city, :zip_code, :country, :state]))
#bunch of other code
end
这是我的部分呈现形式
<%= form_for(cart, :remote => true, :url => update_cart_addresses_path, :html => {:class => 'addresses-class', :id => 'cart-addresses', :autocomplete => :off}) do |u| %>
<% billing = true %>
<% state = 0 %>
<%= u.hidden_field :id %>
<% offset_two = true %>
<% if @exists == true %>
<%= u.fields_for :cart_addresses do |b| %>
<%= b.hidden_field :address_type %>
<% if billing == true %>
<div class="col-xs-12">
<h4>Billing Address</h4>
<% billing = false %>
</div>
<% else %>
<div class="col-xs-12 pad-top-15 checkout-section-padding">
<h4>Shipping Address</h4>
<input type="checkbox" id="same-as-billing" name="same_as_billing" value="1" onclick="toggleShippingAddress(this);">Same as Billing?
</div>
<% end %>
<div class="address">
<div class="col-xs-6">
<%= b.label :first_name %><span class="required-field">*</span>
<%= b.text_field :first_name, class: 'checkout-addr-text-field required', :data => {:hj_whitelist => ''}%>
</div>
<div class="col-xs-6">
<%= b.label :last_name %><span class="required-field">*</span>
<%= b.text_field :last_name, class: 'checkout-addr-text-field required', :data => {:hj_whitelist => ''} %>
</div>
<div class="col-xs-6 pad-top-15">
<%= b.label :email %><span class="required-field">*</span>
<%= b.email_field :email, class: 'checkout-addr-text-field required', :data => {:hj_whitelist => ''} %>
</div>
<div class="col-xs-6 pad-top-15">
<%= b.label :telephone %><span class="required-field">*</span>
<%= b.text_field :telephone, class: 'checkout-addr-text-field required', :data => {:hj_whitelist => ''} %>
</div>
<div class="col-xs-12 pad-top-15">
<%= b.label :address_line_one %><span class="required-field">*</span>
<%= b.text_field :address_line_one, class: 'checkout-addr-text-field required', :data => {:hj_whitelist => ''} %>
</div>
<div class="col-xs-12 pad-top-15">
<%= b.text_field :address_line_two, class: 'checkout-addr-text-field', :data => {:hj_whitelist => ''} %>
</div>
<div class="col-xs-12 pad-top-15">
<%= b.label :city %><span class="required-field">*</span>
<%= b.text_field :city, class: 'checkout-addr-text-field required', :data => {:hj_whitelist => ''} %>
</div>
<div class="col-xs-12 pad-top-15">
<%= b.label :country %><span class="required-field">*</span>
<%= b.select(:country, countries.all.collect {|a| [a.name]}, {:prompt => "Select A Country"}, :onchange => "setStates(#{state});", class: 'checkout-country') %>
</div>
<div class="col-xs-6 pad-top-15">
<div id="<%= "state_" + state.to_s %>">
<%= b.label :state %><span class="required-field">*</span>
<%= b.text_field :state, class: 'checkout-addr-text-field' %>
</div>
</div>
<div class="col-xs-6 pad-top-15">
<%= b.label :zip_code%><span class="required-field">*</span>
<%= b.text_field :zip_code, class: 'checkout-addr-text-field required', :data => {:hj_whitelist => ''} %>
</div>
<% state = state + 1 %>
</div>
<% end %>
<% else %>
<%= u.fields_for :cart_addresses do |b| %>
<% if billing == true %>
<div class="col-xs-12">
<h4>Billing Address</h4>
<% billing = false %>
<%= b.hidden_field :address_type, value: "Billing" %>
</div>
<% else %>
<div class="col-xs-12 pad-top-15 checkout-section-padding">
<h4>Shipping Address</h4>
<input type="checkbox" id="same-as-billing" name="same_as_billing" value="1" onclick="toggleShippingAddress(this);">Same as Billing?
<%= b.hidden_field :address_type, value: "Shipping" %>
</div>
<% end %>
<div class="address">
<div class="col-xs-6">
<%= b.label :first_name %><span class="required-field">*</span>
<%= b.text_field :first_name, class: 'checkout-addr-text-field required', :data => {:hj_whitelist => ''} %>
</div>
<div class="col-xs-6">
<%= b.label :last_name %><span class="required-field">*</span>
<%= b.text_field :last_name, class: 'checkout-addr-text-field required', :data => {:hj_whitelist => ''} %>
</div>
<div class="col-xs-6 pad-top-15">
<%= b.label :email %><span class="required-field">*</span>
<%= b.email_field :email, class: 'checkout-addr-text-field required', :data => {:hj_whitelist => ''} %>
</div>
<div class="col-xs-6 pad-top-15">
<%= b.label :telephone %><span class="required-field">*</span>
<%= b.text_field :telephone, class: 'checkout-addr-text-field required', :data => {:hj_whitelist => ''} %>
</div>
<div class="col-xs-12 pad-top-15">
<%= b.label :address_line_one %><span class="required-field">*</span>
<%= b.text_field :address_line_one, class: 'checkout-addr-text-field required', :data => {:hj_whitelist => ''} %>
</div>
<div class="col-xs-12 pad-top-15">
<%= b.text_field :address_line_two, class: 'checkout-addr-text-field', :data => {:hj_whitelist => ''} %>
</div>
<div class="col-xs-12 pad-top-15">
<%= b.label :city %><span class="required-field">*</span>
<%= b.text_field :city, class: 'checkout-addr-text-field required', :data => {:hj_whitelist => ''} %>
</div>
<div class="col-xs-12 pad-top-15">
<%= b.label :country %><span class="required-field">*</span>
<%= b.select(:country, countries.all.collect {|a| [a.name]}, {:prompt => "Select A Country"}, :onchange => "setStates(#{state});", class: 'checkout-country') %>
</div>
<div class="col-xs-6 pad-top-15">
<div id="<%= "state_" + state.to_s %>">
<%= b.label :state %><span class="required-field">*</span>
<%= b.text_field :state, class: 'checkout-addr-text-field' %>
</div>
</div>
<div class="col-xs-6 pad-top-15">
<%= b.label :zip_code %><span class="required-field">*</span>
<%= b.text_field :zip_code, class: 'checkout-addr-text-field required', :data => {:hj_whitelist => ''} %>
</div>
<% state = state + 1 %>
</div>
<% end %>
<% end %>
<% end %>
<div class="col-xs-12 pad-top-15">
<button id="update-checkout-addresses-btn" class="checkout-addresses-submit-btn btn btn-primary btn-sm" onclick="submitCheckoutAddressesForm(this);" type="button">Update Addresses</button>
</div>
创建新地址记录时,表单将在没有ID的情况下提交。编辑记录时会提交ID。不知何故,似乎表单被提交了不止一次,虽然我确实有javascript在提交时禁用表单。我应该如何在控制器中处理这个以防止发生此错误?或者修改我的观点?
更新
这是我的JS
function submitCheckoutAddressesForm(element){
element.disabled = true;
var e = document.getElementById("same-as-billing")
sameAsBillingCheckout(e);
$(e).closest("form").submit();
}
如果选中“发货地址与开票相同”复选框,则下面的功能可确保表格中的所有元素都相同。我必须克隆元素并将其复制到送货地址的原因是因为元素类型能够由于其他javascript而更改。我认为这是处理此事件最安全的方法,只是为了确保在表单中提交了正确的元素。我没有看到这会影响正在创建的重复记录。
function sameAsBillingCheckout(element){
if(element.checked == true){
var billing = ["#cart_cart_addresses_attributes_0_first_name", "#cart_cart_addresses_attributes_0_last_name", "#cart_cart_addresses_attributes_0_email", "#cart_cart_addresses_attributes_0_telephone",
"#cart_cart_addresses_attributes_0_address_line_one", "#cart_cart_addresses_attributes_0_address_line_two", "#cart_cart_addresses_attributes_0_country", "#cart_cart_addresses_attributes_0_state", "#cart_cart_addresses_attributes_0_city", "#cart_cart_addresses_attributes_0_zip_code"]
var shipping = ["#cart_cart_addresses_attributes_1_first_name", "#cart_cart_addresses_attributes_1_last_name", "#cart_cart_addresses_attributes_1_email", "#cart_cart_addresses_attributes_1_telephone",
"#cart_cart_addresses_attributes_1_address_line_one", "#cart_cart_addresses_attributes_1_address_line_two", "#cart_cart_addresses_attributes_1_country", "#cart_cart_addresses_attributes_1_state", "#cart_cart_addresses_attributes_1_city", "#cart_cart_addresses_attributes_1_zip_code"]
for(i = 0; i < billing.length; i++){
$(shipping[i]).val($(billing[i]).val());
if(billing[i] == "#cart_cart_addresses_attributes_0_country"){
$("#cart_cart_addresses_attributes_1_state").remove();
var e = document.getElementById("cart_cart_addresses_attributes_0_state");
var cln = e.cloneNode(true);
cln.name = "cart[cart_addresses_attributes][1][state]";
cln.id = "cart_cart_addresses_attributes_1_state";
var stateDiv = document.getElementById("state_1");
stateDiv.appendChild(cln);
}
}
}
}
下面的代码与上面的javascript相同。如果表单未被禁用,它只是在用户完成输入后提交表单。
$(function(){
//create one instance for handler:
var myHandler = function(e){
var sAB = document.getElementById("same-as-billing").checked;
var ready = true;
//LOOPS THROUGH ALL THE TEXT FIELDS
$('.checkout-addr-text-field').each(function (){
//IF SAME AS BILLING CHECKBOX SELECTED
if(sAB == true){
//if the field is disabled or the field includes _0_ (means its a billing address field) and the id is not the optional field and the value isn't blank then dont submit the form.
if(this.disabled || this.id.indexOf("_0_") !== -1 && this.id !=
"cart_cart_addresses_attributes_0_address_line_two" && this.value == ""){
ready = false;
}
}else{ // IF SAME AS BILLING CHECKBOX NOT SELECTED
if(this.disabled || this.id !=
"cart_cart_addresses_attributes_0_address_line_two" && this.id !=
"cart_cart_addresses_attributes_1_address_line_two" && this.value == ""){
ready = false;
}
}
});
if(ready == true){
var e = document.getElementById("same-as-billing")
sameAsBillingCheckout(e);
$(e).closest("form").submit();
$("#cart-addresses :input").attr("disabled", true);
}
};
$(document).on('keyup', '.checkout-addr-text-field', debounce(function(e){
myHandler(e);
}, 1700));
$(document).on('change', '#cart_cart_addresses_attributes_0_state', function(e){
myHandler(e);
});
$(document).on('change', '#cart_cart_addresses_attributes_1_state', function(e){
myHandler(e);
});
$(document).on('change', '#cart_cart_addresses_attributes_0_state', function(e){
myHandler(e);
});
$(document).on('click', '#same-as-billing', function(e){
if(this.checked){
myHandler(e);
}
});
});