In-context Relationships

So far, we’ve created a new context each time we create a new resource. However, one of the important ideas behind contexts is to group similar resources, meaning sometimes new resources should go into existing contexts. We already talked about how the Multimedia context could manage related entities such as videos, books and the like in the future.

Sometimes we’ll define relationships within the same context. For example, let’s add categories to our videos. Categories are simple resources. They have a single category name field that will be something like action, comedy or sci-fi. We expect our categories to be mostly fixed. After we define a few of them, we don’t expect them to change often. For this reason, we don’t need to create a controller with a view and templates to manage them from user input. We can create them programatically instead.

Since all multimedia resources have categories and categories are only available to multimedia resources, it makes sense to define these categories within the Multimedia context. We will define the category schema as Multimedia.Category.

Should I Create Another Context?

images/aside-icons/info.png

Sometimes it may be tricky to determine if two resources belong to the same context or not. The fact two resources are related in the database does not imply they belong in the same context. Otherwise, almost all schemas would be within the Accounts context, as the majority of entities in a system belong to a user.

For example, users and videos are related, but they clearly belong in different contexts. On the other hand, categories and videos are also related, but we put them together, as categories are only available to multimedia resources and they do not bring much complexity on their own. In cases you are unsure how to group your resources, prefer distinct contexts per resource and refactor later if necessary. Otherwise you can easily end up with large contexts of loosely related entities. Similarly, if a context grows too large over time, you can always break it apart. To sum it up: When in doubt, put your new resource in its own context.

Let’s once again use generators to define our categories, but this time we’ll use a different generator. Let’s study our options.

Schema and Context Generators

Up to this point, we’ve used the mix ecto.gen.migration generator and mix phx.gen.html. Those generators operate at two different ends of the spectrum when it comes to building our app. The migration generator has a very specific concern and generates only migration files while the html and json generators generate migrations, schemas, contexts, as well as controllers, views, and templates.

It just so happens there are two generators that fit between the HTML generator and the migration: context and schema generators. Let’s briefly discuss those generators and when to use them. Remember, you can get more information about any generator by typing mix help GENERATOR_NAME in your terminal.

In the following examples, we’ll use the upcoming Multimedia.Category as an example. Once we explore all options, we can make an informed generator choice. The candidates are:

  • mix phx.gen.html Multimedia Category categories name:string. This command generates a controller, view, and template on the frontend. On the backend, it generates a Multimedia context, a Multimedia.Category schema, and a migration. This generator, and the similar mix phx.gen.json generator, are typically used when we want to define all conveniences to expose a resource over the web interface.

  • mix phx.gen.context Multimedia Category categories name:string. This command makes a Multimedia context, a Multimedia.Category schema and the associated migration. This generator is useful for generating a resource with all of its context functions without exposing that resource via the web interface. Note that if the context already exists, which is the case for Multimedia, the generator will inject the new category functions into the existing context.

  • mix phx.gen.schema Multimedia.Category categories name:string. This command creates a schema with a migration. It’s useful for creating a resource when you want to define the context functions yourself.

  • mix ecto.gen.migration create_categories. This generator builds a new empty migration. Useful when the schema and context are already laid out, and all you need is to update the database

In our case, we know our categories won’t be managed via a web interface. That rules out mix phx.gen.html. We also know that we want a schema, so we can associate it with videos. That rules out mix ecto.gen.migration, as it does too little.

Therefore, we need to choose between mix phx.gen.context and mix phx.gen.schema. Both choices work fine. If you want Phoenix to generate more code than you need and then trim from there, you’ll generate the context. If you’d rather generate the minimum amount of code and build what you need from scratch, you’ll use the schema generator. Since categories don’t need a web interface, our hunch is that we won’t need most of the generated context functions so we’ll pick the schema generator.

Enough exposition. Let’s get down to business.

Generating Category Migrations

Generate the Multimedia.Category schema like this:

 $ ​​mix​​ ​​phx.gen.schema​​ ​​Multimedia.Category​​ ​​categories​​ ​​name:string
 
 * creating lib/rumbl/multimedia/category.ex
 * creating priv/repo/migrations/20180513025558_create_categories.exs
 
 ...
 
  $ ​​mix​​ ​​ecto.migrate
 ---- END OF OUTPUT ----

As expected, the command generated a category schema and a migration. The schema is backed by the “categories” database table with a name column of type string.

Next, let’s edit our migration to mark the name field as NOT NULL and create a unique index for it:

 defmodule​ Rumbl.Repo.Migrations.CreateCategories ​do
 use​ Ecto.Migration
 
 def​ change ​do
  create table(​:categories​) ​do
  add ​:name​, ​:string​, ​null:​ false
 
  timestamps()
 end
 
  create unique_index(​:categories​, [​:name​])
 end
 end

Now we can add the referential constraints to our Video schema. A Video belongs to a Category, like so:

1: schema ​"​​videos"​ ​do
2:  field ​:description​, ​:string
3:  field ​:title​, ​:string
4:  field ​:url​, ​:string
5: 
6:  belongs_to ​:user​, Rumbl.Accounts.User
7:  belongs_to ​:category​, Rumbl.Multimedia.Category
8: 
9:  timestamps()
10: end

We created a simple belongs-to relationship, so we need to add the category_id to the permitted fields for our changeset:

1: @doc false
2: def​ changeset(video, attrs) ​do
3:  video
4:  |> cast(attrs, [​:url​, ​:title​, ​:description​, ​:category_id​])
5:  |> validate_required([​:url​, ​:title​, ​:description​])
6: end

Now our API users can safely use category_id in the user input we provide to our changeset. Use mix ecto.gen.migration to generate a migration to add the category_id to our video table:

 $ ​​mix​​ ​​ecto.gen.migration​​ ​​add_category_id_to_video
 * creating priv/repo/migrations
 * creating priv/repo/migrations/20180513030504_add_category_id_to_video.exs

With the database table updated, this relationship will allow us to add a new category ID to our existing videos. Now open up your new priv/repo/migrations/xxx_add_category_id_to_video.exs and key this in:

relationships/listings/rumbl/priv/repo/migratio … 80513030504_add_category_id_to_video.change1.exs
 def​ change ​do
  alter table(​:videos​) ​do
  add ​:category_id​, references(​:categories​)
 end
 end

This code sets up a database constraint between videos and categories, one that will ensure that the category_id for a video exists. Finally, migrate your database with your two new migrations:

 $ ​​mix​​ ​​ecto.migrate
 
 [info] == Running Rumbl.Repo.Migrations.CreateCategories.change/0 forward
 [info] create table categories
 [info] create index categories_name_index
 [info] == Migrated in 0.0s
 [info] == Running Rumbl.Repo.Migrations.AddCategoryIdToVideo.change/0 forward
 [info] alter table videos
 [info] == Migrated in 0.0s

We migrated our categories and added the proper foreign keys. The database will maintain the database integrity, regardless of what we do on the Phoenix side. With our relationships established, we can safely associate videos with categories in our user interface, by presenting a list of categories whenever a user creates or edits a video. To do that, we need to learn how to effectively query data. That’s exactly what we have in store for you in the next chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.229.111