繁体   English   中英

获取数据并将其发布到外部 API Rails

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM