I am trying to connect to a third party service using Breeze with a custom JsonResultsAdapter.
The third party service has the "metadata" related to an entity in the root node of the array, then the variables are in a "data" property on the "metadata" object.
The format has two ways of defining relationships. One is via a "#ref" field which references the id of another entity. The other is by having the related object defined inline (instead of the "#ref") which does not have an explicit id, but which is only ever referenced by the "parent" object.
The data looks like:
[{
"id" : "abc",
"type" : "foo",
"data": { "relationshipRef" : { "#ref" : "someid" } }
},
{
"id": "someid",
"type" : "bar",
"data" : { "relationshipInline" : { "type" : "baz",
"data" : { "something" : "whatever",
"innerRelation" : { "#ref" : "abc"}
}
}
}]
I'm currently (in JsonResultsAdapter's visitNode function) moving the properties in the "data" object up into the "root" node, and then replacing any object with an "#ref" property with the value of the "#ref" key and appending an ID to the end (so that relationships can use the original name in the EntityType). IE, the first object would become:
{
"id" : "abc",
"type" : "foo",
"relationshipRefID" : "someid"
}
This works for top level entities and relationships, but I'm having problems with the nested ones.
How would you approach solving this problem?
I was going to use ComplexTypes but the documentation mentioned that they cannot have "navigationProperties" (relationships), which as you can see above is required (the "innerRelation" property).
In some cases, the entities can be nested down to 3 levels or so.
Here is my current visitNode function:
visitNode: function(node, parseContext, nodeContext) {
if(node instanceof Object && node.type != null) {
if(node.deleted) {
//TODO: make sure the object is removed from the manager
return {ignore:true};
}
//We need to tweak the data structure to fit what breeze expects.
//It expects properties to be in the same level as the "metadata" for an object ("type" etc),
//So we need to move the properties from the data object into the node, and fix up relationships.
if(parseContext.entityManager.metadataStore.getEntityType(node.type, true) != null) {
var data = node.data;
for(var key in data) {
var prop = data[key];
//Move any foreign key fields to be "relationID":id instead of "relation":{"#ref":id}
if(prop instanceof Object) {
var ref = prop["#ref"];
if(ref != null) {
node[key+"ID"] = ref
data[key] = null;
continue;
}
}
//TODO: Handle inline references <- This is where I need help!
node[key] = data[key];
}
return {
entityType: node.type,
nodeId: node.id
}
}
else {
return {ignore:true};
}
}
}
Well, apparently I should have tested more before asking here.
It turns out that this works automatically based on the navigationProperties defined in the model! Awesome. I did have to generate ids for the inner nodes that did not have them, but that was simple.
Related
I am trying to generate mock data using relay for storybook.
My query is
const QUERY_LIST = graphql`
query modelControllerAllUsersQuery #relay_test_operation {
allUsers {
pageInfo {
hasNextPage
}
edges {
node {
id
firstName
lastName
}
}
}
}
`
and provided RelayEnvironmentProvider as a decorator to the story. I'm trying to return some default values to my query using custom mock resolvers.
const customMockResolvers = {
...mockResolvers,
allUsers:() => ({
pageInfo:{
hasNextPage:false,
},
edges:[
{
node:{
id :'id',
firstName:'fname',
lastName :'lname',
},
},
],
}),
};
and calling it as
(operation) => MockPayloadGenerator.generate(operation, customMockResolvers)
I don't seem to be able to get the default values returned.
Currently, it is returning
{"allUsers":{"pageInfo":{"hasNextPage":false},"edges":[{"node":{"id":"<UserNode-mock-id-1>","firstName":"<mock-value-for-field-\"firstName\">","lastName":"<mock-value-for-field-\"lastName\">"}}]}}
What am I doing wrong?
When using the #relay-test-operation, the keys within your customMockResolvers object must match the type name of the fields, which can be different from the field names themselves.
For example, you could have the following in your schema:
type Foo {
id: ID!
name: String!
}
and the following query:
query FooQuery #relay_test_operation {
foo {
id
name
}
}
Then the customMockResolvers object would look like this:
const customMockResolvers = {
Foo: () => ({
id: "fooId",
name: "fooName"
})
}
Notice that I'm passing in Foo as the key instead of foo.
You can check your schema and see what the the type name of allUsers is. I suspect it would be something like AllUsers or allUsersConnection, or something similar.
Also, if you're interested in creating Storybook stories for Relay components, I created a NPM package just for that: https://www.npmjs.com/package/use-relay-mock-environment
It doesn't require adding the #relay-test-operation directive to your query, and instead relies only on resolving the String type (which is the default for all scalar properties). You can of course still add the #relay-test-operation directive and also extend the resolvers by providing customResolvers in the config.
You can also extend the the String resolver as well, by providing extendStringResolver in the config.
Feel free to review the source code here if you want to implement something similar: https://github.com/richardguerre/use-relay-mock-environment.
Note: it's still in its early days, so some things might change, but would love some feedback!
I have the Article entity and inside it there is a nested property, let's say Metadata.
I need to count all articles, which have a particular field inside this nested property, let's say indexed, assigned to e.g. 1.
Java Document Snippet:
#Document(indexName = "article", type = "article", useServerConfiguration = true, createIndex = false)
#Setting(settingPath = "/mappings/settings.json")
#Mapping(mappingPath = "/mappings/articles.json")
public class Article {
// getters and setters, empty constructor are omitted for brevity
#Id
private String id;
private Metadata metadata;
// remainder of the body is omitted
}
Metadata.class snippet
public class Metadata {
// getters and setters, empty constructor are omitted for brevity
private Integer indexed;
// remainder of the body is omitted
}
The query I use to retrieve articles, which satisfy the given criteria and which I put as a value of #org.springframework.data.elasticsearch.annotations.Query on top of the custom method:
{
"query": {
"bool": {
"must": [
{
"nested": {
"path": "metadata",
"query": {
"bool": {
"must": [
{
"match": {
"metadata.indexed": 1
}
}
]
}
}
}
}
]
}
}
}
My custom Spring Data ElasticSearch repository snippet with a custom method:
public CustomSpringDataElasticsearchRepository extends ElasticsearchRepository<Article, String> {
#Query("The query from above")
Long countByMetadata_Indexed(int value);
}
When I use the repository method shown above , I get java.lang.IllegalArgumentException: Expected 1 but found n results.
Custom Spring Data Elasticsearch Repository method(without #Query) returns 0(version without underscore returns 0 as well) though it should return everything correctly.
How do I get the correct results using Spring Data ElasticSearch Repository? Why does the custom method without #Query doesn't work as well?
UPD: The version of spring-data-elasticsearch used is 3.1.1.RELEASE.
Repository query methods currently(3.2.4.RELEASE) don't support the count by the fields inside nested fields.
As was mentioned previously, #Query annotation doesn't support custom count queries as of the latest version(3.2.4.RELEASE).
In other words, currently, the only way to do this query through Spring Data ElasticSearch is to use ElasticsearchTemplate bean or ElasticsearchOperations bean.
Credit: P.J.Meisch
I am having trouble configuring a dynamic mapping for an array of results. The result objects I'd like to map are embedded within a "container" that describes the type of the enclosed object.
I have JSON similar to the following:
"list": {
"name": ""
"item_infos": [
{
"type": "TaskItem",
"item": {
"id": 0
"title": "A Task Item",
"task": "..."
}
"url": "..."
},
{
"type": "ReminderItem",
"item": {
"id": 0
"title": "A Reminder Item",
"reminder": "..."
}
"url": "..."
}]
}
I'd like to map this as a List object with an array of Items, where each item might be of a different type. The different types are finite and known.
class List: NSManagedObject {
#NSManaged var name: String
#NSManaged var items: NSOrderedSet
}
class Item: NSManagedObject {
#NSManaged var identifier: Int32
#NSManaged var title: String
// Each mappable `Item` is responsible for providing its own
// mapping object. It's overridden in the subclasses below.
class func responseMappingForManagedObjectStore(dos: RKManagedObjectStore) -> RKEntityMapping { ... }
}
class TaskItem: Item {
#NSManaged var task: String
}
class ReminderItem: Item {
#NSManaged var reminder: String
}
How can I map the embedded item directly under the list using RKDynamicMapping while still making use of the type field? I'm trying something along these lines for the response mapping:
let listMapping = RKEntityMapping(forEntityForName: "List", inManagedObjectStore: store)
responseMapping.addAttributeMappingsFromDictionary(["name": "title"])
let dynamicItemMapping = RKDynamicMapping()
dynamicItemMapping.setObjectMappingForRepresentationBlock { representation in
let itemMapping: RKEntityMapping
switch representation.valueForKeyPath("type") as? String {
case .Some("TaskItem"): itemMapping = TaskItem.responseMappingForManagedObjectStore(objectStore)
case .Some("ReminderItem"): itemMapping = ReminderItem.responseMappingForManagedObjectStore(objectStore)
default: return nil
// This is the bit I'm failing to solve. How can I return a mapping
// that essentially "skips" a level of the JSON, and just maps the
// embedded `item`, not the `item_info`.
let itemInfoMapping = RKObjectMapping(forClass: NSMutableDictionary.self)
itemInfoMapping.addRelationshipMappingWithSourceKeyPath("item", mapping: itemMapping)
return itemInfoMapping
}
listMapping.addPropertyMapping(RKRelationshipMapping(
fromKeyPath: "item_infos",
toKeyPath: "items",
withMapping: dynamicItemMapping))
With this mapping, an exception is raised:
-[__NSDictionaryM _isKindOfEntity:]: unrecognized selector sent to instance 0x7fd2603cb400
Which doesn't surprise me, as the way the dynamic mapping is set up just doesn't feel right anyway – I'm looking for a way to "skip" a level of the JSON and only map the embedded item.
An alternative attempt was to fully specify the fromKeyPath as "item_infos.item" for the relationship to the listMapping, but then I cannot use the type field in the dynamic mapping block to determine the type of Item mapping to use:
// ...
dynamicItemMapping.setObjectMappingForRepresentationBlock { representation in
// `type` inaccessible from the nested item representation
switch representation.valueForKeyPath("type") as? String {
case .Some("TaskItem"): return TaskItem.responseMappingForManagedObjectStore(objectStore)
case .Some("ReminderItem"): return ReminderItem.responseMappingForManagedObjectStore(objectStore)
default: return nil
}
listMapping.addPropertyMapping(RKRelationshipMapping(
fromKeyPath: "item_infos.item",
toKeyPath: "items",
withMapping: dynamicItemMapping))
You can't do exactly what you're trying to do, because you're connecting a core data relationship to what will be an array of dictionaries which contains managed objects.
The simplest solution is to take your skip logic out from where it is and create all of the mappings for the managed objects (task and reminder) by adding item. at the start of the source key (path). So
"item.title" : "title"
I found a bug concerning the ComplexType properties. I have an Object which has a property which is a collection of other objects which I defined in the client-side metadata as a Complex property, i.e. "isComplexType: true". However all properties of a complex type are set to 'undefined'. I did a bit of debugging and found this:
// target and source may not be entities that can also be complexTypes.
function updatePropertyFromRawEntity(dp, target, rawSource) {
var val = getPropertyFromRawEntity(rawSource, dp);
if (val === undefined) return;
if (dp.isComplexProperty) {
var coVal = target.getProperty(dp.name);
dp.dataType.dataProperties.forEach(function (cdp) {
// recursive call
updatePropertyFromRawEntity(cdp, coVal, val);
});
} else {
target.setProperty(dp.name, val);
}
}
The rawSource parameter is an array(in case of complexTypes) is an array of objects. So the problem is when invoking the getPropertyFromRawEntity(), it passes an array not an object:
function getPropertyFromRawEntity(rawEntity, dp) {
var propName = dp.nameOnServer || dp.isUnmapped && dp.name;
return parseValueForDp(rawEntity[propName], dp);
}
So rawEntity[propName] will always be undefined because its an array.
A Breeze ComplexType has a well defined structure specified by metadata. Any property, (except) the key can have its datatype be a complexType. However, what you want, I think, is an "untyped" structure. This can be accomplished by defining the property as having a data type of "Undefined", something like:
var newProp = new DataProperty({
name: "Foo"
dataType: DataType.Undefined,
isNullable: true,
isUnmapped: true
});
entityType.addProperty(newProp);
The "Undefined" dataType will accept data of any type and structure.
I'm using RestKit for posting objects to the server. In my object I have two properties: name and socialId.
I want to send to server only properties that have data.
If name != nil send:
{
"name" : "name",
}
If socialId != 0 send:
{
"socialId" : socialId,
}
But RestKit sends all data. For example:
{
"name" : "",
"socialId" : 0,
}
How can I change this behavior?
The answer is to using [RKDynamicMapping setObjectMappingForRepresentationBlock:]
You should check representation properties in block and add attributes mapping for desired properties.