bookshelf / bookshelf

A simple Node.js ORM for PostgreSQL, MySQL and SQLite3 built on top of Knex.js
http://bookshelfjs.org
MIT License
6.36k stars 571 forks source link

How can I chain static properties #2034

Open vitornogueira opened 4 years ago

vitornogueira commented 4 years ago

Introduction

How can I chain static properties to create something like scopes?

Issue Description

I have a model called Event:

const db = require('../../lib/db');

module.exports = db.model('Event', {
  tableName: 'events',
  uuid: true,
  hasTimestamps: true,
}, {
  async byUser(userId) {
    return this.query()
      .join('user_events', 'events.id', '=', 'user_events.event_id')
      .where('user_events.user_id', '=', userId);
  },
  async past() {
    return this.where('finish_at', '<', new Date());
  },
});

I would like to chain methods to return all events of a user that have already happened. Something like that:

Event.byUser(userId).past();
fl0w commented 4 years ago

You're mixing allot of concepts here. Opting out of bookshelf (with an empty query call), returning promises on synchronous operations (which isn't necessary).

The easy answer is to move these to regular prototype properties instead, so:

const db = require('../../lib/db')

module.exports = db.model('Event', {
  tableName: 'events',
  uuid: true,
  hasTimestamps: true,
  byUser(userId) {
    return this.query()
      .join('user_events', 'events.id', '=', 'user_events.event_id')
      .where('user_events.user_id', '=', userId)
  },
  past() {
    return this.where('finish_at', '<', new Date());
  },
})
const events = await Event.forge().byUser(userId).past()
vitornogueira commented 4 years ago

@fl0w thanks for the answer.

I tried it that way but I got the following error:

TypeError: Event.forge(...).byUser(...).past is not a function

But if I use query and where in the same method works:

byUser(userId) {
  return this
    .query()
    .join('user_events', 'events.id', '=', 'user_events.event_id')
    .join('users', 'user_events.user_id', '=', 'users.id')
    .where('users.id', '=', userId)
    .where('finish_at', '<', new Date());
},

For now I will continue using the same method.

:wink:

fl0w commented 4 years ago

Ah, you are right. That will not work. So, when you opt out of bookshelf (with the empty query method) what’s returned isn’t a bookshelf model, but actually a knex QueryBuilder. And we didn’t extend it with the past method, thats why it doesn’t work.

fl0w commented 4 years ago

Just to be clear (I wrote last response on mobile), try following:

const db = require('../../lib/db')

module.exports = db.model('Event', {
  tableName: 'events',
  uuid: true,
  hasTimestamps: true,
  byUser(userId) {
    return this.query(qb => {
      qb.join('user_events', 'events.id', '=', 'user_events.event_id')
        .where('user_events.user_id', '=', userId)
    })
  },
  past() {
    return this.where('finish_at', '<', new Date());
  },
})

Should work with:

const events = await Event.forge().byUser(userId).past()