Reputation: 27
I was success to get data from external API in rails,
# 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
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?
my response 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
}
}
Upvotes: 0
Views: 2439
Reputation: 102222
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. 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.
I would start by actually creating a model that represents a the data in your application:
# 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:
<%= 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?
method tells the helpers that we updating a model and that it should route to product_path(id)
and use PATCH.
But you also need to move the HTTP call out of the controller into a separate 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. 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.
Your controller then "talks" to the API only though this client:
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
Upvotes: 2