在 Rspecs 测试中使用 HTTP 动词时错误解析浮点数数组

问题描述 投票:0回答:1

我定义了以下类:

shop.rb:

class Shop
    field: :reputation, Float
    embeds_one :location, class_name: "Location"
    accepts_nested_attributes_for :location
end

位置.rb:

class Location
  include Mongoid::Document

  field :address, type: String
  field :coordinates, type: Array
  field :place_id, type: String

  validate :coordinates_must_be_pair_of_float

  private

  def coordinates_must_be_pair_of_float
    unless coordinates.is_a?(Array) && coordinates.size == 2
      errors.add(:coordinates, "must be an array with exactly two elements")
      return
    end

    coordinates.each do |coord|
      unless coord.is_a?(Float)
        errors.add(:coordinates, "must contain only integers")
        return
      end
    end
  end
end

在shop_controller.rb中:

  def create
    shop = Shop.new(shop_params)

    if shop.save
      render json: { message: 'Shop created successfully', shop: shop }, status: :created
    else
      render json: { errors: shop.errors.full_messages }, status: :unprocessable_entity
    end
  end

  private

  def shop_params
    params.require(:shop).permit(
      :reputation,
      location_attributes: [:address, :place_id, coordinates: []],
    )
  end

最后,在

shop_spect.rb

  let(:location) { { address: "St. My Street", coordinates: [-100.0, 100.0], place_id: "12345" } }

  describe "POST /shop" do
    it "creates a new shop" do
      shop_data = {
        reputation: 800
        location_attributes: location,
      }

      post "/shop", params: { shop: shop_data }

      if response.status == 422
        errors = JSON.parse(response.body)["errors"]
        puts "Validation errors: #{errors.join(', ')}" # Display the error messages
      end

      expect(response).to have_http_status(201)

当我使用curl 发布帖子时,如下所示:

curl -X POST \                   
  -H "Content-Type: application/json" \
  -d '{
    "shop": {
      "reputation": 800,
      "location_attributes": {
        "address": "My Street",
        "coordinates": [-100.0, 100.0],
        "place_id": "12345"
      },
    }
  }' \
  "http://localhost:3000/shop"

一切正常,但测试失败,错误代码为

422
,即无法存储实例。一段时间后,我意识到了这个问题:坐标数组的处理方式与声誉的处理方式不同;坐标数组中包含的值的类型为:编码、UTF8。 enter image description here.

这也是测试中 params 的值:

{:shop=>{:price=>800, :location_attributes=>{:address=>"My Street", :coordinates=>[-100.0, 100.0], :place_id=>"12345"}}}

这是控制器中参数的值:

{"shop"=>{"reputation"=>"800", "location_attributes"=>{"address"=>"My Street", "coordinates"=>["-100.0", "100.0"], "place_id"=>"12345"} }, "price"=>"800"}, "controller"=>"advert", "action"=>"create"}

最后,这是我使用

curl
发出请求时控制器中参数的值:

{"shop"=>{"reputation"=>800, "location_attributes"=>{"address"=>"My Street", "coordinates"=>[-100.0, 100.0], "place_id"=>"12345"}}, "controller"=>"advert", "action"=>"create"}

显然标签被转换为字符串,但是为什么在

post
中使用
rspecs
时整数和浮点数也被转换为字符串?

因此,位置类中的验证未成功。为了解决这个问题,我必须将控制器修改为以下内容:

shop_controller.rb

  def create
    shop = Shop.new(shop_params)
    shop.location.coordinates.map!(&:to_f)

    if shop.save
      render json: { message: 'Shop created successfully', shop: shop }, status: :created
    else
      render json: { errors: shop.errors.full_messages }, status: :unprocessable_entity
    end
  end

  private

  def shop_params
    params.require(:shop).permit(
      :reputation,
      location_attributes: [:address, :place_id, coordinates: []],
    )
  end

我不明白为什么会发生这种情况。为什么解析器将数组的内容解释为编码的 UTF8 数据而不是浮点值,与信誉字段的处理方式相同?

还有,有没有办法定义

shop_params
?为什么以下定义无效:

  def shop_params
    params.require(:shop).permit(
      :reputation,
      location_attributes: [:address, :place_id, :coordinates],
    )
  end
ruby-on-rails ruby mongoid
1个回答
0
投票

但是为什么在 rspec 中使用 post 时整数和浮点数也会转换为字符串?

这与 RSpec 关系不大。

在您的规范中,您实际上并没有发送 JSON 请求,因为 POST 的默认值是

application/x-www-form-urlencoded
(在 Rails 中被视为 :html 格式)。

要发送 JSON,请使用:

post "/shop", params: { shop: shop_data }, format: :json

这实际上是由 RSpec 刚刚包装的底层 ActionDispatch::IntegrationTest 提供的帮助器。

HTTP 表单数据参数并未实际输入。它们只是字符串形式的键和值对。

Rack 会按照约定将键解析为哈希值和数组,但它不知道也不关心模型中的属性以及您期望参数的类型。

此外,您的控制器实际上并未将请求格式限制为 JSON,这让这个错误得以通过。我会使用 MimeResponds 来确保您得到异常。

class ShopsController < ApplicationController
  # ...
  def create
    respond_to :json
    shop = Shop.new(shop_params)
    shop.location.coordinates.map!(&:to_f)
    # this will raise if the client requests HTML
    respond_to :json do 
      if shop.save
        render json: { message: 'Shop created successfully', shop: shop }, status: :created
      else
        render json: { errors: shop.errors.full_messages }, status: :unprocessable_entity
      end
   end
  end
end

顺便说一句,使用数组类型非常难闻。我只会定义两个浮点类型字段,因为它不那么奇怪,并且可以免费为您提供类型转换。

class Location
  include Mongoid::Document

  field :address, type: String
  field :place_id, type: String
  field :latitude, type: Float
  field :longitude, type: Float

  validates :longitude, :latitude, presence: true,
                                   numericality: true

  # Sets the latitude and longitude from an array or list
  def coordinates=(*args)
    normalized = [*args].flatten
    self.latitude, self.longitude = *normalized
  end
end
© www.soinside.com 2019 - 2024. All rights reserved.