I'm having some trouble with FaunaDB Indexes. FQL is quite powerful but the docs seem to be limited (for now) to only a few examples/use cases. (Searching by String)
I have a collection of Orders, with a few fields: status, id, client, material and date.
My goal is to search/filter for orders depending on their Status, OPEN OR CLOSED (Boolean true/false).
Here is the Index I created:
CreateIndex({
name: "orders_all_by_open_asc",
unique: false,
serialized: true,
source: Collection("orders"),
terms: [{ field: ["data", "status"] }],
values: [
{ field: ["data", "unique_id"] },
{ field: ["data", "client"] },
{ field: ["data", "material"] },
{ field: ["data", "date"] }
]
}
So with this Index, I want to specify either TRUE or FALSE and get all corresponding orders, including their data (fields).
I'm having two problems:
When I pass TRUE OR FALSE using the Javascript Driver, nothing is returned :( Is it possible to search by Booleans at all, or only by String/Number?
Here is my Query (in FQL, using the Shell):
Match(Index("orders_all_by_open_asc"), true)
And unfortunately, nothing is returned. I'm probably doing this wrong.
Second (slightly unrelated) question. When I create an Index and specify a bunch of Values, it seems the data returned is in Array format, with only the values, not the Fields. An example:
[
1001,
"client1",
"concrete",
"2021-04-13T00:00:00.000Z",
],
[
1002,
"client2",
"wood",
"2021-04-13T00:00:00.000Z",
]
This format is bad for me, because my front-end expects receiving an Object with the Fields as a key and the Values as properties. Example:
data:
{
unique_id : 1001,
client : "client1",
material : "concrete",
date: "2021-04-13T00:00:00.000Z"
},
{
unique_id : 1002,
client : "client2",
material : "wood",
date: "2021-04-13T00:00:00.000Z"
},
etc..
Is there any way to get the Field as well as the Value when using Index values, or will it always return an Array (and not an object)?
Could I use a Lambda or something for this?
I do have another Query that uses Map and Lambda to good effect, and returns the entire document, including the Ref and Data fields:
Map(
Paginate(
Match(Index("orders_by_date"), date),
),
Lambda('item', Get(Var('item')))
)
This works very nicely but unfortunately, it also performs one Get request per Document returned and that seems very inefficient.
This new Index I'm wanting to build, to filter by Order Status, will be used to return hundreds of Orders, hundreds of times a day. So I'm trying to keep it as efficient as possible, but if it can only return an Array it won't be useful.
Thanks in advance!! Indexes are great but hard to grasp, so any insight will be appreciated.
You didn't show us exactly what you have done, so here's an example that shows that filtering on boolean values does work using the index you created as-is:
> CreateCollection({ name: "orders" })
{
ref: Collection("orders"),
ts: 1618350087320000,
history_days: 30,
name: 'orders'
}
> Create(Collection("orders"), { data: {
unique_id: 1,
client: "me",
material: "stone",
date: Now(),
status: true
}})
{
ref: Ref(Collection("orders"), "295794155241603584"),
ts: 1618350138800000,
data: {
unique_id: 1,
client: 'me',
material: 'stone',
date: Time("2021-04-13T21:42:18.784Z"),
status: true
}
}
> Create(Collection("orders"), { data: {
unique_id: 2,
client: "you",
material: "muslin",
date: Now(),
status: false
}})
{
ref: Ref(Collection("orders"), "295794180038328832"),
ts: 1618350162440000,
data: {
unique_id: 2,
client: 'you',
material: 'muslin',
date: Time("2021-04-13T21:42:42.437Z"),
status: false
}
}
> CreateIndex({
name: "orders_all_by_open_asc",
unique: false,
serialized: true,
source: Collection("orders"),
terms: [{ field: ["data", "status"] }],
values: [
{ field: ["data", "unique_id"] },
{ field: ["data", "client"] },
{ field: ["data", "material"] },
{ field: ["data", "date"] }
]
})
{
ref: Index("orders_all_by_open_asc"),
ts: 1618350185940000,
active: true,
serialized: true,
name: 'orders_all_by_open_asc',
unique: false,
source: Collection("orders"),
terms: [ { field: [ 'data', 'status' ] } ],
values: [
{ field: [ 'data', 'unique_id' ] },
{ field: [ 'data', 'client' ] },
{ field: [ 'data', 'material' ] },
{ field: [ 'data', 'date' ] }
],
partitions: 1
}
> Paginate(Match(Index("orders_all_by_open_asc"), true))
{ data: [ [ 1, 'me', 'stone', Time("2021-04-13T21:42:18.784Z") ] ] }
> Paginate(Match(Index("orders_all_by_open_asc"), false))
{ data: [ [ 2, 'you', 'muslin', Time("2021-04-13T21:42:42.437Z") ] ] }
It's a little more work, but you can compose whatever return format that you like:
> Map(
Paginate(Match(Index("orders_all_by_open_asc"), false)),
Lambda(
["unique_id", "client", "material", "date"],
{
unique_id: Var("unique_id"),
client: Var("client"),
material: Var("material"),
date: Var("date"),
}
)
)
{
data: [
{
unique_id: 2,
client: 'you',
material: 'muslin',
date: Time("2021-04-13T21:42:42.437Z")
}
]
}
It's still an array of results, but each result is now an object with the appropriate field names.
Not too familiar with FQL, but I am somewhat familiar with SQL languages. Essentially, database languages usually treat all of your values as strings until they don't need to anymore. Instead, your query should use the string definition that FQL is expecting. I believe it should be OPEN or CLOSED in your case. You can simply have an if statement in java to determine whether to search for "OPEN" or "CLOSED".
To answer your second question, I don't know for FQL, but if that is what is returned, then your approach with a lamda seems to be fine. Not much else you can do about it from your end other than hope that you get a different way to get entries in API form somewhere in the future. At the end of the day, an O(n) operation in this context is not too bad, and only having to return a hundred or so orders shouldn't be the most painful thing in the world.
If you are truly worried about this, you can break up the request into portions, so you return only the first 100, then when frontend wants the next set, you send the next 100. You can cache the results too to make it very fast from the front-end perspective.
Another suggestion, maybe I am wrong and failed at searching the docs, but I will post anyway just in case it's helpful.
My index was failing to return objects, example data here is the client field:
"data": {
"status": "LIVRAISON",
"open": true,
"unique_id": 1001,
"client": {
"name": "TEST1",
"contact_name": "Bob",
"email": "bob#client.com",
"phone": "555-555-5555"
Here, the client field returned as null even though it was specified in the Index.
From reading the docs, here: https://docs.fauna.com/fauna/current/api/fql/indexes?lang=javascript#value
In the Value Objects section, I was able to understand that for Objects, the Index Field must be defined as an Array, one for each Object key. Example for my data:
{ field: ['data', 'client', 'name'] },
{ field: ['data', 'client', 'contact_name'] },
{ field: ['data', 'client', 'email'] },
{ field: ['data', 'client', 'phone'] },
This was slightly confusing, because my beginner brain expected that defining the 'client' field would simply return the entire object, like so:
{ field: ['data', 'client'] },
The only part about this in the docs was this sentence: The field ["data", "address", "street"] refers to the street field contained in an address object within the document’s data object.
This is enough information, but maybe it would deserve its own section, with a longer example? Of course the simple sentence works, but with a sub-section called 'Adding Objects to Fields' or something, this would make it extra-clear.
Hoping my moments of confusion will help out. Loving FaunaDB so far, keep up the great work :)
I'm using Ember Data canary build: 1.0.0-beta.8+canary.267214b9 together with a rails back-end and ActiveModelSerializer. My configuration on ember side looks like this:
App.ApplicationSerializer = DS.ActiveModelSerializer.extend()
App.ApplicationAdapter = DS.ActiveModelAdapter.extend
namespace: "api/v1"
App.Authentication = DS.Model.extend
provider: DS.attr('string')
user: DS.belongsTo('user')
App.User = DS.Model.extend
username: DS.attr('string')
email: DS.attr('string')
authentications: DS.hasMany('authentication')
I have working hasMany and belongsTo relation for a model that isn't side loaded. The JSON for the relation look like this:
{
objectA: {
property1: 'prop1',
property2: 'prop2',
objectB_ids: ['1','2']
}
}
At the moment I try to get a user model with multiple authentications to work. But there the authentications should be side loaded. It doesn't work for the following JSON:
JSON - not working
{
authentications: [{ id:1, provider:"google" }],
user: {
id: '1',
username: 'max',
email: 'max#examle.com',
authentication_ids:[1],
}
}
But it does work for this:
JSON - working
{
authentications: [{ id:1, provider:"google" }],
user: {
id: '1',
username: 'max',
email: 'max#examle.com',
authentications:[1],
}
}
The only useful information I found on the web is this SO question:
Serialising async hasMany relationships
Is this a bug in the DS.ActiveModelSerializer or did I miss some configuration?
EDIT 1:
In the docs of DS.ActiveModelSerializer you can find the following:
It has been designed to work out of the box with the activemodelserializers Ruby gem.
And the version with authentication_ids:[...] is the way, how the ActiveModelSerializers Ruby gem does it out of the box. So maybe it should be added?
I think you're confusing what ActiveModelSerializer does with other conventions of Ember Data. You're working second example is correct. This section describes the current expectation of JSON layout. The _ids is not present.
{
"post": {
"id": 1,
"title": "Rails is omakase",
"comments": ["1", "2"],
"user" : "dhh"
},
"comments": [{
"id": "1",
"body": "Rails is unagi"
}, {
"id": "2",
"body": "Omakase O_o"
}]
}
The ActiveModelSerializer adapter allows you to pass underscored keys in your JSON instead of camelcased keys. For example, if your user had a camelcased name:
App.User = DS.Model.extend
firstName: DS.attr()
Your JSON should look like this:
{
"user": {
"first_name": "kunerd"
}
}
Solved my issue and DS.ActiveModelSerializer works as expected and did accept _ids array for side loaded models.
My problem was, that I had overwritten my App.UserSerializer with that:
App.UserSerializer = DS.RESTSerializer.extend
# some custom logic
,but it has to be:
App.UserSerializer = App.ApplicationSerializer.extend
# some custom logic
Maybe someone has similar problems after changing from DS.RESTSerializer to DS.ActiveModelSerializer`
I am trying to load a Nested list onto my Sencha app. The problem is I am not familiar with it and i am not sure if the json file i am using is correct.
[
{
"text":[
{
"text":"1.1.1",
"leaf":true
}],
"text":[
{
"text":"1.1.1",
"leaf":true
}
]
}
]
This is my store code
//Defining the store for the Nested List
Ext.define('InfoImage.store.nestedListStore', {
extend: 'Ext.data.TreeStore',
requires: 'InfoImage.model.nestedListModel',
id:'nestedListStore',
config:{
//Calling the required model for the Work Item List
model : 'InfoImage.model.nestedListModel',
//Defining the proxy for the Work Item List to pull the data for the List
proxy : {
type : 'ajax',
url : 'app/model/data/list.json',
reader: {
type: 'json',
root: 'items'
}
},
autoLoad: true
}
});
and my main code is
Ext.define("InfoImage.view.nestedList", {
extend:'Ext.NestedList',
xtype:'nestedList',
id:'nestedList',
config:{
fullscreen:'true',
title:'Nested List',
xtype:'nestedList',
//displayField : 'text',
html:'Nested List on its way!!!',
store:'nestedListStore'
//itemTpl:'{text}'
}
});
The output thats displayed is [object object]. I dont know what is missing. ANy help is appreciated.
Firstly, your Json is a VALID json. Always check for valid json by pasting the json on jsonlint.com
Secondly, I see that you have commented out the
displayField:'text'
property. If you don't provide the displayField to the nestedlist, it won't come to know, which items from the data store to show in the list.
Probably, that's why you are getting the [object Object] as your o/p in the list.
Uncomment the above line and check.
It seems that your JSON cannot work with Ext.NestedList because text is a field of your Model and it should not be declared as rootProperty in your JSON file.
Firstly, assume that you have this model definition:
Ext.define('ListItem', {
extend: 'Ext.data.Model',
config: {
fields: ['text']
}
});
According to your data, your JSON file should look like this:
items: [
{
text: '1.1',
items: [
{ text: '1.1.1', leaf: true },
{ text: '1.1.2', leaf: true }
]
}
]
You have to add this config to your Store as well defaultRootProperty: 'items'
I´m using Sencha Touch 2 and have defined a model like this:
Ext.define('csx.model.Profile', {
extend: 'Ext.data.Model',
config: {
fields: [
{name: 'firstname',type: 'string'},
{name: 'lastname', type: 'string'},
],
hasMany: [
{model: 'Telephone', name: 'telephones'},
{model: 'Email', name: 'emails'},
],
},
doSomething: function () {
var firstname = this.get('firstname'); //returns null
var phones = this.telephones(); // throws a TypeError
},
});
Inside my function doSomething I am trying to access the values of the model and the models associated with the hasMany property. The getfunction only returns null and when I try to access the associated models I only get a TypeError saying that the object doesn´t have a telephones function. But when I look at the Sencha Touch 2 guides UsingModels on how to use models it says that the hasMany associations are accessible trough a function resembling the name I specified (telephones).
So how do I access the field values and the associated models inside a model instead?
You should change the model properties in the 'hasMany' config of your model to reflect the full name of your models:
hasMany: [
{model: 'csx.model.Telephone', name: 'telephones'},
{model: 'csx.model.Email', name: 'emails'},
],
That is, supposing you have defined your models Telephone and Email like this:
Ext.define('csx.model.Telephone', {
extend: 'Ext.data.Model',
config: {
fields: [
{name: 'number', type: 'string'},
],
});
Ext.define('csx.model.Email', {
extend: 'Ext.data.Model',
config: {
fields: [
{name: 'address', type: 'string'},
],
});
Then, if you create an instance of your 'Profile' model:
var profile = Ext.create('csx.model.Profile', {firstname:'John',lastname:'Deere'});
you can access your fields like this:
profile.get('firstname');
profile.get('lastname');
and the 'telephones' and 'emails' stores from the hasMany associations like this:
profile.telephones()
profile.emails()
The fact that your 'firstname' field had a null value is because you didn't instantiate your model. The fact that the 'telephones()' function was not created automatically as stated in the docs is because of the wrong 'model' configuration as explained in the beginning of this answer.
Hope this helps.