我定义了以下类:
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。
.
这也是测试中 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
但是为什么在 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