I have a User
schema:
schema "users" do
field :username, :string
belongs_to :invite, App.Admin.Invite
timestamps()
end
and a Invite
schema:
schema "invites" do
field :code, :string
field :expire, Ecto.Date
field :used, :boolean, default: false
has_one :user, App.User, on_delete: :nothing
timestamps()
end
The template part:
<div class="form-group">
<%= label f, :invite, gettext("Invite Code"), class: "control-label control-label--required" %>
<%= inputs_for f, :invite, fn fi -> %>
<%= text_input fi, :code, class: "form-control" %>
<%= error_tag fi, :code %>
<%= error_tag fi, :used %>
<%= error_tag fi, :expire %>
<% end %>
</div>
When inserting a user, I would love to check the associated invite
:
- does the
code
existed ininvites
table? - is the
used
valuefalse
now? - is the
expire
value larger than today?
If all the checks pass, then insert the user and update the associated invite
.
At first, I tried with cast_assoc
in user.ex
:
def changeset(struct, params \\ %{}) do
struct
|> cast_assoc(:invite, with: &App.Admin.Invite.update_changeset/2)
In App.Admin.Invite.update_changeset/2
I defined the checks. But when inserting user, I found that it tried to insert another invite, the action
of associated invite
changeset was set to insert
- it’s not what I want. We just want to update the invite’s used
to true
. Does it mean cast_assoc
can only be used when inserting association?
I also tried to check it in user_controller.ex
file, but it became a mess quickly:
def create(conn, %{"user" => user_params}) do
changeset = User.changeset(%User{}, user_params)
%{"invide" => invite} = user_params
%{"code" => code} = invite
invite = Repo.get_by(Invite, code: code)
changeset = if is_nil(invite) do
Ecto.Changeset.add_error(changeset, :invite, gettext("code is invalid"))
else
if invite.used == true do
Ecto.Changeset.add_error(changeset, :invite, gettext("code is used"))
else
case Ecto.Date.compare(invite.expire, Ecto.Date.utc) do
:lt -> Ecto.Changeset.add_error(changeset, :invite, gettext("code is expired"))
_ ->
Ecto.Changeset.put_assoc(changeset, :invite, invite) |> Ecto.Changeset.put_change(:invite_id, invite.id)
end
end
end
So I’m wondering if there is any better way to handle this use case?
Update
I found this ecto issue https://github.com/elixir-ecto/ecto/issues/1850 similar to my problem.