简体   繁体   English

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

[英]Getting data and post it from and to External API Rails

I was success to get data from external API in 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

In view, when I want create update form I just do like this在视图中,当我想要创建更新表单时,我只是这样做

<%= link_to 'Edit', edit_product_path(@product_id) %>

but when I call it to _form.html.erb Im geing this error但是当我将它调用到 _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 %>

how I get data from external API and put it on form_for and update the data?我如何从外部 API 获取数据并将其放在 form_for 上并更新数据?

my response API我的回复 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
    }
}

The main problem here is that you're not creating an abstraction layer between your application and the outside collaborator.这里的主要问题是您没有在应用程序和外部协作者之间创建抽象层。 By performing a HTTP query directly from your controller and passing the results straight to the view you're making a strong coupling between your application and the API and any changes to the collaborator can break large portions of your application.通过直接从您的 controller 执行 HTTP 查询并将结果直接传递到视图,您在您的应用程序和 API 之间建立了强耦合,并且对协作者的任何更改都可能破坏您的应用程序的大部分。 This creates a fat controller and pushes all the complexity of dealing with the API responses down into the view which both are very bad things.这会创建一个胖 controller并将处理 API 响应的所有复杂性推到视图中,这两者都是非常糟糕的事情。

I would start by actually creating a model that represents a the data in your application :我将从实际创建一个 model 开始,它代表您应用程序中的数据:

# app/models/product.rb
class Product
  include ActiveModel::Model
  include ActiveModel::Attributes
  attribute :id
  attribute :name
  # ...

  def persisted?
    true
  end
end

Note that it doesn't inherit from ApplicationRecord - this is a model thats not backed by a database table and instead just uses the API's that rails provide to make it quack like a model - this means it will work right out the box with forms:请注意,它不是从 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 %>

The persisted? persisted? method tells the helpers that we updating a model and that it should route to product_path(id) and use PATCH.方法告诉助手我们更新了一个 model 并且它应该路由到product_path(id)并使用 PATCH。

But you also need to move the HTTP call out of the controller into a separate class:但是您还需要将 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

This isn't necissarily the only way or even the right way to write this class. The main point is just that you should not be burdoning your controller with more responsibilies.这不一定是编写此 class 的唯一方法,甚至不是正确的方法。重点是您不应该让 controller 承担更多责任。 It has tons of jobs already.它已经有大量的工作。

This http client is the only component that should be touching the application boundy and have knowledge of the API. It can be tested in isolation and stubbed out when needed.这个 http 客户端是唯一应该接触应用程序边界并了解 API 的组件。它可以被隔离测试并在需要时被删除。

Your controller then "talks" to the API only though this client:您的 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