Hi, objection looks awesome, looking to switch from Bookshelf.
Is there a way to add hidden fields to an objection model? Say a User model has a password column containing a hash that I do not want returned in any JSON response. Bookshelf solves this by adding a static hidden array on the model, listing the fields I want hidden.
Any current solution? Couldn't find one in the docs.
Figured it out by overriding $formatJson on the base model
Just for a reference here is that function an user model of one of our projects.
get $secureFields(): string[] {
return ['pwhashcol', 'resettokencol', 'pwsaltcol'];
}
// omit stuff when creating json response from model
$formatJson(json, options) {
json = super.$formatJson(json, options);
return _.omit(json, this.$secureFields);
}
what do you do when you need it included back
Here's how I solved it:
import _ from 'lodash'
import { Model } from 'objection'
class CustomQueryBuilder extends Model.QueryBuilder {
visible (...visible) {
this.context({ visible })
return this
}
}
class Base extends Model {
static get QueryBuilder () {
return CustomQueryBuilder
}
static get hidden () {
return []
}
$afterFind (queryContext) {
const { hidden } = this.constructor
const { visible } = queryContext
if (hidden.length > 0) {
const unset = _.difference(hidden, visible)
for (const property of unset) {
delete this[property]
}
}
}
}
class User extends Base {
static get hidden () {
return ['email']
}
}
// Query:
const publicUser = await User.query().first() // hidden email
const privateUser = await User.query().visible('email').first()
The specified fields are automatically hidden, but you can change this behavior by using the QueryBuilder visible() method.
The major downside is that you can't use any hidden property in your server-side code, because those properties are deleted after the model's retrieval.
Here's how I solved it:
import _ from 'lodash' import { Model } from 'objection' class CustomQueryBuilder extends Model.QueryBuilder { visible (...visible) { this.context({ visible }) return this } } class Base extends Model { static get QueryBuilder () { return CustomQueryBuilder } static get hidden () { return [] } $afterFind (queryContext) { const { hidden } = this.constructor const { visible } = queryContext if (hidden.length > 0) { const unset = _.difference(hidden, visible) for (const property of unset) { delete this[property] } } } } class User extends Base { static get hidden () { return ['email'] } } // Query: const publicUser = await User.query().first() // hidden email const privateUser = await User.query().visible('email').first()The specified fields are automatically hidden, but you can change this behavior by using the QueryBuilder
visible()method.The major downside is that you can't use any hidden property in your server-side code, because those properties are deleted after the model's retrieval.
This works great! Thanks so much for sharing. However, the only issue I found with this approach is that if we use it with modifyGraph like this, it doesn't just make the fields visible for notifications as we would want, it makes them visible for all models including User and Notifications:
const user = await User.query().findById(userId).withGraphFetched({
notifications: true,
}).modifyGraph('notifications', builder => {
builder.visible('createdAt', 'updatedAt');
}).first();
How could this be fixed?
Most helpful comment
Just for a reference here is that function an user model of one of our projects.