I have the following models:
ServiceCategory:
'use strict'
const Model = use('Model')
class ServiceCategory extends Model {
services() {
return this
.belongsToMany('App/Models/Service')
.withTimestamps()
}
}
module.exports = ServiceCategory
'use strict'
/** #type {import('#adonisjs/lucid/src/Schema')} */
const Schema = use('Schema')
class ServiceCategorySchema extends Schema {
up () {
this.create('service_categories', (table) => {
table.increments()
table.string('name')
table.string('slug').index()
table.text('description').nullable()
table.string('icon').nullable()
table.boolean('is_activated').default(false).notNullable().default(true)
table.timestamps()
table.timestamp('deleted_at').nullable()
})
}
down () {
this.drop('service_categories')
}
}
module.exports = ServiceCategorySchema
Service:
'use strict'
const Model = use('Model')
class Service extends Model {
categories() {
return this
.belongsToMany('App/Models/ServiceCategory')
.withTimestamps()
}
}
module.exports = Service
'use strict'
/** #type {import('#adonisjs/lucid/src/Schema')} */
const Schema = use('Schema')
class ServiceSchema extends Schema {
up () {
this.create('services', (table) => {
table.increments()
table.string('name')
table.string('slug').index()
table.text('description').nullable()
table.string('icon').nullable()
table.float('price', 10, 2)
table.boolean('is_negotiable').default(true)
table.boolean('is_activated').default(false).notNullable().default(true)
table.timestamps()
table.timestamp('deleted_at').nullable()
})
}
down () {
this.drop('services')
}
}
module.exports = ServiceSchema
The problem is that when I try to get all ServiceCategories with their Services I just get the ServiceCategories with an empty array of services:
const serviceCategories = await ServiceCategory
.query()
.with('services')
.fetch()
[
{
"id": 1,
"name": "Beleza",
"slug": "beleza",
"description": null,
"icon": null,
"isActivated": true,
"createdAt": "2019-10-21T10:43:32.000Z",
"updatedAt": "2019-10-21T10:43:32.000Z",
"services": []
}
]
Intermediate table:
'use strict'
/** #type {import('#adonisjs/lucid/src/Schema')} */
const Schema = use('Schema')
class ServiceServiceCategorySchema extends Schema {
up () {
this.create('service_service_category', (table) => {
table.increments()
table.integer('service_id').unsigned().index()
table
.foreign('service_id')
.references('id')
.inTable('services')
.onDelete('cascade')
table.integer('service_category_id').unsigned().index()
table
.foreign('service_category_id')
.references('id')
.inTable('service_categories')
.onDelete('cascade')
table.timestamps()
})
}
down () {
this.drop('service_service_category')
}
}
module.exports = ServiceServiceCategorySchema
But I'm able to attach a service to a service_category:
const service = await Service.create(params)
await service.categories().attach([serviceCategoryId])
This successfully associates the service to a service category on the pivot table.
Now, why I'm able to add the relation but I'm unable to load it?
'use strict'
const Model = use('Model')
class ServiceCategory extends Model {
services() {
return this
.belongsToMany('App/Models/Service')
.pivotTable("service_service_category")
.withTimestamps()
}
}
module.exports = ServiceCategory
after adding pivotTable name try this one if working
Related
I cannot make my store work like I want.
I know how to do a store using vue 3 but with Nuxt 3 my value does not update etc
My Function To add something to the store:
<script setup>
function addWeight() {
setters.setWeights(state(), {weight : weightInput.value, date : new Date().getTime()})
}
<script/>
My Store :
export const state = () => ({
weights: []
})
export const getters = {
getWeights(state){
return state.weights;
}
}
export const setters = {
setWeights(state, {weight , date}) {
state.weights.push({
weight, date
});
}
}
export const actions = {
async fetchWeights(state) {
const res = { data : [64, 67, 79, 70, 100, 123, 23]};
state.weights = res.data;
return res.data;
}
}
state.weight or getters.getWeights(state) always return []
bash npm install pinia #pinia/nuxt --legacy-peer-deps
store/index.ts file:
import {defineStore} from "pinia";
interface weightInterface {
weight: number;
date: string;
}
export const useStore = defineStore({
id: 'store',
state : () => ({
weights : Array<weightInterface>()
}),
getters : {
getWeights(): weightInterface[]{
return this.weights;
},
getDateWeights(): weightInterface[] {
//sort by date
this.weights.sort((weight1, weight2) => +weight1.date - +weight2.date);
//from 1238173458 to 09/23/2022
for (let i = 0; i < this.weights.length; i++){
this.weights[i].date = new Date(this.weights[i].date).toLocaleDateString();
}
return this.weights;
}
},
actions : {
addWeight(weight: number, date: string): void {
const newWeight: weightInterface = { weight : weight,date: date};
this.weights.push(newWeight);
}
}
});
Works like a charm
I have a library project in which I have a single module along with few components. In my actual application I want to load the library module dynamically and render the components. Everything working fine in development mode, but when I run my application with production mode always getting empty array of components from factory (with moduleFactory.componentFactories). Below is my code. Can anyone help me on this please. I am using Angular 9.1.0.
import { Injectable, Type, Compiler, Injector, ComponentFactory, NgModuleFactory } from '#angular/core';
import { Observable, of } from 'rxjs';
import { delay, tap } from 'rxjs/operators';
import { ApptorCustomCompHostComponent } from '#apptor/corecomps';
#Injectable({
providedIn: 'root',
})
export class LazyLoaderService {
private modules: Map<string, any> = new Map();
private componentRegistry: any[] = [
{ type: "textbox", className: "ApptorCorecompsWrapperComponent", moduleName: "ApptorCorecompsModule" }
];
constructor(private compiler: Compiler, private injector: Injector) {
}
public async getComponentFactory(componentType: string): Promise<ComponentFactory<any>> {
let compInfo = this.componentRegistry.find(c => c.type === componentType);
if (compInfo) {
let factories = this.modules.get(compInfo.moduleName);
if (!factories) {
await this.loadModule(compInfo.moduleName);
factories = this.modules.get(compInfo.moduleName);
}
let componentFactory = factories.find(f => compInfo.className === f.componentType.name);
return componentFactory;
}
return null;
}
async loadModule(moduleName: string) {
switch (moduleName) {
case 'ApptorCorecompsModule':
this.loadModuleInternal(moduleName, await import('#apptor/corecomps').then(m => m[moduleName]));
break;
case 'lazy':
this.loadModuleInternal(moduleName, await import('#apptor/corecomps').then(m => m[moduleName]));
break;
}
}
private loadModuleInternal(moduleName: string, moduleType: Type<any>) {
const moduleFactory = this.compiler.compileModuleAndAllComponentsSync(moduleType);
const componentFactories = moduleFactory.componentFactories;
this.modules.set(moduleName, componentFactories);
console.log("module loaded is: ", moduleName);
}
}
We made it working as below. Hope this may help others.
import { Injectable, Compiler, Injector, ComponentFactory, ComponentFactoryResolver } from '#angular/core';
import { ReplaySubject } from 'rxjs';
#Injectable({
providedIn: 'root',
})
export class LazyLoaderService {
private modules: Map<string, ReplaySubject<any>> = new Map();
private componentRegistry: any[] = [
{ type: "textbox", className: "ApptorCorecompsWrapperComponent", moduleName: "ApptorCorecompsModule" }
];
constructor(private compiler: Compiler, private injector: Injector,
private factoryResolver: ComponentFactoryResolver) {
}
public async getComponentFactory(componentType: string): Promise<ComponentFactory<any>> {
//this.loadComponent();
const compInfo = this.componentRegistry.find(c => c.type === componentType);
if (!compInfo)
throw new Error("Invalid component type:" + componentType);
let modSubject = this.modules.get(compInfo.moduleName);
if (!modSubject) {
modSubject = this.loadModule(compInfo.moduleName);
}
let ret: Promise<ComponentFactory<any>> = new Promise((resolve, reject) => {
modSubject.subscribe(async (moduleRef) => {
//await this.compileModule(mod);
const compType = moduleRef.instance.controls[compInfo.className];
if (compType) {
let componentFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(compType);
// let componentFactory = this.factoryResolver.resolveComponentFactory(compType);
resolve(componentFactory);
} else {
console.error("Component :" + compInfo.className + " not found");
reject(null);
}
});
});
return ret;
}
public loadModule(moduleName): ReplaySubject<any> {
//let mod:any = null;
let mod: ReplaySubject<any> = new ReplaySubject();
switch (moduleName) {
case 'ApptorCorecompsModule':
import('#apptor/corecomps').then(async m => {
let mm = m[moduleName]
mm = await this.compileModule(mm);
mod.next(mm);
//return mm;
});
break;
}
if (mod) {
this.modules.set(moduleName, mod);
return mod;
}
else {
console.error("Failed to load module: " + moduleName);
throw new Error("Failed to load module: " + moduleName);
}
}
private async compileModule(module: any) {
let ret: Promise<any> = new Promise((resolve, reject) => {
this.compiler.compileModuleAsync(module).then(factory => {
const moduleRef = factory.create(this.injector);
resolve(moduleRef);
});
});
return ret;
}
}
I'm trying to call vuex action in vue component with multiple parameters. But in action method cannot access these passed arguments.
I have already tried passing value in payload as object which is mostly suggested here. but still it is not working.
Please look for
this.getMessageFromServer(payload);
MessageBox.vue
import Vue from 'vue';
import { mapGetters, mapActions } from 'vuex';
import MessageView from './MessageView.vue';
export default Vue.component('message-box',{
components:{
MessageView
},
data() {
return {
messageList :[],
}
},
created() {
this.fetchTimeMessage();
console.log("reaching inside ");
},
computed:{
...mapGetters(['getMessage','getActiveMessageData']),
...mapActions(['getMessageFromServer']),
},
methods: {
fetchTimeMessage:function(){
console.log("fetchTimeMessage : ");
var messageUser = this.getMessage.findIndex((e) => e.muid == this.getActiveMessageData.id);
console.log("fetchTimeMessage : " , {messageUser});
if (messageUser == -1) {
let user_id = this.getActiveMessageData.id;
let user_type = this.getActiveMessageData.type;
console.log("inside fetch Message : " + user_id);
console.log("inside fetch Message : " + user_type);
const payload = {
'uType': user_type,
'uid' : user_id,
'limit': 50
};
this.getMessageFromServer(payload);
}
},
},
});
Vuex modules message.js
const state = {
messages:[],
activeMessage : {}
};
const getters = {
getActiveUserId: (state) => {
let activeUserId = "";
if (!utils.isEmpty(state.activeMessage)) {
activeUserId = state.activeMessage.id;
}
return activeUserId;
},
getActiveMessage:(state) => { return !utils.isEmpty(state.activeMessage);},
getActiveMessageData : (state) => {return state.activeMessage } ,
getMessage: (state) => {return state.messages},
};
const actions = {
getMessageFromServer({ commit, state },{utype,uid,limit}){
console.log("mesage callback asdas : " + uid);
let messageRequest = CCManager.messageRequestBuilder(utype, uid, limit);
messageRequest.fetchPrevious().then(messages => {
//console.log("mesage callback : " + JSON.stringify(messages));
// handle list of messages received
let payload = {
'messsages':messages,
'id': uid
};
console.log("inside action_view : " + JSON.stringify(payload));
//commit('updateMessageList',payload);
})
},
setActiveMessages:function({commit},data){
commit('updateActiveMessage',data);
},
};
const mutations = {
updateMessageList(state,{messages,id}){
console.log("action details" + id);
//uid is not present
var tempObj = {
'muid' : id,
'message' : messages
}
state.messages.push(tempObj);
}
},
updateActiveMessage(state,action){
state.activeMessage = {
type: action.type,
id: action.uid
};
}
};
export default {
state,
getters,
actions,
mutations
};
Change the way you call the action in your component:
this.$store.dispatch('getMessageFromServer', payload);
And pass the payload as a single object in your action function:
getMessageFromServer({ commit, state }, payload)
And you can then access the payload properties in the action like this:
getMessageFromServer({ commit, state }, payload) {
var uid = payload.uid;
var uType = payload.uType;
var limit = payload.limit;
}
There are plenty of tutorials how to test effects in ngrx 3.
However, I've found only 1 or 2 for ngrx4 (where they removed the classical approach via EffectsTestingModule ), e.g. the official tutorial
However, in my case their approach doesn't work.
effects.spec.ts (under src/modules/list/store/list in the link below)
describe('addItem$', () => {
it('should return LoadItemsSuccess action for each item', async() => {
const item = makeItem(Faker.random.word);
actions = hot('--a-', { a: new AddItem({ item })});
const expected = cold('--b', { b: new AddUpdateItemSuccess({ item }) });
// comparing marbles
expect(effects.addItem$).toBeObservable(expected);
});
})
effects.ts (under src/modules/list/store/list in the link below)
...
#Effect() addItem$ = this._actions$
.ofType(ADD_ITEM)
.map<AddItem, {item: Item}>(action => {
return action.payload
})
.mergeMap<{item: Item}, Observable<Item>>(payload => {
return Observable.fromPromise(this._listService.add(payload.item))
})
.map<any, AddUpdateItemSuccess>(item => {
return new AddUpdateItemSuccess({
item,
})
});
...
Error
should return LoadItemsSuccess action for each item
Expected $.length = 0 to equal 1.
Expected $[0] = undefined to equal Object({ frame: 20, notification: Notification({ kind: 'N', value: AddUpdateItemSuccess({ payload: Object({ item: Object({ title: Function }) }), type: 'ADD_UPDATE_ITEM_SUCCESS' }), error: undefined, hasValue: true }) }).
at compare (webpack:///node_modules/jasmine-marbles/index.js:82:0 <- karma-test-shim.js:159059:33)
at Object.<anonymous> (webpack:///src/modules/list/store/list/effects.spec.ts:58:31 <- karma-test-shim.js:131230:42)
at step (karma-test-shim.js:131170:23)
NOTE: the effects use a service which involves writing to PouchDB. However, the issue doesn't seem related to that
and also the effects work in the running app.
The full code is a Ionic 3 app and be found here (just clone, npm i and npm run test)
UPDATE:
With ReplaySubject it works, but not with hot/cold marbles
const item = makeItem(Faker.random.word);
actions = new ReplaySubject(1) // = Observable + Observer, 1 = buffer size
actions.next(new AddItem({ item }));
effects.addItem$.subscribe(result => {
expect(result).toEqual(new AddUpdateItemSuccess({ item }));
});
My question was answered by #phillipzada at the Github issue I posted.
For anyone checking this out later, I report here the answer:
Looks like this is a RxJS issue when using promises using marbles. https://stackoverflow.com/a/46313743/4148561
I did manage to do a bit of a hack which should work, however, you will need to put a separate test the service is being called unless you can update the service to return an observable instead of a promise.
Essentially what I did was extract the Observable.fromPromise call into its own "internal function" which we can mock to simulate a call to the service, then it looks from there.
This way you can test the internal function _addItem without using marbles.
Effect
import 'rxjs/add/observable/fromPromise';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import { Injectable } from '#angular/core';
import { Actions, Effect } from '#ngrx/effects';
import { Action } from '#ngrx/store';
import { Observable } from 'rxjs/Observable';
export const ADD_ITEM = 'Add Item';
export const ADD_UPDATE_ITEM_SUCCESS = 'Add Item Success';
export class AddItem implements Action {
type: string = ADD_ITEM;
constructor(public payload: { item: any }) { }
}
export class AddUpdateItemSuccess implements Action {
type: string = ADD_UPDATE_ITEM_SUCCESS;
constructor(public payload: { item: any }) { }
}
export class Item {
}
export class ListingService {
add(item: Item) {
return new Promise((resolve, reject) => { resolve(item); });
}
}
#Injectable()
export class SutEffect {
_addItem(payload: { item: Item }) {
return Observable.fromPromise(this._listService.add(payload.item));
}
#Effect() addItem$ = this._actions$
.ofType<AddItem>(ADD_ITEM)
.map(action => action.payload)
.mergeMap<{ item: Item }, Observable<Item>>(payload => {
return this._addItem(payload).map(item => new AddUpdateItemSuccess({
item,
}));
});
constructor(
private _actions$: Actions,
private _listService: ListingService) {
}
}
Spec
import { cold, hot, getTestScheduler } from 'jasmine-marbles';
import { async, TestBed } from '#angular/core/testing';
import { Actions } from '#ngrx/effects';
import { Store, StoreModule } from '#ngrx/store';
import { getTestActions, TestActions } from 'app/tests/sut.helpers';
import { AddItem, AddUpdateItemSuccess, ListingService, SutEffect } from './sut.effect';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
describe('Effect Tests', () => {
let store: Store<any>;
let storeSpy: jasmine.Spy;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({})
],
providers: [
SutEffect,
{
provide: ListingService,
useValue: jasmine.createSpyObj('ListingService', ['add'])
},
{
provide: Actions,
useFactory: getTestActions
}
]
});
store = TestBed.get(Store);
storeSpy = spyOn(store, 'dispatch').and.callThrough();
storeSpy = spyOn(store, 'select').and.callThrough();
}));
function setup() {
return {
effects: TestBed.get(SutEffect) as SutEffect,
listingService: TestBed.get(ListingService) as jasmine.SpyObj<ListingService>,
actions$: TestBed.get(Actions) as TestActions
};
}
fdescribe('addItem$', () => {
it('should return LoadItemsSuccess action for each item', async () => {
const { effects, listingService, actions$ } = setup();
const action = new AddItem({ item: 'test' });
const completion = new AddUpdateItemSuccess({ item: 'test' });
// mock this function which we can test later on, due to the promise issue
spyOn(effects, '_addItem').and.returnValue(Observable.of('test'));
actions$.stream = hot('-a|', { a: action });
const expected = cold('-b|', { b: completion });
expect(effects.addItem$).toBeObservable(expected);
expect(effects._addItem).toHaveBeenCalled();
});
})
})
Helpers
import { Actions } from '#ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { empty } from 'rxjs/observable/empty';
export class TestActions extends Actions {
constructor() {
super(empty());
}
set stream(source: Observable<any>) {
this.source = source;
}
}
export function getTestActions() {
return new TestActions();
}
I am trying to fetch data with apollo and then write it to realm. I have created a js file that I know works, because it has worked before. But, when I try to write to a particular model I get an error message. More details as follows:
Code (Not entire code) LocationQuery.js:
const realm = new Realm({ schema: [testBuilding1], schemaVersion: 1 });
let buildingTypeArray = [];
const temp = [];
class LocationQuery extends Component {
static get propTypes() {
return {
data: React.PropTypes.shape({
loading: React.PropTypes.bool,
error: React.PropTypes.object,
sites: React.PropTypes.array,
}).isRequired,
};
}
render() {
if (this.props.data.loading) {
return (null);
}
if (this.props.data.error) {
return (<Text>An unexpected error occurred</Text>);
}
if (this.props.data.sites) {
this.props.data.sites.map((value) => {
buildingTypeArray.push(value.locations);
});
buildingTypeArray.forEach((locationValues) => {
realm.write(() => {
realm.create('testBuilding1', {
building: '273',
});
});
});
}
return null;
}
}
const locationQueryCall = gql`
query locationQueryCall($id: String!){
sites(id: $id){
locations {
building
type
}
}
}`;
const ViewWithData = graphql(locationQueryCall, {
options: props => ({
variables: {
id: 'SCH1',
},
}),
})(LocationQuery);
export default connect(mapStateToProp)(ViewWithData);
The error I get is a big red screen that read:
console.error: "Error in observe.next.... blah blah blah"
The Model I am using:
export const testBuilding1 = {
name: 'testBuilding1',
properties: {
building: 'string',
},
};
The weird thing is that the code works when I use this model:
export const locationScene = {
name: 'locationScene',
properties: {
building: 'string',
},
};
I am calling LocationQuery.js in another piece of code passing it through at render.
Thank you in advance for the help!