How I can make my belongsToMany relation in adonis.js - javascript

I'm trying to make a relationship between two tables.
My relationship is belongsToMany between user => user_bet_match => matchs.
A user can have many user_bet_match and matchs can have many user_bet_match.
My database migration is :
Matchs table :
this.create('matchs', (table) => {
table.increments()
table.integer('round_id').unsigned()
table.integer('league_id').unsigned()
table.integer('hometeam_id').unsigned()
table.integer('awayteam_id').unsigned()
table.string('final_score_hometeam_goal')
table.string('final_score_awayteam_goal')
table.string('halftime_score_hometeam_goal')
table.string('halftime_score_awayteam_goal')
table.date('event_date')
table.integer('event_timestamp')
table.boolean('betailable').defaultTo(false)
table.boolean('is_finish').defaultTo(false)
table.timestamps()
})
User table:
this.create('users', (table) => {
table.increments()
table.string('username', 80).notNullable().unique()
table.string('email', 254).notNullable().unique()
table.string('password', 60).notNullable()
table.timestamps()
})
user_bet_match table :
this.create('user_bet_match', (table) => {
table.increments()
table.integer('user_id').unsigned()
table.integer('match_id').unsigned()
table.string('choice').notNullable()
table.timestamps()
})
My user model:
class User extends Model {
static boot () {
super.boot()
this.addHook('beforeSave', async (userInstance) => {
if (userInstance.dirty.password) {
userInstance.password = await Hash.make(userInstance.password)
}
})
}
tokens () {
return this.hasMany('App/Models/Token')
}
match () {
return this.belongsToMany('App/Models/Match').pivotTable('user_bet_match')
}
My user bet match module:
'use strict'
/** #type {typeof import('#adonisjs/lucid/src/Lucid/Model')} */
const Model = use('Model')
const Database = use('Database')
class UserBetMatch extends Model {
user () {
return this.hasOne('App/Models/User')
}
matchs () {
return this.hasOne('App/Models/Match')
}
}
module.exports = UserBetMatch
And my matchs module:
'use strict'
/** #type {typeof import('#adonisjs/lucid/src/Lucid/Model')} */
const Model = use('Model')
class Match extends Model {
userbetmatchs () {
return this.hasMany('App/Models/UserBetMatch')
}
}
module.exports = Match
And when I make :
let k = user.match().fetch()
With this relation :
match () {
return this.belongsToMany('App/Models/Match').pivotTable('user_bet_match')
}
It's returning me sqlMessage: "Table 'bo7jjjccwliucibms5pf.matches' doesn't exist"
But I never mention of a table "matches"..
I don't know why..

I noticed that you changed the name of the tables in the migration (by default with adonis cli : matches; user_bet_matches)
Try to use this in your models:
static get table () {
return 'matchs' // Your table name
}
^ https://adonisjs.com/docs/4.0/lucid#_table
Lucid does not take into account the migrations.
It's therefore necessary to specify the name of the table if it is not the default one (with adonis cli).
Don't hesitate to tell me if it's not fair.

Related

Cypress sharing data (alias) between spec files

I need to use an alias I got after intercepting a cy.wait(#..) in other tests (in different files), but I am not sure how I could do it.
However, seems like it is possible if save the data in plugins/config space and then fetch using cy.task But I am not sure how to do it. Maybe someone could help me?
I am intercepting this request
cy.intercept('POST', '**/quotes').as('abccreateQuote')
and as well I am getting the quote Id that comes in the response body.
cy.wait('#createQuote').then(({ response }) => {
if (response?.statusCode === 201) {
cy.wrap(response.body.data.id).as('abcQuoteId')
}
})
I need to use this abcQuoteId alias in different tests and located it in different files.
to visit the page
cy.visit(`quotes/${abcQuoteId}/show`)
A task would do it, but less code if you write to a fixture file
cy.wait('#createQuote').then(({ response }) => {
if (response?.statusCode === 201) {
cy.writeFile('./cypress/fixtures/abcQuoteId.json', {abcQuoteId: response.body.data.id})
}
})
Advantage over task, you can check the fixture file manually in case of typos, should look like this:
{
"abcQuoteId": 123
}
and use it like this
cy.fixture('abcQuoteId.json')
.then(fixture => {
const url = `quotes/${fixture.abcQuoteId}/show`
console.log(url) // quotes/123/show
cy.visit(url)
})
This will allow you to create infinite stores with values that will be available all the time while Cypress is running.
Each Store with it's values is available between all spec files.
Usage:
Saving some value:
// spec.file1.js
cy.wait('#createQuote').then(({ response }) => {
if (response?.statusCode === 201) {
cy.task('setItem', {
storeId: 'Global',
item: {
name: 'createQuoteResponse',
value: response.body.data.id,
},
})
}
})
Getting the above value inside another spec file:
// spec.file2.js
cy.task('getItem', {
storeId: 'Global',
item: {
name: 'createQuoteResponse',
},
}).then((item) => {
console.log(item) // Your response code
})
How to implement?
Edit:
Install cypress-store-plugin
npm install #optimumqa/cypress-store
End of Edit
May seem like a lot of code, and it is. But once setup, you won't have to modify or worry about it.
Create a ./cypress/plugins/Store.js file and paste following code:
// `./cypress/plugins/Store.js`
const StoreHelper = require('../support/Store')
const stores = {}
class Store {
constructor(on, config, pluginConfig) {
this.CONFIG = {
...{
logging: false,
},
...(pluginConfig || {}),
}
this.init(on, config, pluginConfig)
}
init(on, config, pluginConfig) {
on('task', {
/**
* #description - Store items to specific store. If store does not exist, it will be created
*
* #param {String} data.id - Store id
* #param {Object} data.item - Object containing item info
* #param {String} data.item.name - Item name
* #param {Any} data.item.value - Item value
*
* #returns {Store.Item|Null}
*/
setItem: (data) => {
let store = stores[data.storeId]
if (!store) {
stores[data.storeId] = new StoreHelper()
store = stores[data.storeId]
}
return store.setItem(data.item) || null
},
/**
* #description - Get items from specific store
*
* #param {String} data.id - Store id
* #param {Object} data.item - Object containing item info
* #param {String} data.item.name - Item name
*
* #returns {Store.Item|Null}
*/
getItem: (data) => {
const store = stores[data.storeId]
if (store) {
return store.getItem(data.item)
}
return null
},
})
}
}
module.exports = Store
Then create one more other file ./cypress/support/Store.js and paste following code in it:
// `./cypress/support/Store.js`
class Store {
constructor() {
/** #type {object} */
this.items = {}
return this
}
getItem(data = {}) {
return this.items[data.name] || null
}
setItem(data = {}) {
this.items[data.name] = new Item(data)
return this.items[data.name]
}
}
class Item {
constructor(data = {}) {
this.name = data.name || ''
this.value = data.value || undefined
return this
}
}
module.exports = Store
Cypress < v10
Inside your ./cypress/plugins/index.js require the plugin like this:
You need to require the Store file from plugins/.
// `./cypress/plugins/index.js`
module.exports = (on, config) => {
require('./Store')(on, config)
}
Cypress >= v10
// `./cypress.config.js`
const { defineConfig } = require('cypress')
const Store = require('./cypress/plugins/Store')
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
Store(on, config)
},
},
})
This is by default enabled in the cypress-boilerplate

Passing Selection Model Table Row To Server in Angular 7

I'm trying to send the selected data in my table row that I am selecting via a checkbox to the server but having questions about how it should be sent via a service. I have the basic skeleton but need help with getting the items to a delete REST API call. Using C# .Net Core JSON call as the server endpoint for this service call.
view.component.ts
#Component({
templateUrl: 'view.component.html'
})
export class ViewComponent implements OnInit, OnDestroy {
// User Fields
currentUser: User;
users: User[] = [];
currentUserSubscription: Subscription;
loading : boolean;
// Action Fields
viewData: any;
viewName: string;
refNumber: number;
currentActionSubscription: Subscription;
displayedColumns: string[] = [];
dataSource: any = new MatTableDataSource([]);
pageSizeOptions: number[] = [10, 20, 50];
#ViewChild(MatSort) sort: MatSort;
#ViewChild(MatPaginator) paginator: MatPaginator;
selection = new SelectionModel<TableRow>(true, []);
defaultSort: MatSortable = {
id: 'defColumnName',
start: 'asc',
disableClear: true
};
defaultPaginator: MatPaginator;
constructor(
private iconRegistry: MatIconRegistry,
private sanitizer: DomSanitizer,
private actionService: ActionService
) {
this.loading = false;
this.iconRegistry.addSvgIcon(
'thumbs-up',
this.sanitizer.bypassSecurityTrustResourceUrl(
'assets/img/examples/thumbup-icon.svg'
)
);
}
loadAction(action: any) {
this.loading = true;
// If there is already data loaded into the View, cache it in the service.
if (this.viewData) {
this.cacheAction();
}
if (this.sort) {
// If there is sorting cached, load it into the View.
if (action.sortable) {
// If the action was cached, we should hit this block.
this.sort.sort(action.sortable);
} else {
// Else apply the defaultSort.
this.sort.sort(this.defaultSort);
}
}
if (this.paginator) {
// If we've stored a pageIndex and/or pageSize, retrieve accordingly.
if (action.pageIndex) {
this.paginator.pageIndex = action.pageIndex;
} else { // Apply default pageIndex.
this.paginator.pageIndex = 0;
}
if (action.pageSize) {
this.paginator.pageSize = action.pageSize;
} else { // Apply default pageSize.
this.paginator.pageSize = 10;
}
}
// Apply the sort & paginator to the View data.
setTimeout(() => this.dataSource.sort = this.sort, 4000);
setTimeout(() => this.dataSource.paginator = this.paginator, 4000);
// Load the new action's data into the View:
this.viewData = action.action;
this.viewName = action.action.ActionName;
this.refNumber = action.refNumber;
// TODO: add uniquifiers/ids and use these as the sort for table
const displayedColumns = this.viewData.Columns.map((c: { Name: any; }) => c.Name);
displayedColumns[2] = 'Folder1';
this.displayedColumns = ['select'].concat(displayedColumns);
// tslint:disable-next-line: max-line-length
const fetchedData = this.viewData.DataRows.map((r: { slice: (arg0: number, arg1: number) => { forEach: (arg0: (d: any, i: string | number) => any) => void; }; }) => {
const row = {};
r.slice(0, 9).forEach((d: any, i: string | number) => (row[this.displayedColumns[i]] = d));
return row;
});
this.dataSource = new MatTableDataSource(fetchedData);
this.loading = false;
}
// Stores the current Action, sort, and paginator in an ActionState object to be held in the action service's stateMap.
cacheAction() {
let actionState = new ActionState(this.viewData);
// Determine the sort direction to store.
let cachedStart: SortDirection;
if (this.sort.direction == "desc") {
cachedStart = 'desc';
} else {
cachedStart = 'asc';
}
// Create a Sortable so that we can re-apply this sort.
actionState.sortable = {
id: this.sort.active,
start: cachedStart,
disableClear: this.sort.disableClear
};
// Store the current pageIndex and pageSize.
actionState.pageIndex = this.paginator.pageIndex;
actionState.pageSize = this.paginator.pageSize;
// Store the refNumber in the actionState for later retrieval.
actionState.refNumber = this.refNumber;
this.actionService.cacheAction(actionState);
}
ngOnInit() {
// Subscribes to the action service's currentAction, populating this component with View data.
this.actionService.currentAction.subscribe(action => this.loadAction(action));
}
/** Whether the number of selected elements matches the total number of rows. */
isAllSelected() {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected === numRows;
}
/** Selects all rows if they are not all selected; otherwise clear selection. */
masterToggle() {
this.isAllSelected()
? this.selection.clear()
: this.dataSource.data.forEach((row: TableRow) => this.selection.select(row));
}
// Delete row functionality
deleteRow() {
console.log(this.selection);
this.selection.selected.forEach(item => {
const index: number = this.dataSource.data.findIndex((d: TableRow) => d === item);
console.log(this.dataSource.data.findIndex((d: TableRow) => d === item));
this.dataSource.data.splice(index, 1);
this.dataSource = new MatTableDataSource<Element>(this.dataSource.data);
});
this.selection = new SelectionModel<TableRow>(true, []);
this.actionService.deleteRow(this.selection).subscribe((response) => {
console.log('Success!');
});
}
ngOnDestroy() {
}
}
view.service.ts
deleteRow(selection: any): Observable<{}> {
console.log('testing service');
return this.http.delete<any>(`http://localhost:15217/actions/deleteRow`);
}
There are 2 things that your code as it currently stands needs to do:
Pass the ids of the selected rows back to the server in some way (generally via the url in a DELETE request)
Subscribe to the observable to materialise it. Currently the http request won't run, because it's an observable without any subscribers. At the very least the call to the service in the component should look a little like this:
this.actionService.deleteRow(this.selection).subscribe((response) => {
console.log('Success!');
});
Edit:
With number 1, it depends on what your server method looks like. If it accepts an array of numeric ids, then view.service.ts would look something like:
deleteRow(selection: SelectionModel<TableRow>): Observable<{}> {
console.log('testing service');
// create an array of query params using the property that you use to identify a table row
const queryParams = selection.selected.map(row => `id=${row.id}`);
// add the query params to the url
const url = `http://localhost:15217/actions/deleteRow?${queryParams.join('&')}`;
return this.http.delete<any>(url);
}
I'm guessing here at how you pass information about table rows to your server. If you're still struggling with this, you will need to provide a bit of information about the DELETE endpoint.
Edit 2:
Now we know a bit more about what the objects look like...
deleteRow(selection: SelectionModel<TableRow>): Observable<{}> {
console.log('testing service');
// create an array of query params using the property that you use to identify a table row
const queryParams = [...selection._selection].map(row => `id=${row.id}`);
// add the query params to the url
const url = `http://localhost:15217/actions/deleteRow?${queryParams.join('&')}`;
return this.http.delete<any>(url);
}

How to implement pagination with GraphQL DataLoader?

I have the following GraphQL query:
{
events {
name
images {
uri
}
}
}
Currently, I am not able (don't know how) to handle pagination in that images field, because it is fetched using DataLoader.
The eventImagesLoader (DataLoader) implementation is the following:
import DataLoader from 'dataloader';
import { getRepository } from 'typeorm';
import { EventImage } from '../entities/event-image';
export const eventImagesLoader = () =>
new DataLoader<string, EventImage[]>(async (eventIds) => {
const images = await getRepository(EventImage)
.createQueryBuilder('image')
.where('image.eventId IN (:...ids)', { ids: eventIds })
.getMany();
const imagesMap = new Map<string, EventImage[]>();
images.forEach((image) =>
imagesMap.has(image.eventId)
? imagesMap.get(image.eventId)!.push(image)
: imagesMap.set(image.eventId, [image])
);
return eventIds.map((eventId) => imagesMap.get(eventId) || []);
});
The problem that I am facing is to handle the pagination in this scenario, because if you see in the above excerpt of code, I am querying to the database in this way:
const images = await getRepository(EventImage)
.createQueryBuilder('image')
.where('image.eventId IN (:...ids)', { ids: eventIds })
.getMany();
Which translates in a SQL like that:
SELECT * FROM `event_image` AS image WHERE image.id IN ("id:1", "id:2", "id:n");
And I use DataLoader in this case to batch all database queries into one (the above one).
So with that, how am I able to handle pagination?
I would like to do something like that:
{
events {
name
images(page: Int) {
uri
}
}
}

How to get nested json objects using react js

I'm using laravel, and im trying to check to see if a user is following a user, if so the text box will change from follow to following.
I can get the id of the user easily but i can't check to see if a user has followable id
I need to reference the pivot object, and this object only shows when i do
<div id="profile" data='{{ $myuser->followers}}'></div>
but i need to use $myuser variable by itself.
This is what i have so far.
Profile.blade.php
<div id="profile" data='{{ $myuser}}'></div>
Profile.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';
export default class Example extends Component {
constructor(props){
super(props);
let id = JSON.parse(this.props.data);
// console.log('data from component', JSON.parse(this.props.data));
this.state = {
btnText: 'Follow',
className: 'follow-button',
user:{
// i can get a user id, but i cant get the followable id.
id:id.id,
followers:id.pivot.followable_id
}
};
}
myfollow(user) {
axios('/user/follow/'+ this.state.user.id , { method: "POST" })
.then(response => {
console.log(response);
});
};
componentDidMount(){
console.log('data from component', this.state.user.followers);
// if (this.state.user.followers === 3){
// this.setState({
// btnText:'Following',
// className:'following-button'
// });
// }
}
UserController.php
public function getProfile($user)
{
$users = User::with(['posts.likes' => function($query) {
$query->whereNull('deleted_at');
$query->where('user_id', auth()->user()->id);
}, 'follow','follow.follower'])
->where('name','=', $user)->get();
$user = $users->map(function(User $myuser){
$myuser['followedByMe'] = $myuser->follow->count() == 0 ? false : true;
return $myuser;
});
if(!$user){
return redirect('404');
}
return view ('profile')->with('user', $user);
}
MyFollow.php (model)
public function followedByMe()
{
foreach($this->follower as $followers) {
if ($followers->user_id == auth()->id()){
return true;
}
}
return false;
}
User.php Model
?php
namespace App;
use App\User;
use App\Post;
use App\GalleryImage;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use App\MyFollow;
use Overtrue\LaravelFollow\Traits\CanFollow;
use Overtrue\LaravelFollow\Traits\CanBeFollowed;
class User extends Authenticatable
{
use Notifiable,CanFollow, CanBeFollowed;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
public function posts()
{
return $this->hasMany(Post::class);
}
public function images()
{
return $this->hasMany(GalleryImage::class, 'user_id');
}
public function likes()
{
return $this->hasMany('App\Like');
}
public function follow()
{
return $this->hasMany('App\MyFollow');
}
public function comments()
{
return $this->hasMany('App\Comment');
}
}

belongsTo using associate doesn't seem to work

I have two "models" in my application that are branched into different files:
ApplicationSession.model.js
module.exports = function(sequelize, DataTypes) {
const { INTEGER, DATE } = DataTypes;
var ApplicationSession = sequelize.define("applicationSession", {
sessionStart: DATE,
sessionEnd: DATE
}, {
associate: (models) => {
ApplicationSession.belongsTo(models.User, {
foreignKey: 'userId',
as: 'User',
});
}
});
return ApplicationSession;
};
User.model.js
module.exports = function(sequelize, DataTypes) {
const { STRING, INTEGER, DATE } = DataTypes;
var User = sequelize.define("user", {
name: STRING
}, {
associate: (models) => {
User.hasMany(models.ApplicationSession);
}
});
return User;
};
When saving the tables (DROPING/RECREATING) force: true and manual dropping just for sanity, there is never a field created for the user.
Here's how I'm loading my different models
import fs from 'fs';
import path from 'path';
import sequelize from '../connection';
var exports = {};
fs.readdirSync(__dirname).forEach(fileName => {
if(~fileName.indexOf('.model.js')) {
const subname = fileName.replace('.model.js', '');
const model = sequelize.import(path.join(__dirname, fileName));
exports[subname] = model;
}
});
export default exports;
When all of the models are declared in a single file, I can use X.belongsTo(Y) without any problems, so I thought I'd try adding this to the bottom of my sequelize.import calls
exports['ApplicationSession'].belongsTo(exports['User'], { as: 'User', foreignKey: 'userId' });
exports['User'].hasMany(exports['ApplicationSession']);
However, that generated a different error:
/node_modules/sequelize/lib/associations/mixin.js:96
throw new Error(this.name + '.' + Utils.lowercaseFirst(Type.toString()) + ' called with something that\'s not an instance of Sequelize.Model');
^
Error: applicationSession.function BelongsTo(source, target, options)
What do I do to make relationships work?
You need to add one more thing to your model loading module to make associations work.
fs.readdirSync(__dirname).forEach(fileName => {
if(~fileName.indexOf('.model.js')) {
const subname = fileName.replace('.model.js', '');
const model = sequelize.import(path.join(__dirname, fileName));
exports[subname] = model;
}
});
// You need to check for every model if it has `associate` class method
// and execute it
Object.keys(exports).forEach(function(modelName) {
if ("associate" in exports[modelName]) {
exports[modelName].associate(db);
}
});
I also saw another mistake in your code. The class method defined in your models associate should be wrapped around classMethods property.
It should look like this :
var ApplicationSession = sequelize.define("applicationSession", {
sessionStart: DATE,
sessionEnd: DATE
}, {
// Add this
classMethods: {
associate: (models) => {
ApplicationSession.belongsTo(models.User, {
foreignKey: 'userId',
as: 'User',
});
}
}
});
You can follow the examples by sequelize in GitHub.

Categories

Resources