Associating arbitrary number of variables in Firebase - firebase

I'm working on a project (using Firebase for the backend) where we want to associate an arbitrary amount of values with a particular key. We would like to delete/add values freely, and I'm having trouble thinking of a solution for this without using string concatenation, which is not ideal for deletion. Any ideas on a way to handle this?

You shouldn't have any trouble setting up your Firebase location as
--keys
----$key
------{someValue}: true
Where $key corresponds to whatever key you're storing, and then you'd just set children values with a boolean "true". Assuming JavaScript, you'd have something similar to:
var myKey = 'someKey';
var someVal = 'someVal';
var someOtherVal = 'foo';
var fb = new Firebase('https://xyz.firebaseio.com/keys/' + myKey);
fb.child(someVal).set(true);
fb.child(someOtherVal).set(true);
Now you can read all of these attributes from the fb reference easily enough.
To delete you could do the following:
var fb = new Firebase('https://xyz.firebaseio.com/keys/' + myKey);
fb.child(someVal).remove();

Related

Ho do I generate a value automatically in Firestore document?

My documents require an integer value which is used as index for queries. Each document contains an index field ( number) where I am assigning the values manually one by one. Maybe somewhere I can put something that stores the current index value and increments it and assigns it to the new document as it's index value whenever I create the new document.
There is no such feature in Cloud Firestore. You will need to come up with all the values yourself. The only thing that Firestore can generate for you automatically is a timestamp based on the server's sense of time.
I can think of two ways to handle this, though I don't know that you want to use an integer based index for your document id. If you delete one, your index is now off. And what about write failures? Race conditions? Etc. You may want to rethink your data structure & organization.
If using an integer is not required as the document id:
// Create a reference to a new document inside your collection
const ref = firebase.firestore().collection('myCollectionName').doc()
// Now you have an auto-generated document id you can use for your code
const myData = {...}
const setDoc = await firebase.firestore().collection('myCollectionName').doc(ref.id).set(myData)
If using an integer is required:
You'll want a separate collection/object that keeps track of the latest index so you don't run into collisions. Then, you'll want to increment that value to get the next index, and then use that as your id. This comes with inherent problems like...what if the data is bad as you try to enter it, but after you've incremented the value...etc.
// Collection: myIndex
// Doc: index
// Value: {lastIndex: 1}
const doc = await firebase.firestore().collection('myIndex').doc('index')
// You now have the last index value using:
const lastIndex = doc.val().lastIndex
const nextIndex = lastIndex + 1
const myData = {...}
// Now run a batched operation to write to both documents
const batch = firebase.firestore().batch()
// Update the index document
const indexUpdateRef = firebase.firestore().collection('myIndex').doc('index')
batch.update(indexUpdateRef, {lastIndex: nextIndex})
// Add your new myData document
const newDataRef = firebase.firestore().collection('myCollectionName').doc(nextIndex)
batch.set(newDataRef, myData)
// Commit the batch
await batch.commit()
As I said - I think this is a really bad idea and workflow, but it's doable. Lots missing from keeping this in sync as well.
In either case above...
You can take advantage of FieldValue.increment() to help auto increment your integer values, but that will add to more reads & writes, longer processing time, and higher charges for all of that. Which is why I started with and maintain that you should probably rethink your data structure or consider a RDB if you want autoincremented indices.

Best way for displaying total Pages for a datasource in Appmaker

I have a Google drive table data source which stores list of open positions. Now in the data source I've set "Query per size" field to 10 so that I can get 10 records per page. I've added a Pager as well to show pagination.
My query is I want to display like "Page 1 of X" to my end users and this X will vary based on certain search filters. What will the best way to achieve this in Appmaker?
I've tried counting total records in a data source as per below code but every time updating that with the search criteria and recounting it is not a proper solution.
//Server side
var newQuery = app.models.Company.newQuery();
var records = newQuery.run();
var totalCount =0;
for(var i=0;i<records.length;i++)
{
totalCount=totalCount+1;
}
return totalCount;
In case you don't have any filters in your table your server code can be as simple as
// Server script
function getPagesCount(pageSize) {
var recordsCount = app.models.MyModel.newQuery().run().length;
var pagesCount = Math.ceil(recordsCount / pageSize);
return pagesCount;
}
As an alternative you can consider creating Calculated Model with a single field PagesCount.
In case you have some filters associated with the table then you'll need to run the query for the pages number with exact same filters.
Most likely the entire setup will not work effectively with Drive Tables since there is no way to query records number without querying records themselves. With Cloud SQL data backend one can create Calculated SQL Model with lightweight native SQL query (here :PageSize is query parameter which should be equal to the query.limit of the actual datasource):
SELECT
Ceil(COUNT(1) / :PageSize) AS RecordsNumber
FROM
TableName
WHERE
...
I've achieved this using Calculated Model as suggested by Pavel.
Steps :
Create a calculated data source with one field count.
In that data source add one parameter searchQuery. This will contain users filter going forward. Currently I have only one search query in which user can search many things. So I've added one parameter only.
In this data source add following server script.
Code:
// Server script
function getTotalRecords(query) {
var receivedQuery = query.parameters.searchQuery;
// console.log('Received query:' + query.parameters.searchQuery);
var records = app.models.Company.newQuery();
records.parameters.SearchText = query.parameters.searchQuery;
if(receivedQuery !== null) {
records.where = '(Name contains? :SearchText or InternalId contains? ' +
':SearchText or LocationList contains? :SearchText )';
}
var recordsCount = records.run().length;
var calculatedModelRecords = [];
var draftRecord = app.models.RecordCount.newRecord();
draftRecord.count = ''+recordsCount;
calculatedModelRecords.push(draftRecord);
return calculatedModelRecords;
}
.
On the Appmaker page bind a label with this data source.
On search query/your filter applied event add following code which Reload this data source and assign value to Parameter.
// Client script
function updateRecordCount(newValue) {
var ds = app.datasources.RecordCount;
ds.query.parameters.searchQuery = newValue;
ds.unload();
ds.load();
}

DynamoDb - .NET Object Persistence Model - LoadAsync does not apply ScanCondition

I am fairly new in this realm and any help is appreciated
I have a table in Dynamodb database named Tenant as below:
"TenantId" is the hash primary key and I have no other keys. And I have a field named "IsDeleted" which is boolean
Table Structure
I am trying to run a query to get the record with specified "TenantId" while it is not deleted ("IsDeleted == 0")
I can get a correct result by running the following code: (returns 0 item)
var filter = new QueryFilter("TenantId", QueryOperator.Equal, "2235ed82-41ec-42b2-bd1c-d94fba2cf9cc");
filter.AddCondition("IsDeleted", QueryOperator.Equal, 0);
var dbTenant = await
_genericRepository.FromQueryAsync(new QueryOperationConfig
{
Filter = filter
}).GetRemainingAsync();
But no luck when I try to get it with following code snippet (It returns the item which is also deleted) (returns 1 item)
var queryFilter = new List<ScanCondition>();
var scanCondition = new ScanCondition("IsDeleted", ScanOperator.Equal, new object[]{0});
queryFilter.Add(scanCondition);
var dbTenant2 = await
_genericRepository.LoadAsync("2235ed82-41ec-42b2-bd1c-d94fba2cf9cc", new DynamoDBOperationConfig
{
QueryFilter = queryFilter,
ConditionalOperator = ConditionalOperatorValues.And
});
Any Idea why ScanCondition has no effect?
Later I also tried this: (throw exception)
var dbTenant2 = await
_genericRepository.QueryAsync("2235ed82-41ec-42b2-bd1c-d94fba2cf9cc", new DynamoDBOperationConfig()
{
QueryFilter = new List<ScanCondition>()
{
new ScanCondition("IsDeleted", ScanOperator.Equal, 0)
}
}).GetRemainingAsync();
It throws with: "Message": "Must have one range key or a GSI index defined for the table Tenants"
Why does it complain about Range key or Index? I'm calling
public AsyncSearch<T> QueryAsync<T>(object hashKeyValue, DynamoDBOperationConfig operationConfig = null);
You simply cant query a table only giving a single primary key (only hash key). Because there is one and only one item for that primary key. The result of the Query would be that still that single item, which is actually Load operation not Query. You can only query if you have composite primary key in this case (Hash (TenantID) and Range Key) or GSI (which doesn't impose key uniqueness therefore accepts duplicate keys on index).
The second code attempts to filter the Load. DynamoDBOperationConfig's QueryFilter has a description ...
// Summary:
// Query filter for the Query operation operation. Evaluates the query results and
// returns only the matching values. If you specify more than one condition, then
// by default all of the conditions must evaluate to true. To match only some conditions,
// set ConditionalOperator to Or. Note: Conditions must be against non-key properties.
So works only with Query operations
Edit: So after reading your comments on this...
I dont think there conditional expressions are for read operations. AWS documents indicates they are for put or update operations. However, not being entirely sure on this since I never needed to do a conditional Load. There is no such thing like CheckIfExists functionality as well in general. You have to read the item and see if it exists. Conditional load will still consume read throughput so your only advantage would be only NOT retrieving it in other words saving the bandwith (which is very negligible for single item).
My suggestion is read it and filter it in your application layer. Dont query for it. However what you can also do is if you very need it you can use TenantId as hashkey and isDeleted for range key. If you do so, you always have to query when you wanna get a tenant. With the query you can set rangeKey(isDeleted) to 0 or 1. This isnt how I would do it. As I said, would just read it and filter it at my application.
Another suggestion thing could be setting a GSI on isDeleted field and writing null when it is 0. This way you can only see that attribute in your table when its only 1. GSI on such attribute is called sparse index. Later if you need to get all the tenants that are deleted (isDeleted=1) you can simply scan that entire index without conditions. When you are writing null when its 0 dynamoDB wont put it in the index at the first place.

Can we use Firebase update function for updating priority as well

I am updating some properties of my object in firebase using the update method. As part of the update call, can i also update the priority of my object?
I have a collection of objects stored in firebase. I am using the Javascript sdk to interact with my data. Whenever i update any property of an object, i set the priority of the object to Firebase.ServerValue.TIMESTAMP constant.
Now, i want to start storing the priority of each object in a property called UpdatedOnTimestampTs defined on the object. Whenever i update any property i will also update UpdatedOnTimestampTs with TIMESTAMP constant.
Now the problem is, I am unable to find a way to call the update function along with priority (no updateWithPriority like setWithPriority). So if I update the UpdatedOnTimestampTs and priority in two different operations using the TIMESTAMP constant i may (or will) end up with a different values.
How can i address this?
I know I'm way late on an answer here, but I'm just coming across the need for something similar now and updateWithPriority doesn't exist (nor a way to easily view/edit priorities in the Forge).
You can include the specially-named key, .priority in your update and it ought to achieve the desired effect.
The .priority key is documented in the Firebase REST API, but can be used in the other libraries.
So, in Javascript, the following would work if you only wanted to update the priority (which, there is setPriority for that):
var fb = new Firebase("http://a_firebase.firebaseio.com");
var ref = fb.child("/path/to/child");
var data = { ".priority": (new Date()).getTime() } //timestamp, as you mentioned
ref.update(data);
If you wanted to change the priority while you're updating other properties, which is what you had asked, just include it with the data you want to update:
var fb = new Firebase("http://a_firebase.firebaseio.com");
var ref = fb.child("/path/to/child");
var data = {
"property1": "updated_val",
"property2": "updated_val2",
".priority": (new Date()).getTime(), //timestamp, as you mentioned
}
ref.update(data);
EDIT: To be clear, this will update the priority at the location of the child reference (i.e. /path/to/child) not the priorities of the individual children that are being updated (i.e. priorities of property1 and property2). If you wanted to include a priority for the children that are being updated, and the children are leaf nodes (i.e. string, number, boolean), then you can use the specially-named .value key in conjunction with the .priority key to do so.
The following example shows how to update the priority for the root location that the update is occurring from (i.e. path/to/child) as well as for both the children that are being update (i.e. property1 and property2). Obviously, you could selectively pick which priorities to update if you didn't want to update them all.
var fb = new Firebase("http://a_firebase.firebaseio.com");
var ref = fb.child("/path/to/child");
var newPriority = (new Date()).getTime(); //timestamp, as you mentioned
var data = {
"property1": {".value": "updated_val", ".priority": newPriority},
"property2": {".value": "updated_val2", ".priority": newPriority},
".priority": newPriority
}
ref.update(data);

How do I do a Global Update in Firebase?

I'm trying to wrap my head around firebase, and one issue I can't seem to work out is how mass updates occur.
Lets say I have 1,000 users and they all have "user_role": "member"
And I decide to update all 1,000 users to "user_role":"paid-member"
in SQL I can just do UPDATE users SET user_role = 'paid-member' ... So what's the Firebase equivalent?
Or more importantly, how would I accomplish something like this in firebase...
UPDATE users SET user_role = 'paid-member' WHERE user_role = 'member'
Unfortunately, it's not possible to do this without knowing the list of users beforehand. If you had all the usernames in an array, you could do something like this:
var usernames = [...];
for (var i = 0; i < usernames.length; i++) {
var ref = new Firebase("https://myfirebase.firebaseio.com/users/" + usernames[i]);
ref.child("user_role").set("paid-member");
}
You'll have to retrieve the list of usernames first using a child_added or value event handler.

Resources