When calling cast_assoc
, I'd like a 'find or create' style behavior. Is there an easy way to do this in Ecto?
As a contrived example, a user
belongs_to
a favorite color
, which is unique
by color.name
. If I create a new user with a favorite color that already exists, I get an error (name has already been taken
). Instead I'd like to set user.color_id
to the pre-existing color
record. Is there a built in feature in Ecto to do this, or will I have to roll my own solution?
User changeset:
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:name])
|> cast_assoc(:color)
end
Failing test:
test "create user with pre-existing color" do
Repo.insert!(%Color{name: "red"})
%User{}
|> User.changeset(%{name: "Jim", color: %{name: "red"}})
|> Repo.insert!
end
The way you put it, I'm afraid you would have to roll your own code: in the provided example, you're dealing with child assoc (user), attempting to manage it's parent (e.g. color). This has to be done manually.
The amount of the code to be added is really not that big though. Code for user creation would look like this:
color = Repo.get_by(Color, name: params["color"]["name"])
if color do
%User{}
|> User.changeset(params)
|> Ecto.Changeset.put_assoc(:color, color)
else
%User{}
|> User.changeset(params)
|> Ecto.Changeset.cast_assoc(:color)
end
|> Repo.insert!
Alternatively, you should reverse your approach - if you know color
exists ahead of time (which I believe you should), you can do:
color = Repo.get_by(Color, name: params["color"]["name"])
color
|> build_assoc(:user)
This would of course require color to have has_one :user, User
or has_many :users, User
association declared in its schema.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With