[英]Getting data and post it from and to External API Rails
我成功地从 rails 中的外部 API 获取数据,
# products_controller.rb
def load_detail_product
@response = HTTParty.get("http://localhost:3000/api/v1/products/#{params[:id]}",:headers =>{'Content-Type' => 'application/json'})
@detail_products = @response.parsed_response
@product = @detail_products['data']['product']
@product.each do |p|
@product_id = p['id']
end
end
在视图中,当我想要创建更新表单时,我只是这样做
<%= link_to 'Edit', edit_product_path(@product_id) %>
但是当我将它调用到 _form.html.erb 时我会遇到这个错误
undefined method `model_name' for #<Hash:0x00007ffb71715cb8>
# _form.html.erb
<%= form_for @product, authenticity_token: false do |form| %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
我如何从外部 API 获取数据并将其放在 form_for 上并更新数据?
我的回复 API
# localhost:3000/api/v1/products/1
{
"messages": "Product Loaded Successfully",
"is_success": true,
"data": {
"product": [
{
"id": 1,
"name": "Chair",
"price": "200000",
"created_at": "2022-03-22T09:24:40.796Z",
"updated_at": "2022-03-22T09:24:40.796Z"
}
]
}
}
# for update PUT localhost:3000/api/v1/products/1
{
"product":
{
"name":"Chair",
"price":20000
}
}
这里的主要问题是您没有在应用程序和外部协作者之间创建抽象层。 通过直接从您的 controller 执行 HTTP 查询并将结果直接传递到视图,您在您的应用程序和 API 之间建立了强耦合,并且对协作者的任何更改都可能破坏您的应用程序的大部分。 这会创建一个胖 controller并将处理 API 响应的所有复杂性推到视图中,这两者都是非常糟糕的事情。
我将从实际创建一个 model 开始,它代表您应用程序中的数据:
# app/models/product.rb
class Product
include ActiveModel::Model
include ActiveModel::Attributes
attribute :id
attribute :name
# ...
def persisted?
true
end
end
请注意,它不是从 ApplicationRecord 继承的——这是一个不受数据库表支持的 model,而只是使用 rails 提供的 API 使其像 model 一样——这意味着它可以直接与 forms 一起工作:
<%= form_with(model: @product) do |f| %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
persisted?
方法告诉助手我们更新了一个 model 并且它应该路由到product_path(id)
并使用 PATCH。
但是您还需要将 HTTP 调用从 controller 移到单独的 class 中:
# app/clients/products_client.rb
class ProductsClient
include HTTParty
base_url "http://localhost:3000/api/v1/products/"
format :json
attr_reader :response
# Get a product from the remote API
# GET /api/v1/products/:id
def show(id)
@response = self.class.get(id)
if @response.success?
@product = Product.new(product_params) # normalize the API data
else
nil # @todo handle 404 errors and other problems
end
end
# Send a PATCH request to update the product on the remote API
# PATCH /api/v1/products/:id
def update(product)
@response = self.class.patch(
product.id,
body: product.attributes
)
# @todo handle errors better
@response.success?
end
private
def product_params
@response['data']['product'].slice("id")
end
end
这不一定是编写此 class 的唯一方法,甚至不是正确的方法。重点是您不应该让 controller 承担更多责任。 它已经有大量的工作。
这个 http 客户端是唯一应该接触应用程序边界并了解 API 的组件。它可以被隔离测试并在需要时被删除。
您的 controller 然后仅通过此客户端与 API 进行“对话”:
class ProductsController < ApplicationController
before_action :set_product, only: [:edit, :update] # ...
# GET /products/:id/edit
def edit
end
# PATCH /products/:id
def update
@product.assign_attributes(product_params)
if @product.valid? && ProductsClient.new.update(product)
redirect_to "/somewhere"
else
render :edit
end
end
private
def set_product
@product = ProductsClient.new.get(params[:id])
# resuse the existing error handling
raise ActiveRecord::RecordNotFound unless @product
end
def product_params
params.require(:product)
.permit(:name) # ...
end
end
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.