Sequelize through association attributes in Pug - express

In a nutshell, I cannot access though association attributes in Pug, only id and created_at can be displayed.
I have Item and Order models, they have many to many relationship though OrderItem model which is hooked to order_items table which works correctly on an API. When orders are sent to Pug using res.render, the resulting object has items which have nested order_items as 'through attributes'. Declared 'through association' attributes in the controller are id, payment, quantity and created_at and they are all seen in the object sent to res.render.
The association is defined in the Order model as:
static associate(models) { Order.belongsToMany(models.Item, {
as:'items',
through: 'order_items',
foreignKey: 'order_id',
otherKey: 'item_id'
})
}
On the Item model, similar association is declared with a few changes. On the controller, this is how I call a specific order:
const foundOrder = await Order.findOne({
where: { id: req.params.id },
include: {
model: Item,
as: 'items',
through: { attributes: ['id', 'quantity', 'payment', 'created_at']}
}
})
In views/orders/details I wanted to view details of a specific order, in the rendered result I only got item.name, item.order_items.id and item.order_items.created_at but failed to get item.order_items.quantity or item.order_items.payment which should be on the same tree level as the id.
When I try to loop through the object and console.log(item.order_items) in the server console I see the entire nested object and indeed quantity and payment are there. They are in string format as I have been trying to see if them being INTEGER was the problem but that did not solve anything.
{
"id":"2ac16f14-75c8-4bb6-b693-f5dc825eb33e",
"quantity":"34",
"payment":"136000",
"created_at":"2022-09-22T10:30:10.000Z"
}{
"id":"b7f06b43-ac79-4fc6-8e46-01c6c5a8cfd2",
"quantity":"80",
"payment":"320000",
"created_at":"2022-09-22T10:30:10.000Z"
}
Likewise, when I put console.log(item.order_items.id) or created_at in the same style in the order page in Pug I get the values for the two items that are attached with that order.
"2ac16f14-75c8-4bb6-b693-f5dc825eb33e"
"b7f06b43-ac79-4fc6-8e46-01c6c5a8cfd2"
On the other hand, if I put in the same view/orders/details.pug the script console.log(item.order_items.quantity) instead of getting 34 and 80, I get:
unidentified
unidentified
What have I done wrong?
Is this a bug with Pug?

Related

How to store tags in two separate collections that are in many-to-many relationship?

A user is allowed to create items, optionally add up to 10 tags. There's going to be a tag cloud on one of the pages, so storing them in a separate collection seems justified.
The Tag schema is modeled as following:
module.exports = mongoose.model("Tag", new mongoose.Schema({
label: {
type: String,
required: true,
lowercase: true,
maxLength: 35
}
}));
The Item schema has a tags key which references documents in Tag:
tags: {
type: [{ type: ObjectId, ref: "Tag" }],
validate: {
validator: n => n <= 10
}
},
Now, it would be easier to just store tags as subdocuments inside each item but as I said, I'll have a tag cloud later. Reading them from the items collection just to render the tag cloud will probably be a bit expensive, so duplicating them seems almost necessary and the lesser of the two devils. Please correct me if I'm wrong.
Anyway, the main question is: how do I add tags in this scenario then? The request content will be a JSON object, sent to /api/collections/items with the POST method. How can I make sure tags are added to both collections and the limit is respected? Because as it stands, the validator for tags in Item only checks the number of references stored, not the amount of tags assigned to an item. Am I wrong?
Ideally, the controller for adding items should be able to process such requests in a single operation:
async function addItem(req, res) {
try {
let item = await Item.create(req.body);
res.status(201).json(item);
} catch (err) {
res.status(400).json({ response: err.message });
}
}
Or am I going to have to first add tags and do something like item.tags.push(tags) afterwards?

Vuex state structure and fetching when using same type of data with different values on different routes

I'm creating a portfolio with vue, vuex and vue-router that will show images.
On the homepage i will show images with 'show_home: true'.
Then there is "tag" pages (portfolio/:tagSlug) that will show images based on a slug, eg. 'weddings' with infinite scroll (auto populate pagination).
An image object will look something like:
{
id: 1,
title: 'Lorem..',
path: '..',
show_home: true
tags: [ {id: 1, slug: 'weddings'} ]
},
{
id: 2,
title: 'Lorem2..',
path: '..',
show_home: false
tags: [ {id: 1, slug: 'weddings'}, {id: 2, slug: 'water'} ]
}
Endpoints examples:
Homepage: GET: ../images/homepage?p=1
Tag page: GET: ../images/:slug?p=1
I can't figure out how I should structure this in vuex and handle the fetching..
Should i just create i single 'images: []' state and populate it with ALL the images after fetching them from the api in each route, then filter them with getters? How can i get the pagination in there in that case? Or do you have a better solution?
Thanks in advance
My preferred approach is to "flatten" the relationships and pull them as needed. This also allows you to only pull what you need from the server or related modules.
tags vuex module:
all: {
1: { <-- indexed by tag id
name: "weddings"
images: [1,2,3,4] <-- image ids
}
}
active: false <-- When there is an active tag, this becomes the id of the tag.
The vuex images module would follow this same pattern:
all: {
1: { <-- indexed by image id
title: 'Lorem..',
path: '..',
show_home: true
tags: [1,2,3,4] <-- tag ids
}
}
active: false <-- When there is an active image, this becomes the id of the image.
Then use a getter to hydrate the images or tags from the respective vuex module.
There is a great write up on this approach on this blog: https://medium.com/js-dojo/structuring-vuex-modules-for-relationships-speed-and-durability-de25f7403643
With this approach you will have fewer and smaller api calls, pagination is manageable and you don't need to worry about stale data in your relationships.
EDITED -- API info:
Two approaches come to mind.
1) always load the images with the tag.
Tag index request would not load any images, just the basic info for each tag.
When the user clicks on a tag, this inits an API call for the tag details:
Tag show request (tags/1 or tags/weddings) would return the tag with loaded relationships:
public function show($id)
{
$tag = Tag::where('id', $id)->with('images')->firstOrFail();
return new TagResource($tag); <-- will have all related images loaded.
}
2) set up a nested REST endpoint if needed
You can use the the resource controllers to shortcut the boilerplate like this:
api.php
Route::apiResource('tags.images', 'tags\TagImageController');
This route will watch your api calls and determine if it is index/store/show/delete. From your front end you can make a call like https://backendsite.com/tags/1/images (If wedding tag has an id of 1)
Then in the TagImageController you would have something like this:
public function index(Request $request, $id)
{
$tag = MemTag::find($id);
$images = $tag->images()->get();
$images->load(Image::allowedIncludes); <- or you can manually list relationships you want to load
return ImageResource::collection($images);
}

In KeystoneJS, how can I make all values initially selected in a Relationship field using many: true?

I am using KeystoneJS. I have a relationship field in my Post model that references another model called Device. I am using many: true to allow users to select multiple items from the Device model.
Is there a way to have them all initially selected when creating a Post?
Right now my relationship field looks like this:
availableDevices: {
type: Types.Relationship,
ref: 'Device',
many: true
}
In your model definition, you can add pre-processing steps to populate fields that you'd like to have a default value in the relationship. I haven't done this with a many relationship, but I have done it with a relationship like this:
Report.schema.pre('save', function (next) {
this.wasNew = this.isNew;
if (!this.employee) {
this.employee = this.createdBy;
}
if (!this.timeEnteredTheHomeStr) {
if(!this.timeEnteredTheHome) {
this.timeEnteredTheHome = Date.now
}
this.timeEnteredTheHomeStr = String(this.timeEnteredTheHome);
}
next();
});
When someone creates a new report object, the employee field is automatically set to the user object referenced in the createdBy field.

Grouping WSAPI data store by Parent Name

I am creating a rallygrid component and would like to have the grid items grouped by their parent's Name attribute (bonus if I could also display the ID of the parent). I added the groupBy:'Parent' configuration to the storeConfig of the grid and was surprised that no results were returned. I also tried using groupBy:'Parent.Name' but still nothing.
I know this is possible with other fields such as Owner, but I'm at a loss as to why the Parent wouldn't be usable as well. Is this a bug, or am I setting the config up incorrectly?
Thanks
Change the storeConfig to keep the records from trying to update after being grouped:
storeConfig : {
remoteSort : false,
remoteGroup : false,
remoteFilter : false,
}
Add a listener to the load event which assigns a root level property to the record and groups by that record value. (For some reason store.group('Parent.Name'); doesn't work.)
load: function(store) {
store.each(function(record) {
record.set('ParentName', record.get('Parent') && record.get('Parent').Name || '-- Unparented --');
});
store.group('ParentName');
}
I thought it was a bug with the SDK too, but per WS API documentation, Parent, unlike Owner, or Feature is not sortable.
So when I use groupField: 'Parent' the grid is empty, and response showed error:
Ext.data.JsonP.callback6({"QueryResult": {..., "Errors": ["Cannot sort using attribute Parent"]
It is trying to sort by Parent, but Parent attribute is not sortable. So the SDK ran into a WS API limitation.
On a side note, I did not use groupBy, instead I used groupField on the store (in this example I grouped by Kanban field) :
var myStore = Ext.create('Rally.data.WsapiDataStore',{
model: 'UserStory',
groupField: 'c_MyKB',
//...
});
And then used features: [{ftype:'grouping'}] in the grid.
this._myGrid = Ext.create('Ext.grid.Panel', {
store: myStore,
features: [{ftype:'grouping'}],
//...

insert in sencha touch data store

Could someone please explain how the insert works to add a record in a datastore
with tha fields: "title", "info" and "price"?
because i tried some things and none of them work. and the sencha website doesnt make it very clear.
Adding a new item to an existing Store isn't that hard actually.
First of you will need to configure your model and store. In your question you name the fields 'title, 'info' and 'price'.
Model:
Ext.regModel('myModel', {
fields: [
{name: 'id', type: 'int' },
{name: 'title', type: 'string' },
{name: 'info', type: 'string' },
{name: 'price', type: 'int' }
]
});
Next you configure the store that will hold the data, based on the above model. I think that, in your case, it should be a model without any data preloaded via, for example, JSON?
So lets make a localstorage (empty store). The Store consists of the model (myModel), you give it a storeID (so that you can later on reference the store by this ID). The proxy is localstorage and the unique ID of the Store will be the ID field of the Model.
Store:
var myStore = new Ext.data.Store({
model: "myModel",
storeId: "myStoreID",
proxy: {
type: "localstorage",
id: "id"
}
});
Now, suppose you have some kind of Form (in which the user can add input a title, info and price, and you want to add these items to the existing store on submittal.
Within the handler of the submittal button you now have to 'call' the store, and perform the add function on it. Within this add function you will have to define the params (the model params) and the data to insert.
Below I have used a mixture of fixed data and a variable to insert.
myStoreID.add({ title: "Mijn Titel", info: "Informatie, price: prijsvar });
The store will now be filled will now be filled with an extra data-record which you can use. Lets say for example that the store is attached to a dataview, then you can perform:
dataView.update();
The above isn't a full tutorial, but I think this will help you along?
Just an update of the YDL answer.
As per the dataView should be related to the updated store, the last sentence dataView.update() should not be needed, due to the automatic update of the views related to a store when it change.
new Ext.DataView({
store: MyStore,
itemSelector: 'div.thumb',
tpl: thumbTpl
});
later, if I do the following, the new item should be displayed in views (List, DataView, etc.) that have MyStore as store.
MyStore.add(newItem);
HTH.
Milton Rodríguez.
If you are trying to pass in an object that was returned from a getValue() on your form, make sure that you run a
myStore.sync();
after you have called the add() method, or you wont see it in your browsers local store.
It is Very easy try these
// first get those values and store in locally
var A_select1=Ext.getCmp('select1').getValue(); // get value
localStorage.setItem("Adult1_select1",A_select1); // set localStore
var AdultSalutation={
'Adult1_select1':A_select1, // assign the value
};
var AdultSalutationstore = Ext.getStore('Adult_AdultSalutationstore'); // get store
AdultSalutationstore.add(AdultSalutation); // add object
AdultSalutationstore.sync(); // sync
AdultSalutationstore.load(); // load