Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I update a relationship in Ecto?

I have a subscription like this.

defmodule Rebirth.Subscription do
  use Rebirth.Web, :model

  schema "subscriptions" do
    ...
    belongs_to :user, Rebirth.User
    ...
  end

  ...

  def update_user(model, params \\ :empty) do
    model
    |> cast(params, @required_fields, @optional_fields)
    |> cast_assoc(:user, required: false)    
  end
end

I want to associate a user to the subscription

So I tried

Rebirth.Subscription.update_user(subscription, %{user_id: 1})

or

Rebirth.Subscription.update_user(subscription, %{user: user})

When I run it I get the following error:

** (ArgumentError) unknown assoc `user` in `cast_assoc`

How do I update the user_id?

Thanks!

like image 331
Joel Jackson Avatar asked Mar 18 '16 03:03

Joel Jackson


1 Answers

cast_assoc is used to "cast associated model" and can be used only with has_one and has_many. belongs_to relation defines the foreign id in the model where it is invoked. has_many and has_one rely on "the other" object having foreign key.

In case you are creating object that has many other objects it makes sense to check all of them if they are valid. cast_assoc will invoke cast in their respective modules.

Your user can have many subscriptions (probably, I am guessing here), so it doesn't make sense to create user when creating subscription and check if the user is valid via cast_assoc. Usually in this case the user will exist in database already.

In your case you only want to check if associated model exists in the cast, so you should use:

|> assoc_constraint(:user)

This does not validate user, but checks if given user_id exists in the database. Now, when you want to update the subscription for a user you can do this:

user = Repo.get(User, id)
subscription = Ecto.build_assoc(user, :subscriptions, other_fields_as_map)

Note it requires has_one or has_many on the user model.

Or you can simply update the user id as you tried before:

Rebirth.Subscription.update_user(subscription, %{user_id: 1})

and this time it will check the database if given user id exists, but You won't be able to pass entire user object here.

And if you want to update associated user, you will have to do it explicitly in two steps. a) get the user, b) update the user using changeset defined in its module.

Final note, if you don't make separate validation for updating users (and I don't think you should in this scenario), it would be good to rename the function from update_user to changeset. The same changeset can be used for creating and updating models.

like image 137
tkowal Avatar answered Sep 20 '22 15:09

tkowal