Sequelize: Polymorphic Association Feature

Created on 27 Jan 2014  路  146Comments  路  Source: sequelize/sequelize

I'm currently rewriting my app from php to javascript, and i'm using sequelize :proudly: as our ORM. On my app i have a kind of associations that i called _Universal_ because they can associate with different models, if they belong to a pre-selected list.

Case sample:
I have a Comment model that is has a universal association called "Owner" with Post, Place or Event, which means that i can create a comment associated with a specified User, but the same can't be associated with other on the list (Post, Place or Event).

The fields that i use for that are owner_model @ VARCHAR(255) and owner_foreign_key @ INT(11).

I've read your docs multiple times but i didn't find anything about it, so my question is, do you have this functionality on sequelize? If not, can i do it with other approach?

Thanks and sorry for my bad english :)

feature

Most helpful comment

ORM's Research:

I searched for some orms docs about polyphormic associations, so here's the list:

  • RubyOnRails

    • They use id and type columns, i think that it is more readable model and foreign_key;

    • belongsTo needs a boolean option that activates polymorphic;

    • Instead of listing the modules on belongsTo, they use as on hasMany to refer to a specific connection, clever indeed.

    class Picture < ActiveRecord::Base
        belongs_to :imageable, polymorphic: true
    end

    class Employee < ActiveRecord::Base
        has_many :pictures, as: :imageable
    end

    class Product < ActiveRecord::Base
        has_many :pictures, as: :imageable
    end
  • CakePHP 2

    • There aren't docs about this, it took me about an hour in the past to figure it out.

    • Despite they don't mention nothing about polyphormic associations, it is possible using the conditions option. The only fact i listed it here was because they load automagicly the associations options, such as conditions when you contain a Model within other.

    • They have another feature that is simply and useful, counterCache, as an option on belongsTo. When some data is added or deleted, it updates the count field on Model who has it. Pretty handy to use with things that you need to count almost on every query.

    class User extends AppModel{
        public $hasMany = array(
            'Project' => array(
                'foreignKey' => 'owner_foreign_key',
                'conditions' => array(
                    'User.owner_model' => 'Project',
                ),
            ),
        );
    }
    class Team extends AppModel{
        public $hasMany = array(
            'Project' => array(
                'foreignKey' => 'owner_foreign_key',
                'conditions' => array(
                    'Team.owner_model' => 'Project',
                ),
            ),
        );
    }
    class Project extends AppModel{
        public $belongsTo = array(
            'User' => array(
                'foreignKey' => 'owner_foreign_key',
                'conditions' => array(
                    'Project.owner_model' => 'User',
                ),
            ),
            'Team' => array(
                'foreignKey' => 'owner_foreign_key',
                'conditions' => array(
                    'Project.owner_model' => 'User',
                ),
            ),
        );
    }
  • CakePHP 3

    • Seems like they changed the entire ORM feature, it looks like sequelize on associations.

    • Options are almost the same as CakePHP 2.

    class TeamTable extends Table {
        public function initialize(array $config) {
            $this->belongsTo('User', [
                'foreignKey' => 'owner_foreign_key',
                'conditions' => array(
                    'Team.owner_model' => 'User',
                ),
                'propertyName' => 'project',
            ]);
        }
    }

What we will need:

  • 2 columns:

    • Describing which model is linked ( [preffix_]model );

    • Describing to which row is linked ( [preffix_]foreign_key );

  • For multi-polyphormic proposes on the same model, we need an reference for each one (it could set alias if it isn't defined), which will serve as preffix for the columns that we will use;

Sequelize APIs possibilities:

There is a list of multiple approaches that could be applied on sequelize using the example of a Project that could be binded as Owner to a single User or a Team, .

Approach 1:

If the first argument of belongsTo is an array, it means that it is polyphormic, and that we already now which models will be associated

User = sequelize.define('User', {})
Team = sequelize.define('Team', {})
Project = sequelize.define('Project', {})

Project.belongsTo( [ Team, User ], { as: 'Owner' })

Team.hasMany(Project)
User.hasMany(Project)

Pros:

  • Simple, it follows the rule of argument a model, but instead of one, is an array of some.

Cons:

  • The array might look ugly

Approach 2:

Sam as Approach 1 but it is an attempt to be Cons free;

User = sequelize.define('User', {})
Team = sequelize.define('Team', {})
Project = sequelize.define('Project', {})

Project.belongsTo( Team, User, { as: 'Owner' } )

Team.hasMany(Project)
User.hasMany(Project)

Pros:

  • ( Same as Approach 1 )

Cons:

  • The looper on arguments has to identify the DAO on arguments, a kind of nasty for a Cons free. :)

Approach 3:

Based on Rails example

User = sequelize.define('User', {})
Team = sequelize.define('Team', {})
Project = sequelize.define('Project', {})

Project.belongsTo( 'Owner', { polyphormic: true } )

Team.hasMany(Project, { as: 'Owner' })
User.hasMany(Project, { as: 'Owner' })

Pros:

  • Doesn't need the list of models that it could accept.

Cons:

  • ( empty )

All 146 comments

Polymorphic associations are currently not supported.
You'll have to introduce your own class methods to simulate associations getters where you need to check a string key too.

It's on the roadmap for table inheritance, but it'll be some time.

Ok. I could write the polymorphic support tonight becaue i really need it.
I just wan't you to explain me a couple of things:

  • What's the difference between associations/has-many-double-linked.js and associations/has-many-single-linked.js?
  • Can i specify my own join string to QuerySelector?

Alright, but polymorphic support is such an important feature that api design should be discussed first. (But you could start up with a API proposal)

"What's the difference between associations/has-many-double-linked.js and associations/has-many-single-linked.js?"
One handles N:M associations and the other handles 1:M associations.

"Can i specify my own join string to QuerySelector?"
No, not currently - What are you looking for? Perhaps we support it in another way

Since you're using hasMany for N:M and 1:M, it could be easier to use hasMany for polymorphic too, with just a couple of differences:

  • models - array of models that could be on association, could be the first argument;
  • name - to be used preffix for fields ( preffix_model, preffix_foreign_key ) and association name, or it could be as;

Normaly if you have a 1:M association User with Post, you have the key on Many (Post). So the Join (on mysql) would be like LEFT OUTER JOINpostsASPostONUser.id=Post.user_id. The only difference with polymorphic would be the extra fieldLEFT OUTER JOIN posts AS Post ON User.id = Post.owner_foreign_key AND Post.owner_model == "User"`.

So if we could send our own join string it was easy, adding a few lines on belongsTo and hasMany to parse the options.

So where do you parse the join?

Well, you can append the join statement with where.

User.findAll({
  include: [{model: Post, where: {owner_model: 'User'}}]
})

Joins are created in selectQuery in dialects/abstract/query-generator.js

I forgot to mention an example:

User = sequelize.define('User', {})
Team = sequelize.define('Team', {})
Project = sequelize.define('Project', {})

Project.belongsTo( [ Team, User ], { as: 'Owner' })

Team.hasMany(Project)
User.hasMany(Project)

Polymorphic should probably be explicit. Not a fan of array to belongsTo.

How do you prefer?

I'm reading query-generator.js and it is quite interesting, specially the required option, i didn't have that on my old ORM and i need it, thanks! :)

required creates an inner join, it's true if you have a where else its false.
I don't have a preference for polymorphic yet, i'll have to look at existing works by other ORMs and get a API proposal up and talk it over with the other guys :)

So if you can live without polymorphic right now that would be great :)

If you could talk here it would be great because, as i need this, i wan't to help with it if you let me. Just for reference, i found other issues that were opened because of polymorphic that should be referenced into this issue ( #462 #131 ).
I have approximately 2 / 3 days until i need it, it would be cool to have the API reference until then, once we have it, i could work on that. :)

Thats a pretty short notice, but awesome that you want to work on it.

If you have the time, feel free to start the proposal yourself in the wiki, perhaps inspired by / with references from other ORMs. Also, hop onto IRC if you can

You can help out all you like @cusspvz.

Step one: Find references to polymorphic associations implemented in other popular orms.

As far as i can tell, internally we'll need a discriminator column and a discriminator value. Both of these options should have sane defaults for sync but also be completely overwriteable for existing databases.

ORM's Research:

I searched for some orms docs about polyphormic associations, so here's the list:

  • RubyOnRails

    • They use id and type columns, i think that it is more readable model and foreign_key;

    • belongsTo needs a boolean option that activates polymorphic;

    • Instead of listing the modules on belongsTo, they use as on hasMany to refer to a specific connection, clever indeed.

    class Picture < ActiveRecord::Base
        belongs_to :imageable, polymorphic: true
    end

    class Employee < ActiveRecord::Base
        has_many :pictures, as: :imageable
    end

    class Product < ActiveRecord::Base
        has_many :pictures, as: :imageable
    end
  • CakePHP 2

    • There aren't docs about this, it took me about an hour in the past to figure it out.

    • Despite they don't mention nothing about polyphormic associations, it is possible using the conditions option. The only fact i listed it here was because they load automagicly the associations options, such as conditions when you contain a Model within other.

    • They have another feature that is simply and useful, counterCache, as an option on belongsTo. When some data is added or deleted, it updates the count field on Model who has it. Pretty handy to use with things that you need to count almost on every query.

    class User extends AppModel{
        public $hasMany = array(
            'Project' => array(
                'foreignKey' => 'owner_foreign_key',
                'conditions' => array(
                    'User.owner_model' => 'Project',
                ),
            ),
        );
    }
    class Team extends AppModel{
        public $hasMany = array(
            'Project' => array(
                'foreignKey' => 'owner_foreign_key',
                'conditions' => array(
                    'Team.owner_model' => 'Project',
                ),
            ),
        );
    }
    class Project extends AppModel{
        public $belongsTo = array(
            'User' => array(
                'foreignKey' => 'owner_foreign_key',
                'conditions' => array(
                    'Project.owner_model' => 'User',
                ),
            ),
            'Team' => array(
                'foreignKey' => 'owner_foreign_key',
                'conditions' => array(
                    'Project.owner_model' => 'User',
                ),
            ),
        );
    }
  • CakePHP 3

    • Seems like they changed the entire ORM feature, it looks like sequelize on associations.

    • Options are almost the same as CakePHP 2.

    class TeamTable extends Table {
        public function initialize(array $config) {
            $this->belongsTo('User', [
                'foreignKey' => 'owner_foreign_key',
                'conditions' => array(
                    'Team.owner_model' => 'User',
                ),
                'propertyName' => 'project',
            ]);
        }
    }

What we will need:

  • 2 columns:

    • Describing which model is linked ( [preffix_]model );

    • Describing to which row is linked ( [preffix_]foreign_key );

  • For multi-polyphormic proposes on the same model, we need an reference for each one (it could set alias if it isn't defined), which will serve as preffix for the columns that we will use;

Sequelize APIs possibilities:

There is a list of multiple approaches that could be applied on sequelize using the example of a Project that could be binded as Owner to a single User or a Team, .

Approach 1:

If the first argument of belongsTo is an array, it means that it is polyphormic, and that we already now which models will be associated

User = sequelize.define('User', {})
Team = sequelize.define('Team', {})
Project = sequelize.define('Project', {})

Project.belongsTo( [ Team, User ], { as: 'Owner' })

Team.hasMany(Project)
User.hasMany(Project)

Pros:

  • Simple, it follows the rule of argument a model, but instead of one, is an array of some.

Cons:

  • The array might look ugly

Approach 2:

Sam as Approach 1 but it is an attempt to be Cons free;

User = sequelize.define('User', {})
Team = sequelize.define('Team', {})
Project = sequelize.define('Project', {})

Project.belongsTo( Team, User, { as: 'Owner' } )

Team.hasMany(Project)
User.hasMany(Project)

Pros:

  • ( Same as Approach 1 )

Cons:

  • The looper on arguments has to identify the DAO on arguments, a kind of nasty for a Cons free. :)

Approach 3:

Based on Rails example

User = sequelize.define('User', {})
Team = sequelize.define('Team', {})
Project = sequelize.define('Project', {})

Project.belongsTo( 'Owner', { polyphormic: true } )

Team.hasMany(Project, { as: 'Owner' })
User.hasMany(Project, { as: 'Owner' })

Pros:

  • Doesn't need the list of models that it could accept.

Cons:

  • ( empty )

Let's start out by designing the API, not the internals :)

Sorry @mickhansen , i didn't finished the comment yet, but it is handy to save it instead of loosing it :) i'm writing some examples and studying the ORM's

@cusspvz yeah i realized that :) Why i deleted my comments, ill check back later ;)

@mickhansen done, feel free to edit. It would be great if you add one or two api approaches to have more variety. I will check this later too, in a couple of hours :) I liked the fact that rails used a reference instead of a list on belongsTo.

@janmeier i don't use IRC for a long time. When talking about code / programming i prefer github, it has markup and a public history. If you have some time available check my comment too.

Project.belongsTo( [ Team, User ], { as: 'Owner' })

Team.hasMany(Project)
User.hasMany(Project)

This doesn't tell Team or User that it's polymorphic.

That's the idea, you just need to config belongsTo and it will configure the hasMany automagicly.
Even if they are triggered backwards, they should detect the configs because of as. But if you preffer:

Project.belongsTo( [ Team, User ], { as: 'Owner' })

Team.hasMany(Project, { as: 'Owner', polymorphic: true })
User.hasMany(Project, { as: 'Owner', polymorphic: true })

Just like it happens on N:M

I very much prefer code that actually says polymorphic somewhere when creating polymorphic assocations.

I cannot come up with an example right now, but there might be cases where you want to associate the same model both polymorphically and not:

Team.hasMany(Project) // this should be polymorpich
Team.hasMany(Project) // this should not

With teams and projects that might not make a lot of sense, but it could happen.

polymorphic: true looks good to me, but we might also want to extend it, to fit existing databases:

Team.hasMany(Project, { as: 'Owner', polymorphic: {
    on: 'kind' // The discriminator column, e.g. the column that holds user / team. Defaults to type
    // We would probably need a config option for descriminator id as well
    key: 'teams' // What should this model be referred to as. Defaults to model name
}})

Yes we need to support multiple associations between models, so we can't just guess that a hasMany call is polymorphic.

Team.hasMany(Project, { as: 'Owner', polymorphic: true })

Isn't really a great case, 'Owner' makes no sense in this case, a Team doesn't have many Owners, it has many Projects.

What do you two think about this?

Project.belongsTo( 'Owner', {
    polymorphic: { // Optional
        (..)
    },
})

Team.hasMany(Project, {
    polymorphic: { // true || string || object
        (...)
    }
})
User.hasMany(Project, {
    polymorphic:  {// true || string || object
        (...)
    }
})


// Where polymorphic could be
    polymorphic: { // If true or empty object, defaults to an object with all defaults, if string defaults to { key: 'string' }
        // Key that defines polymorphic association
        key: false, // Required and ( on belongsTo defaults to 1st argument if false // on hasMany defaults on polymorphic value if it is a string
        preffix: false, // Defaults to key if false
    },

So we could use it like this:

Project.belongsTo( { polymorphic: 'Owner' } ); 
/* OR */
Project.belongsTo( 'Owner' ); 

Team.hasMany( Project, { polymorphic: 'Owner' } );
User.hasMany( Project, { polymorphic: 'Owner' } );

Personaly, on Project.belongsTo i prefer the second one, it's cleaner and equivalent to you current API by supplying a string instead of a Model Class, but it could be an headache on future association approaches. What do you think?

Good morning, i just came up with an idea. Since each polymorphic has a configuration, and the same remains to all their endpoints, why shouldn't we create a class that can be passed to belongsTo and hasMany.

var ProjectOwner = Sequelize.polymorphic({
    // optional
    key: 'Owner',
    // optional
    preffix: 'Owner',
});

Project.belongsTo( ProjectOwner );
Team.hasMany( ProjectOwner );
User.hasMany( ProjectOwner );

The point of this idea is to share a configuration between them, but i really don't like the fact that i can't see to who am i referring on each association.

What do you need prefix for exactly?
But creating a polymorphic class might be an option.

I like the idea of creating a "master" polymorphic class

But how do you show what is the base table (the table that holds the discriminator and id key) - is .polymorphic a wrapper around .define that creates the join table?

Also, I think you should still be able to define the identifier for a table when definition the relation. E.g. what should be the value that goes into the discriminator column when doing Team.hasMany(ProjectOwner) - The default would be 'team', but the user should be able to define it themselves also

@mickhansen prefixshould have the prefix value for the table collumns, and it must default to key. If prefix is Likes, the table collumns should be likes_model and likes_foreign_key (or likes_id, @janmeier i'm liking the idea of id instead of foreign_key)

@janmeier Not a table but an class that would complete the association once we got an belongsTo and a hasMany. Since that ProjectOwner is constructed by Polyphormic instead of DAO, hasMany and belongsTo should know that they are handling a Polyphormic setting, and just add the association to Polyphormic. When it has at least one belongsTo and an hasMany it configures the associations between both.

By the way, I found another problem to cover up now on the API. I was chatting with my crew when i realised that we could user Polyphormic on an N:M, and we were only thinking on an 1:M. Imagine a N:M situation that one part is a polymorphic, that will get things nasty and API should be easy as hell to configure a complex thing.

So it's time for a sample, what's the easiest way for you to configure an association N:M between an Lighter that could have been on top of a Table and a Chair.

HABTM table should be something like:

| lighter_id | base_model | base_id |
| --- | --- | --- |

Also, I think you should still be able to define the identifier for a table when definition the relation.

@janmeier
Team.hasMany( ProjectOwner, { as: 'Crew' })?

Team.hasMany( ProjectOwner, { as: 'Crew' })?

something along those lines - but don't call it as, that would confuse it with alias i think (implying you could call Team.getCrew())

Sorry, i got it wrong, i thinked that you were talking about alias for the current model.
How do you currently define, on an one-to-many, the identifier for the foreign model?

as key is an alias for the current model or the foreign one? I read on docs and interpreted as for foreign model, i' confused...

// again the Project association to User
Project.hasMany(User, { as: 'Workers' })

if it is for the foreign model it's right this way:

Team.hasMany( ProjectOwner, { as: 'Plan' });
Team.getPlans();

@mickhansen , @janmeier any updates about this? We need to know how API will work to contribute. :)

@cusspvz @janmeier is out of the country for the weekend. We have a meetup on tuesday where me and @janmeier will discuss this more in detail - I'll get back to you.

We most likely want to do our association refactoring before doing this, not sure yet.

Deal, just keep in touch, i have some new proposals to this feature.

@cusspvz great, do tell us your proposals :)

Result of meetup:

var Comment = sequelize.define('comment')
var Commentable = Sequelize.polymorphic('commentable', {

})
Comment.belongsTo(Commentable)
// or implicitly by:
var Commentable = Comment.polymorphic('commentable', {
    'type_column': 'commentable_type',
    'key_column': 'commentable_id',
    as: 'Parent'
})

Question.hasMany(Commentable) //type = question by defualt
Question.hasMany(Commentable, {
    as: 'Answer'
    type: 'answer'
})

Question.findAll({
    include: [
        Commentable,
        {model: Commentable, as: 'Answer'}
    ]
})
ForumPost.hasMany(Commentable)

Hey, cool i liked!! Sorry for my late response.

I have a problem indeed.

The type of relation that you were defining is an 1:M one, where the M is the polymorphic one. But how we will handle on N:M? It's fair easy to understand that on a join table we can have two fields that links a polymorphic association. My problem with that is that i don't have yet any API proposals about this issue, in fact i just came to this problem because i need it to multiple models:

Category N:M [ Place, Event, Offer, ... ]

We know on an 1:M that the model that is configured with belongsTo is the one that saves data, but if we have a N:M we couldn't just add an attribute (i think) telling who saves because it is always the join table.

Could you get any solution to this?

We actually spent 15 minutes trying to come up with a N:M case, but i guess we focused on having it be polymorphic on both sides. Tags/Categories are the ideal scenario for this.

I'll talk to Jan about it and cover a case with N:M where one side is polymorphic with the possibility of a through model and attributes.

We had a short talk about this on our way home. This might not be final, but an idea would be

var Categorizable = sequelize.polymorphic('categorizable', {

})

Place.hasMany(Category, { through: Categorizable });
Event.hasMany(Category, { through: Categorizable });

Category.hasMany(Categorizable, { through: Categorizable });

The last line, category has many categorizalbes through categorizable is a bit ugly. But I think that is the only way to show the other side of the m:n relation.

On the polymorhpic side you can call

place.getCategories()
event.getCategories()

And on the non-polymorhpic:

category.getCategorizables()
category.getCategorizables({ where: { type: 'event' }})

@janmeier we need to cover having attributes on the polymorphic through model
so i oppose polymorphic should perhaps create a pseudo model that is only synced if its in a N:M relation

Damn.... Perhaps it's not a good idea to make Model.polymorphic an alias for sequelize.poly + belongs to. What do you think about

var ItemCategory = sequelize.define('item_category', {
    primary: Sequelize.BOOLEAN
});
var Categorizable = ItemCategory.polymorphic('categorizable', {

})

Place.hasMany(Category, { through: Categorizable });
Event.hasMany(Category, { through: Categorizable });

Category.hasMany(Categorizable, { through: Categorizable });

Yea we might have to drop having that part of the sugar. belongsTo(PolyInstance) just seems :/

That API seems like it would work. I suppose you should also be able to define a polymorphic through without a model - So through: string?

Let me think about this too, should be there any easier way than defining the polymorphic on the Throught model. I really wan't to make api easier as _sugar_ . :)

Well obviously we want that too :)

I just came up with this idea. The problem that we are facing is that we have to define the target, so if it could be one or other, why not target it by options? And if both are targeted, just throw an error.

If called throught belongsTo it will define automagicly the polymorphicTarget option.

Comment = Sequelize.define('comment')
Category = Sequelize.define('category')
Place = Sequelize.define('place')
Event = Sequelize.define('event')

// Polymorphic
Categorizable = Sequelize.polymorphic('categorizable', {})
Commentable = Sequelize.polymorphic('commentable', {})

// Associations
Comment.belongsTo( Commentable )
Categorizable.hasMany( Categorizable, { polymorphicTarget: true })

Place.hasMany( Categorizable )
Event.hasMany( Categorizable )

Place.hasMany( Commentable )
Event.hasMany( Commentable )

Personally, i don't like the polymorphicTarget name.
@janmeier i like the Sequelize method polymorphic but not the instance one, i think it is useless as we could configure it thought hasMany or belongsTo.

You are mixing concerns, could y ou please show seperate 1:M and N:M associations?
" i think it is useless as we could configure it thought hasMany or belongsTo." - You just asked for sugar, it's sugar not having to define stuff like that :)

In the Comment case wouldn't having Comment as the polymorphicTarget be the only sane thing? (Real world wise)

We also still need to cover using a through model with attributes.

Categorizable.hasMany( Commentable, { polymorphicTarget: true }) typo?

I apologize, my english isn't sharp yet to transmit what i think.

My idea is to proxy association configuration from hasMany and belongsTo to the Polymorphic Class if they catch the object to be constructed by it. The only options that we need to configure both sides of Polymorphic association (even on 1:M or N:M), besides the global options like type_column or key_column, is if the current association side is the source of data.

Logically, following the current API for associations, on belongsTo we don't need that option because we already know that it is an 1:M relation and that the model being configured is the source one, but on a N:M we need that specification, we need to know who should be the source, even that the source is stored on the join table.

Comment = Sequelize.define('comment')
Category = Sequelize.define('category')
Place = Sequelize.define('place')
Event = Sequelize.define('event')

// Polymorphic global configuration
Categorizable = Sequelize.polymorphic('categorizable', {})
Commentable = Sequelize.polymorphic('commentable', {})

// 1:M
Comment.belongsTo( Commentable )
Place.hasMany( Commentable )
Event.hasMany( Commentable )

// N:M
Category.hasMany( Categorizable, { polymorphicSource: true })
Place.hasMany( Categorizable )
Event.hasMany( Categorizable )

polymorphicSource is actually better than polymorphicTarget

Could you seperate the two associations and their models? It makes no sense that Categorizable has many comments in my eyes - Lets try to keep it real world examples ;)

source is definetely better than target.

But this still doesn't cover attributes on a through model, which you might want.

Updated: Sorry, my mistake. they were swapped

Comment = Sequelize.define('comment')
Category = Sequelize.define('category')
Place = Sequelize.define('place')
Event = Sequelize.define('event')

// Polymorphic global configuration
Categorizable = Sequelize.polymorphic('categorizable', {})
Commentable = Sequelize.polymorphic('commentable', {})

// 1:M
Comment.belongsTo( Commentable )
Place.hasMany( Commentable )
Event.hasMany( Commentable )

// N:M
Categorizable.hasMany( Category, { polymorphicSource: true })
Place.hasMany( Categorizable )
Event.hasMany( Categorizable )

I tried to make your API more real world like.
I really dont like Categorizable.hasMany( Category ) though, it makes no sense. Would make more sense for it to be Category.hasMany(Categorizable) but there's no way to know that is actually a N:M. Then polymorphicSource would define it to be N:M, and thats weird (also not future proof)

But this still doesn't cover attributes on a through model, which you might want.

Could you please specify one?

You might want to order/prioritize your categories on your events. Then you'd need a field on the join table

@mickhansen about Categorizable.hasMany( Category ... it was my mistake again, i waked up earlier :) sorry for that too

@cusspvz yea i figured it was a typo / error - but i wasn't sure ;)

I like @janmeier's N:M proposal, expect for:

Category.hasMany(Categorizable, { through: Categorizable }); which we agreed is terrible but don't really see a way around it.

Category.hasMany(Categorizable, { through: Categorizable })
I see the point of using this way because the through should be the model or string name of the join table, right?
If we use this approach we will limit to multiple polymorphic associations on same link, the fact of having other polymorphic on same N:M association. I can't figure out an example now.

Even if you create a Model with other columns you could link like this:
Category.hasMany(Categorizable, { through: 'categories_polymorphic' })

Ok, i got one example. It isn't quite good but it will serve to explain the limits that i don't wan't to create:

Category fields:
id, name

Event fields:
id, name

Place fields:
id, name

OtherModel fields:
id, text

CategoriesPolymorphic (join table) fields:
category_id, other_model_id, owner_type, owner_id

We don't support threeway associations at all, so i don't see how we would do that with polymorphic.
Your latest case will have to be done with a real model as the through modell, and then have that belongTo a poly.

Hey, sorry for my late answer, i'm coding a lot on my project but I will start to work on this feature this week. :)

Regards

Me and Jan are planning to work on it aswell, but getting inconsistencies and performance issues fixed are a higher priority currently, + documentation of existing features.

Just an update for anyone wondering. We're doing a lot of refactoring currently, when that is done we'll do association scopes, then we'll build poly on top of that.

Thanks @mickhansen , i have made some changes on sequelize localy but the associaton architecture isn't making this easy.

Association code is complex and a bit messy, and being worked on currently for schema support.

Great discussion so far--I think this is a very important feature for implementation and would be an indispensable part of this library. One question though: what's the point of having to define the target model as a target? The target model shouldn't be any different just because the model with the association is referring to it polymorphically.

Our current implementation in our custom-rolled LAMP framework, we can handle 1:M and N:M simply through the association. BelongsTo/HasMany relationships simply have useReferences: true so the ORM knows to handle the FK with a model/ID instead of just an ID. I like the idea of being able to pass a class in case you want to override the field names used, but requiring a Commentables class seems to turn your edge-case into your every-case and is probably burdensome for most implementations. The second problem is that the property doesn't know what to call itself without naming it explicitly. My proposal follows:

1:M

Comment.belongsTo([Place, Event], {as: 'Owner'});

N:M

Invoice.hasMany([Payment, LateFee], {as: 'Modifiers'});

Super simple implementation, and polymorphism is inferred. If arguments[0] is an array, as must be provided. On the implementation side, the 1:M does a where on two fields instead of 1, the N:M does N+1 queries where N is the number of models in the association.

@arcreative , forgive me if i'm wrong but as i understood, sequelize needs to create associations configuration object on each model so they work as expected in each one.

@mickhansen My problem on them was with the target and source. If you and @janmeier want to discuss another association object properties, maintaining the API i could help.

@arcreative How do you identify the reverse combinations? For your comment.belongsTo you need to identify the reverse hasMany so you can use getComments and prefetch, then you run into an issue where they'd have to guess the discriminator and key column since there might be multiple poly associations between the same models. We had that exact API up for discussion but theres quite a few cases it doesn't solve when it comes to the existant codebase.

Hi @mickhansen , how is it going with this feature? Have you think about getting an array on target attribute of object association on the polymorphic side?

Regards!

I didn't saw this ORM when searching. It seems like a nice API.

Personally i'm not a big fan of Bookshelfs API when it comes to poly. It's also not an API well suited to the state of Sequelize.

I don't like the way they define the alias on association, and lot of other things, i found it to be much more complex than sequelize. The cool thing i found on polymorphic was the morph[One, Many, To] because it is explicit that we are handling a polymorphic association, but it also has the cons that we found on other, as configuration for aliases, foreignKeys, columns, etc...

If we force the API approach to be like the other ones, we have, without doubt, to create a polymorphic class, which won't be good to be configured among multiple files (for those who have associations on each model).

Otherwise, we could use the "host" model, or morph[One, Many], to kept the relatives configuration (except alias).

Did this die for now? We're moving from rails to node/sequelize... and we need polymorphism. We might be able to devote resources if the proposal is firm but not implemented.

@randallb no one had the time to implement it yet. Proposal is firm, we have a pretty good idea of how to implement everything, the work just needs to be done honestly. Me and @janmeier do most of the contributions but both have work etc :)

I need this too, but the problem is that i have to read entire sequelize project to know from the top of the head the better way if implementing it. I could do this if you (@mickhansen or @janmeier) give me some documentation or explain me how things are working and what ideas are you implementing that would affect associations. It would be a pleasure for me to support it cause i really believe that sequelize could be the # 1 ORM for javascript.

First step is to create the association.scope feature.
What it means that you should be able to do ModelA.hasMany(ModelB, {as: 'SomeThing', scope: {yolo: 'swag'}}). And then all finds and inserts relating to that insertion would automaticaly inject the scope into where/values. We would then built the polymorphic magic methods on top of that.

Right, i've built already something like that. On my Models i have a function called needed which are running after the associate one.

captura de ecra 2014-07-16 as 21 49 14

When i call a function with Needed after, like findAllNeeded or findAndCountAllNeeded, with help of the extendOptions util function #1736 , i just merge the provided options with the needed before proxy it to the intended function.

Is the extendOptions util function #1736 useful for scope ?

Although I have difficulty parsing through the source to write code that would make this work, I would like to present a suggestion to the API/syntax of this feature if it has not been decided upon already.

From the PHP background I would say an easy to pick up polymorphic relation use comes from Eloquent (Laravel framework's ORM), which follows a similar a pattern to Rails. They extend their polymorphic relationships based on the regular relationships.

Definition

To define a 1:M polymorphic relationship:

User = Sequelize.define('user', {
}, {
  associate: function(models) {
    // Similar to belongsTo(), morph terminology helps to avoid confusion like this: https://github.com/sequelize/sequelize/issues/1307#issuecomment-34145538
    User.morphTo('userable');
    // Or to be more explicit:
    User.morphTo([Models.Employee, Models.Customer], {
        in: 'userable',
    });
});

Employee = Sequelize.define('employee', {
}, {
  associate: function(models) {
    // Similar to hasMany()
    Employee.morphsMany(models.User, {
      in: 'userable',
    });
});

Customer = Sequelize.define('customer', {
}, {
  associate: function(models) {
    // Similar to hasMany()
    Customer.morphsMany(models.User, {
      in: 'userable',
    });
});

This way, you don't need to explicitly pass in more options while maintaining a familiar syntax.

Tables

The underlying tables might look something like this:

User

| id | fields | userableId (FK to the referenced id) | userableType |
| --- | --- | --- | --- |
| 1 | content | 1 | Employee |
| 2 | content | 1 | Customer |
| 3 | content | 3 | Employee |

Employee

| id | fields |
| --- | --- |
| 1 | content |
| 2 | content |
| 3 | content |

Customer

| id | fields |
| --- | --- |
| 1 | content |

Fetching

I find fetching syntax to be more important (compared to definition for example) when using an ORM.

User.findAll({
  include: [{
    morphs: true,
  }],
}).success(function(users) {
}).error(function(err) {
});

User.findAll({
  include: [{
    morphs: true,
  }, {
    // Optionally limit to one model:
    model: Employee,
    where: {
      name: {
        like: 'abc%',
      },
    },
  }, {
    // Or specify both's models querying parameters
    // model: Customer,
    // Other querying parameters
  }],
}).success(function(users) {
}).error(function(err) {
});

Anyways, just some thoughts on this because I wasn't sure the above syntax was going to be clear enough?

If there was some way I can help I would love to!

@tonglil can you try to explain me what do you mean with the morphs?

User.findAll({
  include: [{
    morphs: true,
  }],
})

Morph as in "polymorphic". So in your quote it would mean you want to eager load the polymorphic relationships.

Think about morphMany as hasMany, and morphTo as belongsTo.

When defining it, all you need to do is change the "associate" object, without having to add a "needed" object to map the polymorphism.

morphs: true would load all polymorphed associations? That is not ideal imo, we would need a way to specifcy exactly what polymorphic associations to load. But i supposed it could be combined with the includeAll work that was done (by @overlookmotel iirc)

Perhaps all if that was specified, otherwise you have to still define what you want to load as per traditional syntax.

@tonglil also your case for specifying a single model doesn't really make it clear that you are including a morphed association, there might be multiple associations between the models (although i suppose it's unlikely).

Your definie API is interesting though, and we haven't started implementing yet so the API discussion is not locked :)

Interesting, do you mean that a model can be in both a polymorphic and regular relationship with one other model?

From my train of thought if I wanted to eager load both polymorphic and regular relationships to other models, this is what I would do (continuing from above):

User = Sequelize.define('user', {
}, {
  associate: function(models) {
    // To allow the polymorphism:
    User.morphTo('userable');
    // Other "regular" relationships
    User.hasMany(models.Item);
    User.belongsTo(models.Order);
});

Item = Sequelize.define('item', {
}, {
  associate: function(models) {
    Item.belongsTo(models.User);
});

Order = Sequelize.define('order', {
}, {
  associate: function(models) {
    Order.hasMany(models.User);
});

When loading no relationships (query #1):

User.findAll();

"Auto-" loading all/only polymorphic relationships (query #2):

User.findAll({
  include: [{
    morphs: true,
  }],
});

Or to force the user to have to explicitly define which relationships to load (query #3):

User.findAll({
  include: [{
    model: Employee,
    where: {
      name: {
        like: 'abc%',
      },
    },
  }, {
    model: Customer,
  }, {
    model: Item,
  }, {
    model: Order,
  }],
}).success(function(users) {
}).error(function(err) {
});

If you don't want the user to be able to load all/only polymorphic relationships like query #2, then include: [{morphs: true}] doesn't need to be supported since each relationship (polymorphic or not) would be seen as just another relationship like in query #3 (which is why I asked that question at the top of this comment).

Also do you mean your case for specifying a single model doesn't really make it clear that you are including a morphed association for the definition or the query?

Either way I'm down with whatever you guys do because getting it out as a feature is better than not having it available. Great job so far! :+1:

What i mean is that we need to distinguish between associations.

Suppose that User is associated to Employee both via a regular association and via a polymorph association. We then need to be able to distinguish the association somehow in the include (so we load the right one). It's possible that this is a completely made up case though, i just like to cover all my bases.

The only cons, but important one, that i'm seeing on distinguish options by using a polymorphic Class is that you could have problems with initializing on MVC structure, since models associations load by their name order so sometimes the targets will load first than sources and vice-versa and the only solution i'm seeing its a very dirty one, different file for polymorphic definitions.

After this i think that our best solution is a bad one.

Unless they are created internaly, and hasMany checks by using alias if it is already created:

API

Source.belongsToOneOf( alias, targets, options )
Source.belongsToOneOf( alias, options )
Source.belongsToOneOf( options )
// returns polymorphic instance

Source.belongsToOneOf( 'Targetables', [ TargetA, TargetB, TargetC ], {
  as: false, // If false, defaults to model name + 'ables', if true, defaults to Alias (Targetable), if string... doesn't default.
  targets: false, // Defaults to second argument, or empty array
  prefix: false, // Defaults to alias (underscored or camelcased)
  keyName: false, // without preffix, defaults to 'fk' or 'id'
  typeName: false, // without preffix, defaults to 'model' or 'type'
});


Target.hasMany( alias )
Target.hasMany( model )

Target.hasMany( 'Targetables' );

Examples

Model definitions

Source = S.define('Source', {});
TargetA = S.define('TargetA', {});
TargetB = S.define('TargetB', {});
TargetC = S.define('TargetC', {});

Association 1

Comment.belongsToOneOf( 'Commentables' )

TargetA.hasMany( 'Commentables' )
TargetB.hasMany( Comment, { as: 'Commentables' })
TargetC.hasMany( Comment )

Association 2

Comment.belongsToOneOf( 'Commentables', [ TargetA, TargetB, TargetC ] )

TargetA.hasMany( 'Commentables' )

TargetB.hasMany( Comment, { polymorphic: 'Commentables' })
TargetC.hasMany( Comment )

Association 3

var polymorphic;

polymorphic = Comment.belongsToOneOf( 'Commentables', [ TargetA, TargetB, TargetC ] )

TargetA.hasMany( polymorphic )
TargetB.hasMany( polymorphic )
TargetC.hasMany( polymorphic )

since models associations load by their name order so sometimes the targets will load first than sources and vice-versa do you mean they may not be loaded in here: associate: function(models) {} due to ordering when the containing model is being initialized?

I thought that wasn't loaded until all of the models were passed through (iirc from older docs that demonstrated this method).

I do not mind belongsToOneOf instead of morphTo, however I feel like re-using hasMany for the inverse side doesn't make it clear that it is a polymorphic association, which is why I suggested using morph as a differentiating keyword.

I only proposed belongsToOneOf because on my language makes more sense (translated word by word). Right now i'm more focused on API and internal working.

I load my models on associate function, but since my source model could be User or Comment, there is a large lack of Models between the letters C and U that could be sources or targets. As the only flow control documented is to start associations after all models definition, polymorphic associations must be clever enough to wait for the other parts as currently ones are already.

I think we should focus on cover up all the polymorphic cases with API options so @mickhansen, @janmeier and @sdepold could gather what is possible or not within sequelize current code structure.

By the way @mickhansen , is there any sense on creating a currently compatible feature structure (by creating belongsTo and hasMany under the hood on each polymorphic) before the dependences are met and then rewrite it? This should make this feature available to who needs this before rewriting the associations code (which relies on one source vs one target).

I will hopefully start working on association scopes (basically inject values into create/get/include of associations) which would pave the way for polymorphic. The closer we can get to a perfect API for poly the better, we might have to accept some tradeoffs.

@cusspvz not quite sure i follow? We always recommend doing your relation calls after all your models are available.

What an exciting discussion! Seriously!

I do love the simplicity of just defining extra conditions similar to what CakePHP does.

@fakewaffle mind throwing us an API proposal of how that might look in javascript/sequelize? :)

@cusspvz not quite sure i follow? We always recommend doing your relation calls after all your models are available.

I'm aware of that. I think i was not explicit enough because of my english vocabulary issues, but i will try again. :)

We discussed on top about creating a class to handle configurations between Polymorphic associations, so we could share that along them. Problem is that, as it happens currently with belongsTo and hasMany, we will never know who is called first, but on direct associations that is fairly easy to handle, on poly isn't not so quite. My last API proposal covers that with a required alias at definition.

By the way @mickhansen , is there any sense on creating a currently compatible feature structure (by creating belongsTo and hasMany under the hood on each polymorphic) before the dependences are met and then rewrite it? This should make this feature available to who needs this before rewriting the associations code (which relies on one source vs one target).

My question/concern was: if it is possible to implement ASAP this feature with scopes, by creating hasMany and belongsTo under the hood and then implement a new association type matching the input API, but always in mind that we should rewrite this. I'm asking you this because it is easy to implement by this way and it will be cool for who needs this right away, but as it doesn't cover all options (explaination above) we can't discard the need to rewrite it as it should.

There is a must on following the current association API, i understand that, but there are some exception, an example of them is as / alias...

As a developer, you have two ways of giving a name to your polymorphic associated object:

  • always the same name: no matter what model it is, it will always have the choosed alias;
  • always by their own name: always the associated model's name;

The first option is what concerned me since it doesn't work with current join methods, because the alias can't be duplicated, not on sequelize neither on mysql, so if we want to implement it current structure shouldn't work. Offtopic: It can be done with a subQuery that UNION's multiple SELECT's (should me more effective this way than multiple joins since you could have different WHERE's on different Models).

@mickhansen just to update this issue, i think that it is definitely a need to implant the subQuery as a join, and with unions for polymorphic.

On my app, yesterday one of my co-workers seen one of the biggest limitations of mysql, the 61 join limit. On the way sequelize prepares the query, this makes impossible to have a big data polymorphic query.

+1

Starting work on association scopes in https://github.com/sequelize/sequelize/pull/2268.
It's not polymorphic but it lays the ground work for both Sequelize to do polymorphic and for individual users to build poly on top of it (users might be able to do it quicker than us since we have to consider a bunch of cases).

@mickhansen what is the difficulty of implementing UNION joins with current association structure?

@cusspvz that's too vague for me to comment on, in what context? UNION between different levels of includes?

scopes are almost completely implemented in #2268. It's easy to build 1:M polymorphic on top of this, and one way N:M polymorphic (reverse will come in the actual polymorphic implementation, since we likely need UNION as @cusspvz suggests)

When i'm SELECTing the model that is set as belongsTo in polymorphic, if we want to join that association, we can't (with normal JOIN) have the same Alias for multiple tables. My question is, how hard is to implement on current association finding, something that results like this on polymorphic:

SELECT PolyModel.*
FROM PolyModel
LEFT JOIN (
  SELECT * FROM Model1
  UNION
  SELECT * FROM Model2
) AS PolyTarget ON key = PolyTarget.id #+ scope conditions?

Well when we implement poly it will have it's own join logic, so wouldn't have much to do with the current association finding sql generation.

If you are talking about implementing it as a user into the current include setup, it's not possible.

Well when we implement poly it will have it's own join logic, so wouldn't have much to do with the current association finding sql generation.

This answers my question :)

Alright :) Poly will likely be in the same place, but likely an if statement and it's own SQL for dealing, since we need unions etc.

I would be happy to contribute on this. Unfortunately at the time i'm hiper busy but as soon as i can, if this isn't implemented i would do it. :)

Just to know, it is not in the official roadmap so its not to be expected soon, right? Thanks

@apneadiving There isn't exactly an official roadmap, right now we're mostly focused on fixing bugs in the 2.0 RC though - But poly isn't that far off with the new association scope implementation.

I understand.
Do you feel like its more a question of 6 months or 1 year? (of course I cant expect a precise deadline, I'd just like to get a rough idea). Same for custom primary keys, they are quite convenient :)

Custom primary keys have been in for ages, there is no support for composite primary keys tho.
Poly will hopefully get done somewhere in the next two months, likely sooner - I'll probably be the one coding it so depends on when i have time.

We'll be waiting for this feature : ), great discussion.

I think that we have enough cases to close the API so we can start to implement it, i will move some of our resources to this today.

Could you both meet and discuss which API would you agree to implement?

ping @mickhansen @janmeier

I'm actually not totally sold on the current API proposals right now.

Do you have any different proposal in mind?

These are the scenarios API requisites to have in mind when proposing:

  • as could be:

    • individual - so we can use Model's name, or even an as for each one;

    • global - so we can use one name to to point all models;

  • Should be compatible with through for N:M cases
  • prefix so we can add a prefix on column names;
  • There should be a way to change the column names;

If you more requisites for API please provide them. :)

If we've decided on an API format, I'd like to try my hand at implementing the Postgres dialect support for polymorphic association. Postgres has actual schema-level support for polymorphic references, so perhaps a quick look at their solution may actually offer some insight into which API format would work best? @mickhansen, @cusspvz

@dkushner we haven't decided on the API specifically. However one side of polymorphic support has been coded via association scope support.

At this point we would want a cross dialect implementation without too many differences since it's an often asked features and we would want to support it for all dialects for a good while.

@mickhansen in which milestone's roadmap are you thinking to place this feature?

@cusspvz it will be after the final 2.0 release, and not sure if before or after JSONB support.

@mickhansen any news on plans for this feature?

@danamajid Not really, development will start when someone has the time :)

@mickhansen did you guys already settle on an API? (great discussion btw)

@danamajid I have some thoughts on a simple preliminary api which would primarily rely on passing association references to include, which would work around most of the issues we've discussed here.

So something like a polymorphic hasMany could look like:

Post.comments = Post.morphMany(Comment, {
  discriminator: 'commentable',
  foreignKey: 'commentable_id'
});

Post.findAll({include: [Post.comments]});

@mickhansen IMHO, one of the most important specs on this API is the ability to choose if you wan't to specify an alias from the source side to all target models or one alias per target model or simply keeping model's name.

Examples:
Comment -> [ Post, Photo ] as Content
Comment -> [ Post as Review, Photo as Image ]
Comment -> [ Post, Photo ]

I've arranged some time to supply a resume of this issue into a draft of specs so we can cover them all before providing more API approaches. Keep in mind that, as those are drafts, everything is open to discussion including names.

Draft Specs:

  1. We should be able to place 1:M and N:M polymorphic associations;
  2. Polymorphic related options/association object should be shared between associations, which means that API should provide a method for sharing them or we should try to do that internally;
    2.1. alias - global alias for entire polymorphic configs
    2.2. prefix - should default to alias (underscored or camelcased)
    2.3. typeAttribute - should default to prefix + ( '_type' || 'Type ) (underscored or camelcased)
    2.4. idAttribute - should default to prefix + ( '_fk' || 'Fk' ) (underscored or camelcased)
    2.5. source - source model
    2.6. targets - array of target objects, each one should have:
    2.6.1. model - target Model
    2.6.2. alias - target alias, should default to Model's name, unless polymorphic alias is set.
    2.6.3. scopes -maybe here?
  3. API shouldn't differ from what sequelize already has, so we can maintain ability to colect options such as scope and so on.

Probably API should maintain the ability to return Model instance so we don't create a BC for those who are chaining/stacking associations.

We removed that a while back to support including association referneces.

I'm currently developing a ( currently private, just until requirehit goes into an usable state ) plugin that is supporting data exchange and data availability on browser-side and it mimics current sequelize API for easier implementation by our engineers. I've done all basic stuff yesterday (Model, Instance, DataTypes and other classes) and today i'm finishing associations, but i've arranged a totally different way to store associations between models and currently is supporting all binding types on both sides.

@mickhansen perhaps you may want to look how associations plugin is working and take some ideas for specs?

// model configuration
database.define( 'Conversation', {

        name: DTs.STRING,
// ...

    }, {

        associations: [
            {   // polymorphic
                id: 'OwnersConversations',
                with: [
                    {
                        model: 'User',
                        as: 'Users',
                        many: true,
                    },
                    {
                        model: 'Agent',
                        as: 'Agents',
                        many: true,
                    },
            },
            {
                id: 'LastConversationMessage',
                with: {
                    model: 'ConversationMessage',
                    as: 'LastMessage',
                    many: false,
                },
            },
            {
                id: 'ConversationMessages',
                with: {
                    model: 'ConversationMessage',
                    as: 'Messages',
                    many: true,
                },
            },
        ],

        autosync: true,
        seen: true,

    });

// chunk of association.js

      var Association = F.Class.extend({

        initialize: function ( model, options ) {

            options =
                typeof options === 'object' && options ||
                {};

            if ( ! options.id ) {
                throw new TypeError( "you must provide options.id" );
            }

            // Handle association detection + creation
            var association =
                associations[ options.id ] ||
                ( associations[ options.id ] = F.extend( this, {
                    id: options.id,
                    A: [],
                    B: [],
                    links: [],
                }));

            // Try to gather source and target links
            var targets = parseWith( options.with ).map(function ( loptions ) {
                    return new Association.Link( association, null, 'B', loptions );
                });

            if ( ! F.Util.isArray( targets ) ) {
                throw new TypeError( "invalid target type" );
            }

            if ( targets.length === 0 ) {
                throw new Error( "targets array is empty!!" );
            }

            // Targets should all be on same side
            targets.reduce(function ( lastSide, target ) {

                if( lastSide && target._side !== lastSide ) {
                    throw new Error( "targets aren't all on same side!!" );
                }

                return target._side;
            });

            // Check if source exist, and if not, it should be created on other's
            // target side.
            var source = new Association.Link(
                association,
                model,
                targets[0]._otherSide,
                options
            );

        },

        type: function () {
            var association = this;
            return ([ 'A', 'B' ]).map(function ( side ) {
                side = association[ side ];
                return '' +
                    // Poly or not
                    ( side.length > 1 && 'p' || '' ) +
                    // one or many
                    ( side.reduce(function ( s, v ) {
                        return s || v.many;
                    }) && 'M' || '1' );
            }).join( ':' );
        },

    });

    Association.Link = F.Class.extend({

        initialize: function ( association, model, side, options ) {
            var link = this;

            // Association
            this.association =
                association instanceof Association && association ||
                false;

            if ( ! this.association ) {
                throw new TypeError( "association not valid" );
            }

            // Options init
            options = typeof options === 'object' && options || {};

            // Model
            this.model =
                typeof model === 'string' && F.Database.models[ model ] ||
                model instanceof F.Database.Model && model ||
                typeof options.model === 'string' && F.Database.models[ options.model ] ||
                options.model instanceof F.Database.Model && options.model ||
                false;

            if ( ! this.model ) {
                throw new TypeError( "model not valid" );
            }

            // alias
            this.as =
                options.as ||
                this.model.name;

            // check if there is already a link on association regarding same
            // model and alias
            for( var i in this.association.links ) {
                var checklink = this.association.links[i];

                if(
                    this.model === checklink.model
                ) {
                    return checklink.update( options );
                }
            }

            this.update( options );

            // Side
            this._side =
                side === 'A' && side ||
                side === 'B' && side ||
                false;

            if ( ! this._side ) {
                throw new TypeError( "side not valid" );
            }

                // set side
                this.side = this.association[ this._side ];
                this._otherSide = this._side === 'A' ? 'B' : 'A';
                this.otherSide = this.association[ this._otherSide ];

                // Add this link to the right side
                this.side.push( this );
                this.association.links.push( this );


            // Create binds on this model, from other side links
            this.otherSide.forEach(function ( otherLink ) {
                link.model.associateLink( otherLink );
            });

            // Create binds on other side link models, from this link
            this.otherSide.forEach(function ( otherLink ) {
                otherLink.model.associateLink( link );
            });

        },

        update: function ( options ) {

            this.many = options.many !== undefined ?
                !! options.many || false:
                !! this.many || false;

            if( ! this.many ) {
                // attributeModel
                this.attributeModel = options.attributeModel !== undefined ?
                    typeof options.attributeModel === 'string' && (
                        this.model.getAttribute( options.attributeModel ) ||
                        this.model.setAttribute( options.attributeModel, DTs.STRING )
                    ) ||
                    options.attributeModel instanceof F.Database.Attribute && options.attributeModel ||
                    false:
                    this.attributeModel ||
                    false;

                // attributeKey
                this.attributeKey = options.attributeKey !== undefined ?
                    typeof options.attributeKey === 'string' && (
                        this.model.getAttribute( options.attributeKey ) ||
                        this.model.setAttribute( options.attributeKey, DTs.INTEGER )
                    ) ||
                    options.attributeKey instanceof F.Database.Attribute && options.attributeKey ||
                    false:
                    this.attributeKey ||
                    false;
            }

            return this;
        },

    });

:+1:

Bountysource

We'd also _really_ love to see the API finalized and implemented! :smiley:

A use case that we're struggling with right now, but we're having a hard time thinking of an API for with the suggestions so far, is as follows:

  • Many types of Events (e.g., Procedures, Encounters, Observations)
  • Each event can have many DataSources (where the information about the event came from; multiple data sources can give information about the same event)
  • Many types of DataSources (e.g. XML, CSV, PDF, SomeAPI)
  • Each data source can give information about many events
  • Want to query for the data sources of a particular event, and to query for the events from a different data source

We'd also really love to see support for native Postgres table inheritance.

The discussion on this feature has been awesome :+1:

@troyastorino A lot can be implemented with association scopes, what we're basically missing for poly is a bit easier API and then reverse (UNION JOIN) include.

Is this going to be implemented then or what? This is something I really depend on right now...

@KieronWiltshire Eventually it will. It's on my list but i haven't had the time to finalize the design and implement it.

@mickhansen I would offer to help, but I'm extremely busy with a project, and one of my features can't progress any further until this is implemented. If possible could you please make it a priority?

@kieronwiltshire everyone working on this project is busy with projects - we only work on sequelize in our spare time :-)

If you need this feature, but don't have any time for it, then consider raising a lot bounty?

@KieronWiltshire Yeah come on! You can't just demand that volunteer maintainers prioritize things you need for no reason other than that you need it, and without offering anything in return. Open source is give and take, and the Sequelize maintainers do a _lot_ of work on this project for very little gain. Please be respectful.

And now a more helpful answer: If I remember right, if you search around the issues in this Github repo, I think you'll find some discussion of workarounds for achieving this with current Sequelize using scopes.

@overlookmotel I didn't realize he was a volunteer. I understand that came across as being needy. My apologies, I'm just not competent enough or have the time to help implement something like this.

@KieronWiltshire Most open source projects are on a volunteer basis, only a select few ever get any kind of corporate sponsorship :)

What's the status on this?

Has anybody looked at the Bookshelf.js way of handling this?

Essentially you want to do a lookup in a pivot table where 2 conditions are met, firstly: the model stored is the model you are querying, secondly the id matches the model you are trying to load.

I have seen alot of good examples here, that have been shot down due to them "not quite fitting", however surely as an open source library you would rather have this than not have it at all?

Please add this to Milestone 5.0

Guys, I'm in shock after this topic above. Thanks for the work! I'll wait for implementation. Because I need it every time.

@GatoPresto you'll be waiting a long time lmao

Any word on whether this will be added to sequelize in the near future?

@KieronWiltshire I don't expect it in the near future. But hope springs eternal 馃ぃ

This is currently available in Sequelize. check out the official documentation
Sequelize Scope

Just to add my 2 pennies on this subject whether welcomed or not but...

I can't help but think the reason this entire concept is so difficult to grasp and design appropriately for an easy to use API is the fact it's such a bad design practice. I always think if something massively hard to process in your brain then you're probably thinking incorrectly.

Some more info on this and a design alternative are described here http://duhallowgreygeek.com/polymorphic-association-bad-sql-smell/

And how I interpret this:

image

  • 1 Image belongs to 1 Action
  • 1 Article belongs to 1 Action
  • 1 Action has Many Comments

This way, you are commenting on a generic action.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If this is still an issue, just leave a comment 馃檪

Was this page helpful?
0 / 5 - 0 ratings