Spring Data MongoDB - Aggregation with other collection as well - spring

I'm working on Spring Boot v.2.1.3.RELEASE and Spring Data MongoDB. Due to critical requirement, I had to model like below assuming Employee knows multiple technologies, but primary language would be anyone.
So, I decided to keep technology collection separate and somehow related Employee and technology in the Employee Collection.
{
"_id" : ObjectId("5ec65750fdcd4e960f4b2f24"),
"technologyCd" : "AB",
"technologyName" : "My ABC",
"ltechnologyNativeName" : "XY",
"status" : "A"
}
So, I've done association like below -
Note: Multiple Technologies can be associated with one Employee
One Employee can be associated with multiple technologies
Employee can have one and only one Primary Technology
{
"_id" : ObjectId("5ec507c72d8c2136245d35ce"),
"firstName" : "John",
"lastName" : "Doe",
"email" : "j.d#gmail.com",
.......
.......
.......
"employeeTechnologyRefs" : [
{
"technologyCd" : "AB",
"primaryTechnologySw" : "Y",
"Active" : "A"
},
{
"technologyCd" : "AB",
"primaryTechnologySw" : "N",
"Active" : "A"
},
{
"technologyCd" : "PR",
"primaryTechnologySw" : "N",
"Active" : "A"
},
{
"technologyCd" : "PR",
"primaryTechnologySw" : "N",
"Active" : "A"
}
],
"countryPhoneCodes" : [
"+352"
],
....
...
}
I used below query, how to query over the Technology documents to get the result and map it and create final object?
Criteria criteria = new Criteria();
criteria.andOperator(
StringUtils.isNotBlank(firstName) ? Criteria.where("firstName").is(firstName.toUpperCase())
: Criteria.where(""),
StringUtils.isNotBlank(lastName) ? Criteria.where("lastName").is(lastName.toUpperCase())
: Criteria.where(""),
StringUtils.isNotBlank(email) ? Criteria.where("email").is(email.toUpperCase())
: Criteria.where(""),
StringUtils.isNotBlank(technologyCd) ? Criteria.where("employeeTechnologyRefs.technologyCd").is(technologyCd.toUpperCase())
: Criteria.where(""));
MatchOperation matchStage = Aggregation.match(criteria);
GroupOperation groupOp = Aggregation
.group("firstName", "lastName", "email","_id")
.addToSet("employeeTechnologyRefs").as("employeeTechnologyRefs");
ProjectionOperation projectStage = Aggregation.project("employeeTechnologyRefs");
Aggregation aggregation = Aggregation.newAggregation(matchStage, groupOp, projectStage);
AggregationResults<CustomObject> results = mongoTemplate.aggregate(aggregation, mongoTemplate.getCollectionName(Employee.class), CustomObject.class);
System.out.println(results);
Result should look like below
{
"_id" : ObjectId("5ec507c72d8c2136245d35ce"),
"firstName" : 442,
"lastName" : "LU",
"email" : "LUX",
.......
.......
.......
"employeeTechnologyRefs" : [
{
"technologyCd" : "AB",
"technologyName" : "My ABC",
"ltechnologyNativeName" : "XY",
"primaryTechnologySw" : "Y",
"Active" : "A"
},
{
"technologyCd" : "AB",
"technologyCd" : "AB",
"technologyName" : "My ABC",
"ltechnologyNativeName" : "XY",
"primaryTechnologySw" : "Y",
"Active" : "A"
},
{
"technologyCd" : "PR",
"technologyCd" : "AB",
"technologyName" : "My ABC",
"ltechnologyNativeName" : "XY",
"primaryTechnologySw" : "Y",
"Active" : "A"
},
{
"technologyCd" : "PR",
"technologyCd" : "AB",
"technologyName" : "My ABC",
"ltechnologyNativeName" : "XY",
"primaryTechnologySw" : "Y",
"Active" : "A"
}
],
....
}

If you use below lookup operation in your code, you should able to get the answer as expected, and you don't need group operation in your code.
Edited Answer: This is how whole code should look like. One more thing, you don't need projection and if you need try projecting specific field only, and as part of the lookup operation do not use the same field as it is in your collection, otherwise it will override existing data from employee collection.
Criteria criteria = new Criteria();
criteria.andOperator(
StringUtils.isNotBlank(firstName) ? Criteria.where("firstName").is(firstName.toUpperCase())
: Criteria.where(""),
StringUtils.isNotBlank(lastName) ? Criteria.where("lastName").is(lastName.toUpperCase())
: Criteria.where(""),
StringUtils.isNotBlank(email) ? Criteria.where("email").is(email.toUpperCase())
: Criteria.where(""),
StringUtils.isNotBlank(technologyCd) ? Criteria.where("employeeTechnologyRefs.technologyCd").is(technologyCd.toUpperCase())
: Criteria.where(""));
MatchOperation matchStage = Aggregation.match(criteria);
/*GroupOperation groupOp = Aggregation
.group("firstName", "lastName", "email","_id")
.addToSet("employeeTechnologyRefs").as("employeeTechnologyRefs");
*/
LookupOperation lookupOperation = LookupOperation.newLookup().
from("technology_collection_name").
localField("employeeTechnologyRefs.technologyCd").
foreignField("technologyCd").
as("employeeTechnologyRefsOtherName");
/* ProjectionOperation projectStage = Aggregation.project("employeeTechnologyRefs");
*/
// And if you want to project specific field from employee array you can use something like.
ProjectionOperation projectStage = Aggregation.project("employeeTechnologyRefs.fieldName")
Aggregation aggregation = Aggregation.newAggregation(matchStage, lookupOperation, projectStage);
AggregationResults<CustomObject> results = mongoTemplate.aggregate(aggregation, mongoTemplate.getCollectionName(Employee.class), CustomObject.class);
System.out.println(results);

Related

Mongo Spark Java Connector Group by

i am storing events on my server from client mobile application, event store is mongodb.
I have mongo-spark connector which fetches list of these events and should display them using rest api. It should be streaming for later, but for now i am trying to display it as a single call.
So far i have written my controller as given below:
#RestController
#RequestMapping("/analytics")
class EventController #Autowired constructor(val eventMongoServiceImpl: EventMongoServiceImpl,
val javaSparkContext: JavaSparkContext) {
#GetMapping("/event")
fun getEvent(): ResponseEntity<EventResponse> {
val customRdd: JavaMongoRDD<Document> = MongoSpark.load(javaSparkContext)
val toDF = customRdd.toDF()
}
}
Please help me filter these result given below for rest api:
[
{
"key": "Event A",
"description": "Event A Description",
"count": 3
},
{
"key": "Event B",
"description": "Event B Description",
"count": 0
}
]
I have a data set as given below:
/* 1 */
{
"_id" : ObjectId("5e61e38eb8425d3b1c7679ea"),
"name" : "Event A",
"description" : "Event A Description",
"date" : ISODate("2020-03-05T18:30:00.000Z"),
"_class" : "x"
}
/* 2 */
{
"_id" : ObjectId("5e61e416b8425d3b1c7679ec"),
"name" : "Event A",
"description" : "Event A Description",
"date" : ISODate("2020-03-05T18:30:00.000Z"),
"_class" : "x"
}
/* 3 */
{
"_id" : ObjectId("5e61e47fb8425d3b1c7679ee"),
"name" : "Event A",
"description" : "Event A Description",
"date" : ISODate("2020-03-05T18:30:00.000Z"),
"_class" : "x"
}
You should be able on the dataframe to make something like this
val aggDf = toDf
.groupBy("name")
.agg(count("name"), max("description"))
Now on the new dataframe aggDf you can do aggDf.toJson and have the results. If columns does not match the output you can tweak them with withColumnRenamed

com.google.gson.JsonArray.getAsString error on fullTextQuery.getResultList() in Hibernate Search query

I'm trying to fetch data from idexes with a hibernate search fulltext query.
Below is the index structure:
{
"_index" : "basclt1400",
"_type" : "com.csc.pt.svc.data.to.Basclt1400TO",
"_id" : "00,0006682,CPP,05,00",
"_score" : 1.0,
"_source" : {
"id" : "00,0006682,CPP,05,00",
"location" : "00",
"master0co" : "05",
"policy0num" : "0006682",
"symbol" : "CPP",
"module" : "00",
"cltseqnum" : 281,
"addrseqnum" : "1",
"policies_location" : [
"00",
"00"
],
"policies_master0co" : [
"05",
"05"
],
"policies_policy0num" : [
"0006682",
"0006682"
],
"policies_trans0stat" : [
"V",
"P"
],
"policies_id02" : [
"02",
"02"
],
"policies_symbol" : [
"CPP",
"CPP"
],
"policies_module" : [
"00",
"00"
],
"policies_tot0ag0prm" : [
"1532.00",
"1532.00"
],
"policies_issue0code" : [
"N",
"N"
],
"policies_id" : [
"02,00,0006682,CPP,05,00,V",
"02,00,0006682,CPP,05,00,P"
]
}
This structure may change as per data under the index, at some places the data under "policies_policy0num" field there may be just one record, like below, and it works fine with this structure:
"_index" : "basclt1400",
"_type" : "com.csc.pt.svc.data.to.Basclt1400TO",
"_id" : "00,0012410,CPP,05,00",
"_score" : 1.0,
"_source" : {
"id" : "00,0012410,CPP,05,00",
"location" : "00",
"master0co" : "05",
"policy0num" : "0012410",
"symbol" : "CPP",
"module" : "00",
"cltseqnum" : 281,
"addrseqnum" : "1",
"policies_location" : [
"00"
],
"policies_master0co" : [
"05"
],
"policies_policy0num" : [
"0012410"
],
"policies_trans0stat" : [
"P"
],
"policies_id02" : [
"02"
],
"policies_symbol" : [
"CPP"
],
"policies_module" : [
"00"
],
"policies_tot0ag0prm" : [
"0.00"
],
"policies_issue0code" : [
"N"
],
"policies_id" : [
"02,00,0012410,CPP,05,00,P"
]
}
}
I'm trying to fetch this like below:
Iterator itr = fullTextQuery.getResultList().iterator();
List<MasterSearchPmsp0200DataArr> policyArrayFinal = new ArrayList<MasterSearchPmsp0200DataArr>();
List<MasterSearchPmsp0200DataArr> quoteArrayFinal = new ArrayList<MasterSearchPmsp0200DataArr>();
while(itr.hasNext()){
Object[] obj = (Object[]) itr.next();
char issueCode = (char) obj[5];
if(issueCode == 'N' || issueCode == 'R') {
policyArrayFinal.add( new MasterSearchPmsp0200DataArr((String) obj[0], Long.valueOf(to.getCltseqnum()),
(String) obj[1], (String) obj[2], (String) obj[3], (String) obj[4],
(char) obj[5], (char) obj[6]));
}else {
quoteArrayFinal.add( new MasterSearchPmsp0200DataArr((String) obj[0], Long.valueOf(to.getCltseqnum()),
(String) obj[1], (String) obj[2], (String) obj[3], (String) obj[4],
(char) obj[5], (char) obj[6]));
}
}
and it's throwing the below error, just for the records where we have multiple data under policies_policy0num.
java.lang.IllegalStateException
at com.google.gson.JsonArray.getAsString(JsonArray.java:226)
at org.hibernate.search.elasticsearch.query.impl.PrimitiveProjection.addDocumentField(PrimitiveProjection.java:69)
at org.hibernate.search.elasticsearch.query.impl.PrimitiveProjection.addDocumentField(PrimitiveProjection.java:43)
at org.hibernate.search.elasticsearch.query.impl.TwoWayFieldBridgeProjection.convertFieldValue(TwoWayFieldBridgeProjection.java:60)
at org.hibernate.search.elasticsearch.query.impl.TwoWayFieldBridgeProjection.convertHit(TwoWayFieldBridgeProjection.java:43)
at org.hibernate.search.elasticsearch.query.impl.QueryHitConverter.convert(QueryHitConverter.java:186)
at org.hibernate.search.elasticsearch.query.impl.IndexSearcher.convertQueryHit(IndexSearcher.java:138)
at org.hibernate.search.elasticsearch.query.impl.ElasticsearchHSQueryImpl.queryEntityInfos(ElasticsearchHSQueryImpl.java:233)
at org.hibernate.search.query.hibernate.impl.FullTextQueryImpl.doHibernateSearchList(FullTextQueryImpl.java:238)
at org.hibernate.search.query.hibernate.impl.FullTextQueryImpl.list(FullTextQueryImpl.java:223)
at org.hibernate.search.query.hibernate.impl.FullTextQueryImpl.getResultList(FullTextQueryImpl.java:122)
Attaching the error point snaspshot:
error snapshot
How should I handle this scenario under hibernate search java code.
Adding the query code:
Query query = queryBuilder.keyword().onField("cltseqnum").matching(to.getCltseqnum()).createQuery();
FullTextQuery fullTextQuery = fullTextSession.createFullTextQuery(query, Basclt1400TO.class);
fullTextQuery.setProjection( "policies_policy0num", "policies_symbol",
"policies_module", "policies_master0co","policies_location", "policies_issue0code",
"policies_trans0stat");
You didn't provide the code used to build the query, but from what I can see you are using projections.
Projections do not support multi-valued fields, so you simply cannot make this work, unless you project on the whole document (using org.hibernate.search.elasticsearch.ElasticsearchProjectionConstants.SOURCE) and parse it yourself, which would be a terrible hack.
I would recommend using "traditional" entity loading (without projections) and getting the data from your entities. Unless you've got tremendous performance constraints, this should result in decent performance, especially if you tune your Hibernate ORM mapping correctly.

Simple Schema populate default value from collection

How is it possible to populate simple schema's default value with a call to a collection in Meteor js instead of defining the "tests" within the defaultValue as below? If possible to have the defaultValue return all from TestList = new Mongo.Collection('testList').
StudentSchema = new SimpleSchema({
tests: {
type: [Object],
blackbox: true,
optional: true,
defaultValue:[
{
"_id" : "T2yfqWJ3a5rQz64WN",
"category_id" : "5",
"active" : "true",
"category" : "Cognitive/Intelligence",
"abbr" : "WJ-IV COG",
"name" : "Woodcock-Johnson IV, Tests of Cognitive Abilities",
"publisher" : "Riverside Publishing"
},
{
"_id" : "Ai8bT6dLYGQRDfvKe",
"category_id" : "5",
"active" : "true",
"category" : "Cognitive/Intelligence",
"abbr" : "WISC-IV",
"name" : "Wechsler Intelligence Scale for Children-Fourth Edition",
"publisher" : "The Psychological Corporation"
},
{
"_id" : "osAuaLrX97meRZuda",
"category_id" : "7",
"active" : "true",
"category" : "Speech and Language",
"abbr" : "WOJO",
"name" : "Wechsler Intelligence",
"publisher" : "The Psychological Corporation"
},
{
"_id" : "57c62a784b94c533b656dba8",
"category_id" : "5",
"active" : "true",
"category" : "Behavioral",
"abbr" : "CARS",
"name" : "CARS",
"publisher" : "The Psychological Corporation"
}
],
);
},
Dynamically loading all entries from "TestList" collection into "tests" array.
TestList = new Mongo.Collection('testList');
StudentSchema = new SimpleSchema({
tests: {
type: [Object],
blackbox: true,
optional: true,
autoValue: function () {
return TestList.find().fetch();
},

Why is my mongo query not using index only?

Please, observe:
MongoDB shell version: 2.4.1
connecting to: test
> use dummy
switched to db dummy
> db.invoices.find({'items.nameTags': /^z/}, {_id: 1}).explain()
{
"cursor" : "BtreeCursor items.nameTags_1_created_1_special_1__id_1_items.qty_1_items.total_1 multi",
"isMultiKey" : true,
"n" : 55849,
"nscannedObjects" : 223568,
"nscanned" : 223568,
"nscannedObjectsAllPlans" : 223568,
"nscannedAllPlans" : 223568,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 86,
"nChunkSkips" : 0,
"millis" : 88864,
"indexBounds" : {
"items.nameTags" : [
[
"z",
"{"
],
[
/^z/,
/^z/
]
],
"created" : [
[
{
"$minElement" : 1
},
{
"$maxElement" : 1
}
]
],
"special" : [
[
{
"$minElement" : 1
},
{
"$maxElement" : 1
}
]
],
"_id" : [
[
{
"$minElement" : 1
},
{
"$maxElement" : 1
}
]
],
"items.qty" : [
[
{
"$minElement" : 1
},
{
"$maxElement" : 1
}
]
],
"items.total" : [
[
{
"$minElement" : 1
},
{
"$maxElement" : 1
}
]
]
},
"server" : "IL-Mark-LT:27017"
}
>
Here is the definition of the index:
> db.system.indexes.find({name : 'items.nameTags_1_created_1_special_1__id_1_items.qty_1_items.total_1'}).pretty()
{
"v" : 1,
"key" : {
"items.nameTags" : 1,
"created" : 1,
"special" : 1,
"_id" : 1,
"items.qty" : 1,
"items.total" : 1
},
"ns" : "dummy.invoices",
"name" : "items.nameTags_1_created_1_special_1__id_1_items.qty_1_items.total_1"
}
>
Finally, here is an example invoice document (with just 2 items):
> db.invoices.findOne({itemCount: 2})
{
"_id" : "85923",
"customer" : "Wgtd Fm 91",
"businessNo" : "314227928",
"billTo_name" : "Wgtd Fm 91",
"billTo_addressLine1" : "3839 Ross Street",
"billTo_addressLine2" : "Kingston, ON",
"billTo_postalCode" : "K7L 4V4",
"purchaseOrderNo" : "boi",
"terms" : "COD",
"shipDate" : "2013-07-10",
"shipVia" : "Moses Transportation Inc.",
"rep" : "Snowhite",
"items" : [
{
"qty" : 4,
"name" : "CA 7789",
"desc" : "3 pc. Coffee Table set (Silver)",
"price" : 222.3,
"total" : 889.2,
"nameTags" : [
"ca 7789",
"a 7789",
" 7789",
"7789",
"789",
"89",
"9"
],
"descTags" : [
"3",
"pc",
"c",
"coffee",
"offee",
"ffee",
"fee",
"ee",
"e",
"table",
"able",
"ble",
"le",
"e",
"set",
"et",
"t",
"silver",
"ilver",
"lver",
"ver",
"er",
"r"
]
},
{
"qty" : 4,
"name" : "QP 8681",
"desc" : "Ottoman Bed",
"price" : 1179.1,
"total" : 4716.4,
"nameTags" : [
"qp 8681",
"p 8681",
" 8681",
"8681",
"681",
"81",
"1"
],
"descTags" : [
"ottoman",
"ttoman",
"toman",
"oman",
"man",
"an",
"n",
"bed",
"ed",
"d"
]
}
],
"itemCount" : 2,
"discount" : "10%",
"delivery" : 250,
"hstPercents" : 13,
"subTotal" : 5605.6,
"totalBeforeHST" : 5295.04,
"total" : 5983.4,
"totalDiscount" : 560.56,
"hst" : 688.36,
"modified" : "2012-10-08",
"created" : "2014-06-25",
"version" : 0
}
>
My problem is that mongodb does not use index only according to the aforementioned explain() output. Why? After all I only request the _id field, which is part of the index.
In general, I feel that I am doing something very wrong. My invoices collection has 65,000 invoices with the total of 3,291,092 items. It took almost 89 seconds to explain() the query.
What am I doing wrong?
You are using arrays and subdocuments. Covered Indexes dont work with either of these.
From the mongo docs:
An index cannot cover a query if:
any of the indexed fields in any of the documents in the collection includes an array. If an indexed field is an array, the index becomes a multi-key index index and cannot support a covered query.
any of the indexed fields are fields in subdocuments. To index fields in subdocuments, use dot notation. For example, consider a collection users with documents of the following form:
http://docs.mongodb.org/manual/tutorial/create-indexes-to-support-queries/

ShopifyAPI Gem: strange format for Order.to_json's discount_codes attribute

I'm working on retrieving all the orders for a given shop, using code that looks like this:
orders = ShopifyAPI::Order.find(:all, :params => {:financial_status => 'paid'})
orders.each do |order|
order_json = order.to_json
post_json_to_server(order_json)
end
But for some reason, when I inspect the JSON created by order.to_json, the discount_codes and client_details attributes look like this:
"client_details":{"":{"accept_language":"en-US,en;q=0.8","browser_ip":"199.185.98.174","session_hash":"6b37d22ebcdf097f5ab4e1c9e596a504c4cdc4c41c4f2b29a3a7aae4ead559c3","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.47 Safari/536.11"}}
"discount_codes":[{"":{"amount":"20.00","code":"CZV57KSE8VMV"}}]
Why is there a leading {"": in both of these lists? Is something misconfigured in my test store?
Here is a complete dump (captured with RequestBin) of the JSON I'm sending:
{ "billing_address" : { "address1" : "asdf",
"address2" : "",
"city" : "asdf",
"company" : "",
"country" : "United States",
"country_code" : "US",
"first_name" : "asdf",
"last_name" : "asdf",
"latitude" : "45.176384",
"longitude" : "-123.045601",
"name" : "asdf asdf",
"phone" : "",
"province" : "Alaska",
"province_code" : "AK",
"zip" : "asdf"
},
"browser_ip" : "199.185.98.174",
"buyer_accepts_marketing" : true,
"cancel_reason" : null,
"cancelled_at" : null,
"cart_token" : "bbf42c99f456f9ccda30554022fec659",
"client_details" : { "" : { "accept_language" : "en-US,en;q=0.8",
"browser_ip" : "199.185.98.174",
"session_hash" : "6b37d22ebcdf097f5ab4e1c9e596a504c4cdc4c41c4f2b29a3a7aae4ead559c3",
"user_agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.47 Safari/536.11"
} },
"closed_at" : null,
"created_at" : "2012-07-10T17:23:53-04:00",
"currency" : "CAD",
"customer" : { "accepts_marketing" : true,
"created_at" : "2012-07-10T17:23:53-04:00",
"email" : "asdf#example.com",
"first_name" : "asdf",
"id" : 94366870,
"last_name" : "asdf",
"last_order_id" : null,
"last_order_name" : null,
"note" : null,
"orders_count" : 0,
"state" : "disabled",
"tags" : "",
"total_spent" : "0.00",
"updated_at" : "2012-07-10T17:35:23-04:00"
},
"discount_codes" : [ { "" : { "amount" : "20.00",
"code" : "CZV57KSE8VMV"
} } ],
"email" : "asdf#example.com",
"financial_status" : "authorized",
"fulfillment_status" : null,
"fulfillments" : [ ],
"gateway" : "bogus",
"id" : 134753494,
"landing_site" : "/",
"landing_site_ref" : null,
"line_items" : [ { "fulfillment_service" : "manual",
"fulfillment_status" : null,
"grams" : 0,
"id" : 219421970,
"name" : "Grass-roots methodical instruction set",
"price" : "19.00",
"product_id" : 95843140,
"quantity" : 2,
"requires_shipping" : true,
"sku" : "",
"title" : "Grass-roots methodical instruction set",
"variant_id" : 224399478,
"variant_inventory_management" : null,
"variant_title" : null,
"vendor" : "Shopify"
} ],
"name" : "#1004",
"note" : "",
"note_attributes" : [ ],
"number" : 4,
"order_number" : 1004,
"payment_details" : { "avs_result_code" : null,
"credit_card_bin" : "1",
"credit_card_company" : "Bogus",
"credit_card_number" : "XXXX-XXXX-XXXX-1",
"cvv_result_code" : null
},
"processing_method" : "direct",
"referring_site" : "",
"shipping_address" : { "address1" : "asdf",
"address2" : "",
"city" : "asdf",
"company" : "",
"country" : "United States",
"country_code" : "US",
"first_name" : "asdf",
"last_name" : "asdf",
"latitude" : "45.176384",
"longitude" : "-123.045601",
"name" : "asdf asdf",
"phone" : "",
"province" : "Alaska",
"province_code" : "AK",
"zip" : "asdf"
},
"shipping_lines" : [ { "code" : "International Shipping",
"price" : "20.00",
"source" : "shopify",
"title" : "International Shipping"
} ],
"subtotal_price" : "18.00",
"tax_lines" : [ ],
"taxes_included" : false,
"token" : "51116b93d2d774b6c537a3bcc8861506",
"total_discounts" : "20.00",
"total_line_items_price" : "38.00",
"total_price" : "38.00",
"total_price_usd" : "37.28",
"total_tax" : "0.00",
"total_weight" : 0,
"updated_at" : "2012-07-10T17:35:20-04:00"
}
In an attempt to get the Order formatted the same way as the JSON sent by a webhook, I ended up doing it myself - here is the code:
ActiveResource::Base.include_root_in_json = true
orders = ShopifyAPI::Order.find(:all, :params => {:financial_status => 'paid'})
orders.each do |order|
order_json = order.as_json
%w(billing_address customer line_items payment_details shipping_address shipping_lines).each do |attribute|
order_json[attribute] = order.send(attribute).as_json
end
if order_json['discount_codes'].length > 0
order_json['discount_codes'] = [order_json['discount_codes'].as_json[0][nil]]
end
order_json['client_details'] = order_json['client_details'].as_json[nil]
post_json_to_server(order_json)
end
By doing it this way, I was able to turn the JSON into something that mapped exactly to what Shopify's order paid webhook sends.
When you use to_json without telling ActiveRecord how to treat the root it can do that.
You can tell ActiveRecord to include the root when rendering JSON.
ActiveRecord::Base.include_root_in_json = true
You would then see
{"order":{...}} and not {"":{...}}
Often you can just use the syntax
order_json = order.to_json(:root => true)
in order to get the key (which is the root) you want. Using JSON is still kinda like walking inside a carnival jumpy ride for kids...

Resources