Querying TAFFYDB nested records - taffydb

I have created a data model using TAFFYDB. Some of the fields have nested records. I am facing difficulties querying and updating the nested records.
For example:
var friends = TAFFY([
{
"id":1,
"gender":"M",
"first":"John",
"last":"Smith",
"city":"Seattle, WA",
"comp":
[
{
"id":1,
"audience":"cavern"
},
{
"id":2,
"audience":"cottage"
}
]
},
{
"id":2,
"gender":"F",
"first":"Basic",
"last":"Smith",
"city":"Seattle, WA",
"comp":
[
{
"id":1,
"audience":"bush"
},
{
"id":2,
"audience":"swamp"
}
]
}
]);
Supposing I need to update any of the comp field's audience, how will I go about it?

With regards to queries:
When you have simpler nested arrays, you should be able to select specific records using the has and hasAll methods. However, there is an open issue that states neither of these methods work correctly. There are commits but since the issue has been left open, I assume they are not 100% fixed.
For for complex nested data, like your example, the only thing I found was this old mailing list conversation talking about some sort of find method. No such method seems to exist though nor is there any mention of it in the docs.
With regards to updates:
You should be able to update the "comp" data by passing in the modified JSON that goes with it (assuming you are able to get the data out of the db in the first place) into a normal update. However, there is an open bug showing that update does not work when record values are objects. So even if you were able to query the data and were able to modify it, you wouldn't be able to update a record anyway because of the bug. You can however do a remove and an insert.
Despite what I found above, I did some testing and found that you can update files by passing in objects. So this is a quick example of how to do a simple update:
// To show what TAFFYDB looks like:
console.log(friends().stringify());
"[{"id":1,"gender":"M","first":"John","last":"Smith","city":"Seattle, WA","comp":[{"id":1,"audience":"cavern"},{"id":2,"audience":"cottage"}],"___id":"T000003R000002","___s":true},{"id":2,"gender":"F","first":"Basic","last":"Smith","city":"Seattle, WA","comp":[{"id":1,"audience":"bush"},{"id":2,"audience":"swamp"}],"___id":"T000003R000003","___s":true}]"
// Get a copy of the comp file from the database for what you want to modify.
// In this example, let's get the **first** record matching people with the name "John Smith":
var johnsComp = friends({first:"John",last:"Smith"}).first().comp;
// Remember, if you want to use select("comp") instead, this will return an array of results.
// So to get the first result, you would need to do this despite there being only one matching result:
// friends({first:"John",last:"Smith"}).select("comp")[0];
// There are no nested queries in TAFFYDB so you need to work with the resulting object as if it were normal javascript.
// You should know the structure and you can either modify things directly, iterate through it, or whatever.
// In this example, I'm just going to change one of the audience values directly:
johnsComp[0].audience = "plains";
// Now let's update that record with the newly modified object.
// Note - if there are more than one "John Smith"s, then all of them will be updated.
friends({first:"John",last:"Smith"}).update({comp:johnsComp});
// To show what TAFFYDB looks like after updating:
console.log(friends().stringify());
"[{"id":1,"gender":"M","first":"John","last":"Smith","city":"Seattle, WA","comp":[{"id":1,"audience":"plains"},{"id":2,"audience":"cottage"}],"___id":"T000003R000002","___s":true},{"id":2,"gender":"F","first":"Basic","last":"Smith","city":"Seattle, WA","comp":[{"id":1,"audience":"bush"},{"id":2,"audience":"swamp"}],"___id":"T000003R000003","___s":true}]"
For a better targeted query or update (something that perhaps acts like a nested query/update), you can possibly try passing in a function. If you look at the docs, there is a simple example of this for update():
db().update(function () {this.column = "value";return this;}); // sets column to "value" for all matching records

I have an example, in this case i made an update to a nested field.
To acces the data you can do like this:
console.log( JSON.stringify(
data({'id':'489'}).get()[0].review[0][0].comments
))
This is an example how it works

Related

Phalcon Save: Can I update only specified columns if update?

I have a table with this columns:
param_id
val_value
val_flag
date_value
tmp_value
tmp_flag
And I have an array of elements, I always have param_id and date_value, but sometimes i have val columns and other times I have tmp columns.
I use $data->save() because sometimes I need to create a new register in db and other times I need to update the register with param_id and date_value.
The question is: Is there any way to do an insert/update but when is an update, only update tmp columns or val columns? I think a find First is my only option, but maybe there is another way.
Thank you.
[EDIT]
I'm trying with the whitelist but it does not work. Let me explain how the method works: I get a request to a web service, process the xml and generate an array of elements with the information collected, after processing these elements I have an array of elements of the class appropriate to save, but these may be new or existing and may contain tmp or val values, I have tried with this but I still change the values to null.
if ($medida->tipo == 'temporal'){
$whiteList = array('val_value','val_flag');
}else if ($medida->tipo == 'validado'){
$whiteList = array('tmp_value','tmp_flag');
}
$dato->save(null, $whitelist);
I do not have data of the post, I use null instead, I have also tried to use an array with the manual assignment of the data obtaining the same result.
Here are two options that can help you:
1) Use 'whitelist' for the save() method.
$obj->save($this->request->getPost(), ['tmp_1', 'tmp_2']);
More info in the documentation.
2) Use the 'Event Manager'.
The methods beforeCreate() and beforeUpdate() will be useful so you can decide which fields to use.
More info in the documentation.
Also if you really want phalcon phql to update only columns which changed you need to enable dynamic update.

Hydrating Database

I am new to learning and understanding how Hydration works, just wanted to point that out first. I'm currently able to Hydrate Select and Insert queries without any problems.
I am currently stuck on trying to Hydrate Update queries now. In my entity I have setup the get/set options for each type of column in my database. I've found that the ObjectProperty() Hydrator works best for my situation too.
However whenever I try to update only a set number of columns and extract via the hydrator I am getting errors because all the other options are not set and are returning null values. I do not need to update everything for a particular row, just a few columns.
For example in my DB Table I may have:
name
phone_number
email_address
But I only need to update the phone_number.
$entity_passport = $this->getEntityPassport();
$entity_passport->setPrimaryPhone('5551239876');
$this->getTablePassport()->update($this->getHydrator()->extract($entity_passport), array(
'employeeid' => '1'
));
This returns an error because setName() and setEmailAddress() are not included in this update and the query returns that the values cannot be null. But clearly when you look at the DB Table, there is data already there. The data that is there does not need to be changed either, only in this example does the PrimaryPhone() number.
I've been looking and reading documentation all over the place but I cannot find anything that would explain what I am doing wrong. I should note that I am only using Zend\Db (Not Doctrine).
I'm assuming I've missed something someplace due to my lack of knowledge with this new feature I'm trying to understand.
Perhaps you don't Hydrate Update queries... I'm sort of lost / confused. Any help would be appreciated. Thank you!
I think you're having a fundamental misconception of hydration. A hydrator simply populates an entity object from data (hydrate) and extracts data from an entity object (extract). So there are no separate hydrators for different types of queries.
In your update example you should first retrieve the complete entity object ($entity_passport) and then pass it to the TableGateway's update method. You would retrieve the entity by employeeid, since that's the condition you're using to update. So something like this:
$entity_passport = $passportMapper->findByEmployeeId(1);
$entity_passport->setPrimaryPhone('5551239876');
$this->getTablePassport()->update($this->getHydrator()->extract($entity_passport), array(
'employeeid' => $entity_passport->getId()
));
This is assuming you have some sort of mapper layer. Otherwise you could use your passport TableGateway (I assume that's what getTablePassport() returns, no?).
Otherwise, if you think retrieving the object is too much overhead and you just want to run the query you could use just a \Zend\Db\Sql\Sql object, ie:
$sql = new \Zend\Db\Sql\Sql($dbAdapter);
$update = $sql->update('passport')
->set(array('primary_phone' => $entity_passport->getPrimaryPhone()))
->where(array('employeeid' => $employeeId));
Edit:
Maybe it was a mistake to bring up the mapper, because it may cause more confusion. You could simply use your TableGateway to retrieve the entity object and then hydrate the returned row:
$rows = $this->getTablePassport()->select(array('employeeid' => 1));
$entity_passport = $this->getHydrator($rows->current());
[...]
Edit 2:
I checked your gist and I noticed a few things, so here we go:
I see that your getTablePassport indeed does return an object which is a subclass of TableGateway. You have already set up this class for it to use a HydratingResultset. This means you don't need to do any manual hydrating when retrieving objects using the gateway.
You also already implemented a Search method in that same class, so why not just use that? However I would change that method, because right now you're using LIKE for every single column. Not only is it very inefficient, but it will also give you wrong results, for example on the id column.
If you were to fix that method then you can simply call it in the Service object:
$this->getTablePassport->Search(array('employeeid' => 1));
Otherwise you could just implement a separate method in that tablegateway class, such as
public function findByEmployeeId($employeeId)
{
return $tableGateway->select(array('employeeid' => $employeeId));
}
This should already return an array of entities (or one in this specific case). P.S. make sure to debug and check what is actually being returned when you retrieve the entity. So print_r the entity you get back from the PassportTable before trying the update. You first have to make sure the retrieval code works well.

CoreData (MagicalRecord) returning the wrong values

To save the primary animal in a zoo I do this:
-(void)makePrimary:(Animal*)animal
Zoo *currentZoo = [Zoo findFirstByAttribute:#"zooId" withValue:self.currentZooId];
currentZoo.primaryAnimalId = animal.animalId;
[[NSManagedObjectContext defaultContext] saveToPersistentStoreAndWait];
DLog(#"primaryAnimalId: %d", currentZoo.primaryAnimalId.intValue); //logs "3"
}
After doing this, the primaryAnimalId for currentZoo logs as 3. I am using the Firefox SQLite Manager to verify this. ZPRIMARYANIMALID is 3 there as well.
Now, I navigate to a different section of the app, where I need to display the primary animal. So I do this:
-(Animal*)getPrimaryAnimalForCurrentZoo
{
Zoo *currentZoo = [Zoo findFirstByAttribute:#"zooId" withValue:self.currentZooId];
DLog(#"primaryAnimalId: %d", currentZoo.primaryAnimalId.intValue); //logs 2
Animal *primaryAnimal = [Animal MR_findFirstByAttribute:#"animalId" withValue:currentZoo.primaryAnimalId];
return primaryAnimal;
}
Much to my chagrin, I get "2" for the primaryAnimalId, and thus the wrong animal is returned. "2" is the previous value of the animal before I change it via makePrimary:. currentZooId is 0 in both methods.
What I don't understand is how I can get the wrong value back from Core Data when I can clearly see the correct value currently in the database (in SQLite Manager). How is this possible and how do I fix it?
Did you update self.currentZooId to reflect your changes?
Update:
So, you're asking for the first record in your data set here. The thing is,you're not specifying an order. That is, findFirst just asks the data store for all, and then just returns the first in the list (have a look at the code for more specifics). Since that list can be in any order, you're going to get whatever the data store gives you in at particular instance. What you want to do is add a sort parameter to findFirst to make sure you always get the item you expect. Look at the headers to find the correct firstFirst method with a sort parameter.

Mapping relationships with AFIncrementalStore

I'm in the process of integrating AFIncrementalStore into my project but I'm having a hard time figuring out how to make relationships work.
Let's say I have two models: Document and Page. A document has many pages (one-many). Each model has an inverse relationship to the other.
So I can do:
myDocument.pages
or
myPage.document
I'm trying to fetch all the pages that belong to a document. I have an AFIncrementalStore subclass and an AFHTTPClient subclass that implement the necessary methods.
I can see that the right REST URI is called (eg. /documents/:id/pages). I can also see that the pages get downloaded and stored into the pages table in the sql database.
The problem is the relationship between the two isn't made. When I look at the DB the foreign key column is empty.
Also if I do myDocument.pages it doesn't return anything.
Do I have to manually link these two when new data is fetched? I'm reading through the AFIncrementalStore source but nothing is sticking out.
Thoughts?
Alright, after a few hours or debugging and reading through AFIS line by line I got it to work, but it required some changes to the REST API.
Initially I had my API return something like the following (example):
GET /documents/12/pages
Response:
{
title: "My Title",
body: "Some body text ..."
}
But in order to make it work nicely with AFIS I had to add a reference to the Document in there, like so:
Response:
{
title: "My Title",
body: "Some body text ...",
document:
{
id: "12"
}
}
As long as in CoreData the relationship is properly setup, AFIS will loop through relationships and automatically find the relationship "document" and look for that key in the returned object. If your server key doesn't match your core data property then you can override one of the AFIncrementalStoreHTTPClient protocol methods to specify the right object.
This is working fine for now, I'll report back if something goes wrong.
Hope that helps someone else out.

How do I filter out attributes that shouldn't be in a POST request in Backbone?

I have a Rails application with the backbone-rails gem. Which works out fine but Backbone tries to send a request with all the attributes of the model. Is there a way I can filter out some of the attributes that will be POST'd on an update/new? This would work great for those virtual attributes and attributes that can't be mass assigned.
There is no harm in posting attributes that cannot be mass assigned. You will see a warning, but everything will work.
There are basically two ways of actually removing unwanted attributes. The first is to customize the Model's toJSON(). For example:
myModel = Backbone.Model.extend({
function: toJSON() {
var json = _.clone(this.attributes);
delete json.somethingIdontWant
delete json.somethingElse
return json
}
})
The second, and less clean way, is to explicitly pass the data in your call to Model.save(). If you are using the default Backbone.sync() method, then this data will be used instead. For example:
mything.save({
data: {just: "the stuff", that: "i want to post"}
})
You can probably figure out a way to generalize either of those approaches, depending on which one works for you.

Resources