Upserting with codeigniter's mongo-db library - php

I am using a mongodb-codeigniter library by Alex Bilbie.
I have a collection called "user_visits". I insert into collection like below.
$query = $this->mongo_db->insert('user_restaurant_visits',[
'_id' => 1,
'user_id' => $user_id,
'pages_visited' => [
'page_id' => $restaurant_id
'visited' => [
'deal' => $purchase_deal,
'ordered' => $purchased_item
]
]
]);
Now all I would want to know is,
I would like to add the documents by "upsert=true" boolean flag as specified in here which will insert if the field is not present and update if the field is present. And I could not find a way to do so in the library I use! Am I misguided?
Is this a good way? is there anything wrong in the way I have organized the fields (I mean Schema as we say in RDBMS). I specifically ask this because, some feel nested arrays are better than embedded documents. like philnate says here in his answer and comments
If I'd want to upsert, increment a field, and addToSet, in the same query, is this possible with the library I currently use?
Let me know if I miss something, I can clarify in comments. I am totally new to NoSQL DBs.
I am sorry if that looked amaeturish.

Answer of one of your question :
How to Use "Upsert" in CodeIgniter while updating :
// Where Condition, if any
$this->mongo_db->where(array('condition_key' => 'condition_value'));
// Update Data Array
$this->mongo_db->set($mongoArray);
// Set Options
$option = array('upsert' => true);
// Call Update Function
$this->mongo_db->update('Collection_Name', $option);
I hope this will help you :)

Related

How to sync multiple values of the same Attribute in Laravel?

I developing an eCommerce ( with Multiple Product Attributes feature ) website using Laravel 5.4. Everything is working fine. But When I try to sync multiple values of the same Attribute in Pivot table. Laravel ignores the duplicate pares. For example, I've an Attribute called "Network" which has 3 values: 2G, 3G, 4G. A mobile supports 3G and 4G network. I want to sync 3G and 4G value in database. Laravel ignores one of them.
Products Table:
ID - Product Name
1 - Test Mobile
Attributes Table
ID - AttributeName
1 - Network
AttributeValues Table
ID - AttributeID - AttributeValue
1 - 1 - 2G
2 - 1 - 3G
3 - 1 - 4G
ProductAttributes Table
ID - AttributeID - ProductID - AttributeValue
1 - 1 - 1 - 3G
1 - 1 - 1 - 4G
I want to store the Product Attributes in "ProductAttributes" table something like that. But Laravel Ignore one of them.
I am saving the data like that:
$product = Product::create([
'name' => 'Test Mobile'
]);
$product->attributes()->sync([
1 => ['AttributeValue' => '3G'],
1 => ['AttributeValue' => '4G']
]);
Any suggestions, Ideas?
I know this is two years late, but I was dealing with the same issue today, and figured I may leave the solution here, in case anyone looks for it in the future. If you use your original code:
$product->attributes()->sync([
1 => ['AttributeValue' => '3G'],
1 => ['AttributeValue' => '4G']
]);
the second item in the array will overwrite the first one, so in the end, there will only be a "4G" entry in the database. This is not really a laravel issue, it is how PHP associative arrays are implemented - you basically cannot have two items in the array on the same index.
There are actually two ways to solve this issue
1) first one is very inefficient, but it is functional. I am leaving it here only for the record, because that was the original way I solved the issue. Instead of your code, you would need something like this
$product->attributes()->sync([]); // empty relation completely
// add first item
$product->attributes()->syncWithoutDetaching([
1 => ['AttributeValue' => '3G'],
]);
// add second item without detaching the first one
$product->attributes()->syncWithoutDetaching([
1 => ['AttributeValue' => '4G'],
]);
this is EXTREMELY inefficient, because it needs one query to delete all existing data from the relation, and then add new items one by one. You could also run the syncWithoutDetaching inside a loop, and overall inefficiency would greatly depend on how many items you need to sync.
2) the first solution was not good enough, so I kept digging and experimenting, and figured out a way how to make this happen. Instead of putting your items on specific index in the array, you can send array without specific indexes given, and put the ID in the array itself. Something like this
$product->attributes()->sync([
['AttributeID' => 1, 'AttributeValue' => '3G'],
['AttributeID' => 1, 'AttributeValue' => '4G']
]);
by doing it this way, you can actually send two items with the same AttributeID to the sync() method, without one overwriting the other one
For now,
$product->attributes()->sync([]);
$product->attributes()->sync([
['AttributeID' => 1, 'AttributeValue' => '3G'],
['AttributeID' => 1, 'AttributeValue' => '4G']
]);
Looking at Becquerel's response of April 5th, response #2, and in reading the source code of the sync() method in /laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php (this is, I think, Laravel 2.4), I do not see (or, cannot identify) code that would support this "array of array" functionality. In fact, here's the source-code of the entire method:
public function sync($ids, $detaching = true)
{
$changes = [
'attached' => [], 'detached' => [], 'updated' => [],
];
if ($ids instanceof Collection) {
$ids = $ids->modelKeys();
}
// First we need to attach any of the associated models that are not currently
// in this joining table. We'll spin through the given IDs, checking to see
// if they exist in the array of current ones, and if not we will insert.
$current = $this->newPivotQuery()->pluck($this->otherKey);
$records = $this->formatSyncList($ids);
$detach = array_diff($current, array_keys($records));
// Next, we will take the differences of the currents and given IDs and detach
// all of the entities that exist in the "current" array but are not in the
// the array of the IDs given to the method which will complete the sync.
if ($detaching && count($detach) > 0) {
$this->detach($detach);
$changes['detached'] = (array) array_map(function ($v) {
return is_numeric($v) ? (int) $v : (string) $v;
}, $detach);
}
// Now we are finally ready to attach the new records. Note that we'll disable
// touching until after the entire operation is complete so we don't fire a
// ton of touch operations until we are totally done syncing the records.
$changes = array_merge(
$changes, $this->attachNew($records, $current, false)
);
if (count($changes['attached']) || count($changes['updated'])) {
$this->touchIfTouching();
}
return $changes;
}
Now, Laravel is full of dependency-injection and other Magick (apparently similar to Perl's notion of "map?"), but I don't see anything here that will do what Becquerel says it will. And, generally speaking, Laravel's documentation really doesn't come out and say what it does do with repeated values in many-to-many relationships, or if it is cognizant of them at all.
I also notice that the implementation of the method as shown above is actually very similar to the "alternative #1" that he cites as "extremely inefficient." It seems to classify the keys into three buckets ... never seeming to allow for repetition, by my reading ... and then to perform insert, update and delete operations as needed. (No SQL "transactions" anywhere, that I can see, which also surprises me very much ... are they "magickally" there somehow?)
I simply can't determine if Laravel, when presented with more than one occurrence of a value in the (set of related records in the) foreign table, does anything sensible like return them as an array.
I've made this very long-winded response in hopes of eliciting further comments. Thanks.
Use the sync method to store/update the data in the Controller using the relationship :
$product-> attribute()->sync($request->input('product_ids', []));
sync() function in Laravel automatically get reads of duplicates. You can force it with
$product->attribute()->syncWithoutDetaching([
1 => ['AttributeValue' => '3G'],
1 => ['AttributeValue' => '4G']
]);
Good luck mate!

how to track every actions on records with ElasticSearch?

How we can insert unique record to our types with ElasticSearch, for example 3 user updated the same record and we should store 3 record in our index with 3 updated_by and updated_at field. I'm using Elasticquent and now for example when i want to update a record from course table after that i do the bellow:
for example user with id 1 update the record.
$course = new Course();
......
.....
$course->save();
$course->updated_at = '2016-01-06'
$course->updated_by = '1';
$course->addToIndex();
and then user with id 2 update the same record
but inside index the document that created in first user update action is rewrite by the second user action.
I dont understand why you need elasticsearch for this purpose. You can easily maintain a course_history table : id, course_id, updated_by, updated_at. Then to find any history of course editions, you can simply make a join. If you need document store for some performance benefits, I suggest choose some different one as elasticsearch is better for search and aggregations. It is not made for a document store. However, if this is really your requirement, then I would suggest maintaining a history table and then use associations with elasticsearch to make it searchable.
In such case it is better to store multiple documents. What you are trying to do cannot be done in a single document easily. An easier, and IMO better, solution is to store multiple documents. So with every update you sore new document. Recorc ID will be one of the fields you can search for afterwards.
[
{
"_index": "logs",
"_type": "log_record",
"record_id": 1,
"updated_by": 100,
"updated_at": "2016-01-01T00:00:00.000Z"
},
{
"_index": "logs",
"_type": "log_record",
"record_id": 1,
"updated_by": 200,
"updated_at": "2016-01-01T01:00:00.000Z"
}
]
Now you can easily search for what you need and deleted specific records if necessary.
You don't really care about the decument ID here. You can let ES generate it for you. All you need is to store the correct record ID inside document.
Edit:
Here is how it may look in Elasticquent (please notice I don't know details of Elasticquent).
Lets say you have a class like CourseChageLog with the following mapping:
class CourseChageLog extends Eloquent
{
use ElasticquentTrait;
public $record_id;
public $updated_by;
public $updated_at;
protected $mappingProperties = array(
'record_id' => array(
'type' => 'integer',
'index' => 'not_analyzed'
),
'updated_by' => array(
'type' => 'integer',
'index' => 'not_analyzed'
),
'updated_at' => array(
'type' => 'date',
'index' => 'not_analyzed'
),
);
}
CourseChageLog::putMapping();
The point is you need to ask the correct question. You don't ask how to store something. You ask what question you want to ask Elasticsearch and then you create an appropriate document form it. Don't create documents and then wonder if they can answer your question.

How to create autoincrement field in Mandango

I'm using in my project mandago ODM for mongodb.
http://mandango.org
I know that in MongoDb you can define JS functions on fields but I don't know how to do it with mandango. I create autoincrement ID field in more clever way than getting last record then incrementing it in PHP and saving in db. So my question is how to create an autoincrement field in mandago ODM?
I'd put some code but there's really nothing to put just pure code classes generated by Mondator.
After some research I have found out how to solve problem.
You need to add in your model mapping file 'idGenerator' => 'sequence'
in my case it looks as following:
$modelMapping = array(
'Model\User' => array(
'isEmbedded' => false,
'idGenerator' => 'sequence',
...
It will autoincrement _ID key in your document.

Manually packaging data for CakePHP's save()

I'm trying to package up some data for the save() function in cakephp. I'm new to PHP, so I'm confused about how to actually write the below in code:
Array
(
[ModelName] => Array
(
[fieldname1] => 'value'
[fieldname2] => 'value'
)
)
Thank you!
To answer your question, you can create the array structure you need, and save it, by doing this:
<?php
$data = array(
'ModelName' => array(
'fieldname1' => 'value',
'fieldname2' => 'value'
)
);
$this->ModelName->save($data);
?>
Please note:
Based on what you've written above in your comments it looks like you're not keeping to the CakePHP conventions. It's possible to do things this way but you'll save yourself a lot of time and trouble if you decided to stick with the CakePHP defaults as much as possible, and only do it your own way when you have a good reason to.
A couple things to remember are:
Model names should be singular. This means that your model should be called Follower instead of Followers.
The model's primary key in the database should be named just id, not followers_id, and should be set as PRIMARY KEY and AUTO_INCREMENT in your database.
If you decide not to follow the conventions you'll probably find yourself scratching your head, wondering why things aren't working, every step of the way. Try having a look at the CakePHP documentation for more details.
I think you need to do like below:
$this->Followers->create();
$this->data['Followers']['user_id'] = $user_id;
$this->data['Followers']['follower_id'] = $follower_id; // If it is primary and auto increment than you don't need this line.
$this->Followers->save($this->data)

Saving with HABTM in CakePHP

I am creating multiple associations in one go and there are a few problems when it comes to saving.
I have the following code:
<?php
foreach($userData as $user) {
$data = array('User' => array('id' => $user['id']), 'Site' => array('id' => $user['site_id']));
$this->User->save($data);
}
?>
I have experimented with formatting the data array in different ways although I always encounter the same problems. Either the previous entries get moved when a new one is inserted or the current one gets updated.
I could just use the following although I need a behavior to trigger.
$this->User->SiteUser->save($data);
Edit: Also $this->User->create(); doesn't seem to do much.
The IRC helped work out what was wrong, once the unique key was set to false everything was able to save correctly.
//In the user model
var $hasAndBelongsToMany = array(
'Site' => array(
'className' => 'Site',
'unique' => false
)
);
Try resetting the id before a new save(), possibly on both models:
$this->User->id = null;
Cake decides whether to update or insert entries based on the set id, and save() sets an id automatically. Not sure why create() doesn't take care of this for you.
Also, if you want to save HABTM data, you should need to use saveAll() instead of save(). Also see this question.

Categories