Hi我有一个表Package,我通过表单并使用params填充它。此表有一个FK到另一个表Location,该表保存包的纬度、lng和地址。"位置"表使用GeoKit。
我的表单有包的字段和一个允许用户输入位置名称的字段。谷歌地图帮助用户通过自动完成来填写详细信息,并将结果保存为隐藏字段中的json。
我正在尝试使用强参数作为
private
def package_params
params.require(:package).permit( :state, :delivery_date, :length, :height, :width, :weight, destination: [:id, :address, :lat, :lng], origin: [:id, :address, :lat, :lng] )
end
我也试过
private
def package_params
params.require(:package).permit( :state, :delivery_date, :length, :height, :width, :weight, destination_attributes: [:id, :address, :lat, :lng], origin_attributes: [:id, :address, :lat, :lng] )
end
但起源&destination属性不再在params的包对象中传递。
包装型号为
class Package < ActiveRecord::Base
belongs_to :user
has_many :bids, dependent: :destroy
belongs_to :origin, :class_name => 'Location', :foreign_key => 'origin'
belongs_to :destination, :class_name => 'Location', :foreign_key => 'destination'
has_many :locations, autosave: true
accepts_nested_attributes_for :origin, :destination
....
end
位置模型是
class Location < ActiveRecord::Base
acts_as_mappable
validates :address, presence: true
validates :lat, presence: true
validates :lng, presence: true
end
创建方法是
def create
@package = current_user.packages.build(package_params)
if @package.save
......
end
package.save失败。这就是我收到的错误。
PackagesController#create中的ActiveRecord::AssociationTypeMismatch预期位置(#70350522152300),得到字符串(#703505 07797560)
我能想出几个解决办法,但我想让它发挥作用,所以我从中学习。我试着阅读rails api并在谷歌上搜索了几天,但我没能让它发挥太大作用。
岗位数据为
Parameters: {
"utf8"=>"✓",
"authenticity_token"=>"ZYkfpQBu6fvX7ZmzRw2bjkU+3i6mH0M7JLeqG4b99WI=",
"origin_input"=>"Kimmage, Dublin, Ireland",
"package"=>{
"origin"=>"{
"address":"Kimmage, Dublin, Ireland",
"lat":53.32064159999999,
"lng":-6.298185999999987}",
"destination"=>"{
"address":"Lucan, Ireland",
"lat":53.3572085,
"lng":-6.449848800000041}",
"length"=>"22",
"width"=>"222",
"height"=>"22",
"weight"=>"0 -> 5",
"delivery_date"=>"2014-10-31"},
"destination_input"=>"Lucan, Ireland",
"commit"=>"Post"}
我知道来源和目的地没有被反序列化,但我不知道为什么它们没有。我必须手动反序列化字符串吗?我可以在package_params中这样做吗?
创建这个的表单如下
<%= form_for(@package, :html => {:class => "form-horizontal", :role => 'form'}) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="form-group">
<input type="text" name="origin_input" placeholder="From" onFocus="geolocate(); autocompleteLocation(this,package_origin)" class="form-control" />
<%= f.hidden_field :origin, class: "form-control" %>
</div>
<div class="form-group">
<input type="text" name="destination_input" placeholder="Destination" onFocus="geolocate(); autocompleteLocation(this,package_destination)" class="form-control" />
<%= f.hidden_field :destination, class: "form-control" %>
</div>
<div class="form-inline form-group">
<div class="input-group col-md-3">
<%= f.text_field :length, placeholder: "L", class: "form-control" %>
<span class="input-group-addon">cm</span>
</div>
<div class="input-group col-md-3">
<%= f.text_field :width, placeholder: "W", class: "form-control" %>
<span class="input-group-addon">cm</span>
</div>
<div class="input-group col-md-3">
<%= f.text_field :height, placeholder: "H", class: "form-control" %>
<span class="input-group-addon">cm</span>
</div>
</div>
<div class="form-group input-group">
<p>Please select the weight range of your package, Weights are in kg</p>
<% options = options_from_collection_for_select(@weights, 'weight', 'weight') %>
<%= f.select :weight, options, class: "form-control dropdown" %>
</div>
<div class="form-group">
<%= f.date_field :delivery_date, class: "form-control" %>
</div>
<%= f.submit "Post", class: "btn btn-large btn-primary", id: "package_post" %>
<% end %>
<%= render 'shared/places_autocomplete' %>
问题
您接收AssociationTypeMismatch
的错误是由于将origin:
和destination:
放入strong_params中造成的。Rails认为您正在尝试像@post.comment = @comment
那样关联对象。
即使有适当的序列化¶ms的反序列化这种方法不起作用。Rails看到您当前正在尝试的strong_params如下:
# Not deserialized
@package.origin = '{ "address":"Kimmage, Dulbin, Ireland", ... }'
# Deserialized. However, this still won't work.
@package.origin = { address: "Kimmage, Dublin, Ireland", ...}
Rails在这两种情况下都需要一个对象。您可以通过使用正确反序列化的案例进入控制台来测试这一点:
$ rails c
irb(main): p = Package.new
irb(main): p.destination = { address: "Kimmage, Dublin, Ireland" } # => Throws ActiveRecord::AssociationTypeMismatch.
那么,为什么它不起作用呢?因为Rails不是将它传递给一个实际的对象,而是将您传递的内容解释为字符串或散列。为了通过strong_params关联对象,Rails查找并使用accepts_nested_attributes
方法(您已经尝试过了)。然而,正如下面所解释的,这对你来说是行不通的。
这里的问题是您试图关联数据的方式。使用接受嵌套属性是通过父对象关联和保存子对象。在您的案例中,您正试图使用accepts_nested_attributes_for方法通过子对象(包)关联并保存两个父对象(原点和目的地)。Rails不会以这种方式工作。
文档的第一行(强调矿):
嵌套属性允许您通过父级保存关联记录上的属性。
在您的代码中,您试图通过子项关联并保存/更新它。
解决方案
解决方案1
您需要的是表单中的origin_id
和location_id
,将accepts_nested_attributes
从您的模型中排除,因为您不需要它,然后使用ID保存您的包:
params.require(:package).permit(:width, :length, :height, :whatever_else, :origin_id, :location_id)
然后,在提交表单之前使用AJAX请求,将这两个位置的origin_id
和destination_id
插入到隐藏字段中。如果这些位置还不存在,您可以使用find_or_create_by方法在检索时创建这些位置。
解决方案2
- 在控制器中的before_action中查找或创建父资源
@destination & @origin
- 将
@origin
和@destination
关联到@package
您不需要接受t_nested_attributes_for任何内容。您可以像往常一样保存程序包(确保修改package_params)。
class PackagesController < ApplicationController
before_action :set_origin, only: [:create]
before_action :set_destination, only: [:create]
def create
@package = current_user.packages.build(package_params)
@package.destination = @destination
@package.origin = @origin
if @package.save
# Do whatever you need
else
# Do whatever you need
end
end
private
# Create the package like you normally would
def package_params
params.require(:package).permit( :state, :delivery_date, :length, :height, :width, :weight)
end
def set_origin
# You can use Location.create if you don't need to find a previously stored origin
@origin = Location.find_or_create_by(
address: params[:package][:origin][:address],
lat: params[:package][:origin][:lat],
lng: params[:package][:origin][:lng],
)
end
def set_destination
# You can use Location.create if you don't need to find a previously stored destination
@destination = Location.find_or_create_by(
address: params[:package][:destination][:address],
lat: params[:package][:destination][:lat],
lng: params[:package][:destination][:lng],
)
end
end
为了确保你有一个有有效来源和目的地的包裹,然后在你的模型中验证:
class Package < ActiveRecord::Base
validates :origin, presence: true
validates :destination, presence: true
validates_associated :origin, :destination
end