-
-
Save webuilder240/da94ab9661fbbf5b89f8ca11e094fcd7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require_relative 'errors' | |
module ApiModel | |
module Base | |
extend ActiveSupport::Concern | |
include ActiveModel::Model | |
include ActiveModel::Attributes | |
include ActiveModel::Serializers::JSON | |
included do | |
private | |
attr_accessor :response | |
end | |
class_methods do | |
def api_url | |
'http://localhost:3000' | |
end | |
def client | |
Faraday.new(url: api_url) do |faraday| | |
faraday.adapter Faraday.default_adapter | |
faraday.request :json | |
faraday.response :json, parser_options: { symbolize_names: true } | |
end | |
end | |
def raise_api_exception(response) | |
case response.status | |
when 404 | |
raise ApiNotFound.new(response), "Record not found" | |
when 422 | |
raise ApiInvalidError.new(response), "Invalid data" | |
when 400..499 | |
raise HttpClientError.new(response), "Client error" | |
when 500..599 | |
raise HttpServerError.new(response), "Server error" | |
else | |
raise ApiError.new(response), "Api Error" | |
end | |
end | |
def request(method, *args, &block) | |
response = client.public_send(method, *args) do |req| | |
yield req if block_given? | |
end | |
if response.success? | |
response | |
else | |
raise_api_exception(response) | |
end | |
end | |
end | |
def has_error? | |
errors.any? | |
end | |
def validate! | |
raise RecordInvalidError.new(self) unless valid? | |
end | |
private | |
def request(method, *args, &block) | |
@response = self.class.client.public_send(method, *args) do |req| | |
yield req if block_given? | |
end | |
if response.success? | |
true | |
else | |
handle_api_errors | |
end | |
end | |
def request!(method, *args, &block) | |
@response = self.class.client.public_send(method, *args) do |req| | |
yield req if block_given? | |
end | |
if response.success? | |
self | |
else | |
raise_api_exception | |
end | |
end | |
def raise_api_exception | |
case response.status | |
when 404 | |
raise ApiNotFound.new(response), "Record not found" | |
when 422 | |
add_api_errors | |
raise RecordInvalidError.new(self, response) | |
when 400..499 | |
raise HttpClientError.new(response), "Client error" | |
when 500..599 | |
raise HttpServerError.new(response), "Server error" | |
else | |
raise ApiError.new(response), "Api Error" | |
end | |
end | |
def handle_api_errors | |
raise_api_exception | |
rescue RecordInvalidError | |
false | |
end | |
def add_api_errors | |
return unless response.status == 422 | |
error_message = :unknown_error | |
# Get the custom error message if available, otherwise use the default message | |
custom_error_key = "activemodel.errors.models.#{self.class.name.underscore}.response.#{error_message}" | |
common_error_key = "activemodel.errors.api_record.response.#{error_message}" | |
error_message_text = I18n.t(custom_error_key, default: I18n.t(common_error_key)) | |
errors.add(:response, error_message_text) | |
error_response = response.body | |
if error_response.is_a?(Hash) | |
error_response.each do |key, error_details| | |
error_details.each do |message| | |
errors.add(key, message) | |
end | |
end | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module ApiModel | |
class RecordInvalidError < StandardError | |
attr_reader :record, :response | |
def initialize(record, response = nil) | |
@record = record | |
@response = response | |
message = "Validation failed: #{record.errors.full_messages.join(', ')}" | |
super(message) | |
end | |
end | |
class ApiError < StandardError | |
attr_reader :response | |
def initialize(response) | |
@response = response | |
end | |
end | |
class HttpClientError < ApiError; end | |
class HttpServerError < ApiError; end | |
class ApiNotFound < HttpClientError; end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Api | |
class Payment | |
include ApiModel::Base | |
attribute :user | |
attribute :card_number, :integer | |
validates :card_number, presence: true, numericality: { only_integer: true, greater_than: 0 } | |
validates :user, presence: true | |
def authorize | |
return false unless valid? | |
params = { | |
user_id: user.id, | |
card_number: card_number | |
} | |
request(:post, "/users/#{user.id}/payments", params) | |
end | |
def authorize! | |
validate! | |
params = { | |
user_id: user.id, | |
card_number: card_number | |
} | |
request!(:post, "/users/#{user.id}/payments", params) | |
end | |
end | |
end | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Api | |
class Product | |
include ApiRecord::Base | |
attribute :id, :integer | |
attribute :title, :string | |
attribute :description, :string | |
attribute :price, :integer | |
attribute :discount_percentage, :decimal | |
attribute :rating, :decimal | |
attribute :stock, :integer | |
attribute :brand, :string | |
attribute :category, :string | |
attribute :thumbnail, :string | |
attribute :images | |
validates :id, presence: true, numericality: { only_integer: true } | |
validates :title, presence: true, length: { maximum: 255 } | |
validates :description, presence: true | |
validates :price, presence: true, numericality: { greater_than_or_equal_to: 0 } | |
validates :discount_percentage, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 } | |
validates :rating, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 5 } | |
validates :stock, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 } | |
validates :brand, presence: true, length: { maximum: 255 } | |
validates :category, presence: true, length: { maximum: 255 } | |
validates :thumbnail, presence: true, format: { with: /\Ahttps?:\/\/.+\z/ } | |
def request_params(param) | |
param | |
end | |
def self.api_url | |
"https://dummyjson.com" | |
end | |
def self.index | |
response = request(:get, "/products", {}) | |
if response.success? | |
body = response.body['products'] | |
ApiRecord::Relation.new(body.map do |attributes| | |
new(attributes.deep_transform_keys!(&:underscore)) | |
end, response) | |
else | |
raise_api_exception(response) | |
end | |
end | |
def self.create(params) | |
record = new(params) | |
record.save | |
record | |
end | |
def self.create!(params) | |
record = new(params) | |
record.save! | |
record | |
end | |
def self.update!(params) | |
self.attributes = params | |
save! | |
end | |
def self.update(params) | |
self.attributes = params | |
save | |
end | |
def save | |
return false unless valid? | |
result = perform_save_request | |
if result | |
if response.body.present? | |
body = response.body.deep_transform_keys!(&:underscore) | |
self.attributes = body | |
end | |
end | |
result | |
end | |
def save! | |
validate! | |
perform_save_request! | |
if response.body.present? | |
body = response.body.deep_transform_keys!(&:underscore) | |
self.attributes = body | |
end | |
self | |
end | |
def destroy | |
request(:delete, "/products/#{id}") | |
if response.success? | |
true | |
else | |
handle_api_errors() | |
false | |
end | |
end | |
def destroy! | |
request(:delete, "/products/#{id}") | |
if response.success? | |
self | |
else | |
raise_api_exception() | |
end | |
end | |
private | |
def perform_save_request! | |
params = self.class.request_params(attributes) | |
params.delete("id") | |
if new_record? | |
request!(:post, "/products", params) | |
else | |
request!(:put, "/products/#{id}", params) | |
end | |
end | |
def perform_save_request | |
params = self.class.request_params(attributes) | |
params.delete("id") | |
if new_record? | |
request(:post, "/products", params) | |
else | |
request(:put, "/products/#{id}", params) | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ProductsController < ApplicationController | |
before_action :set_product, only: [:show, :update, :destroy] | |
def index | |
@products = Api::Product.index.data | |
end | |
def show | |
end | |
def create | |
@product = Api::Product.new(params) | |
if @product.save | |
render :show, status: 201 | |
else | |
render json: { @product.errors }, status: 422 | |
end | |
end | |
def update | |
if @product.update(params) | |
render :show | |
else | |
render json: { @product.errors }, status: 422 | |
end | |
end | |
def destroy | |
@product.destroy | |
head :no_content | |
end | |
private | |
def set_product | |
@product = Api::Product.find(params[:id]) | |
end | |
# Only allow a list of trusted parameters through. | |
def product_params | |
params.require(:product).permit(:title) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module ApiRecord | |
Relation = Struct.new(:data, :response) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment