Rails嵌套属性不会在隐藏的表单输入中从JSON字符串创建对象



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那样关联对象。

即使有适当的序列化&params的反序列化这种方法不起作用。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_idlocation_id,将accepts_nested_attributes从您的模型中排除,因为您不需要它,然后使用ID保存您的包:

params.require(:package).permit(:width, :length, :height, :whatever_else, :origin_id, :location_id)

然后,在提交表单之前使用AJAX请求,将这两个位置的origin_iddestination_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

相关内容

  • 没有找到相关文章

最新更新