How I can delete key and value in array of hash? - ruby-on-rails

I need delete all key created_at and updated_at in array of hashs . My hash looks like :
assessment_with_desc = {
"id": 1,
"name": "First assessment",
"created_at": "2020-03-14T20:13:27.006Z",
"updated_at": "2020-03-14T20:13:27.006Z",
"description_with_child_models": [
{
"id": 3,
"title": "First category",
"created_at": "2020-02-20T15:32:46.379Z",
"updated_at": "2020-03-14T20:16:11.530Z",
"accessment_id": 1,
"sub_categories": [
{
"id": 1,
"title": "First sub_category",
"category_id": 3,
"created_at": "2020-02-20T15:40:49.793Z",
"updated_at": "2020-02-20T15:40:49.793Z",
"stages": [
{
"id": 5,
"title": "First stage",
"sub_category_id": 1,
"created_at": "2020-02-20T15:44:10.603Z",
"updated_at": "2020-02-20T15:44:10.603Z"
}
]
}
]
}
]
}
I did it, but it work only in this case assessment_with_desc.delete('created_at') and assessment_with_desc.delete('updated_at'):
assessment_with_desc.delete('created_at')
assessment_with_desc.delete('updated_at')
assessment_with_desc['description_with_child_models'].delete('created_at')
assessment_with_desc['description_with_child_models'].delete('updated_at')
assessment_with_desc['description_with_child_models'][0]['sub_categories'].delete('created_at')
assessment_with_desc['description_with_child_models'][0]['sub_categories'].delete('updated_at')
assessment_with_desc['description_with_child_models'][0]['sub_categories'][0]['stages'].delete('created_at')
assessment_with_desc['description_with_child_models'][0]['sub_categories'][0]['stages'].delete('updated_at')

You can recursively invoke a function to remove the key/value in case the key is equal to :created_at or :updated_at:
def recursively_delete_timestamps(object)
object.transform_values do |value|
next value unless value.is_a?(Array)
value.map do |inner_hash|
recursively_delete_timestamps(inner_hash)
end
end.reject do |key, _|
key.in?(%i[created_at updated_at])
end
end
recursively_delete_timestamps(assessment_with_desc)
# {:id=>1,
# :name=>"First assessment",
# :description_with_child_models=>
# [{:id=>3,
# :title=>"First category",
# :accessment_id=>1,
# :sub_categories=>
# [{:id=>1,
# :title=>"First sub_category",
# :category_id=>3,
# :stages=>[{:id=>5, :title=>"First stage", :sub_category_id=>1}]}]}]}
Notice, the hash remains unchanged. Nor transform_values, map or reject modify the original object, but return a new one.

Related

How to implement Rails POST API to send data to external API based on body request

I have been trying to integrate an API on a rails app.
It is structured in such a way that I have to write my own POST API to communicate back and forth with it.
For example: POST dividebuy/api/getorderdetails
Example Request:
{
"orderId":"6667",
"retailerStoreCode":"default",
"storeAuthentication":"5LIH1TaW8ewd",
"storeToken":"3aa7Sgt76sz7"
}
Example Response:
{
"order_detail": {
"store_order_id": "64",
"store_order_increment_id": "145006485",
"store_token": ENV['DIVIDEBUY_TOKEN'],
"store_authentication": ENV['DIVIDEBUY_AUTHENTICATION'],
"logo_url": "https://moduleinstalledmagento1.dbuytest.info/media/dividebuy/",
"grand_total": 318.4,
"subtotal": 265.33,
"subtotalInclVat": 318.4,
"discount": 0,
"discountApplied": "beoforeVat",
"shipping": 0,
"shippingInclVat": 0,
"shipping_label": "Free Shipping - Free",
"shipping_method": "freeshipping_freeshipping",
"is_default_shipping": 0,
"is_default_billing": 0,
"vat": 53.07
},
"product_details": [
{
"name": "Some product name",
"sku": "SKU",
"qty": "1.0000",
"price": "249.1700",
"priceInclVat": "299.0000",
"rowTotal": "249.1700",
"rowTotalInclVat": "299.0000",
"discount": "0.0000",
"short_description": "Some Product",
"product_type": "simple",
"product_weight": "35.5000",
"product_visibility": "4",
"DivVat": "20",
"image_url": "some url.jpg"
}
],
"shipping_address": {
"first_name": "name",
"last_name": "name",
"email": "retailer#dividebuy.co.uk",
"street": [
"Address 1",
"Adress 2"
],
"postcode": "DE4 3ED",
"region": "County",
"city": "town"
},
"billing_address": {
"first_name": "name",
"last_name": "name",
"email": "retailer#dividebuy.co.uk",
"street": [
"Address 1", "Adress 2"
],
"postcode": "ST15 8YR",
"region": "County",
"city": "town"
}
}
so the controller I have is structured like this:
class Dividebuy::Api::GetorderdetailsController < ApplicationController
protect_from_forgery with: :null_session
skip_before_action :verify_authenticity_token, only: :create
# GET /product
def index
product = Spree::Product.all
render json: product, status: 200
end
# POST /product
def create
data = {
"order_detail": {
"store_order_id": "64",
"store_order_increment_id": "145006485",
"store_token": ENV['DIVIDEBUY_TOKEN'],
"store_authentication": ENV['DIVIDEBUY_AUTHENTICATION'],
"logo_url": "https://moduleinstalledmagento1.dbuytest.info/media/dividebuy/",
"grand_total": 318.4,
"subtotal": 265.33,
"subtotalInclVat": 318.4,
"discount": 0,
"discountApplied": "beoforeVat",
"shipping": 0,
"shippingInclVat": 0,
"shipping_label": "Free Shipping - Free",
"shipping_method": "freeshipping_freeshipping",
"is_default_shipping": 0,
"is_default_billing": 0,
"vat": 53.07
},
"product_details": [
{
"name": "Some product name",
"sku": "SKU",
"qty": "1.0000",
"price": "249.1700",
"priceInclVat": "299.0000",
"rowTotal": "249.1700",
"rowTotalInclVat": "299.0000",
"discount": "0.0000",
"short_description": "Some Product",
"product_type": "simple",
"product_weight": "35.5000",
"product_visibility": "4",
"DivVat": "20",
"image_url": "some url.jpg"
}
],
"shipping_address": {
"first_name": "name",
"last_name": "name",
"email": "retailer#dividebuy.co.uk",
"street": [
"Address 1",
"Adress 2"
],
"postcode": "DE4 3ED",
"region": "County",
"city": "town"
},
"billing_address": {
"first_name": "name",
"last_name": "name",
"email": "retailer#dividebuy.co.uk",
"street": [
"Address 1", "Adress 2"
],
"postcode": "ST15 8YR",
"region": "County",
"city": "town"
}
}
user = User.where(email: 'test#example.com')
if user.valid_password?(params[:password])
render json: data, status: :created
else
render json: { error: "error:" }, status: 400
end
end
end
Here is what I have on my routes for this API:
namespace :dividebuy, defaults: { format: 'json' } do
namespace :api do
resources :getorderdetails, only: [:index, :create]
end
end
My question is basically from the examples given in the documentation of the request & response as shown above, what is the best way to integrate with this API communication on my rails app?
I just hardcoded some data and passed it as a response, probably there is a better way to do that.
More clarity, to test I use postman:
localhost:3000/dividebuy/api/getorderdetails
Any help or guidance will be much appreciated.
Since I just did something similar myself, I can tell you my approach, which follows my guiding principle of "fat model skinny controller".
All the api-related structures that you show in the GetOrderDetailsController should be moved into a service class. Since I see the word Spree in your question, I'll call it SpreeService. So the first step at refactoring is to change your GetOrderDetailsController:
# app/controllers/dividebuy/api/get_order_details_controller.rb
def create
spree = SpreeService.new(params)
response = spree.create_order
if response.errors
# render the error page
else
# render the success page, with order number etc
end
end
This gives the general idea, specifics depend on your app.
In the SpreeService class, you can further decompose the request into smaller methods. As you app develops, you will find more and more functionality to delegate to the SpreeService class.
# app/domain_models/spree_service.rb
require 'net/http'
class SpreeService
BaseUrl = "api.spree.com"
def initialize(params)
# capture the params of interest
end
def create_order
response = Net::HTTP.request(...)
end
private
def create_order_query
# define the hash of all the date to make new order
end
end

Can't sort hash tried few different ways

I want to sort hash by position, I am using sort_by but it is not sorting out, as it should
hash = {
"a": {"name": "a", "type": "text", "position": 1, "required": "false"},
"b": {"name": "b", "type": "text", "position": 4, "required": "false"},
"c": {"name": "c", "type": "text", "position": 2, "required": "false"},
"d": {"name": "d", "type": "text", "position": 3, "required": "false"}
}
to sort this I am using following command
temp = hash.sort_by { |k,v| k[0]['position'] }
There is no error but I am getting save above hash without any sorting. Even I am using temp to create new hash and but it is same. where I want to it should be sorted by position 1,2,3,4. It is part of Ruby on Rails where I am creating these fields.
sort_by is called with two arguments, k and v which refer to the entry's key and value.
Since you want to sort by position, you have to use v[:position]:
hash.sort_by { |k, v| v[:position] }
#=> [[:a, {:name=>"a", :type=>"text", :position=>1, :required=>"false"}],
# [:c, {:name=>"c", :type=>"text", :position=>2, :required=>"false"}],
# [:d, {:name=>"d", :type=>"text", :position=>3, :required=>"false"}],
# [:b, {:name=>"b", :type=>"text", :position=>4, :required=>"false"}]]

Converting a hash-inside-array to single key multiple value hash in ruby

I am trying to load data from redis db. I have a api only rails app and trying to render the json data as per requirement.
Currently I am able to get the data from redis in the following format.
[
{
"id": 1,
"name": "Stephenie Meyer",
"created_at": "2018-04-17T07:40:50.417Z",
"updated_at": "2018-04-17T07:40:50.417Z"
},
{
"id": 2,
"name": "V.C. Andrews",
"created_at": "2018-04-17T07:40:50.613Z",
"updated_at": "2018-04-17T07:40:50.613Z"
},
{
"id": 3,
"name": "Sophie Kinsella",
"created_at": "2018-04-17T07:40:50.646Z",
"updated_at": "2018-04-17T07:40:50.646Z"
}
]
How can convert this in a way such that the key value pairs of name,created and updated will be hash to a id key-value pair.
Into this
{"id": 1,
{
"name": "Stephenie Meyer",
"created_at": "2018-04-17T07:40:50.417Z",
"updated_at": "2018-04-17T07:40:50.417Z"
}
}
helper method for getting redis data.
def fetch_authors
authors = $redis.get('authors')
if authors.nil?
authors = Author.all.to_json
$redis.set("authors", authors).to_json
$redis.expire("authors", 5.hour.to_i)
end
JSON.load authors
end
And displaying on index page using
def index
#authors = fetch_authors
render json: #authors
end
The closest to what you want would probably be:
input = ...
input.map { |hash| [hash.delete(:id) || hash.delete('id'), hash] }.to_h
#⇒ {{1=>{:name=>...},
# {2=>{:name=>...},
# {3=>{:name=>...}}
Not exactly what you want because that's not correct syntax but you can achieve something similar with group_by
arr = [
{
"id": 1,
"name": "Stephenie Meyer",
"created_at": "2018-04-17T07:40:50.417Z",
"updated_at": "2018-04-17T07:40:50.417Z"
},
{
"id": 2,
"name": "V.C. Andrews",
"created_at": "2018-04-17T07:40:50.613Z",
"updated_at": "2018-04-17T07:40:50.613Z"
},
{
"id": 3,
"name": "Sophie Kinsella",
"created_at": "2018-04-17T07:40:50.646Z",
"updated_at": "2018-04-17T07:40:50.646Z"
}
]
arr.group_by { |e| e[:id] }
This will return
{
1 => [
{
:id => 1,
:name => "Stephenie Meyer",
:created_at => "2018-04-17T07:40:50.417Z",
:updated_at => "2018-04-17T07:40:50.417Z"
}
],
2 => [
{
:id => 2,
:name => "V.C. Andrews",
:created_at => "2018-04-17T07:40:50.613Z",
:updated_at => "2018-04-17T07:40:50.613Z"
}
],
3 => [
{
:id => 3,
:name => "Sophie Kinsella",
:created_at => "2018-04-17T07:40:50.646Z",
:updated_at => "2018-04-17T07:40:50.646Z"
}
]
}

Reverse a nested array in Ruby

I'm attempting to take a JSON API response, with nested associated resources, and reverse the associations in a Rails app.
So, imagine I get a response like this:
{
"spenders": [
{
"id": 1,
"name": "John Doe",
"accounts": [
{
"id": 1,
"name": "Account One"
},
{
"id": 2,
"name": "Account Two"
}
]
},
{
"id": 2,
"name": "Jane Doe",
"accounts": [
{
"id": 1,
"name": "Account One"
},
{
"id": 3,
"name": "Account Three"
}
]
}
]
}
My goal is to convert this into structure like this:
{
"accounts": [
{
"id": 1,
"name": "Account One",
"spenders": [
{
"id": 1,
"name": "Stephen Margheim"
},
{
"id": 2,
"name": "Greg Barendt"
}
]
},
{
"id": 2,
"name": "Account Two",
"spenders": [
{
"id": 1,
"name": "Stephen Margheim"
}
]
},
{
"id": 3,
"name": "Account Three",
"spenders": [
{
"id": 2,
"name": "Greg Barendt"
}
]
}
]
}
Now, I can do this fairly well with iteration over the hash and building a new hash:
spenders_hash = {}
accounts.each do |account|
account.spenders.each do |spender|
if spenders_hash.key? spender.id
spenders_hash[spender.id][:accounts] << account
else
spenders_hash[spender.id] = hash_from_spender_and_account(spender, account)
end
end
end
spenders_hash
def hash_from_spender_and_account(spender, account)
{
id: spender.id,
name: spender.name,
accounts: [account],
}
end
I'm hoping to find [1] a more flexible solution that isn't reliant on knowing the key names in advance and [2] hopefully more efficient.
Thoughts?

Ruby array construction

I have the following json,
{
"items": [
{
"name": "table",
"item_group": [
{
"code": "code1",
"section": [
{
"unit": "centimeter",
"values": [
{
"display": "151.13 centimeter"
}
]
},
{
"unit": "centimeter (qualifier value)",
"values": [
{
"display": "170.39 centimeter"
}
]
}
],
"total": 2
}
],
"more_results": false,
"total": 1
}
]
}
How do I iterate through this and create an array of "values". I want something like [151.13 centimeter, 170.39 centimeter,....]. Please show some direction in this. thanks.
Note: I have to do this in Haml.
I know there are multiple ways of doing this. But if you really have many complex JSON queries, you can try ruby-jq gem. It is very fast, as it uses linux pipes
require 'jq'
require 'jq/extend'
json_content = {
"items": [
{
"name": "table",
"item_group": [
{
"code": "code1",
"section": [
{
"unit": "centimeter",
"values": [
{
"display": "151.13 centimeter"
}
]
},
{
"unit": "centimeter (qualifier value)",
"values": [
{
"display": "170.39 centimeter"
}
]
}
],
"total": 2
}
],
"more_results": false,
"total": 1
}
]
}
jq_filter = ' .items | .[].item_group |.[].section| . [ ] .values | . [ ] .display'
final_array = json_content.jq(jq_filter)
# => final_array = ["151.13 centimeter", "170.39 centimeter"]
Assuming you know the structure of the underlying hash, you can do this:
Code
require 'json'
JSON.parse(json_str)['items'].each_with_object([]) do |g,arr|
g['item_group'].each do |gg|
gg['section'].each do |ggg|
ggg['values'].each { |gggg|arr << gggg['display'][/\d+\.\d+/].to_f }
end
end
end
#=> [151.13, 170.39]
Explanation
The steps are as follows:
a = JSON.parse(json_str)
b = a['items']
enum = b.each_with_object([])
#=> #<Enumerator: [{"name"=>"table",..."total"=>1}]:each_with_object([])>
We can see the elements of the enumerator by converting it to an array:
enum.to_a
#=> [[{"name"=>"table", "item_group"=>[{"code"=>"code1",
# "section"=>[{"unit"=>"centimeter",
# "values"=>[{"display"=>"151.13 centimeter"}]},
# { "unit"=>"centimeter (qualifier value)",
# "values"=>[{"display"=>"170.39 centimeter"}]}], "total"=>2}],
# "more_results"=>false,
# "total"=>1},
# []]]
Notice that this array has a single element, a two-element array containing a hash and an empty array.
The first element of enum is passed to the block and assigned to the block variables using parallel assignment:
g, arr = enum.next
#=> {"name"=>"table",
# "item_group"=>[
# {"code"=>"code1",
# "section"=>[
# {"unit"=>"centimeter",
# "values"=>[
# {"display"=>"151.13 centimeter"}
# ]
# },
# {"unit"=>"centimeter (qualifier value)",
# "values"=>[
# {"display"=>"170.39 centimeter"}
# ]
# }
# ],
# "total"=>2}],
# "more_results"=>false,
# "total"=>1}
arr #=> []
c = g['item_group']
#=> [{"code"=>"code1",
# "section"=>[
# {"unit"=>"centimeter",
# "values"=>[
# {"display"=>"151.13 centimeter"}
# ]
# },
# {"unit"=>"centimeter (qualifier value)",
# "values"=>[
# {"display"=>"170.39 centimeter"}
# ]
# }
# ],
# "total"=>2}]
Note c has the form [hash].
The first (and only) element of c is passed to its block and assigned to its block variable:
gg = c.first
d = gg['section']
#=> [{"unit"=>"centimeter",
"values"=>[
{"display"=>"151.13 centimeter"}
]
},
{"unit"=>"centimeter (qualifier value)",
"values"=>[
{"display"=>"170.39 centimeter"}
]
}
]
ggg = d.first
#=> {"unit"=>"centimeter",
# "values"=>[
# {"display"=>"151.13 centimeter"}
# ]
# }
e = ggg['values']
#=> [{"display"=>"151.13 centimeter"}]
gggg = e.first
#=> {"display"=>"151.13 centimeter"}
f = gggg['display']
#=> "151.13 centimeter"
g = f[/\d+\.\d+/]
#=> "151.13"
i = g.to_f
#=> 151.13
arr << i
#=> [151.13]
arr
#=> [151.13]
The remaining calculations are similar.
Alternative expression
If, as in the example, the arrays JSON.parse(json_str)['items'], g['item_group'] and ggg['values'] each contain a single element (a hash), you could instead write:
JSON.parse(json_str)['items'].first['item_group'].first['section'].
each_with_object([]) do |g, arr|
arr << g['values'].first['display'][/\d+\.\d+/].to_f
end
#=> [151.13, 170.39]
though I doubt that would be significantly more efficient.

Resources