having a list in firebase,
List
key1
item
key2
item
key3
item
I need to get the list key count, which is in the above case = 3 for this list,
I tried the counter property but get nothing
any idea ?
The most basic Angular 2 way would be to just iterate over the keys and to increment a counter as you do it.
this.af.database.list(`list`)
.subscribe(keys => {
keys.forEach(key => this.counter = this.counter + 1)
})
A valid objection to this method is that in iterating over all these keys gets you all their children's data. Firebase's shallow query uri parameter allows you to iterate over the keys without loading the data from their children. To do this in Angular 2 you'll have to use the Http module. Here's a rough approximation of that.
import { Http, Response } from '#angular/http';
constructor(private af: AngularFire, private http: Http) { }
countKeys() {
this.http.get('https://your-database.firebaseio.com/keys.json?shallow=true')
.subscribe(response => {
Object.keys(response.json()).forEach(key => this.counter = this.counter + 1))
}
}
Related
I'm new to ember and trying to figure out how to unit test, using sinon, the sessionStorage based on url parameters when that page is visited. I've tried a few things but still can't get the desired result. It passes even if I change the 'sessionValue' without editing the query param.
Thank you in advance.
ember component
beforeModel(transition) {
//transition will contain an object containing a query parameter. '?userid=1234' and is set in the sessionStorage.
if(transition.queryparam.hasOwnProperty('userid')){
sessionStorage.setItem('user:id', transition.queryparam)
}
}
Ember test
test('Session Storage contains query param value', async assert => {
let sessionKey = "user:id";
let sessionValue = "1234"
let store = {};
const mockLocalStorage = {
getItem: (key) => {
return key in store ? store[key] : null;
},
setItem: (key, value) => {
store[key] = `${value}`;
},
clear: () => {
store = {};
}
};
asserts.expect(1);
let spy = sinon.spy(sessionStorage, "setItem");
spy.calledWith(mockLocalStorage.setItem);
let stub = sinon.stub(sessionStorage, "getItem");
stub.calledWith(mockLocalStorage.getItem);
stub.returns(sessionValue);
await visit('/page?userid=1234');
mockLocalStorage.setItem(sessionKey, sessionValue);
assert.equal(mockLocalStorage.getItem(sessionKey), sessionValue, 'storage contains value');
})
Welcome to Ember!
There are many ways to test, and the below suggestion is one way (how I would approach interacting with the SessionStorage).
Instead of re-creating the SessionStorage API in your test, how do you feel about using a pre-made proxy around the Session Storage? (ie: "Don't mock what you don't own")
Using: https://github.com/CrowdStrike/ember-browser-services/#sessionstorage
Your app code would look like:
#service('browser/session-storage') sessionStorage;
beforeModel(transition) {
// ... details omitted ...
// note the addition of `this` -- the apis are entirely the same
// as SessionStorage
this.sessionStorage.setItem('user:id', ...)
}
then in your test:
module('Scenario Name', function (hooks) {
setupApplicationTest(hooks);
setupBrowserFakes(hooks, { sessionStorage: true });
test('Session Storage contains query param value', async assert => {
let sessionKey = "user:id";
let sessionValue = "1234"
let sessionStorage = this.owner.lookup('browser/session-storage');
await visit('/page?userid=1234');
assert.equal(sessionStorage.getItem(sessionKey), '1234', 'storage contains value');
});
})
With this approach, sinon isn't even needed :)
I am looking to create a query-builder for my Amplify Datastore.
The function should process an an array of conditions, that need to be applied to the query and return the according Predicate.
This is easily done, if there is only one filter, but I would like to be able to process any amount of filters.
My goal is to be able to write the queries like so:
Datastore.query(Post, *queryBuilder(filters)*)
Where I can pass an array of filters with a filter looking like this:
filter = {
connector: 'or' |
property: rating
predicate: 'gt'
value: 4
}
and the query builder returns the Predicate in the below mentioned format.
I have tried to chain and return multiple functions in the query builder, but I was not able to figure out a pattern for how to create the correct predicate function.
For reference, this is how queries are built according to the docs: https://docs.amplify.aws/lib/datastore/data-access/q/platform/js#predicates
const posts = await DataStore.query(Post, c => c.rating("gt", 4));
and for multiple conditions:
const posts = await DataStore.query(Post, c =>
c.rating("gt", 4).status("eq", PostStatus.PUBLISHED)
);
Let's say we have the model:
type Post #model{
id: ID!
category: String
city: String
content: String
}
And we want to query & filter by city and category by a dynamic amount of variables. Then we can make a function as such on our script:
const fetchData = async props => {
/*
More configurable wrapper for Datastore.query calls
#param props: {model: Model, criteria: [{fieldId, predicate, value}]}.
*/
try {
let criteria;
if (props.criteria && typeof props.criteria === 'object') {
criteria = c => {
props.criteria.forEach(item => {
const predicate = item.predicate || 'eq';
c[item.fieldId](predicate, item.value);
});
return c;
};
} else {
criteria = props.criteria;
}
return await DataStore.query(props.model, criteria);
} catch (e) {
throw new Error(e);
}
}
So now if we want to execute this we can pass the parameters:
// where Post = models.Post
const myResult = fetchData({model: Post, criteria: [
{ fieldId: 'category',
predicate: 'eq',
value: 'news'
},
{
fieldId: 'city',
predicate: 'eq',
value: 'SomeCityName'
}]
})
Unfortunately I do not know of a way to also query linked relationships as you would using a direct graphQL api query while using DataStore and this method I presented only uses implicit AND between criteria.
I don't know if this has changed since you asked the question but, based on the documents, it looks like multiple conditions have an implicit and, but you can explicitly chain them with or/and/not:
const posts = await DataStore.query(Post, c => c.or(
c => c.rating("gt", 4).status("eq", PostStatus.PUBLISHED)
));
My data like ['2', '13', '13A', '14-1'], How can i get the correct order with filter? Thanks everyone.
IIUC, you are storing numbers (2, 10, etc.) as strings ('2', '10', etc.) in your database.
LoopBack relies on the database to perform ordering (sorting).
Here are few things to try:
Modify your model definition to store the property as number. LoopBack is smart and will coerce string values provided by the user (REST API clients) to numbers before they are stored in the database. This would be my preferred solution, because it does not require any complex code in your application and preserves performance.
Depending on the database you are using, it may be possible to configure it to treat string values as numbers for sorting. This is not LoopBack specific, I can't really help you with that.
As a last resort, you can sort the records in-memory, LoopBack is already doing that for location-based queries when the database does not support them. The idea is to tell the database to return all records matching the filter criteria and then apply order, limit, skip and other options inside your Node.js process. Please note this comes with a severe performance hit and will work only for reasonably-sized data.
As for the 3rd option: implementation wise, you need to override find method in your model class.
// common/models/my-model.js
module.exports = function(MyModel) {
MyModel.on('modelRemoted', () => {
MyModel._findRaw = MyModel.find;
MyModel.find = findWithCustomSort;
});
}
function findWithCustomSort(filter, options, cb) {
if (!cb) {
if (typeof options === 'function') {
cb = options;
options = undefined;
} else if (!options && typeof filter === 'function') {
cb = filter;
filter = undefined;
}
}
const dbFilter = {
where: filter.where,
include: filter.include,
fields: filter.fields,
};
if (cb) {
this._findRaw(dbFilter, options, (err, found) => {
if (err) return cb(err);
else cb(null, sortResults(filter, found))
});
} else {
return this._findRaw(dbFilter, options)
.then(found => sortResults(filter, found));
}
}
function sortResults(filter, data) {
// implement your sorting rules, don't forget about "limit", "skip", etc.
}
UPDATE
Is there a way to use sql for query in custom method?
Yes, you can execute any SQL by using MyModel.dataSource.connector.execute function, see Executing native SQL. There is one catch though - this method is callback based, you cannot use Promise API or async/await.
const idValue = 1;
MyModel.dataSource.connector.execute(
'SELECT * FROM MyModel WHERE id=?',
[idValue]
(err, results) => {
if (err) console.error('query failed', err);
else console.log('found data', results);
});
I have created a service where I get all the elements of my database:
Service
getElements() {
return (this.eleList= this.firebase.list("elements"));
}
Component
eleList: Element[];
getBets() {
return this.databaseService
.getElements()
.snapshotChanges()
.subscribe(item => {
this.eleList= [];
item.forEach(element => {
let x = element.payload.toJSON();
x["$key"] = element.key;
this.eleList.push(x as Element);
});
});
}
With these two methods what I do is to store all my elements in this.eleList.
I would like to create a new method, named filterByName(name), where I would update this.eleList to an array which contains only the ones that contain namein the object, for example, this.eleList[1].name
I do not know if Firebase provides a way to short it, or I need to use Javascript/Typescript for it.
Firebase takes full advantage of the observables and async pipes.
You should take advantage of that :
eleList$ = new Subject();
getElements() {
this.this.firebase.list("elements")
.pipe(take(1))
.subscribe(list => this.eleList$.next(list));
}
getBets() {
this.databaseService
.getElements()
.snapshotChanges()
.pipe(
map(item => items.map(element => ({
...element.payload.toJSON(),
'$key': element.key
})))
)
.subscribe(elements => this.eleList$.next(list));
}
Now for a sorted list :
sortedList$ = this.eleList$.pipe(
map(elements => elements.filter(element => !!element.name))
);
I'm trying to write a GraphQL server that queries an AWS DynamoDB store.
For the purpose of this question, the GraphQL part is irrelevant except for the fact that the arguments come in the form:
{
key1: value1,
key2: value2,
key3: value3
}
These key/value pairs are used to query against the database. Strict equality only, nothing fancy. All arguments are optional.
Here's what I came up with:
import { DynamoDB } from 'aws-sdk';
function constructParams(tableName, fields = {}) {
const keys = Object.keys(fields);
if (keys.length === 0) {
return {
TableName: tableName,
};
}
const filters = keys.map(key => `#${key} = :${key}`);
const attributeNames = keys.reduce((memo, key) => Object.assign(memo, {
[`#${key}`]: key,
}), {});
const attributeValues = keys.reduce((memo, key) => Object.assign(memo, {
[`:${key}`]: fields[key],
}), {});
return {
TableName: tableName,
FilterExpression: filters.join(' AND '),
ExpressionAttributeNames: attributeNames,
ExpressionAttributeValues: attributeValues,
};
}
function query(tableName, fields = {}) {
const docClient = new DynamoDB.DocumentClient({ region: 'ap-southeast-2' });
const params = constructParams(tableName, fields);
return new Promise((resolve, reject) => {
docClient.scan(params, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data.Items);
}
});
});
}
export default query;
This works fine. But goodness it's cumbersome. My constructParams function feels like so much unnecessary boilerplate. Such a function would be completely unnecessary with MongoDB.
Put it this way, why does it feel like I'm writing SQL for a NoSQL database?
What you've written is indeed the shortest code possible for what you are trying to achieve, because that's how DynamoDB expects filtering information for a Scan.
You can also consider moving filtering from DynamoDB-side to client-side, if it helps decrease code complexity. This won't change the consumed read capacity units, so your read cost will remain the same.
Sadly, what I'm trying to achieve just isn't possible with DynamoDB.
The scan method superficially offers this functionality. But it really just returns the entire table set and then filters afterward. Once your data set reaches a certain size, scan ceases to work.
The query method is what you really should be using. But it won't permit queries on more than two keys. This is simply appalling. It is profoundly inadequate for a so called database.
In other words, the DynamoDB API isn't just syntactically primitive. It's also functionally primitive.