Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#feature-request OOP style retrieval of polymorphic many-to-many associations (example: tag/keyword functionality on a CMS) #2073

Open
captainhusaynpenguin opened this issue May 7, 2020 · 3 comments

Comments

@captainhusaynpenguin
Copy link

captainhusaynpenguin commented May 7, 2020

Introduction

I'm writing a previous CMS project of mine written in PHP on Laravel, later on Ruby on Hanami with Rails' ORM to Sails.js with Bookshelf.js

The issue at hand is implementing tagging functionality via the Keyword model.

I'm not sure whether it is because non-oop style of bookshelf or missing guides online that I can't work my way around it, but however I try I couldn't get a simple polymorphic functionality worked out.

Background structure of the database/models

  • keywords table: id, keyword, etc.
  • keywordables table: id, keyword_id, keywordable_id, keywordable_type, etc.
  • posts table: id, loc, etc.
  • stories table: id, loc, etc.

and all other sorts of contents with their respective tables.

Issue Description

A more detailed tutorial on doing basic things on associations, especially polymorphic associations is really missing. For example: how to get all the keywords that have an association with a Post model, while skipping the ones that are in the database because they are associated with other kinds of content like Story & etc but have no association with a Post model.

Steps to reproduce issue

In the PHP code, I do something like this: Keyword class defined in a separate library and then an additional function is added to it in this new library like this:

namespace IAtelier\BPLog;

use IAtelier\Atelier\Keyword as KeywordBase;
use IAtelier\BPLog\Post as Post;

class Keyword extends KeywordBase
{
    public function posts()
    {
        return $this->morphedByMany(Post::class, 'keywordable');
	}
}

then I can effortlessly retrieve all the keywords that have at least one association of the post model, by simply writing:

$tags = Keyword::has('posts')->get();

In Bookshelf I have designed a similar structure, two separate libraries, and Keyword is defined in like this:

let Keyword = sails.hooks.orm.bookshelf.model('Keyword', {
    tableName: 'keywords',
    hasTimestamps: true,
    keywordable() {
        return this.morphTo('keywordable', ['keywordable_type', 'keywordable_id'], 'Post', 'Book');
    }
});

module.exports = Keyword;

I hardwire added Post and Book while in reality, I rather preferred to do the same thing as I did in PHP: adding additional functions to the Keyword class from the second library.

Post model is an extension on Book model, like this:

var Book = sails.hooks.orm.bookshelf.model(
	'Book',
	{
....
        keywords() {
			return this.morphMany('Keyword', 'Keywordable', ['keywordable_type', 'keywordable_id']);
		},
....

and the Post definition:

let Post = sails.hooks.orm.bookshelf.model(
    'Post',
    sails.hooks.orm.bookshelf.model('Book').extend({
        tableName: 'posts',
....

Here comes writing the same line of code I wrote in php to get all the keywords that are associated with one or more posts:

const Keyword = sails.hooks.orm.bookshelf.model('Keyword');
const Post = sails.hooks.orm.bookshelf.model('Post');

Keyword.forge({id: 194}).fetch().then( function(model){
    return model.keywordable('Post').forge({keywordable_type: 'posts'}).fetch().then(k => {return k;});
}).catch();

However whatever I try I get errors! For example: the above segment itself is just finding a single keyword and should return the reverse associated Book submode, which in this case is a Post model. However it returns errors!

Expected behaviour

I was looking/hoping for something like this:


Keyword.fetchAll({ has: 'Post'});

While being able to add the post function in advance:

Keyword.assign({
     post: function(){ return this.keywordable('post'); }
});

Actual behaviour

I get all sorts of error depending on how I bend the code, but never anything remotely close to what I'm trying to achieve 🤷‍♂️

Questions

  • do I need to create a Keywordable model before linking things together? How does the relationship definition on it should look like?
  • on a more advanced note: is it possible to bind additional functions to the model later somewhere else after the definition.
@captainhusaynpenguin

This comment has been minimized.

@captainhusaynpenguin
Copy link
Author

captainhusaynpenguin commented May 10, 2020

Important Update

After much wandering in the darkness I realized this one is related to issue #1324; So, @chamini2 I/we highly appreciate your efforts. Only that MorphTo and MorphToMany are two totally separate animals!

PS. I realized this trying to iron out a collaboration for developing this feature with one of the ORM solutions in Node.js community.

@captainhusaynpenguin captainhusaynpenguin changed the title Question: OOP style retrieval of polymorphic many-to-many associations (example: tag/keyword functionality on a CMS) #feature-request OOP style retrieval of polymorphic many-to-many associations (example: tag/keyword functionality on a CMS) May 13, 2020
@captainhusaynpenguin
Copy link
Author

captainhusaynpenguin commented May 13, 2020

I'm new to Node.js and Jr anything other than DOM manipulation, also the total absence of guide-like documentation and in-depth tutorials for bookshelf, makes the learning curve higher. So, for the time being, I can't develop the feature myself, however, this is how I hacked my way around it, with the addition of taking advantage of 'virtuals' plugin which was removed from the core of the project [WHY?] and I thought sharing it would elaborate on the description & help someone to actually implement it in the core:

var Book = sails.hooks.orm.bookshelf.model(
	'Book',
	{
        async keywords() {
			
	const Keyword = sails.hooks.orm.bookshelf.model('Keyword');

	let type = this.virtuals.kind() + 's';

	var getKeywords = async function(array) {
		const Model = sails.hooks.orm.bookshelf.model('Keyword');
		let keywords = {};
		for (const [id, value] of Object.entries(array) ) {
			keywords[id] = await Model.forge({id: value.keyword_id })
				.fetch()
				.then(model => { return model; });
		}
		return keywords;
	}

	let result = await sails.hooks.orm.bookshelf.knex('keywordable')
		.where({
			keywordable_type: type,
			keywordable_id: this.id,
		}).select('keyword_id').then(getKeywords);
	
	return result;
       },
...

Thanks in advance & much success wishes for the future contributor(s)!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant