-
Notifications
You must be signed in to change notification settings - Fork 568
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
Associations: belongsTo does not work when combined with through #1031
Comments
As promised, here's the association definition with the SQL output:
|
Can you explain why those queries are incorrect and what you expected them to be? Also, can you check if Sorry, I meant |
Yep. Here's select `author`.*
, `author_book`.`id` as `_pivot_id`
, `author_book`.`book_id` as `_pivot_book_id`
from `author`
inner join `author_book` on `author_book`.`id` = `author`.`author_book_id`
where `author_book`.`book_id` in (?) This is incorrect because there is no The primary problem with the select `author`.* -- line 1
, `author_book`.`id` as `_pivot_id` -- line 2
, `author_book`.`author_id` as `_pivot_author_id` -- line 3
from `author` -- line 4
inner join `author_book` on `author_book`.`author_id` = `author`.`id` -- line 5
inner join `book` on `author_book`.`id` = `book`.`author_book_id` -- line 6
where `book`.`id` in (?) -- line 7 Line 2 should be |
I'm in the process of fixing this issue in my fork. Looking through the tests, I see that the use case for Blog has many Post. I think this is the source of my confusion because this is not the use case that I was describing above. Therefore I think no changes should be made to the The relationship I'm describing is having an optional foreign key live on a join table (rather than as a nullable foreign key in the table itself). Here's an example schema for such a relationship where a user has either zero or one shopping carts. In other words, a shopping cart belongs to a user. CREATE TABLE user (
id integer NOT NULL PRIMARY KEY,
name varchar(255) NOT NULL
);
CREATE TABLE cart (
id integer NOT NULL PRIMARY KEY
);
CREATE TABLE user_cart (
id integer NOT NULL PRIMARY KEY,
user_id integer NOT NULL,
cart_id integer NOT NULL
);
ALTER TABLE user_cart ADD CONSTRAINT user_cart_user_id_fk FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE user_cart ADD CONSTRAINT user_cart_cart_id_fk FOREIGN KEY (cart_id) REFERENCES cart (id) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE user_cart ADD CONSTRAINT user_cart_user_id_unique UNIQUE (user_id); Note that the Maybe the solution is to support explicit |
Right, that seems to be correct. I thought that getting the "grandparent" What about
|
I share the same problem. I am using bookshelf-soft-delete package. So I am dealing with records that are soft deleted. So I have three tables.
And the example models var Person, House, Home;
Person = Model.extend({
tableName: 'people',
house: function() { return /* THE ACTUAL PLACE FOR RELATION DECLARATION */ }
});
Home = Model.extend({
tableName: 'homes',
house:
});
House = Model.extend({
tableName: 'houses'
});
Person.forge({id: 1}).house().fetch()
.then(function(model) {
/* Handle single model */
}); I would like to have query that is for fetching single record. Something like this SELECT `houses`.* FROM `houses`
INNER JOIN `homes` ON `homes`.`house_id` = `houses`.id
WHERE `homes`.`person_id` = ?
AND `homes`.`deleted_at` IS NULL
AND `houses`.`deleted_at` IS NULL
LIMIT 1 Now some relation declarations and their generated SQL: select `houses`.* from `houses`
where (`houses`.`deleted_at` is null)
and `houses`.`id` = ? limit ? this.belongsTo(House).through(Homes, 'id'); select `houses`.*,
`homes`.`id` as `_pivot_id`,
`homes`.`house_id` as `_pivot_house_id`
from `houses`
inner join `homes`
on `homes`.`house_id` = `houses`.`id`
inner join `people`
on `homes`.`id` = `people`.`id`
-- `homes`.`id` should be `homes`.`person_id`
-- `people`.`id` key is the foreign key declared by "through"
where (`houses`.`deleted_at` is null)
and `people`.`id` = ? limit ? this.hasOne(House); select `houses`.*
from `houses`
where (`houses`.`deleted_at` is null)
and `houses`.`person_id` = ? limit ? this.hasOne(House).through(Home, 'xxx') select `houses`.*,
`homes`.`id` as `_pivot_id`,
`homes`.`person_id` as `_pivot_person_id`
from `houses`
inner join `homes`
on `homes`.`id` = `houses`.`id`
-- Should join `homes`.`house_id` instead of `homes`.`id`
-- `houses`.`id` key is the foreign key declared by "through"
where (`houses`.`deleted_at` is null)
and `homes`.`person_id` = ? limit ? this.belongsToMany(House).through(Home, 'house_id', 'person_id'); select `houses`.*,
`homes`.`id` as `_pivot_id`,
`homes`.`person_id` as `_pivot_person_id`,
`homes`.`house_id` as `_pivot_house_id`
from `houses`
inner join `homes`
on `homes`.`house_id` = `houses`.`id`
where (`houses`.`deleted_at` is null)
and `homes`.`person_id` = ? this.belongsToMany(House).through(Home, 'house_id', 'person_id').query(function(q){q.limit(1);}); select `houses`.*,
`homes`.`id` as `_pivot_id`,
`homes`.`person_id` as `_pivot_person_id`,
`homes`.`house_id` as `_pivot_house_id`
from `houses`
inner join `homes`
on `homes`.`house_id` = `houses`.`id`
where (`houses`.`deleted_at` is null)
and `homes`.`person_id` = ? limit ? The last one is the closest I could get. But So @ricardograca. As you see I tried also hasOne through variant. Is there a nice way for solving this? As stated in #1025 comment, through() method initally served other purpose. And as @chrisbroome stated in the end of this comment I would suggest the same fix for it. |
Dirty solution by monkey patching Bookshelf. Person = Model.extend({
tableName: 'people',
house: function() {
return this._relation('belongsTo', House, {
foreignKey: 'house_id',
joinClauses: function(knex) {
knex.join('homes', 'homes' + '.' + this.key('foreignKey'), '=', 'houses' + '.' + this.targetIdAttribute);
knex.join('people', 'people' + '.' + this.throughIdAttribute, '=', 'homes' + '.' + this.key('throughForeignKey'));
},
joinColumns: function(knex) {
var columns;
columns = [];
columns.push(this.throughIdAttribute);
columns.push(this.key('foreignKey'));
Array.prototype.push.apply(columns, this.pivotColumns);
knex.columns(_.map(columns, function(col) {
return 'homes' + '.' + col + ' as _pivot_' + col;
}));
},
whereClauses: function(knex, response) {
var key;
key = 'people' + '.' + this.targetIdAttribute;
knex.where(key, this.parentFk);
}
}).init(this).through(Home, 'person_id');
}
}); This could be optimized. This generates following SQL: select `brands`.*,
`homes`.`id` as `_pivot_id`,
`homes`.`house_id` as `_pivot_house_id`
from `houses`
inner join `homes` on `homes`.`house_id` = `houses`.`id`
inner join `people` on `people`.`id` = `homes`.`person_id`
where (`houses`.`deleted_at` is null)
and `people`.`id` = ? limit ? |
There is a slight problem with this solution. Nested eager loading doesn't work. |
Managed to get it working following hacky way: Person = Model.extend({
tableName: 'people',
house: function() {
return this._relation('belongsTo', House, {
foreignKey: 'person_id',
joinClauses: function(knex) {
knex.join('homes', 'homes' + '.' + this.otherKey, '=', 'houses' + '.' + this.targetIdAttribute);
},
joinColumns: function(knex) {
var columns;
columns = [];
columns.push(this.throughIdAttribute);
columns.push(this.key('foreignKey'));
Array.prototype.push.apply(columns, this.pivotColumns);
knex.columns(_.map(columns, function(col) {
return 'homes' + '.' + col + ' as _pivot_' + col;
}));
},
whereClauses: function(knex, response) {
var key;
key = 'people' + '.' + this.key('foreignKey');
knex.where(key, this.parentId);
}
}).init(this).through(Home, 'person_id', 'house_id');
}
}); select `brands`.*,
`homes`.`id` as `_pivot_id`,
`homes`.`person_id` as `_pivot_person_id`
from `houses`
inner join `homes` on `homes`.`house_id` = `houses`.`id`
where (`houses`.`deleted_at` is null)
and `homes`.`person_id` = ? limit ? |
Current specification of belongsToMany has a bug. Its `otherKey` and `foreignKey` parameter description are mistakenly partially swapped. Many issues have come up because of this problem. eg. bookshelf#397, bookshelf#1031, bookshelf#1276
The project leadership of Bookshelf recently changed. In an effort to advance the project we close all issues older than one year. If you think this issue needs to be re-evaluated please post a comment on why this is still important and we will re-open it. We also started an open discussion about the future of Bookshelf.js here #1600. Feel free to drop by and give us your opinion. |
I've found something similar to #1025, specifically when using
belongsTo
withthrough
.Let's say I have
Author
,Book
, andAuthorBook
models.Author
andBook
are standalone entities, andAuthorBook
is a join table with the usualauthor_id
andbook_id
foreign keys.In my
Book
model, if i have a relation defined like this:Then everything works as expected. The join table is used and the correct results come back.
However, let's say we decide that a book can have only zero or one author (I know that doesn't make much sense but bear with me). Also, we've still decided to use a join table to model the relationship. We can model that with exactly the same table structure, with the addition of a
UNIQUE CONSTRAINT
onbook_id
. We can even use the exact sameauthors
association defined earlier. However, because ofbelongsToMany
the result will be a Collection instead of a Model.From what I've ready, the solution is to use
belongsTo
withthrough
. For example:However, this never produces the correct join statement for me. Even using the 2nd and 3rd params to
through
, I'm never able to get it to generate the correct sql. Consequently, the above actually gives a SQL error.I've gotta run right now, but I'll come back and post the generated SQL later tonight.
The text was updated successfully, but these errors were encountered: