Strapi: Get all nested properties for deeply nested relation - javascript

I recently started working with strapi and have been figuring out how to go about content relations and so on... Now I have reached a point when I have multiple content relations dependent on each other.
This is my strucute:
Collection Types:
Categories
Articles
with content relation: Article has one category
Single Types:
Homepage
with content relation: Homepage has many articles
Now what I want to do is to to get all nested properties of a category for articles that are assigned to homepage just by simply making a GET request from /homepage
What I currently get is a json structure like this:
{
"id": 1,
"hero": {
....
},
"featuredArticles": {
....
},
"displayedArticles": [
{
"id": 2,
"category": 5,
}
]
}
What is the expected output:
{
"id": 1,
"hero": {
....
},
"featuredArticles": {
....
},
"displayedArticles": [
{
"id": 2,
"category": [
{
"id": 5,
"title": "Foundation"
}
],
}
]
}
My suspicion is that the properties of categories is basically too nested when trying to fetching from /homepage rather than /articles directly.
I found out that handling this could work with modifying the controllers right in the strapi directory but I haven't figured it out quite.
Strapi Controller Docs
Is here anybody who knows solution to this?

Firstly you'll need a custom controller function for this. In /api/homepage/controllers/homepage.js you can export your custom find function.
There you can define which fields you want to populate:
module.exports = {
find: ctx => {
return strapi.query('homepage').find(ctx.query, [
{
path: 'displayedArticles',
populate: {
path: 'category',
},
},
]);
}
};
For reference see the most recent beta docs:
Customization
Second way: populate it as per requirement
module.exports = {
find: async (ctx) => {
const homePage = await strapi.query('homepage').find(ctx.query);
const categories = await strapi.query('categories').find();
if (homePage[0].displayedArticles) {
homePage[0].displayedArticles.forEach(async (content) => {
if(categories){
content.category = categories.find((category) => {
return category.id === content.category;
});
}
});
}
if (homePage[0].displayedComponents) {
homePage[0].displayedComponents.forEach(async (content) => {
if(categories){
content.category = categories.find((category) => {
return category.id === content.category;
});
}
});
}
return homePage;
}
};

Related

Why doesn't sequelize include associated model to a nested object in the results?

I have two associated models: TariffsModel and PoliciesModel. PoliciesModel has tariff_id which refers to specific tariff by its ID. And this works but Sequelize returns the result in a weird way:
instead of returning:
PoliciesModel {
id: 1,
owner_party_id: 2,
tariff: {
id: 1,
is_active: true,
...
},
...
}
it returns:
PoliciesModel {
id: 1,
owner_party_id: 2,
tariff.id: 1,
tariff.is_active: true,
...
}
Here's the example: enter image description here
I initiate the relation like this:
const {
PoliciesModel,
TariffsModel,
} = this.sequeluze.models;
PoliciesModel.belongsTo(TariffsModel, { foreignKey: 'tariff_id', as: 'tariff' });
and make the query like this:
const policies = await PoliciesModel.findAll({
where: { owner_party_id: userAccount.party_id },
include: {
model: TariffsModel,
as: 'tariff',
},
raw: true,
});
What could be the reason of this kind of behavior?
I tried calling hasMany/hasOne methods and using as parameter, but it didn't help.
In your Sequelize query, if you remove the property raw then you get the response as expected.
const policies = await PoliciesModel.findAll({
where: { owner_party_id: userAccount.party_id },
include: {
model: TariffsModel,
as: 'tariff',
}
});
The query should be like the following one.

Return only a subdocument from document in mongoose

I am working on an app that uses MongoDB (I use Mongoose) as its database.
I have a question, suppose I have this kind of schema:
[{
"user_id":"2328292073"
"username":"Bob",
"subscriptions":[
{
"id":"38271281,
"payments":[
{
"id":"00001",
"amount":"1900"
},
{
"id":"00002",
"amount":"2000"
},
{
"id":"00003",
"amount":"3000"
}
]
}
]
}]
In my case I want to get the payments array for subscription with id = '38271281' of user with id '2328292073', but I just want to retrieve the payment array, nothing else
My query is the following:
Mongoose.findOne({
"user_id": "2328292073",
"subscriptions.id": "38271281"
},
{
"subscriptions.payments": 1
})
But I get the entire document of subscriptions. How can i get the payment array only?
you can try using unwind if you want filteration from db only.
Mongoose.aggregate([
{
'$match': {
'user_id': '2328292093'
}
}, {
'$unwind': {
'path': '$subscriptions'
}
}, {
'$match': {
'subscriptions.id': '38271281'
}
}
])
if you will have multiple documents having same subscription id then you have to group it .
using code level filter function can also be one another approach to do this .
You can try aggregation operators in projection in find method or also use aggregation method,
$reduce to iterate loop of subscriptions and check the condition if id matched then return payment array
db.collection.find({
"user_id": "2328292073",
"subscriptions.id": "38271281"
},
{
payments: {
$reduce: {
input: "$subscriptions",
initialValue: [],
in: {
$cond: [
{ $eq: ["$$this.id", "38271281"] },
"$$this.payments",
"$$value"
]
}
}
}
})
Playground

How can I get data from a json via GraphQL?

enter code here {
"compdata": [
{
"id": 1,
"title": "FlexBox",
},
{
"id": 2,
"title": "Grid layout",
},
]
}
enter code here **file in:-- src-data-data.json**
enter code here export const IndexQuery = graphql`
query IndexQuery {
dataJson {
compdata {
id
example
}
}
}
`
Blockquote giving me error Cannot read properties of undefined (reading 'compdata')
You only need to follow the docs about Sourcing from JSON.
That said, you don't need to use GraphQL when sourcing from a JSON local file, you can just import the object such as:
import React from "react"
import JSONData from "../../content/My-JSON-Content.json"
const SomePage = () => (
<div style={{ maxWidth: `960px`, margin: `1.45rem` }}>
<ul>
{JSONData.compdata.map((data, index) => {
return <li key={`content_item_${index}`}>{data.title}</li>
})}
</ul>
</div>
)
export default SomePage
In this case, you can import your JSON data as JSONData and loop it through the array of compdata.
However, if you still want to use GraphQL, you will need to use the gatsby-transformer-json plugin, what will create a queryable GraphQL node from your JSON source:
Install it by:
npm install gatsby-transformer-json
And use it your gatsby-config.js:
module.exports = {
plugins: [
`gatsby-transformer-json`,
{
resolve: `gatsby-source-filesystem`,
options: {
path: `./src/your/json/folder`,
},
},
],
}
The alias of the node will rely on your folder structure and your naming (which hasn't been provided), in the docs example, given a letters.json such as:
[{ "value": "a" }, { "value": "b" }, { "value": "c" }]
So in your GraphiQL playground (localhost:8000/___graphql) you will be able
to query allLettersJson node:
{
allLettersJson {
edges {
node {
value
}
}
}
}
You can add a typeName option to fix your naming such as:
{
resolve: `gatsby-transformer-json`,
options: {
typeName: `Json`,
},
},
In that case, the created node will be allJson.

Firebase extract name of object

I have a simple question today.
I retrieve data from my firebase database:
const response = await fetch('For pricacy purpose I replaced this link to my firebase database.');
const resData = await response.json();
console.log(resData);
Also I log the results in the console, the following text is what I retrieve:
Object {
"-MPOg49jvG-md0twgj-D": Object {
"id": 1,
},
"-MPTgHoTXzIcY_KpBHkc": Object {
"id": 2,
},
"-MPTgmANDZkMv7f_A9TG": Object {
"id": 4,
},
"-MPTgmc2fuu5XSUawuW7": Object {
"id": 3,
},
}
Now my question: I want to access not the id that is in the objects but rather the "name" of the object itself. If you look at the first element:
"-MPOg49jvG-md0twgj-D": Object {
"id": 1, }
i want to access this "-MPOg49jvG-md0twgj-D" and store it in a constant but I dont know how to do it. Any idea would be appriciated.
If I'm understanding correctly, you already fetched resData as a JavaScript object and want to get the keys? These are some ways that possibly could help you.
const resData = {
"-MPOg49jvG-md0twgj-D": {
id: 1
},
"-MPTgHoTXzIcY_KpBHkc": {
id: 2
},
"-MPTgmANDZkMv7f_A9TG": {
id: 4
},
"-MPTgmc2fuu5XSUawuW7": {
id: 3
}
};
// method 1
console.log(Object.keys(resData));
// method 2
for (const key in resData) {
console.log(key, resData[key]);
}
// method 3
console.log(Object.getOwnPropertyNames(resData));
Hope this can help, please correct me if I got it wrong.

Vuex doesn't react with complex object

I've started to use Vuex in order to remplace the EventBus, because the data in my app has started to get a little bit complex.
In my context, I have a question entity, with multiple answers, when the user insert another answer I want to show the last one; (here I use two different components: one to show the answers and other to answer the question) but when the server response OK with the new answer, and the mutation change the state.answers, the computed property doesn't react and doesn't show the new answer:
Here is my data structure:
"answers": {
"118": {
"id": 118,
"description": "objective",
"created_at": "2019-11-12T19:12:36.015Z",
"dojo_process_id": 1,
"question_id": 1,
"user_id": 10
}
"127": {
"id": 127,
"description": "asdddd",
"created_at": "2019-11-12T19:38:19.233Z",
"dojo_process_id": 1,
"question_id": 1,
"user_id": 10
},
"128": {
"id": 128,
"description": "asddddasddd",
"created_at": "2019-11-12T20:00:17.572Z",
"dojo_process_id": 1,
"question_id": 1,
"user_id": 10
}
},
Here is the code for my store:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
...
answers: {},
...
},
getters: {
findBy: state=> filter => {
let result= Object.values(state[filter.model]).
filter(data => data[filter.field] === filter.criteria);
return result;
}
},
mutations: {
setAnswers(state, answers) {
state.answers = answers;
},
setAnswer(state, answer) {
state.answers[answer.id] = answer;
},
},
actions: {
replaceCompleteProcess(context, data) {
...
context.commit('setAnswers',data.answers);
...
},
cleanCompleteProcess(context) {
...
context.commit('setAnswers',{});
...
},
saveAnswer(context, answer) {
context.commit('setAnswer', answer);
}
}
});
And this is how the script of my component is structured:
export default {
name: "show-question",
computed: {
question: function () {
return this.$store.getters.question(this.questionId)
},
answers: function () {
return this.$store.getters.findBy({
model: 'answers',
field: 'question_id',
criteria: this.questionId,
sort: true
});
},
answer: function () {
if (this.answers && this.answers.length > 0) {
return this.answers[0].description;
} else {
return '';
}
}
},
props: {
questionId: {
type: [Number],
default: 0
}
},
data() {
return {
sending: false,
answerData: this.answer
}
},
methods: {
sendAnswer () {
this.sending = true;
questionConnector.answerQuestion(this,this.question.id,this.dojoProcessId, this.answerData)
},
// this one is called from AXIOS
answerWasOK(answer) {
this.sending = false;
this.$store.dispatch('saveAnswer', answer);
this.answerData = '';
}
}
}
So, if I understand how to use Vuex, when I call this.$store.dispatch('saveAnswer', answer), the state will be updated, and the computed property answers would be updated, and I'll be able to show the new changes in the component, But it doesn't work.... the computed property just doesn't react.
I had read a lot about vuex and how "it not work well" with complex data, so I normalize my data. but it is the same... also I tried to use vuex-orm, but I have a lot of problems with the one-many relation, and I cant do it work.
EDIT: Solution
I did a small test with the ideas from the answers and it works
setAnswer(state, answer) {
let newAnswers = state.answers;
state.answers = {};
newAnswers[answer.id] = answer;
state.answers = newAnswers;
}
When you are working with Objects you have to do it like this
setAnswer(state, answer) {
Vue.set(state.answers, answer.id, answer);
},
This is clearly mentioned in the documentation.
When adding new properties to an Object, you should either:
Use Vue.set(obj, 'newProp', 123), or
Replace that Object with a fresh one. For example, using the object spread syntax we can write it like this:
state.obj = { ...state.obj, newProp: 123 }
You are storing a list of answers inside an object. Nothing wrong with that, since you know how to deal with. It turns out that Vue.js observers don't track new object attributes (which is exactly what you are doing there, creating new attributes, instead of modifying a list/array).
My first suggestion is to change this object to an array. But if you can't, due to your ORM or other reason of your project standard, you should take a look about Reactivity of Vue.js. The quickest solution, in my opinion, is to use a watcher:
https://v2.vuejs.org/v2/api/#watch
Some links that can be helpful to understand Vue.js reactivity:
Reactivity in Depth
https://v2.vuejs.org/v2/guide/reactivity.html
How to actively track an object property change?
https://forum.vuejs.org/t/how-to-actively-track-an-object-property-change/34402/1

Categories

Resources