我成功地从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
}
}
这里的主要问题是您没有在应用程序和外部合作者之间创建抽象层。通过直接从控制器执行HTTP查询并将结果直接传递到视图,您就在应用程序和API之间建立了强大的耦合,对协作器的任何更改都可能破坏应用程序的大部分。这创建了一个胖控制器,并将处理API响应的所有复杂性推到视图中,这两者都是非常糟糕的事情。
我将从实际创建一个模型开始,该模型表示应用程序中的数据:
# app/models/product.rb
class Product
include ActiveModel::Model
include ActiveModel::Attributes
attribute :id
attribute :name
# ...
def persisted?
true
end
end
请注意,它不是从ApplicationRecord继承的-这是一个没有数据库表支持的模型,而只是使用rails提供的API使其像模型一样嘎嘎叫-这意味着它将直接与表单一起工作:
<%= 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?
方法告诉助手我们正在更新模型,它应该路由到product_path(id)
并使用PATCH。
但是您还需要将HTTP调用从控制器移到一个单独的类中:
# 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
这不是写这个类的唯一方法,甚至不是正确的方法。主要的一点是,你不应该让你的控制器承担更多的责任。它已经有很多工作了。
这个http客户端是唯一一个应该灵活地接触应用程序并了解API的组件。它可以单独进行测试,并在需要时被淘汰。
然后你的控制器";会谈;仅通过此客户端发送到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