tom nurkalla's objectionjs notes
This commit is contained in:
parent
bb74b451d6
commit
957bb24c21
1 changed files with 219 additions and 0 deletions
219
db_relations.js
Normal file
219
db_relations.js
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
/* Taken from Tom Nurkkala's Youtube Video on Object-RElational Mapping, which uses KnexJS and ObjectionJS to demonstrate
|
||||
Basic SQL relationships
|
||||
|
||||
Models in relation
|
||||
source: model containing relationMappings definition
|
||||
related: model at other end of relation
|
||||
|
||||
Relation Attribute FK(s) in
|
||||
One-toMany BelongsToOneRelation Source
|
||||
HasManyRelation Related
|
||||
|
||||
Many-to-Many ManyToManyRelation Source
|
||||
|
||||
One-to-One BelongsToOneRelation Source
|
||||
HasOneRelation Related
|
||||
HasOneThroughRelation Join table
|
||||
|
||||
See video at around 48:00 for further details.
|
||||
Also look up Database Relations and also ObjectionJS docs for further details on each one.
|
||||
*/
|
||||
|
||||
// MANY TO ONE RELATION ////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Firstly we extend the ObjectionJS Model class so that we can reference both the Country and City classes/tables later
|
||||
|
||||
class Country extends Model {
|
||||
static get tableName() {
|
||||
return "country";
|
||||
}
|
||||
}
|
||||
|
||||
class City extends Model {
|
||||
static get tableName() {
|
||||
return "city";
|
||||
}
|
||||
// Note that we are still in the City extends Model here, we simply grab the city table here reference the Country class so that we can link
|
||||
// their primary keys as a BelongsToOneRelation relationship...
|
||||
|
||||
// Note that the country object returned here could have been called anything, it simply is an object that is returned which
|
||||
// refers to the relation we are creating here.
|
||||
|
||||
static get relationMappings() {
|
||||
return {
|
||||
// return city.$relatedQuery("country") below refers to THIS.
|
||||
country: {
|
||||
relation: Model.BelongsToOneRelation, // Defines a Many-to-One relationship
|
||||
modelClass: Country,
|
||||
join: {
|
||||
from: "city.country_id",
|
||||
to: "country.country_id",
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Then within our server's GET() request, we can ask for that particular relation to be displayed to us:
|
||||
|
||||
// Firstly we have the verbose version of a Many-to-one Lazy query, which is also an example of LAZY loading:
|
||||
|
||||
City.query()
|
||||
.where("city_id", 45)
|
||||
.first() // just give us the first result
|
||||
.then((city) => {
|
||||
console.log(city); // return us the City Class object
|
||||
return city.$relatedQuery("country"); // refers to the country object created above
|
||||
})
|
||||
// secondary .then() statement returns what is outputted from the to: relation
|
||||
// note that it doesn't just return us the country.country_id, that is just the foreign key that BINDS it to other tables,
|
||||
// we are referencing in this case, the WHOLE country table
|
||||
.then((country) => console.log(country))
|
||||
.catch((error) => console.log(error.message));
|
||||
|
||||
// But this is loaded using a "Lazy" fashion and is still a little clunky, there is a better way...
|
||||
|
||||
// This is an example of a Many-to-One (otherwise known as a BelongsToOneRelation) Eager query:
|
||||
|
||||
City.query()
|
||||
.withGraphFetched("country") // Load this relation first
|
||||
.where("city_id", 45)
|
||||
.first() // again, just the first result
|
||||
.then((city) => console.log(city))
|
||||
.catch((error) => console.log(error.message));
|
||||
|
||||
// ONE TO MANY RELATION ////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Great! withGraphFetched() does a lot of the work for us that id displayed above...
|
||||
// But what if we want to know the relation of the CITY to the COUNTRY instead,
|
||||
// This is what is known as a One-to-Many relation, and it can be defined like so:
|
||||
|
||||
class City extends Model {
|
||||
// class City references the table named "city"
|
||||
static get tableName() {
|
||||
return "city";
|
||||
}
|
||||
}
|
||||
|
||||
class Country extends Model {
|
||||
static get tableName() {
|
||||
return "country";
|
||||
}
|
||||
static get relationMappings() {
|
||||
return {
|
||||
// note that we named this relationship in plural, this is to help us understand what KIND of relationship we are creating (One-to-Many)
|
||||
cities: {
|
||||
relation: Model.HasManyRelation, // Defines a One-to-Many relation
|
||||
modelClass: City,
|
||||
join: {
|
||||
from: "country.country_id",
|
||||
to: "city.country_id",
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// And here's what the query() looks like (LAZY loading version):
|
||||
|
||||
Country.query()
|
||||
.where("country_id", 17)
|
||||
.first()
|
||||
.then((country) => {
|
||||
console.log(country);
|
||||
return country.$relatedQuery("cities");
|
||||
})
|
||||
.then((cities) => console.log(cities))
|
||||
.catch((error) => console.log(error.message));
|
||||
|
||||
// This will return to us a single instance (because of first()) of our Country Class with an array returned from
|
||||
// our $relatedQuery() method. Again it returns us an ARRAY of CLASS City instances.
|
||||
|
||||
// And of course, we would probably want to do this using an EAGER loading version:
|
||||
|
||||
Country.query()
|
||||
.withGraphFetched("cities")
|
||||
.where("country_id", 17)
|
||||
.first()
|
||||
.then((country) => console.log(country))
|
||||
.catch((error) => console.log(error.message));
|
||||
|
||||
|
||||
// MANY TO MANY RELATION ///////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Lastly let's look at a Many-to-Many Relationship (3 or more table join):
|
||||
|
||||
// In this instance, Tom Nurkkala uses the example of a film/actor database, which uses an "Associative Table",
|
||||
// called film_actor, which basically ONLY has references to two other tables (two foreign keys), in this case
|
||||
// the film table and the actor table.
|
||||
|
||||
class Actor extends Model {
|
||||
static get tableName() {
|
||||
return "actor";
|
||||
}
|
||||
fullName() {
|
||||
return `${this.first_name} ${this.last_name}`;
|
||||
}
|
||||
}
|
||||
|
||||
class Film extends Model {
|
||||
static get tableName() {
|
||||
return "film";
|
||||
}
|
||||
static get relationMappings() {
|
||||
return {
|
||||
// again, note the plural syntax here, to help us understand what we expect to be returned
|
||||
actors: {
|
||||
relation: Model.ManyToManyRelation,
|
||||
modelClass: Actor,
|
||||
// Because there are 3 tables involved here, we have to express their relations
|
||||
join: {
|
||||
from: "film.film_id",
|
||||
// References our "Associative Table", which again, has two FOREIGN keys, referenced below:
|
||||
through: {
|
||||
from: "film_actor.film_id", // references the film.film_id
|
||||
to: "film_actor.actor_id", // references the actor.actor_id
|
||||
},
|
||||
to: "actor.actor_id",
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// And once again, we can query() this relation in a LAZY Loading fashion:
|
||||
|
||||
Film.query()
|
||||
.select("film_id", "title")
|
||||
.where("film_id", 17)
|
||||
.first()
|
||||
.then((film) => {
|
||||
console.log("TITLE", film.title);
|
||||
return film.$relatedQuery("actors");
|
||||
})
|
||||
// after the 'actors' relation query is fulfilled, the returned array of actors is looped through,
|
||||
// and the fullName() method defined above is invoked, returning the actor's full name
|
||||
.then((actors) =>
|
||||
actors.forEach((actor) => console.log("ACTOR", actor.fullName()))
|
||||
)
|
||||
.catch((error) => console.log(error.message));
|
||||
|
||||
// And yes, the better EAGER version:
|
||||
|
||||
Film.query()
|
||||
.select("film_id", "title")
|
||||
.where("film_id", 17)
|
||||
.withGraphFetched("actors")
|
||||
.first()
|
||||
// Note the different syntax here, it's because Objection withGraphFetched() in a Many-to-Many relation returns us
|
||||
// a single CLASS instance (first()) where film has an ARRAY referring to the RELATION "actors"
|
||||
// This was defined in our relationMappings() method above...
|
||||
// so referencing that array is done slightly DIFFERENTLY
|
||||
.then((film) => {
|
||||
console.log("TITLE", film.title);
|
||||
film.actors.forEach((actor) => console.log("ACTOR", actor.fullName()));
|
||||
})
|
||||
.catch((error) => console.log(error.message));
|
||||
Loading…
Add table
Add a link
Reference in a new issue