Form_for not showing changes made by put_change/3 or change/2

Hello,

Consider this changeset:

def changeset(model, params = %{}) do
  model
  |> cast(params, ~(name age gender))
  |> put_change(:name, "Mr. Rogers")
end

And this controller:

def new(conn, params) do
  changeset = %Person{} |> Person.changeset(params)
  render conn, "new.html.haml", changeset: changeset
end

The name Mr. Rogers is not shown on the form rendered by form_for. Why??

If I change my controller action to be:

def new(conn, params) do
  changeset = %Person{ name: "Mr. Rogers" } |> Person.changeset(params)
  render conn, "new.html.haml", changeset: changeset
end

or

def new(conn, params) do
  params = Map.put(params, :name, "Mr. Rogers")
  changeset = %Person{} |> Person.changeset(params)
  render conn, "new.html.haml", changeset: changeset
end

Then it works fine.

What I’m basically trying to do is initialize some data on my model/changeset. It’s dynamic data, that’s why I have to use put_change/3.

I’m using ecto-2.0-rc5.

Thanks for the help.

2 Likes

Hello!

@cjbottaro, I used change/2 to fill my form with predefined data.

web/models/user.ex

def common_changeset(model, params \\ %{}) do
  model
  |> cast(params, @required_fields, @optional_fields)
  |> unique_constraint(:email)
  |> validate_length(:email, min: 3, max: 100)
  |> validate_length(:name,  min: 1, max: 100)
  |> assoc_constraint(:role)
end

def changeset(model, params \\ %{}) do
  model
  |> set_default_role_if_empty
  |> common_changeset(params)
end  

defp set_default_role_if_empty(model) do
  case model.role_id do
    nil ->
      customer_role_id = Repo.one from r in Role, select: r.id, where: r.name == "customer"
      change(model, %{role_id: customer_role_id})
    _ ->
      model
  end
end
1 Like

Yes, so your example is like my two examples where it works. The case where mine doesn’t work is when put_change or change is called after the call to cast.

Does your’s still work if you change it to be:

def changeset(model, params \\ %{}) do
  model
  |> cast(params, @required_fields, @optional_fields)
  |> set_default_role_if_empty
  |> unique_constraint(:email)
  |> validate_length(:email, min: 3, max: 100)
  |> validate_length(:name,  min: 1, max: 100)
  |> assoc_constraint(:role)
end

(notice set_default_role_if_empty is called after cast)

And by work, I mean the change to role_id is visible in your HTML form.

To be super pedantic, in my example, I can see the change on the changeset if I IEx.pry or I do this in the console. It’s just that form_for will not show the changeset’s change; it only shows what the underlying model/data has.

Thanks again.

1 Like

@cjbottaro, I was wrong - in my case I don’t show role_id on form, and it fills with default value during user creation.

Case 1. Does not fill prefix in form, but adds default prefix during user creation.

def changeset(struct, params \\ %{}) do
  struct
  |> cast(params, [:prefix, :name])
  |> validate_required([:name])
  |> unique_constraint(:name)
  |> set_default_prefix
end 

defp set_default_prefix(struct) do
  struct
  |> change(%{prefix: "Mr."})
end

Case 2. Fills prefix in form and adds it during user creation.

def changeset(struct, params \\ %{}) do
  struct
  |> set_default_prefix
  |> cast(params, [:prefix, :name])
  |> validate_required([:name])
  |> unique_constraint(:name)
end 

defp set_default_prefix(struct) do
  struct
  |> cast(%{prefix: "Mr."}, [:prefix])
end

Seems, that changeset.params are used to send values to form_for (as you are using from controller):

iex(1)> alias Formex.User
nil   

iex(2)> struct = %User{}
%Formex.User{__meta__: #Ecto.Schema.Metadata<:built>, id: nil, inserted_at: nil,
 name: nil, prefix: nil, updated_at: nil}   

iex(3)> changeset = struct |> Ecto.Changeset.change(%{prefix: "Mr."})
#Ecto.Changeset<action: nil, changes: %{prefix: "Mr."}, errors: [],
 data: #Formex.User<>, valid?: true>    

iex(4)> changeset.params
nil   

iex(5)> struct = %User{}
%Formex.User{__meta__: #Ecto.Schema.Metadata<:built>, id: nil, inserted_at: nil,
 name: nil, prefix: nil, updated_at: nil}   

iex(6)> changeset = struct |> Ecto.Changeset.cast(%{prefix: "Mr."}, [:prefix])
#Ecto.Changeset<action: nil, changes: %{prefix: "Mr."}, errors: [],
 data: #Formex.User<>, valid?: true>    

iex(7)> changeset.params
%{"prefix" => "Mr."}
2 Likes