Hello,
I’ve been in the process of rewriting parts of my frontend with LiveView (written with React/Relay until then). I’m really happy how things turned out so far and it has been a a lot of fun to dive into LiveView:
Now I did rewrite some CRUD controller logic to leverage real-time validation of forms and was wondering if I’m doing things wrong when working with changeset associations…
For example, I have a Comment
schema:
schema "comments" do
belongs_to :repo, Repo
belongs_to :author, User
belongs_to :parent, __MODULE__
has_many :children, __MODULE__, foreign_key: :parent_id
has_many :revisions, CommentRevision
field :body, :string
timestamps()
end
and a very basic changeset for this schema:
@doc """
Returns a changeset for the given `params`.
"""
@spec changeset(t, map) :: Ecto.Changeset.t
def changeset(%__MODULE__{} = comment, params \\ %{}) do
comment
|> cast(params, [:repo_id, :author_id, :parent_id, :body])
|> validate_required([:repo_id, :author_id, :body])
|> assoc_constraint(:repo)
|> assoc_constraint(:author)
|> assoc_constraint(:parent)
end
So far, when working with changesets with associations, I’ve been leaving fields such as :repo_id
and :author_id
when creating new changeset and add them in my controller before inserting the model into the database.
# in controller new/2
changeset = Comment.changeset(%Comment{})
# in controller create/2
comments_params = Map.put(comment_params, "repo_id", repo.id)
comments_params = Map.put(comment_params, "author_id", current_user(conn).id)
changeset = Comment.changeset(%Comment{}, comment_params)
Not that the changeset in new/2
is actually not valid and has few errors regarding association fields:
#Ecto.Changeset<
action: nil,
changes: %{},
errors: [
repo_id: {"can't be blank", [validation: :required]},
author_id: {"can't be blank", [validation: :required]},
body: {"can't be blank", [validation: :required]}
],
data: #GitGud.Comment<>,
valid?: false
>
When using this changeset, the errors where not displayed when rendering the <form>
until :action
is set to :insert
.
But now I’d like to use the @changeset
state in my LiveView to disable the submit button if the changeset is not valid:
<%= submit "Add comment", class: "button is-success", disabled: !@changeset.valid? %>
In order to do so, I need to fix errors for :repo_id
and :author_id
when initiating the changeset otherwise the changeset will also be invalid…
So here’s the actual question of my post .
When working with changesets with fields that should not be rendered in the <form>
, is it a good approach to pass these fields directly to the data struct:
changeset = Comment.changeset(
%Comment{
repo_id: repo.id,
author_id: current_user(socket).id
}
)
Or better pass them as parameters:
changeset = Comment.changeset(
%Comment{},
%{"repo_id" => repo.id, "author_id" => current_user(socket).id}
)
Or remove :repo_id
and :author_id
from the changeset entirely and always set them manually before persisting to the database:
@doc """
Returns a changeset for the given `params`.
"""
@spec changeset(t, map) :: Ecto.Changeset.t
def changeset(%__MODULE__{} = comment, params \\ %{}) do
comment
|> cast(params, [:parent_id, :body])
|> validate_required([:body])
|> assoc_constraint(:parent)
end
# in controller new/2
changeset = Comment.changeset(%Comment{})
# in controller create/2
changeset = Comment.changeset(
%Comment{
repo_id: repo.id,
author_id: current_user(socket).id
},
comment_params
)
Thank you in advance