My project is a micro-services that connect two major services, my project fetches data from one server, format the data, and then use the data to generate an XML file, and then upload the XML to another service. I'm just wondering if there is any design pattern for this kind of micro-services.
this is the JSON received from the backend server:
{
"employee_id": 100464,
"organization_id": 93,
"start_date": "2018-09-05",
"first_name": "Tom",
"departments": [
{
"id": 2761,
"name": "Sale",
"organization_id": 93
},
{
"id": 2762,
"name": "Product",
"organization_id": 93
}
],
"primary_department": {
"id": 2761,
"name": "Product",
"organization_id": 93
}
}
This is the data format I want for, so I need to do some data formatting:
{
"employee_id": 100464,
"organization_id": 93,
"first_name": "Tom",
"target_department": {
"department_id": 2761,
"name": "Product",
"organization_id": 93,
"is_primary_department": true
}
}
the logic to determine the target_department is
departments = hsh.delete :departments
primary_department = hsh.delete :primary_department
hsh[:target_department] = departments.select do |department|
department[:id] ==another_obj[:group_id]
end.first
hsh[:target_department][:is_home_department] = (hsh[:target_department][:id] == primary_department[:id])
hsh[:target_department][:department_id] = hsh[:target_department].delete :id
As you can see, I need to remove, rename, and reformat attributes and the structure of the data.
During the process, there are many potential issues: attributes not existed?
My question is what's the best practice for dealing with this issue from programming designing perspective?
I am using Rails, so any good gem or project is dealing with a similar issue?
I don't know about best practices, but this ought to do what you want.
{
"employee_id": 100464,
"organization_id": 93,
"start_date": "2018-09-05",
"first_name": "Tom",
"departments": [
{
"id": 2761,
"name": "Sale",
"organization_id": 93
},
{
"id": 2762,
"name": "Product",
"organization_id": 93
}
],
"primary_department": {
"id": 2761,
"name": "Product",
"organization_id": 93
}
}.with_indifferent_access.except(:start_date, :departments).transform_keys do |k|
k == 'primary_department' ? 'target_department' : k
end.tap do |hsh|
if hsh['target_department']
hsh['target_department']['is_primary_department'] = true
hsh['target_department']['department_id'] = hsh['target_department']['id']
hsh['target_department'].delete('id')
end
puts hsh
end
In console, this will return:
{
"employee_id"=>100464,
"organization_id"=>93,
"first_name"=>"Tom",
"target_department"=>{
"name"=>"Product",
"organization_id"=>93,
"is_primary_department"=>true,
"department_id"=>2761
}
}
BTW, in your example output, you show the target_department name as "aProduct". Is that a typo?
Working directly with json/xml type data programmatically is often tedious. I once worked at a company that did this EVERYWHERE and it was painful. I would suggest deserializing your data into an object graph representation. Also have a result class that can be constructed by querying/processing your input object graph. Then just serialize your result object back to json at the end.
You can likely find a built-in or open source solution for handling the serialization and deserialization for you.
Related
I am trying to access a Netsuite Ruby Hash (Netsuite gem) and return orders which need updating from the db, the process uses two service objects, one to get the orders from db, (ListOrdersService), and the this file to compare those against modified ones in Netsuite. Is all working except I am having problems getting some of the nested values in Netsuite in to the output. Code is below with a troublesome item commented out. Its just an each method which compares dates then puts the needed orders in returned value.
def process_order_updates
get_order_updates = []
# Get all our open orders from DB
bj_open_orders = ListOrdersService.new.call
# Get all identical open orders from Netsuite
bj_open_orders.each do |item|
netsuite_sales_orders = NetSuite::Records::SalesOrder.get(item['sales_order_internal_id'])
# Compare the last modified date from Netsuite to the last checked date from app DB
if netsuite_sales_orders.present? && netsuite_sales_orders.last_modified_date > item['last_checked_date']
# If the last modified date is newer, then we create a new hash with the updated order info
get_order_updates << {
sales_order_internal_id: item['sales_order_internal_id'],
order_status: item['order_status']
# quantity_fulfilled: item['items_list']['item']['quantity_fulfilled']
}
puts "still open order #{item['sales_order_internal_id']} needs to be updated, it was last checked by at #{item['last_checked_date']} but it was just modified, on #{netsuite_sales_orders.last_modified_date}"
end
end
puts "Here are the orders that need to be updated: #{get_order_updates}"
end
The Netsuite file code I am referencing is below, and trying to get quantity_fulfilled, quantity_billed, and some others in the file. items_list is a top level item
"item_list": {
"list": [
{
"attributes": {
"item": {
"internal_id": "110",
"external_id": null,
"type": null,
"attributes": {
"name": "000002 Kerosene (UN1223) 3.PGIII (D/E)"
}
},
"expand_item_group": false,
"quantity": "1000.0",
"units": {
"internal_id": "1",
"external_id": null,
"type": null,
"attributes": {
"name": "ltr"
}
},
"description": "Kerosene (UN1223) 3.PGIII (D/E)",
"price": {
"internal_id": "-1",
"external_id": null,
"type": null,
"attributes": {}
},
"rate": "0.81",
"amount": "810.0",
"is_closed": false,
"gross_amt": "850.5",
"line": "1",
"cost_estimate_type": "_averageCost",
"cost_estimate": "900.79",
"quantity_back_ordered": "0.0",
"quantity_billed": "0.0",
"quantity_committed": "1000.0",
"quantity_fulfilled": "0.0",
"tax1_amt": "40.5",
"tax_code": {
"internal_id": "2214",
"external_id": null,
"type": null,
"attributes": {
"name": "VAT:RDR-5%"
}
},
Any tips on how to get those items, directly or with a hashmap welcome Thanks
I have some geojson grids that map through to some annualised sales data over a period of 25 years. I am really struggling to filter this sales data by year to show the trends in a chloropleth map.
d3 = require('d3-dsv');
map_json = FileAttachment("Time_line#2.geojson").json()
sales_data = FileAttachment("Timeline_test#1.csv").csv()
vegalite = require('#observablehq/vega-lite')
vegalite ({
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"width": 600,
"height": 350,
"data": {
"name": "mapdata",
"values": map_json,
"format": {"property": "features"},
},
"params": [{
"name" : "AnnualPeriod",
"value": 1995,
"bind" : {"input": "range", "min":1995, "max":2020,"step":1 }
}],
"transform" : [{
"lookup": "properties.id",
"from": {
"data": { "values": sales_data,},
"format":"csv",
"key": "derived_boundary_id",
"fields": ["sales_volume"],
},
},],
"layer": [
{
"mark": "geoshape",
"encoding": {
"color": {
"field": "sales_volume",
"type": "quantitative",
"scale": {"scheme": "Oranges"},
},
"stroke": { "value": "#ff75"},
},
},
]
})
I have tried to add transform.filter and cannot get it to work. At the moment it appears to be taking the first sales_data record for each of the boundary_ids.
I would like the data to be filtered according to the setting of the AnnualPeriod slider.
I think I need to include something like
"transform" :[{"filter": "datum.year == AnnualPeriod"}]
I have tried it in the transform section, with the lookup between the sales_data and the geojosn objects.
I have also tried to filter in and around the geoshape mark but neither work.
Does anyone have any ideas?
The is a sample of the sales_data:
sales_volume,year,derived_boundary_id
5,2015,602212
2,2016,602212
2,2019,602212
5,1995,602213
7,1996,602213
6,1997,602213
7,1998,602213
9,1999,602213
10,2000,602213
7,2001,602213
5,2002,602213
5,2003,602213
9,2004,602213
5,2005,602213
...
where the last column maps to an "id" in the geojson data.
and this is the 'map' that I get. Always the same, irrespective of the slider setting.
I have eventually worked out how to get this to work.
Create the main data source as the sales data and attach the maps/geojson to this via a transform/lookup.
It seems so simple now, but I thought I would post the result so others can see how it can be achieved.
These observations may help others:
The transform can take the "year" filter as well as the map_json lookup.
The map/json lookup is referenced as "geo" to make it easier to understand
The geoshape mark then references, via the encoding, this geojson "geo" object.
Thanks to Mike Bostock and the Observable and Vega-Lite team for their excellent work.
vegalite ({
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"width": 600,
"height": 600,
"data": {
"values": sales_data,
},
"params": [{
"name" : "AnnualPeriod",
"value": 1995,
"bind" : {"input": "range", "min":1995, "max":2020,"step":1 }
}],
"transform" : [
{"filter" : "year(datum.year) === AnnualPeriod"},
{
"lookup": "derived_boundary_id",
"from" : {
"data": {
"values": map_json,
"format": {"property": "features"},
},
"key":"properties.id",
},
"as":"geo",
}
],
"mark":"geoshape",
"encoding": {
"shape": {
"field":"geo",
"type":"geojson",
},
"color":{
"field":"sales_volume",
"type":"quantitative",
"scale": {
"scheme":"Oranges",
"domain": [0,15],
},
}
}
})
How's your progress? I am not familiar with Choropleths, but by comparing with the Vega Examples, I spot 2 differences you may wanna take a look:
Choropleth needs a projection
Map data should be placed in lookup transform, and sale data in normal data. Ref 1 Ref 2
Do feel free to correct me if I am wrong. If you need more help, please share an editor with dummy data because I found it hard to make good use of the sale data you provided :'(
I have a ruby (2.4.0p0) rails (5.0.2) controller from which I wish to return a json result containing a list of Thing objects as well as some high level info (such as next and previous from Kaminari paging).
Consider a Thing with an association to Owner. Thing has a owner_id attribute.
For #things = Thing.page(1).per(2) I will be able to use
render json: #things
and get;
[
{ "id": 1, "owner_id": 1, "name": "thing1" },
{ "id": 2, "owner_id": 1, "name": "thing2" }
]
Good. If I then create a serializer called ThingSerializer.rb and define owner such that it adds "owner":"CatInHat" instead of "owner_id":1
This works as well;
[
{ "id": 1, "owner": "CatInHat", "name": "thing1" },
{ "id": 2, "owner": "CatInHat", "name": "thing2" }
]
This is good, but, my problem comes when I want to add higher level data and label the list as "results" such as when I try;
render json: { next:"some_url_link",previous:"some_other_url_link", results: #bags}
I'd like to get;
{ "next":some_url_link,
"prev":some_other_url_link,
"results":[ { "id": 1, "owner": "CatInHat", "name": "thing1" }, { "id": 2, "owner": "CatInHat", "name": "thing2" } ]
}
What I get is nearly the above but with "owner_id":1 instead of "owner":"CatInHat" - my serializer does not seem to be used when I label and nest my list of things. What is the appropriate way to use my serializer and get this output?
If I create config/initializers/active_model_serializers.rb and add
ActiveModel::Serializer.config.adapter = :json_api
It gives me an api which is similar but I don't know if it can be customized to fit the spec I need above.
thank you for any help
It looks like the serialization logic in render json: ... only kicks in if the attribute is an ActiveRecord object or an array of ActiveRecord objects. Since you are giving it a hash, it will not inspect the individual attributes and recursively apply the serializers.
You can try manually applying ThingSerializer:
render json: {
next: ...,
prev: ...,
results: #things.map { |thing|
ThingSerializer.new(thing).attributes
},
}
I'm parsing some JSON from a mixed content source, and with it trying to store it with ActiveRecord.
At the moment I'm using a ton of variables:
json['settings']['newsletters']['weekly']
json['info']['address']['city']
Or trying to make things a little easier:
newsletters = json['settings']['newsletters']
newsletters['weekly']
address = json['info']['address']
address['city']
But this is all getting very messy, and not DRY.
I think the better way to do this would be to iterate over each element that is a hash (and therefore 'complex'), and assign it it's own object. This way, I don't have to declare a trillion variables, they can instead be assigned from the context of the JSON input.
So, I can do something like this:
user = json['input']
user.settings.newsletters.weekly
user.info.address.city
This is inspired by what ActiveResource documents:
# Any complex element (one that contains other elements) becomes its own object:
#
# {"id":1,"first":"Tyler","address":{"street":"Paper St.","state":"CA"}}
tyler = Person.find(1)
tyler.address # => <Person::Address::xxxxx>
tyler.address.street # => 'Paper St.'
Here is the JSON, reduced for brevity's sake:
{
"username": "robert_fitzsimmonds",
"emails": [{
"id_number": 1,
"address": "robert_fitzsimmonds#yahoo.com",
"confirmed": false
}, {
"id_number": 2,
"address": "robert_fitzsimmonds#gmail.com",
"confirmed": true
}],
"settings": {
"marketing": {
"main": true,
"weekly": false,
"daily": false
},
"language": "English"
},
"info": {
"address": {
"line_1": "31 Mole Road",
"line_2": "",
"city": "London",
"post_code": "NE4 5RJ"
},
"shared_account": false
}
}
Would such an iteration be the most efficient solution, or is it best to stick to long, messy variables?
Use the hash_dot gem if you can https://github.com/adsteel/hash_dot
I am thinking of writing a web application that crawls an API and returns this information in JSON form.
However, I am only after one number, then current price (in this sample, "227"). How can I access that in Ruby? I have no clue where to begin. I've never dealt with text like this.
For discussion's sake, suppose I save this output into instance variable #information
{
"item": {
"icon": "http://services.runescape.com/m=itemdb_rs/4332_obj_sprite.gif?id=4798",
"icon_large": "http://services.runescape.com/m=itemdb_rs/4332_obj_big.gif?id=4798",
"id": 4798,
"type": "Ammo",
"typeIcon": "http://www.runescape.com/img/categories/Ammo",
"name": "Adamant brutal",
"description": "Blunt adamantite arrow...ouch",
"current": {
"trend": "neutral",
"price": 227
},
"today": {
"trend": "neutral",
"price": 0
},
"day30": {
"trend": "positive",
"change": "+1.0%"
},
"day90": {
"trend": "positive",
"change": "+1.0%"
},
"day180": {
"trend": "positive",
"change": "+2.0%"
},
"members": "true"
}
}
First follow this post to parse this JSON in to Hash
Parsing a JSON string in Ruby
say the parsed hash name is my_hash then the following should give you price
my_hash['item']['current']['price']
Edit:
As you said you want to save it in #information
#information = my_hash['item']['current']['price']
Even you can use hashie it gives your json into readable structural code
Install Hashie
gem install hashie
then in your code all that json take in a variable my_json
myhash = Hashie::Mash.new(my_json)
#information = my_hash.item.current.price
Tips:-
if your json is dynamic and it may respond some other structural element so you can maintain exceptional code
#information = my_hash.item.try(:current).try(:price)