I have the following models:
Usuario has many RolesUsuario belongsTo Rol
I want to setup a hasManyThrough in Usuario so that I can access the list of roles for a user with a simpler preload. The documentation for the hasManyThrough doesn't fully explain how to model this scenario.
export default class Rol extends BaseModel {
public static table = 'roles'
#hasMany(() => RolUsuario, {
foreignKey: 'idRol',
})
public rolesUsuarios: HasMany<typeof RolUsuario>
#column({ isPrimary: true })
public id: number
#column()
public nombre: string
}
export default class RolUsuario extends BaseModel {
public static table = 'roles_usuarios'
#belongsTo(() => Usuario, {
foreignKey: 'idUsuario',
})
public usuario: BelongsTo<typeof Usuario>
#belongsTo(() => Rol, {
foreignKey: 'idRol',
})
public rol: BelongsTo<typeof Rol>
#column({ isPrimary: true })
public id: number
#column()
public idUsuario: string
#column()
public idRol: string
}
export default class Usuario extends BaseModel {
#hasMany(() => RolUsuario, {
foreignKey: 'idUsuario',
})
public rolesUsuarios: HasMany<typeof RolUsuario>
#hasManyThrough([() => Rol, () => RolUsuario], { // HELP!!!
foreignKey: 'idUsuario',
throughForeignKey: 'idRol',
})
public roles: HasManyThrough<typeof Rol>
#column({ isPrimary: true })
public id: string
}
Then when I do this:
await Usuario.query().preload('roles')
I get this error:
"E_MISSING_MODEL_ATTRIBUTE: "Usuario.roles" expects "idRol" to exist on "Rol" model, but is missing"
Okay I don't fully understand why but this works:
#hasManyThrough([() => Rol, () => RolUsuario], {
foreignKey: 'idUsuario',
throughForeignKey: 'id',
throughLocalKey: 'idRol',
})
public roles: HasManyThrough<typeof Rol>
Related
This is a sample code of resolver and i want to test this with the jest on nestJS.
#Resolver()
export class UserResolver {
constructor(private readonly userService: UserService) {}
#UseGuards(GqlAccessGuard)
#Query(() => User)
async fetchUser(#CurrentUser() currentUser: ICurrentUser) {
return this.userService.findUserById({ id: currentUser.id });
}
#Mutation(() => User)
async createUser(#Args('createUserInput') createUserInput: CreateUserInput) {
return this.userService.create(createUserInput);
}
}
When I'm trying to test the "fetchUser" api of this resolver I'm stucked with the #UseGuard(). I don't know how can i import or provide the 'GQlAccessGuard' into the test code. Since I use the NestJs to build Graphql-codefirst server I used custom guard that extends AuthGuards to convert Context that request has.
export class GqlAccessGuard extends AuthGuard('access') {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}
#Injectable()
export class JwtAccessStrategy extends PassportStrategy(Strategy, 'access') {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: 'jwt-access-token-key',
});
}
async validate(payload: any) {
return {
id: payload.sub,
email: payload.email,
role: payload.role,
};
}
}
const createUserInput: CreateUserInput = {
email: 'test#gmail.com',
name: 'test',
password: 'testpwd',
phone: '010-1234-5678',
role: Role.USER,
};
class MockGqlGuard extends AuthGuard('access') {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}
describe('UserResolver', () => {
let userResolver: UserResolver;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
imports: [JwtModule.register({})],
providers: [UserResolver, JwtAccessStrategy],
})
.useMocker((token) => {
if (token === UserService) {
return {
create: jest.fn().mockReturnValue({
id: 'testuuid',
email: 'test#gmail.com',
name: 'test',
password: 'testpwd',
phone: '010-1234-5678',
role: Role.USER,
}),
};
}
})
.overrideGuard(GqlAccessGuard)
.useValue(MockGqlGuard)
.compile();
userResolver = moduleRef.get<UserResolver>(UserResolver);
});
describe('create', () => {
it('should return user created', async () => {
const result: User = {
id: 'testuuid',
email: 'test#gmail.com',
name: 'test',
password: 'testpwd',
phone: '010-1234-5678',
role: Role.USER,
};
expect(await userResolver.createUser(createUserInput)).toStrictEqual(
result,
);
});
});
});
I'm so curious about this and spent several days to search about it. also want to know how can i deal with the customized decorator(createParamDecorator that i made) to use on the test code.
please help me on this and provide me with some references.
I'm having problems retrieving an list from my state in the store, because the list is wrapped around with an object.
Here is my code
beer-list.component.ts
#Component({
selector: 'app-beer-list',
templateUrl: './beer-list.component.html',
styleUrls: ['./beer-list.component.css']
})
export class BeerListComponent implements OnInit {
public beers$: Observable<Beer[]>;
constructor(private store: Store<BeerState>) {
}
ngOnInit(): void {
this.beers$ = this.store.select((state: BeerState) => state.beers);
this.store.dispatch(getBeersOnTap());
}
beer.actions.ts
export const getBeersOnTap = createAction(
'[Beer List] Get beers'
);
export const getBeersOnTapSuccess = createAction(
'[Beer List] Get beers succeeded',
props<{beers: Beer[]}>()
);
export const getBeersOnTapFailed = createAction(
'[Beer List] Get beers failed'
);
beer.reducers.ts
export interface BeerState {
beers: Beer[];
}
export const initialState: BeerState = {
beers: []
};
export const beerReducer = createReducer(
initialState,
on(getBeersOnTapSuccess, (state, {beers}) => ({...state, beers: beers}) // Payload comes from an effect
));
export function reducer(state: BeerState | undefined, action: Action) {
return beerReducer(state, action);
}
What I'm retrieving from selecting beers from the store:
{
beers: {
beers: [
...
The beerReducer is probably added to a beers feature? So you will have to select the beer state first.
this.beers$ = this.store.select((state: AppState) => state.beers.beers);
Ok. I finally figured it out. I needed to adjust my state tree and add some selectors to select the beers from the store.
Here is my updated NgRx code where changes are commented:
beer-list.component.ts
#Component({
selector: 'app-beer-list',
templateUrl: './beer-list.component.html',
styleUrls: ['./beer-list.component.css']
})
export class BeerListComponent implements OnInit {
public beers$: Observable<Beer[]>;
constructor(private store: Store<AppState>) {
}
ngOnInit(): void {
this.beers$ = this.store.select(selectBeers); // Using selector here
this.store.dispatch(getBeersOnTap());
}
}
app.state.ts (new file)
export interface AppState {
beerState: BeerState
}
beer.selectors.ts (new file)
const selectBeerState = (state: AppState) => state.beerState;
export const selectBeers = createSelector(
selectBeerState,
(state) => state.beers
)
I just use TypeORM and find the relationship column is undefined
#Entity({name: 'person'})
export class Person {
#PrimaryGeneratedColumn('uuid')
id!: string;
#OneToOne( () => User)
#JoinColumn()
user!: User;
#Column({
type: "enum",
enum: PersonTitle,
default: PersonTitle.Blank
})
title?: string;
#Column({type: 'varchar', default: ''})
first_name!: string;
#Column('varchar')
last_name!: string;
#ManyToOne(() => Organization, org => org.people, { nullable: true})
belong_organization!: Organization;
and I also have Organization entity:
export class Organization {
#PrimaryGeneratedColumn('uuid')
id!: string;
...
}
when I use Repository like:
const db = await getDatabaseConnection()
const prep = db.getRepository<Person>('person')
presult = await prep.findOne({where: {id}})
console.log(result)
my result is:
Person {
id: '75c37eb9-1d88-4d0c-a927-1f9e3d909aef',
user: undefined,
title: 'Mr.',
first_name: 'ss',
last_name: 'ls',
belong_organization: undefined, // I just want to know why is undefined? even I can find in database the column
expertise: [],
introduction: 'input introduction',
COVID_19: false,
contact: undefined
}
the database table like:
"id" "title" "first_name" "last_name" "expertise" "COVID_19" "userId" "belongOrganizationId" "introduction"
"75c37eb9-1d88-4d0c-a927-1f9e3d909aef" "Mr." "test" "tester" "nothing" "0" "be426167-f471-4092-80dc-7aef67f13bac" "8fc50c9e-b598-483e-a00b-1d401c1b3d61" "input introduction"
I want to show organization id, how typeORM do it? Foreign-Key is present undefined?
You need to either lazy load the relation or you need to specify the relation in the find
Lazy:
#Entity({name: 'person'})
class Person {
...
#ManyToOne(() => Organization, org => org.people, { nullable: true})
belong_organization!: Organization;
...
}
...
async logOrganization() {
const db = await getDatabaseConnection()
const prep = db.getRepository<Person>('person')
presult = await prep.findOne({where: {id}})
console.log(await result.belong_organization)
}
Find
const prep = db.getRepository<Person>('person')
presult = await prep.findOne({
where: { id },
relations: ["belong_organization"]
})
You could also always do an eager load, but i'd advise against this since then it would always do the join when it fetches a person.
If you want to query the belong_organizationId you need to add its field to the person entity. This field is usual something like belongOrganizationId
That would make
#Entity({name: 'person'})
class Person {
...
#Column()
belongOrganizationId:number
#ManyToOne(() => Organization, org => org.people, { nullable: true})
belong_organization!: Organization;
...
}
This would make it possible to query for its id too.
You could also query it more directly but this leaves you with some pretty ugly and unmaintainable code:
const findOptions: {
where :{
id,
'belong_organization.id': belong_organizationId
}
}
I want to add createdAt and updatedAt to each model on loopback 4
can not find name 'MixinTarget'.
Type parameter 'T' of exported function has or is using private name 'MixinTarget'.
If I try from documentation above error occurs.
MixinTaget must be imported from #loopback/core:
import {MixinTarget} from '#loopback/core';
import {Class} from '#loopback/repository';
export function TimeStampMixin<T extends MixinTarget<object>>(baseClass: T) {
return class extends baseClass {
// add a new property `createdAt`
// eslint-disable-next-line #typescript-eslint/ban-ts-comment
// #ts-ignore
public createdAt: Date;
constructor(...args: any[]) {
super(args);
this.createdAt = new Date();
}
printTimeStamp() {
console.log('Instance created at: ' + this.createdAt);
}
};
}
Further reading
As of the writing of this answer, the docs hasn't been updated to reflect the latest clarifications.
https://github.com/strongloop/loopback-next/blob/4932c6c60c25df885b4166b7c4c425eba457a73b/docs/site/Mixin.md
To resolve this issue, I didn't use the mixin approach. I added the following fields to my model.
#property({
type: 'date',
default: () => new Date(),
postgresql: {
columnName: 'updated_at',
},
})
updatedAt?: Date;
It should work as expected
Code
class AclRowLevelsController extends AppController {
public $components = array(
// Don't use same name as Model
'_AclRowLevel' => array('className' => 'AclRowLevel')
);
public function view() {
$this->_AclRowLevel->checkUser();
...
}
}
class AclRowLevelComponent extends Component {
public function initialize(Controller $controller) {
$this->controller = $controller;
$this->AclRowLevel = ClassRegistry::init('AclRowLevel');
}
public function checkUser($permission, $model) {
$row = $this->AclRowLevel->find('first', array(
'conditions' => array(
'model' => $model['model'],
'model_id' => $model['model_id'],
'user_id' => $this->controller->Auth->user('id')
)
));
}
}
class AclRowLevelsControllerTest extends ControllerTestCase {
public function testViewAccessAsManager() {
$AclRowLevels = $this->generate('AclRowLevels', array(
'components' => array(
'Auth' => array(
'user'
),
'Session',
)
));
$AclRowLevels->Auth
->staticExpects($this->any())
->method('user')
->with('id')
->will($this->returnValue(1));
$this->testAction('/acl_row_levels/view/Task/1');
}
Problem
The query in the AclRowLevel component requires the Auth user id. I want to simulate user_id value '1' for the unit test.
The mocked Auth method 'user' in my test is not working for the call from the component. So the user id in that query has value null.
How should this be done?
Do a debug($AclRowLevels->Auth); to check if it really was mocked. It should be a mock object. If it is not for some reason try:
$AclRowLevels->Auth = $this->getMock(/*...*/);
The code inside checkUser() should go into the model by the way. Also I doubt this has to be a component at all. This seems to be used for authorization, so why not making it a proper authorization adapter?
This is what I was looking for:
$AclRowLevels->Auth
->staticExpects($this->any())
->method('user')
->will($this->returnCallback(
function($arg) {
if ($arg === 'id') {
return 1;
}
return null;
}
));