Home > Software engineering >  One way one to many relation
One way one to many relation

Time:11-12

I have a Recipe and a Tag model. Currently, the recipe contains an array of id's belonging to Tag:

    @Entity()
    export class Recipe extends BaseEntity {
      @PrimaryGeneratedColumn('uuid')
      public id!: string;
    
      @Column({ type: 'varchar' })
      public title!: string;
    
      @Column({ type: 'varchar' })
      public description!: string;
    
      @Column({ type: 'simple-array', nullable: true })
      public tags: string[];

    }

@Entity()
export class Tag extends BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  public id!: string;

  @Column({ type: 'varchar' })
  public name!: string;
}

However, I am currently not making use of the relational capabilities of TypeORM. I was wondering though, how would i go about doing this? Since the relation only works one way, i.e. the one Recipe having many Tags.

CodePudding user response:

I could be wrong, but I believe by default, you must declare both ways--even if you only intend to use a single direction of the relationship.

For example, you need to declare that a Recipe has many Tags you also have to set up the Tag to Recipe relationship even if you aren't going to use it.

Given your example, you'll need to set up a one:many and a many:one relationship.

Since Recipe will "own" the tags, it will have the one:many:

// recipe.entity.ts

@OneToMany(() => Tag, (tag) => tag.recipe)
tags: Tag[];

Then the inverse will look like this:

// tag.entity.ts

@ManyToOne(() => Recipe, (recipe) => recipe.tags)
@JoinColumn({
    name: 'recipeId',
})
recipe: Recipe;

If you're considering having many recipes own the same tag, you may need to consider using a many:many relationship

EDIT

I suppose you could technically store an array of id's in a column to represent tags for any given recipe. The question here is, what happens if you decide you need further info on any given tag?

IMO, (and it's just that so take all of this with a grain of salt). You are bending your recipe table to also store relationship info.

I have found it to be more helpful to keep my "buckets" (tables) as specific as possible. That'd leave us with:

recipes | tags | recipes_tags
-----------------------------

That way my recipes table just has recipes & that's it. "Just give me all recipes...". Tags is the same, "just show me all tags"

The two things are completely different entities. By setting up a ManyToMany relationship, we're telling TypeORM that these two columns are related--without "muddying" either of their underlying data.

You can add/remove columns on the pivot table should you decide you want more info about the relationship. At that point, you'd still be working with the relationship, not a tag or recipe so your data would still be nice & lean!

Another example from one of my own use cases...

I have an Activity and a Resource any given resource can have one or more Activities. (activities = tags/ resources = recipes)

// activity.entity.ts

...
@PrimaryGeneratedColumn('uuid')
  id: string;

@Column()
name: string;

...

@ManyToMany((type) => Resource, (resource) => resource.activities)
resources: Resource[];
// resource.entity.ts

@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
name: string;

...

@JoinTable()
@ManyToMany((type) => Activity, (activity) => activity.resources)
activities: Activity[];

The above generates a resources_activities_activities table. Within that table is:

resourceId | activityId
------------------------

I could add additional columns here as well. createdBy or status or something else that is specific to the relationship. Each entry in this table has a relationship back to the activity and the resource--which is great!

I realize we've gone outside the scope of your original question, but I think this is a pretty small step outside, for a potential big win later on.

When I make a request to get a resource: example.com/resources/123 I get something like this back:

"id": "123"
...
"activities": [
    {
        "id": "f79ce066-75ba-43bb-bf17-9e60efa65e25",
        "name": "Foo",
        "description": "This is what Foo is.",
        "createdAt": "xxxx-xx-xxxxx:xx:xx.xxx",
        "updatedAt": "xxxx-xx-xxxxx:xx:xx.xxx"
    }
]
...

Likewise, any time I get an activity, I also get back any resources that are related to it. In my front-end I can then easily do something like resource.activities.

  • Related