Building Relationships

After the migration, Ecto generated a schema. This file is responsible for identifying the fields in a way that ties in to both the database table and the Elixir struct. Now let’s take a look at the schema in lib/rumbl/multimedia/video.ex:

 schema ​"​​videos"​ ​do
  field ​:description​, ​:string
  field ​:title​, ​:string
  field ​:url​, ​:string
  field ​:user_id​, ​:id
 
  timestamps()
 end

Our schema sets up a user_id field, of type :id, while our migration defines a :user_id foreign key. To relate our data at the schema level, we need to tell Ecto about our Video to User association. Replace your field :user_id, :id line in your video schema with the following association:

 belongs_to ​:user​, Rumbl.Accounts.User

Ecto will now use this information to build the right association between our schemas.

The video module also includes a changeset function, similar to the one that we defined for Accounts.User. The top of the pipeline kicks it all off. The cast function prepares tainted user input containing your specified fields for safe inclusion into the database. We require all fields to be present. cast uses a whitelist to tell Ecto which fields from user-input may be allowed in the input. validate_required is a validation that tells Ecto which fields must be present from that list of fields‘.

 @doc false
 def​ changeset(video, attrs) ​do
  video
  |> cast(attrs, [​:url​, ​:title​, ​:description​])
  |> validate_required([​:url​, ​:title​, ​:description​])
 end

The :user_id field is neither castable nor required in the previous example, because many times the field doesn’t come from external data such as forms but, rather, directly from the application. That’s exactly our case. We’ll make sure to associate the current user from the session to each new video.

With our belongs_to in place, we now have a complete one-to-many association. Now a user effectively has many videos. By defining these relationships, we can now use Ecto’s association features. Fire up a new iex -S mix session so we can put the new Video code through its paces.

Let’s create a new video to see how our new association ties things together:

 iex>​ {​:ok​, video} = Rumbl.Multimedia.create_video(%{
 ...>​ ​title:​ ​"​​New Video"​, ​url:​ ​"​​http://example.com"​, ​description:​ ​"​​new video"
 ...>​ })
 {:ok, %Rumbl.Multimedia.Video{...}}
 
 iex>​ video.user
 #Ecto.Association.NotLoaded<association :user is not loaded>

Ecto associations are explicit! When you want Ecto to fetch some records, you need to ask. When you don’t ask, you can be sure that you won’t get them. This decision may seem tedious at first, but it’s useful. One of the most time-consuming things about dealing with persistence frameworks is that they can often fetch rows you don’t need or fetch in inefficient ways. When these kinds of changes cascade, you can quickly run up a tab that you’re unable to pay.

Digging deeper, you can see that referencing video.user returns Ecto.Assocation.NotLoaded.

Let’s load some videos, like this:

 iex>​ video = Rumbl.Repo.preload(video, ​:user​)
 
 iex>​ video.user
 nil

There’s not much to see here yet, but we are making progress. Repo.preload accepts one name or a collection of association names. It loads the associated data for you—in this case, :user. After Ecto tries to fetch the association, we can reference the video.user, which returns nil since our video doesn’t yet have an associated user. To make this even more meaningful, we need some associated data.

Let’s attach a video to one of our users:

 iex>​ alias Ecto.Changeset
 iex>​ alias Rumbl.Repo
 
 iex>​ user = Rumbl.Accounts.get_user_by(​username:​ ​"​​josevalim"​)
 %Rumbl.Accounts.User{...}
 
 iex>​ changeset =
 ...>​ video |> Changeset.change() |> Changeset.put_assoc(​:user​, user)
 #Ecto.Changeset<​...>
 
 iex>​ video = Repo.update!(changeset)
 %Rumbl.Multimedia.Video{...}
 
 iex>​ video.user
 %Rumbl.Accounts.User{username: "josevalim"}

Part of a framework’s job is to make tedious things easier. In this case, Ecto.Changeset.put_assoc allows us to place an association as a change into the changeset with a little less ceremony. This is how you would make the same change without the useful put_assoc function:

 iex>​ video =
 ...>​ video
 ...>​ |> Changeset.change()
 ...>​ |> Changeset.put_change(​:user_id​, user.id)
 ...>​ |> Repo.update!()
 
 %Rumbl.Multimedia.Video{}

You didn’t even have to remember the specific foreign key for the User association. Now that our video has a user, let’s try preload again:

 iex>​ video = Repo.get(Rumbl.Multimedia.Video, video.id)
 %Rumbl.Multimedia.Video{
  ...,
  user: #Ecto.Association.NotLoaded<association :user is not loaded>,
 }
 
 iex>​ video = Repo.preload(video, ​:user​)
 %Rumbl.Multimedia.Video{
  ...,
  user: %Rumbl.Accounts.User{username: "josevalim"}
  user_id: 1
 }

Preload is great for bundling data. Other times we may want to fetch the user associated with a video, without storing the user in the video struct, like this:

 iex>​ query = Ecto.assoc(video, ​:user​)
 #Ecto.Query<​...>
 
 iex>​ Repo.one(query)
 %Rumbl.Accounts.User{username: "josevalim"}

assoc is another convenient function from Ecto that returns an Ecto.Query with the user scoped to the given video. We convert this query into data by calling Repo.one. As you’ll learn in the next chapter, we’ll be able to further manipulate this query, allowing us to slice the data in any way we want.

If you’re a careful reader, you might have noticed the one-way relationship between videos and users. We generally want to avoid having cyclic dependencies between our contexts. It is expected for the Video schema to depend on User, but if we also allow the User schema to reach out to schemas in other contexts, the responsibilities between the accounts and multimedia contexts will blur over time.

Now let’s dig deeper into related data.

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

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