Cakephp How Can I add virtual field in select query? - cakephp

In front end I have to display all favorites icon green depend on is_fav status true.
So, I'm trying to create an API, Where I want to add is_fav with all product entity. My expected json will looks like
{
"id": 14,
"title": "Orange",
"price": 600,
"is_fav" : true
}
So, I'm trying to add is_fav with product entities , where is_fav is a virtual function.
In entity I have tried like below
protected function _getIsFav()
{
//to DO : I will write a query here
return true;
}
In query I have tried to add in query in select like below
$favorites = TableRegistry::getTableLocator()
->get( 'Products' )
->find()
->select([
'id',
'Products.id',
'Products.title',
'Products.price',
'is_fav' => $this->is_fav, //getting error
])
;
I'm getting error, How can I add is_fav with product entities. Is it possible ? If not how can I add is_fav like my json ?

This worked for me. CakePHP 4.3
In the Entity
protected $_virtual = ['label'];
protected function _getLabel()
{
return $this->name . ' - (' . $this->company. ')';
}
The query does not mention the virtual field
$customers = $this->Users->find()
->select(['id', 'name', 'company'])
->contain(['Roles' => function ($q) {
return $q->select(['id','name','level','price_level'])
->where(['Roles.level' => 50]);
}])
->order('Users.name');
and then the JSON that output
[
{
"id": 1019,
"name": "Abby xxxx",
"company": "Abby yyy",
"role": {
"id": 4,
"name": "Practitioner",
"level": 50,
"price_level": 2
},
"label": "Abby xxxx - (Abby yyy)"
}
]

Related

I need to get a nested element in a json array

There is some list of elements in json format, it looks like this:
[{
'id': 0,
"name": "Category name1",
"services": [{
"id": 0,
"name": "Product name1"
}, {
"id": 1,
"name": "Product name2"
}]
},
{
'id': 1,
'name': "Category name2",
"services": [{
"id": 0,
"name": "Product name1"
}, {
"id": 1,
"name": "Product name2"
}]
}
]
I'm trying to get only the entire "services" array from the first category. Conditionally, I'm trying to get it as follows:
this.class = this.http.get('/assets/products.json');
this.class.forEach(element => {
if (element.id == ID) //The ID is obtained when calling the function in which this code is executed
{
console.log(element.services);
}
}
However, this gives me absolutely nothing and "undefined" is output to the console, however, with the same array and under the same conditions on the site https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach it (foreach and console.log) outputs everything I need.
//The same foreach only on the mozilla website
array1.forEach(item => {
if(item.id==1){ //1 вместо ID
console.log(item.services);
}});
Output to the console: Array [Object { id: 0, name: "Product name1"}, Object { id: 1, name: "Product name2"}].
P.S. I don't really need this list of products in the console, I'm trying to pass a variable, but since undefined is passed to the variable, as well as to the console, I can't use the variable and call it to display products on the page. All this is used in the typescript angular project.
The HttpClient methods like get return observables, to which you need to subscribe to in order for the request to even get executed. In your situation, the class property is only holding a reference to the observable returned by calling this.http.get. Try subscribing to the observable and use the result to extract the data that you need.
this.http.get<any[]>('/assets/products.json').subscribe((data) => {
this.class = data;
this.class.forEach((element) => {
if (element.id == ID) {
console.log(element.services);
}
});
});

Mongodb aggregate return data conditionally

I am trying to return ONLY data records which email not exist in other collection with mongodb aggregation. Example:
LetterCollection:
[
{
"email": "some#email.com",
"data": "someData"
}, {
"email": "some2#email.com",
"data": "someData2"
},
]
UserCollection:
[
{
"id": "1"
"email": "some#email.com",
}
]
Get all letters which user email not exist in users collection.
Expected result:
[{
"email": "some2#email.com",
"data": "someData2"
}]
How can I achieve that?
Run an aggregate operation on the letters collection in which the pipeline first does a $lookup to the users collection, joined on the email field.
Documents returned from this pipeline stage contain an array field with the joined documents, so you would need a further $match pipeline step to filter documents where this new field does not have any documents. This can be done by checking the length of the array field with $size and filtering using the $expr expression with the $eq comparison operator.
The result can be further $projected to remove the new field in those documents that match to give the final desired output.
The above pipeline can be expressed as:
db.letters.aggregate([
{ '$lookup': {
'from': 'users',
'localField': 'email',
'foreignField': 'email',
'as': 'users'
} },
{ '$match': { '$expr': { '$eq': [ { '$size': '$users' }, 0 ] } } },
{ '$project': { 'users': 0 } }
])

How to parse field name using laravel

My Controller Code:
public function usersGroups(Request $request)
{
$resultArray = [];
$users = DB::table('users')
->join('user_basic_info','users.id','=','user_basic_info.user_id')
->select('users.id','user_basic_info.first_name')->get();
$resultArray['users'] = $users;
$groups = DB::table('group')
->select('group.id','group.name')->get();
$resultArray['groups'] = $groups;
return \Illuminate\Support\Facades\Response::json($resultArray, 200);
}
My Response:
{
"users": [
{
"id": 1,
"first_name": "Admin"
},
{
"id": 2,
"first_name": "Admin"
},
{
"id": 3,
"first_name": "Admin"
},
{
"id": 4,
"first_name": "Admin"
}
],
"groups": [
{
"id": 1,
"name": "Our Lira"
},
{
"id": 2,
"name": "CM"
}
]
}
here i there is field name first_name and i want to change it to name without changing the field name in table just for this API i want to change first_name to name.
How i can achieve this?
Your help needs here
You can simply make an alias using MySQL:
$users = DB::table('users')
->join('user_basic_info','users.id','=','user_basic_info.user_id')
->select('users.id','user_basic_info.first_name as name')->get();
Notice the as name in the select query.
Use an API Resource
These are classes that let you transform your models to whatever JSON format/layout you want.
i am not so good in api but you can achive it through setting alaias name
public function usersGroups(Request $request)
{
$resultArray = [];
$users = DB::table('users')
->join('user_basic_info','users.id','=','user_basic_info.user_id')
->select('users.id','user_basic_info.first_name as name')->get();
$resultArray['users'] = $users;
$groups = DB::table('group')
->select('group.id','group.name')->get();
$resultArray['groups'] = $groups;
return \Illuminate\Support\Facades\Response::json($resultArray, 200);
}
Can You try this if there is any error kindly comment below

Pouchdb view returning empty result

Good morning,
I'm currently working with Couchdb and Pouchdb and I'm having a problem with one query on Pouchdb side.
I have a database with different documents setup like this:
{
"_id": "fd87b66087503d760fa501fa49029f94",
"_rev": "1-e2be19d447c98d624c2c8492eaf0a3f4",
"type": "product",
"name": "Blanc de Morgex et de la Salle Brut Extreme 2014",
"category": "Wine",
"subcategory": null,
"zone": "Italy",
"nation": "Valle d'Aosta",
"province": "Morgex, AO",
"cellar": "Cave Mont Blanc",
"price": 30,
"structure": null,
"year": 2014,
"mescita": null,
"tag": null
}
The query I wrote should return the available years of products that match some filters. This is the query, with reduce : _count:
function (doc) {
if(doc.category && doc.type == 'product' && doc.year != null) {
emit(doc.year , 1);
}
}
If I try it with Postman adding the group = true parameter everything works and the result is something like:
{
"rows": [
{
"key": 2004,
"value": 2
},
{
"key": 2006,
"value": 2
},
{
"key": 2008,
"value": 2
}
]
}
The problem is when i run this view with Pouchdb with the following code which return a JSON with an empty array:
wine_db.query('wine_list/years', {reduce: '_count', key : "Bollicine", group : true, group_level: 2}).then(function(doc) {
years_list = doc;
console.log('getting year list');
console.log(doc);
}).catch(function(err){
console.log(err);
});
I've tried to play a little with the parameters of the function and even changing the function to return just a list of all the years, but nope.
I can't find the problem neither a different solution so I'm open to every suggestion you can have.
Another solution (group result)
Working on the indications and on the solution suggested by #user3405291 I finally found a way to group the results by year.
Since the emit function return a complex key ['CATEGORY', YEAR] I can use the startkey and endkey parameters to query the result just for a section of the index returned keeping this way the reduce function enable to group the result.
In the end the view function is:
function (doc) {
if(doc.category && doc.type == 'product' && doc.year) {
emit([doc.category, doc.year], doc.year );
}
}
And the Pouchdb query:
wine_db.query('wine_list/years',
{
startkey : ['CATEGORY'],
endkey : ['CATEGORY', {}],
group: true
}
).then(function (doc) {
years_list = doc;
console.log(years_list);
}).catch(function (err) {
console.log(err);
});
The result, where value is the total number of elements with that index:
{
"rows": [
{
"key": [
"Bollicine",
2004
],
"value": 2
},
{
"key": [
"Bollicine",
2006
],
"value": 2
},
{
"key": [
"Bollicine",
2008
],
"value": 2
}
]
}
In your view map function you emit the year as the key/index:
emit(doc.year , 1);
Now, I'm not sure why your are doing your query with a key like {key : "Bollicine"}:
wine_db.query('wine_list/years', {key : "Bollicine"})
.then(res=>{console.log(res)})
Of course you would get an empty response, because your view is actually indexing your docs according to year. I think you might want to do a query with a key like: {key : "2014"}
UPDATE
Based on your comments, I feel like you need to find docs based on both year and category. I'm not sure if I understand what you want, but this may help you: change your view map function like this:
function (doc) {
if(doc.category && doc.type == 'product' && doc.year) {
emit([doc.year, doc.category] , 1);
}
}
The above view will index your docs according to both year and category. You then query your view like this:
wine_db.query('wine_list/years', {key : ['2014', 'Bollicine']})
.then(res=>{console.log(res)})
The above query will give you all the docs with year field equal to 2014 and category field equal to Bollicine.
Second Update
your code works, but I just get the result for the year 2014. What I'm trying to accomplish is to get all the available years given a specific category
One solution is this:
function (doc) {
if(doc.category && doc.type == 'product' && doc.year) {
emit(doc.category, doc.year);
}
}
The above view will index your docs according to category as key and will return the year as value. Therefore you can query like this:
wine_db.query('wine_list/years', {key : 'Bollicine'})
.then(res=>{console.log(res)})
You should get a response like this, by which you have all the available years for Bollicine category:
{
"total_rows": 400,
"offset": 0,
"rows": [
{
"key": "Bollicine",
"value": "2014"
},
{
"key": "Bollicine",
"value": "2015"
},
{
"key": "Bollicine",
"value": "2018"
}
]
}

$loaded is not working properly when the server data is changed

I am new to firebase and angularjs. For my sales application I would like to use both. So, in my app I am using AngularJS v1.5.8 + Firebase v3.3.0 + AngularFire 2.0.2. I have sales and users objects in firebase db, and has a business logic that one user can sell multiple products, but one product can have only one owner (user).
Here is the users and sales objects in database:
{
"sales" : {
"-KQlb5N6A9rclc5qcWGD" : {
"price" : 8,
"quantity" : {
"count" : 12,
"type" : "porsiyon"
},
"status" : "sale",
"title" : "Patlicanli Borek",
"user" : "-KQ52OJd-lwoDIWzfYFT"
},
"-KQlcScsq8cidk7Drs04" : {
"price" : 12,
"quantity" : {
"count" : 10,
"type" : "porsiyon"
},
"status" : "sale",
"title" : "Deneme",
"user" : "-KQ5-mZBt6MhYy401gGM"
},
"-KQzXHwOv2rC73scjV46" : {
"price" : 12,
"quantity" : {
"count" : 11,
"type" : "porsiyon"
},
"status" : "sale",
"title" : "Pacanga",
"user" : "-KQ5-mZBt6MhYy401gGM"
},
"-KSCBgpArtnKunUuEuVr" : {
"price" : 15,
"quantity" : {
"count" : 15,
"type" : "porsiyon"
},
"status" : "sale",
"title" : "Iskembe",
"user" : "-KQ52OJd-lwoDIWzfYFT"
}
},
"users" : {
"-KQ5-mZBt6MhYy401gGM" : {
"address" : "Halkali kucukcekmece",
"email" : "burak.kahraman#gmail.com",
"name" : "Burak Hero",
"nick" : "Burak'in Mutfagi"
},
"-KQ52OJd-lwoDIWzfYFT" : {
"address" : "Izmir kaynaklar",
"email" : "ayse#gmail.com",
"name" : "Ayse Kahraman",
"nick" : "Ayse'nin Mutfagi"
}
}
}
What I want to do is when my app is opened, it will show all sales together with corresponding user details. (just like main page of letgo application) Which means I should implement a simple join between sales and users objects. As far as I searched throughout internet and api docs, there is no way to implement this kind of join in a single call to firebase. (Pl correct me if I am wrong) So I used below method with using $loaded function inside of my SalesService to implement join.
angular.
module('core.sales')
.service('SalesService', function ($firebaseArray, $firebaseObject, UsersService) {
this.getAllSalesJoin = function () {
var sales;
var refSales = firebase.database().ref('sales');
sales = $firebaseObject(refSales);
sales.$loaded()
.then(function () {
angular.forEach(sales, function (sale) {
var saleUser = UsersService.getUserDetail(sale.user);
saleUser.$loaded()
.then(function () {
sale.user = saleUser;
});
});
});
return sales;
};
});
As you see I am fetching all sales, after it finishes, looping for each sale to get and set related user detail by calling another UsersService shown below
angular.
module('core.users')
.service('UsersService', function ($firebaseArray,$firebaseObject) {
this.getUserDetail = function (userId) {
var user;
var refUser = firebase.database().ref('users/'+userId);
user = $firebaseObject(refUser);
return user;
};
});
So far so good, when I call SalesService.getAllSalesJoin function within my Controller and print the JSON object using <pre>{{$ctrl.allSales | json}}</pre>, everything works as I wanted, below is the Controller code and printed JSON object in the template.
angular.
module('saleList').
component('saleList', {
templateUrl: 'MCTs/sale-list/sale-list-template.html',
controller: ['SalesService','UsersService', function SaleListController(SalesService,UsersService,$scope) {
this.allSales = SalesService.getAllSalesJoin();
}]
});
Template shows the merged objects
{
"$id": "sales",
"$priority": null,
"-KQlb5N6A9rclc5qcWGD": {
"price": 8,
"quantity": {
"count": 12,
"type": "porsiyon"
},
"status": "sale",
"title": "Patlicanli Borek",
"user": {
"$id": "-KQ52OJd-lwoDIWzfYFT",
"$priority": null,
"address": "Izmir kaynaklar",
"email": "ayse#gmail.com",
"name": "Ayse Kahraman",
"nick": "Ayse'nin Mutfagi"
}
},
"-KQlcScsq8cidk7Drs04": {
"price": 12,
"quantity": {
"count": 10,
"type": "porsiyon"
},
"status": "sale",
"title": "Deneme",
"user": {
"$id": "-KQ5-mZBt6MhYy401gGM",
"$priority": null,
"address": "Halkali kucukcekmece",
"email": "burak.kahraman#gmail.com",
"name": "Burak Hero",
"nick": "Burak'in Mutfagi"
}
},
.....
But the problem is, when server data is changed (new sale is entered or old one is deleted), angular automatically understands the change but it applies the change to the view without implementing or calling my joined function, it simply prints only the sales object not the merged one with users. Below is the showing object after server data is changed.
{
"$id": "sales",
"$priority": null,
"-KQlb5N6A9rclc5qcWGD": {
"price": 8,
"quantity": {
"count": 12,
"type": "porsiyon"
},
"status": "sale",
"title": "Patlicanli Borek",
"user": "-KQ52OJd-lwoDIWzfYFT"
},
"-KQlcScsq8cidk7Drs04": {
"price": 12,
"quantity": {
"count": 10,
"type": "porsiyon"
},
"status": "sale",
"title": "Deneme",
"user": "-KQ5-mZBt6MhYy401gGM"
},
....
I am confused why it behaves like that? Is my way to implement join using $loaded wrong? Or should I use another method to implement this kind of join? I am looking forward to see your priceless suggestions and ideas.
$loaded() only fires when the initial data has loaded. From the reference documentation (emphasis mine):
Returns a promise which is resolved when the initial object data has been downloaded from the database.
This is the main reason I often say: "if you're using $loaded(), you're doing it wrong".
You're right about needing to join data with multiple calls. In AngularFire you can extend $firebaseArray to perform such an operation. For a great example of how to do this, see this answer by Kato: Joining data between paths based on id using AngularFire
Thank for the guide #Frank. I read all your suggestions and found the solution. For contributing stackoverflow knowledge and to help others here is the complete solution for the problem.
I first created a new factory that extends $firebaseArray and override $$added and $$updated methods to perform join to Users object each time when the data is updated or added.
angular.
module('core.sales').factory("SalesFactory", function ($firebaseArray, Sales) {
return $firebaseArray.$extend({
$$added: function (snap) {
return new Sales(snap);
},
$$updated: function (snap) {
return this.$getRecord(snap.key).update(snap);
}
});
});
angular.
module('core.sales').factory("Sales", function ($firebaseArray, $firebaseObject) {
var refUsers = firebase.database().ref('users');
function Sales(snapshot) {
this.$id = snapshot.key;
this.update(snapshot);
}
Sales.prototype = {
update: function (snapshot) {
var oldTitle = angular.extend({}, this.title);
var oldPrice = angular.extend({}, this.price);
var oldQuantity = angular.extend({}, this.quantity);
this.userId = snapshot.val().user;
this.title = snapshot.val().title;
this.status = snapshot.val().status;
this.price = snapshot.val().price;
this.quantity = snapshot.val().quantity;
this.userObj = $firebaseObject(refUsers.child(this.userId));
if (oldTitle == this.title && oldPrice == this.price &&
oldQuantity.count == this.quantity.count && oldQuantity.type == this.quantity.type)
return false;
return true;
},
};
return Sales;
});
As you see, SalesFactory uses another factory called Sales. In that particular factory I retrieve all properties of Sales object and assign each of them to its corresponding property. And that is the case I am performing join to Users object by creating new property : this.userObj
One thing is missing that is just calling the new Factory instead of $firebaseArray
this.getAllSalesArray = function () {
var sales;
var refSales = firebase.database().ref('sales');
sales = SalesFactory(refSales);
return sales;
};
All in all, all Sales object joined with related User is printed to the view is,
[
{
"$id": "-KQlb5N6A9rclc5qcWGD",
"userId": "-KQ52OJd-lwoDIWzfYFT",
"title": "Patlicanli Borek",
"status": "sale",
"price": 12,
"quantity": {
"count": 11,
"type": "tabak"
},
"userObj": {
"$id": "-KQ52OJd-lwoDIWzfYFT",
"$priority": null,
"address": "İzmir kaynaklar",
"email": "ayse#gmail.com",
"name": "Ayşe Kahraman",
"nick": "Ayşe'nin Mutfağı"
}
},
{
"$id": "-KQlcScsq8cidk7Drs04",
"userId": "-KQ5-mZBt6MhYy401gGM",
"title": "Deneme",
"status": "sale",
"price": 12,
"quantity": {
"count": 10,
"type": "porsiyon"
},
"userObj": {
"$id": "-KQ5-mZBt6MhYy401gGM",
"$priority": null,
"address": "Halkalı küçükçekmece",
"email": "burak.kahraman#gmail.com",
"name": "Burak Hero",
"nick": "Burak'ın Mutfağı"
}
},
...
]

Resources