I have a model user and model page linked via belongsToMany. How to make sure that when the user's model is loaded the user automatically loads pages for him?
class User extends Lucid {
onCreate() {
this.related('pages').load()
}
}
console.log(yield User.find(1))
// {id: 1, pages: [...]}
I was wondering this the other day, I have a product that belongs to a merchant and I need the merchant data every time I used the product so auto-loading would be useful.
@webdevian, I achieved this effect by redefining the "find" method. But it seems to me that this is not the best solution.
class User extends Lucid {
static * find (key) {
const u = yield super.find(key)
yield u.related('page').load()
return u
}
@cawa-93
I believe it's fine to have your own methods to achieve the desired behavior, unless you are writing it at multiple places.
I don't find anything bad with this approach
Yes, but only in this case, the loading of the relationship does not happen everywhere. For example, if you redefine the standard findBy method:
static * findBy (key, value) {
const u = yield super.findBy(key, value)
yield u.related('permissions').load()
return u
}
, then in this code, the relationships are not loaded:
user = yield User.query().where('id', '1108519435909240').first()
console.log(user.toJSON())
{ id: '1108519435909240',
name: 'Alex Kozack',
is_premium: 1,
avatar: 'https://graph.facebook.com/v2.1/1108519435909240/picture?type=normal',
created_at: '2017-05-15 13:56:50',
last_login_at: 2017-06-12T07:54:23.000Z }
md5-b0fcced0c6bb1179da2b6f498c0fb982
```js
{ id: '1108519435909240',
name: 'Alex Kozack',
is_premium: 1,
avatar: 'https://graph.facebook.com/v2.1/1108519435909240/picture?type=normal',
created_at: '2017-05-15 13:56:50',
last_login_at: 2017-06-12T07:54:23.000Z,
permissions:
[ { name: 'edit_users',
_pivot_user_id: '1108519435909240',
_pivot_permission_id: 1 } ] }
Perhaps there is a way to use the constructor() of class?
@cawa-93 Yup, this seems to be kind of pain. I believe with afterFind hook it will be easier to do these things.
I will be adding that hook in next release. For now, all I can suggest it to have your own custom find method, which does some work for you. For example
static * fetchAndLoad (method, ...args) {
const user = yield super[method](...args)
yield user.related('permissions').load()
return user
}
and use it as
yield User.fetchAndLoad('find', '1108519435909240')
// or
yield User.fetchAndLoad('findBy', 'id', '1108519435909240')
I know this is not pretty, but get the job done. If you want even more control, I suggest creating a service and then create your own methods to fetch user and then load relations.
Thank you. Having an afterFind hook makes life much easier. I'll wait for a new release with this.
I tried to do this on query:
static query () {
return super.query().innerJoin('companies', 'companyId', 'companies.id')
}
It kind of worked but I ran into problems with ambiguous field names later on, then ran into more issues with pagination. I think I probably need to use methods like whereHas inside a query scope instead to get the multi-table querying I need
This was my better solution that also works for find and findBy
static query () {
return super.query().with('merchant')
}
This still causes problems with using count() in the chain, so I modified it to include a flag to disable loading the relationship.
static query (includeMerchant = true) {
if (includeMerchant) {
return super.query().with('merchant')
}
return super.query()
}
@webdevian How in your case can you preconvert the data?
When the User object loads, it has a permissions field:
user = yield User.find('1108519435909240')
console.log(user.toJSON().permissions)
```json
[
{"id": 0, "desc": "Some text"},
{"id": 1, "desc": "Some text"},
{"id": 2, "desc": "Some text"},
]
I want to get a one-dimensional array of identifiers
```json
[ 0, 1, 2 ]
I think you'd need a getter field to do that conversion
getPermissionsArray () {
if (this.relations && this.relations.permissions) {
return this.relations.permissions.map(permission => permission.id)
}
return undefined
}
So I have been cleaning up Lucid and thought of supporting this behaviour in a better way.
Now you will have afterFind hook, which can use you load the relations. Also I was thinking to allow something where you can define the toJSON output when model is part of a relationship.
So in your case, when you fetch permissions for a user, inside your Permission model, you can say
class Permission extends Lucid {
toObject () {
if (this.hasParent) {
return this.id
}
return super.toObject()
}
}
And and when you do
user.toJSON().permissions
you get an array of ids.
Hey ! 馃憢
What do you think to also add a getter inside the Model named with that will autoload those relation?
```js
class User extends Lucid {
static get with () {
return ['pages', 'permissions']
}
permission () {
// ...
}
pages () {
// ...
}
}
@romainlanz That would be really useful
@RomainLanz Few questions
find, findBy?with, does relationships concat with each other?with manually, I can do with('pages', (builder) => builder.where('is_published', true))For 3. Could a withPages method be used to define query constraints, and if no corresponding method is present it defaults to no constraints. This could work a bit like getters methods
For 1. I think it should apply to all but perhaps this behaviour could be changed using these "with methods"
@thetutlage
For 1 I also think it should apply to all. If we specified getter with, then all model objects should load the relationship, no matter how they were created (find, findBy, query.fetch etc)
For 3
Define default query constraints
class User extends Lucid {
static get with () {
return ['pages', 'permissions']
}
* pages () {
return this
.hasOne('App/Model/Page')
.where('is_active', true)
}
* permission () {
// ...
}
}
Define Runtime Query Constraints or redefine default constraints
const user = yield User.find(1).scope('pages', (builder) => builder.where('is_active', true))
I believe this is too custom and confusing. What I suggest is to use global query scopes for this
User.addGlobalScope(function (builder) {
builder.with('pages').with('permissions')
})
@thetutlage Is globalScope already built into 3.x? This seems like the simplest, most sensible solution.
@webdevian Yes it's in 3.x https://github.com/adonisjs/adonis-lucid/blob/develop/src/Lucid/Model/index.js#L296
But I believe this issue https://github.com/adonisjs/adonis-lucid/issues/139 will prevent having access to with inside scope callback.
Can u try once?
Adding this.addGlobalScope(builder => builder.with('merchant')) in the model's boot method seems to work 馃憤
Cool 馃憤
Going to close the issue
At the moment I can not check. Will this code work:
this.addGlobalScope(builder => builder.with('perrmissions').scope('perrmissions', b => b.pluck('id') ))
https://github.com/adonisjs/adonis-lucid/issues/128#issuecomment-309555796
I wanted an easy way to override the autoloading in some circumstances so I created a dynamic scope to set a flag to be read later on in the global scope:
/**
* Set a flag for global scope later on to not autoload a relationship
* @param {String} relationship Which relationship to stop auto-loading
*/
static scopeWithout (builder, relationship) {
builder['without' + relationship.toLowerCase().replace(/\b[a-z]/g, letter => letter.toUpperCase())] = true
}
Then in my global scope...
this.addGlobalScope((builder) => {
if(!builder.withoutParent) {
builder.with('parent')
}
}
So then I can disable the autoload of the parent relationship with:
yield Category.query().without('parent').fetch()
Hey 馃憢 !
GlobalScope seems the best method!
Great news that it works fine. 馃槃
I know this is a closed issue but I'm having a similar one in Adonis v5 now: preload a relationship when it exists, but not throws an exception when it not exists. It's better explained in this question here.
I know this is a closed issue but I'm having a similar one in Adonis v5 now: preload a relationship when it exists, but not throws an exception when it not exists. It's better explained in this question here.
still having this issue, did you resolve @tomrlh ?
Most helpful comment
So I have been cleaning up
Lucidand thought of supporting this behaviour in a better way.Now you will have
afterFindhook, which can use you load the relations. Also I was thinking to allow something where you can define thetoJSONoutput when model is part of a relationship.So in your case, when you fetch
permissionsfor a user, inside yourPermissionmodel, you can sayAnd and when you do
you get an array of
ids.