bookshelf icon indicating copy to clipboard operation
bookshelf copied to clipboard

reverse relationship lookup with polymorphic relation

Open jamesdixon opened this issue 10 years ago • 23 comments

Hello,

I'm using a Polymorphic relation to allow an Account to be associated with two different user types: Customer and Staff. I have the models defined as follows:

var Customer = bookshelf.Model.extend({
  tableName: 'customer',
  accounts: function() {
    return this.morphMany(Account, 'concernable');
  }
});

var Staff = bookshelf.Model.extend({
  tableName: 'staff',
  accounts: function() {
    return this.morphMany(Account, 'concernable');
  }
});

var Account = bookshelf.Model.extend({
  tableName: 'account',
  concernable: function () {
    return this.morphTo('concernable', 'Staff', 'Customer');
  }
});

What I'm attempting to do is fetch all accounts that belong to a specific business and return the customers associated with those accounts. Here's what I've tried with no luck:

           Account.where({
              business_id: filter.businessId,
              concernable_type: 'customer'
            })
            .fetchAll({
              withRelated: ['concernable']
            })
            .then((response) => {
              return reply.jsonapi(response, 'customer');
            });

I receive the following error: The target polymorphic model was not found. If I change withRelated to customer, then the error is that customer is not found on the model.

Is what I'm attempting to do possible?

Thanks! James

jamesdixon avatar Mar 14 '16 05:03 jamesdixon

@rhys-vdw any chance you might be able to provide me with some insight on this? much appreciated!

jamesdixon avatar Mar 15 '16 03:03 jamesdixon

I am running into this as well.

Looking at the tests, it looks like Account withRelated concernable should work: https://github.com/tgriesser/bookshelf/blob/master/test/integration/relations.js#L545

it('eager loads morphTo (photos -> imageable)', function() {
  return Photo.fetchAll({withRelated: ['imageable']}).tap(checkTest(this));
});

https://github.com/tgriesser/bookshelf/blob/master/test/integration/helpers/objects.js#L215

var Photo = Bookshelf.Model.extend({
  tableName: 'photos',
  imageable: function() {
    return this.morphTo('imageable', Site, Author);
  },
  imageableParsed: function() {
    return this.morphTo('imageable', SiteParsed, AuthorParsed);
  }
});

https://github.com/tgriesser/bookshelf/blob/master/test/integration/helpers/objects.js#L54

var Site = Bookshelf.Model.extend({
...
  photos: function() {
    return this.morphMany(Photo, 'imageable');
  },
...
});

BKStephens avatar Mar 22 '16 18:03 BKStephens

Good find, @BKStephens. Suppose it could possible be a bug then.

jamesdixon avatar Mar 22 '16 19:03 jamesdixon

I am experiencing the same error

It could be related to the registry plugin.

return this.morphTo('concernable', 'Staff', 'Customer');

instead of

return this.morphTo('concernable', Staff, Customer);

danielepiccone avatar Apr 08 '16 12:04 danielepiccone

@dpiccone This is related to #1195 #1194.

rhys-vdw avatar Apr 08 '16 12:04 rhys-vdw

Sorry guys, I expect this was a bug, as detailed in #1194. Just merged the PR. I'll try to push an update soon.

rhys-vdw avatar Apr 08 '16 12:04 rhys-vdw

:bow:

danielepiccone avatar Apr 08 '16 12:04 danielepiccone

Thanks, @rhys-vdw -- really appreciate it!

jamesdixon avatar Apr 08 '16 19:04 jamesdixon

@rhys-vdw any eta on when you might push out that update? Thanks!

jamesdixon avatar Apr 14 '16 05:04 jamesdixon

Resolved by PR #1195

vellotis avatar Jun 17 '16 14:06 vellotis

@vellotis any idea when we might see this in a new release?

jamesdixon avatar Jun 20 '16 20:06 jamesdixon

@vellotis I'm still seeing this issue as of v0.10

@dpiccone was this ever resolved for you?

jamesdixon avatar Aug 25 '16 04:08 jamesdixon

@vellotis I've tracked this down to the following line: https://github.com/tgriesser/bookshelf/blob/6a77562cc9d4ceac1370fb1231fccdd9f7f7c467/src/eager.js#L45

The issue is that assumptions are made as to how the morphed fields are named. In my models, everything is camelCased via the format() method, so when the morphed column is retrieved, it comes back empty and the aforementioned error is thrown.

All that said, it seems like it should be calling the Model's format() method, but unfortunately, I'm struggling to find a way to call it in that file.

Thoughts?

cc: @rhys-vdw

jamesdixon avatar Aug 25 '16 04:08 jamesdixon

Just an FYI that if I change those lines to be camelCased attributes, I no longer receive the error and I can see that the proper queries are executed, but the returned models contain no relations 😢

jamesdixon avatar Aug 25 '16 04:08 jamesdixon

@jamesdixon #1195 is in v0.10.

I'm reopening it. Could you provide valid and invalid queries for that issue?

vellotis avatar Aug 25 '16 06:08 vellotis

@vellotis I can't really generate a valid/invalid query for this as it never gets to that point because of the aforementioned bug.

However, in my case, I'm trying to retrieve all accounts, which sit on the opposite side of the polymorphic relation. I would expect that Bookshelf looks at the polymorphic fields (concernable_id and concernable_type) and generates the proper lookup query from the concernable_type field.

The issue is that Bookshelf is making an assumption about how the field is named on the model. In cases where someone is using format to override the defaults, this causes breakage as demonstrated. Let me know if this is helpful.

jamesdixon avatar Aug 25 '16 15:08 jamesdixon

@vellotis?

jamesdixon avatar Sep 13 '16 18:09 jamesdixon

@vellotis Here is more details :

  • When I let the default columnNames in snake_case it works when helpers.js#morphCandidate is called by relations.js because my attributes are in snake_case. But when the function is called by eager.js my attributes are formated so they are in camelCase so the foreignTable is undefined in helpers.js#morphCandidate
  • I tried to specify the columnNames in camelCase in my model but obviously it works when it's call by eager.js but not relations.js

It seems that for relation.js the function format is correctly called, but not for eager.js as @jamesdixon already mentioned.

cc: @rhys-vdw

TinOo512 avatar Sep 14 '16 17:09 TinOo512

Facing the exact same issue in 0.10.2

var Post = bookshelf.Model.extend({
  tableName: 'posts',
  comments: function() {
    return this.morphMany(Comment, 'keyable');
  }
});

var Comment = bookshelf.Model.extend({
  tableName: 'comments',
  keyable: function() {
    return this.morphTo('keyable', Post);
  }
});

I found that the function is called by eager.js (same as in @TinOo512 's case) foreignTable's value is Post but the value in candidates comes as posts in helpers.js#morphCandidate

DGulshan avatar Nov 09 '16 13:11 DGulshan

@DGulshan Are you using Model Registry ? Otherwise if you override format/parse it should be the same issue than mine so this fork should fix this. Feel free to try it and add :+1: if it resolve your issue :)

TinOo512 avatar Nov 09 '16 14:11 TinOo512

@TinOo512 Yes, I'm using Model Registry. So I still get the same error. Thanks though! :)

DGulshan avatar Nov 09 '16 14:11 DGulshan

Is there any workaround for this bug? My models are camelCased, and when I call .fetch(withRelated{['myCamelCasesMorphTo']} I get the The target polymorphic model was not found error.

ashercoren avatar Jan 01 '17 18:01 ashercoren

Request Table

const bookshelf = require('./bookshelf');
require('./Comment');
require('./InventoryItem');
require('./User');
require('./Activity');
const UserAssignment = require('./UserAssignment');

module.exports = bookshelf.model('Request', {
  tableName: 'requests',
  uuid: true,
  hasTimestamps: true,

  comments() {
    return this.morphMany('Comment', 'note');
  },
  inventory_item() {
    return this.belongsTo('InventoryItem');
  },
  activities() {
    return this.morphMany('Activity', 'parent');
  },
  user_assignments() {
    return this.morphMany(UserAssignment, 'parent');
  },
});

User Assignment Table

const bookshelf = require('./bookshelf');
require('./User');
require('./Assignment');
const Request = require('./Request');
const InventoryItem = require('./InventoryItem');

module.exports = bookshelf.model('UserAssignment', {
  tableName: 'user_assignments',
  uuid: true,
  hasTimestamps: true,
  user() {
    return this.belongsTo('User', 'user_id', 'global_id');
  },
  parent() {
    return this.morphTo('parent', 'Request');
  },
});

It was not accepting 'requests' or 'Requests' as parent_type until I made the following update, which does NOT match this documentation

- return this.morphTo('parent', Request, InventoryItem);
+ return this.morphTo('parent', 'Request', 'InventoryItem');

bradleyridge avatar Mar 29 '19 10:03 bradleyridge