I need to have a model called 'Package' which can have one or more children of several different types. For example, a package can contain guides, as well as forms and other content types (some of which will be added later). Each of these content items, from different tables, can be in multiple packages. So I have the following schema:
Package table
=============
id
name
....
PackageContent table
=============
id
packageId
contentType
contentId
Guide table
=============
id
name
...
Form table
=============
id
name
How can I define the 'content' association for my packages in my Package.js model file in sails.js? I have not been able to find any information on combination foreign keys in sails.js or Waterline. I would hope to find something along the lines of:
// models/Package.js
module.exports = {
attributes = {
name: 'text',
....
content: {
through: 'PackageContent',
collection: contentType,
via: 'contentId'
}
}
};
I have a similar problem recently. My solution is to create different foreign keys in the intermediary model and set attributes as 'required:false'. In your example, the PackageContent table could look like this:
//PackageContent.js
module.exports={
attributes={
package:{
model:'package',
},
guide:{
model:'guide'
require:false
},
form:{
model:'form'
require:false
}
//other attributes...
}
}
In order to avoid duplicated package+content combination, you may need to write a beforeValidate method to check the duplication. I am not sure if this is a 'good design', but it solves the probelm
Related
Couldn't find this in the docs.
Is there any standard way, without creating a custom widget, or overriding the view template, to show a Many to Many relationships in a CRUD's showOperation in Backpack for Laravel? If the answer is NO, what would be your approach to implement it?
Let's say I have a Course Model, and a User model, and there is a Many to Many between both
class Course extends Model
{
public function students()
{
return $this->belongsToMany(User::class, 'course_students');
}
}
class User extends Model
{
public function courses()
{
return $this->belongsToMany(Course::class, 'course_students');
}
}
In the Show Operation for the Course. How do I show a Table with all students?
Indeed, you can use the relationship column for this
Excerpt:
Output the related entries, no matter the relationship:
1-n relationships - outputs the name of its one connected entity;
n-n relationships - enumerates the names of all its connected entities;
Its name and definition is the same as for the relationship field
type:
[
// any type of relationship
'name' => 'tags', // name of relationship method in the model
'type' => 'relationship',
'label' => 'Tags', // Table column heading
// OPTIONAL
// 'entity' => 'tags', // the method that defines the relationship in your Model
// 'attribute' => 'name', // foreign key attribute that is shown to user
// 'model' => App\Models\Category::class, // foreign key model
],
Backpack tries to guess which attribute to show for the related item.
Something that the end-user will recognize as unique. If it's
something common like "name" or "title" it will guess it. If not, you
can manually specify the attribute inside the column definition, or
you can add public $identifiableAttribute = 'column_name'; to your
model, and Backpack will use that column as the one the user finds
identifiable. It will use it here, and it will use it everywhere you
haven't explicitly asked for a different attribute.
I am trying to get the company details along with every employee. using node.js
I tried using "include".
return Employee.db.findAll({where: {salary: amount}}, include: Company.db],
});
Note: empId is the foreign key to employee(id).
in db intialization:
EmployeeDb.belongsTo(CompanyDb, {foreignKey: 'companyId', targetKey: 'id'});
CompanyDb.hasMany(EmployeeDb, {foreignKey: 'companyId', sourceKey: 'id'});
I was expecting company object inside all the employee objects. But the company details are not retrieved
The below should work for you.
Changed Employee.db to EmployeeDb and Company.db to CompanyDb
include expects an Array.
return EmployeeDb.findAll(
{
where: {salary: amount},
include: [CompanyDb]
}
);
I'm trying to model a cataloging system in DynamodDB. It has "Catalogs" which contains "Collections". Each "Collection" can be tagged by many "Tags".
In an RDBMS I would create a table "Catalogs" with a 1:n relationship with "Collections". "Collections" would have an n:n with "Tags" as a Collection can have multiple Tags and a Tag can belong to multiple Collections.
The queries I want to run are:
1) Get all catalogs
2) Get catalog by ID
3) Get collections by catalog ID
I read on AWS I can use the adjacency list map design (because I have the n:n with "Tags"). So here is my table structure:
PK SK name
cat-1 cat-1 Sales Catalog
cat-1 col-1 Sales First Collection
cat-1 col-2 Sales Second Collection
cat-2 cat-2 Finance Catalog
tag-1 tag-1 Recently Added Tag
col-1 tag-1 (collection, tag relationship)
The problem here is I have to use a scan which I understand to be inefficient in order to get all "Catalogs" because a query's PK has to be an '=' and not a 'Begins With'.
The only thing I can think of is creating another attribute like "GSI_PK" and add "Catalog_1" when the PK is cat-1 and the SK is cat-1, "Catalog_2" when the PK is cat-2 and SK is cat-2. I've never really see this done so I'm not sure if it's the way to go and it takes some maintenance if I ever want to change IDs.
Any ideas how I would accomplish this?
In that case, you can have the PK be the type of the object and the SK be a uuid. A record would look like this { PK: "Catalog", SK: "uuid", ...other catalog fields }. You can then do a get all catalogs by doing a query on the PK = Catalog.
To store the associations you can have a GSI on two fields sourcePK and relatedPK where you could store records that associate things. To associate an object you would create a record like e.g. { PK: "Association", SK: "uuid", sourcePK: "category-1", relatedPK: "collection-1", ... other data on the association }. To find objects associated with the "Catalog" with id 1, you would do a query on the GSI where sourcePK = catalog-1.
With this setup you need to be careful about hot keys and should make sure you never have more than 10GBs of data under the same partition key in a table or index.
Let's walk through it. I'll use GraphQL SDL to layout the design of the data model & queries but you can just apply the same concepts to DynamoDB directly.
Thinking data model first we will have something like:
type Catalog {
id: ID!
name: String
# Use a DynamoDB query on the **Collection** table
# where the **catalogId = $ctx.source.id**. Use a GSI or make catalogId the PK.
collections: [Collection]
}
type Collection {
id: ID!
name: String
# Use a DynamoDB query on the **CollectionTag** table where
# the **collectionId = $ctx.source.id**. Use a GSI or make the collectionId the PK.
tags: [CollectionTag]
}
# The "association map" idea as a GraphQL type. The underlying table has a collectionId and tagId.
# Create objects of this type to associate a collection and tag in the many to many relationship.
type CollectionTag {
# Do a GetItem on the **Collection** table where **id = $ctx.source.collectionId**
collection: Collection
# Do a GetItem on the **Tag** table where **id = $ctx.source.tagId**
tag: Tag
}
type Tag {
id: ID!
name: String
# Use a DynamoDB query on teh **CollectionTag** table where
# the **tagId = $ctx.source.id**. If collectionId is the PK then make a GSI where this tagId is the PK.
collections: [CollectionTag]
}
# Root level queries
type Query {
# GetItem to **Catalog** table where **id = $ctx.args.id**
getCatalog(id: ID!): Catalog
# Scan to **Catalog** table. As long as you don't care about ordering on a filed in particular then
# this will likely be okay at the top level. If you only want all catalogs where "arePublished = 1",
# for example then we would likely change this.
allCatalogs: [Catalog]
# Note: You don't really need a getCollectionsByCatalogId(catalogId: ID!) at the top level because you can
# use `query { getCatalog(id: "***") { collections { ... } } }` which is effectively the same thing.
# You could add another field here if having it at the top level was a requirement
getCollectionsByCatalogId(catalogId: ID!): [Collection]
}
Note: Everywhere I use [Collection] or [Catalog] etc above you should use a CollectionConnection, CatalogConnection, etc wrapper type to enable pagination.
How do I configure Sails.js / Waterline to default to pluralized relational database table names that correspond to singular models (same as Rails)?
(E.g. A model called 'Person' should default to a PostgreSQL table called 'people'.)
Just add the tableName: 'people' property to the model:
// Person.js
module.exports = {
tableName: 'people',
attributes: {
id: 'integer',
name: 'string'
}
};
There does not appear to be a global setting in Sails.js that pluralizes database table names automatically for models with singular names.
You can put this in your blueprints.js or local.js file:
blueprints: { // if in your local.js wrap in this object
pluralize: true
}
It won't get it right every time, so the tableName property is still useful for odd cases, but for most pluralized terms it will work.
In my app I have 2 table, books and tags, and the link table book_tags. The link table also contains the number of times the book was tagged with this particular tag. I can add a tag by doing
$book->add_tag($tag, { tag_count => 10 });
However when I retrieve the tags for a book
#tags = $book->tags();
it does not seem to return the values from the link table.
Is there a way to get the values from the link table without doing it by hand?
Since you have a join table named book_tags, you will have to create a many_to_many relationship from the books table to the has_many relation of the books table itself. The has_many relation should retrieve the ids of the tags associated with the book from the table book_tags. You may have to add relationships similar to:
In Your::App::Schema::Result::Book:
__PACKAGE__->has_many( book_tags => 'Your::App::Schema::Result::BookTag', 'book_id' );
__PACKAGE__->many_to_many( tags => 'book_tags', 'tag' );
In Your::App::Schema::Result::BookTag:
__PACKAGE__->belongs_to( tag => 'Your::App::Schema::Result::Tag', 'tag_id' );
You can proxy properties through relationships like this:
Schema::Tag->has_many('book_tags' => 'Schema::BookTag',
undef, {
proxy => [ 'tag_count' ],
}
);
Then you can access 'tag_count' like this:
my $tag_rs = $book->tags;
for( my $tag = $tag_rs->next ){
print $tag->tag_count;
}
You can find more at the relationship docs.
Thanks! I have already done this part, and it works.
My tables look like this:
BOOK:
book_id book_title [other fields]
TAG:
tag_id tag_name [other fields]
BOOK_TAG:
book_id tag_id tag_count
So I have a many-to-many relationship between tags and books with an additional attribute, tag_count, which is stored in the link table.
I can add a tag to a book by doing
$book->add_to_tags($tag, { tag_count => 10 } );
which populates the tag_count field in the BOOK_TAG table.
But when I do
$book->tags();
it does not automatically retrieve tag_count field from BOOK_TAG. I can write
$tag = $schema->resultset('BookTag')->find( { book_id=>$book->book_id, tag=>$tag->tag_id });
$tag_count = $tag->tag_count();
I'm trying to see if there is an easier way to get the extra attribute from the link table.