I’m attempting to wrap my head around JSONB columns, embedded schemas, and using them in forms. To that end, I’ve created a bit of a tester to try things out.
So far, here’s the module(s):
defmodule MapTypeTester.Users.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :email, :string
field :name, :string
embeds_one(:favorite_colors, MapTypeTester.FavoriteColors)
timestamps()
end
@doc false
def changeset(user, attrs) do
user
|> cast(attrs, [:name, :email])
|> cast_embed(:favorite_colors)
|> validate_required([:name, :email])
end
end
defmodule MapTypeTester.FavoriteColors do
alias MapTypeTester.Users.User
use Ecto.Schema
embedded_schema do
field(:color_one, :string)
field(:color_two, :string)
field(:color_three, :string)
belongs_to(:users, User)
end
def changeset(favorite_colors, attrs) do
favorite_colors
|> Ecto.Changeset.cast(attrs, [:color_one, :color_two, :color_three])
end
end
The controller (I’m in the middle of some changes right now, and the error I am hitting is what stopped me):
defmodule MapTypeTesterWeb.UserController do
use MapTypeTesterWeb, :controller
alias MapTypeTester.Users
alias MapTypeTester.Users.User
alias MapTypeTester.FavoriteColors
def index(conn, _params) do
users = Users.list_users()
render(conn, "index.html", users: users)
end
def new(conn, _params) do
changeset = Users.change_user(%User{})
colors_changeset = Users.change_colors(%FavoriteColors{})
render(conn, "new.html", changeset: changeset, colors_changeset: colors_changeset)
end
def create(conn, %{"user" => user_params}) do
case Users.create_user(user_params) do
{:ok, user} ->
conn
|> put_flash(:info, "User created successfully.")
|> redirect(to: Routes.user_path(conn, :show, user))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
def show(conn, %{"id" => id}) do
user = Users.get_user!(id)
render(conn, "show.html", user: user)
end
def edit(conn, %{"id" => id}) do
user = Users.get_user!(id)
changeset = Users.change_user(user)
render(conn, "edit.html", user: user, changeset: changeset)
end
def update(conn, %{"id" => id, "user" => user_params}) do
user = Users.get_user!(id)
case Users.update_user(user, user_params) do
{:ok, user} ->
conn
|> put_flash(:info, "User updated successfully.")
|> redirect(to: Routes.user_path(conn, :show, user))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "edit.html", user: user, changeset: changeset)
end
end
def delete(conn, %{"id" => id}) do
user = Users.get_user!(id)
{:ok, _user} = Users.delete_user(user)
conn
|> put_flash(:info, "User deleted successfully.")
|> redirect(to: Routes.user_path(conn, :index))
end
end
and the form:
<%= form_for @changeset, @action, fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<%= label f, :name %>
<%= text_input f, :name %>
<%= error_tag f, :name %>
<%= label f, :email %>
<%= text_input f, :email %>
<%= error_tag f, :email %>
<%= form_for @colors_changeset, @action, fn fcolor -> %>
<%= label fcolor, :favorite_colors[:color_one] %>
<%= text_input fcolor, :favorite_colors[:color_one] %>
<%= error_tag fcolor, :favorite_colors[:color_one] %>
<%= label fcolor, :favorite_colors[:color_two] %>
<%= text_input fcolor, :favorite_colors[:color_two] %>
<%= error_tag fcolor, :favorite_colors[:color_two] %>
<%= label fcolor, :favorite_colors[:color_three] %>
<%= text_input fcolor, :favorite_colors[:color_three] %>
<%= error_tag fcolor, :favorite_colors[:color_three] %>
<% end %>
<div>
<%= submit "Save" %>
</div>
<% end %>
So far, when I attempt to go to localhost:4000/users
, I am met with:
[error] #PID<0.478.0> running MapTypeTesterWeb.Endpoint (connection #PID<0.477.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: GET /users
** (exit) an exception was raised:
** (Protocol.UndefinedError) protocol Phoenix.HTML.Safe not implemented for %MapTypeTester.FavoriteColors{color_one: nil, color_three: nil, color_two: nil, id: nil, users: #Ecto.Association.NotLoaded<association :users is not loaded>, users_id: nil} of type MapTypeTester.FavoriteColors (a struct). This protocol is implemented for the following type(s): Decimal, Phoenix.LiveView.Rendered, Phoenix.LiveView.Comprehension, Phoenix.LiveView.Component, Phoenix.LiveComponent.CID, List, Tuple, Integer, Float, NaiveDateTime, BitString, DateTime, Atom, Phoenix.HTML.Form, Time, Date
(phoenix_html) lib/phoenix_html/safe.ex:1: Phoenix.HTML.Safe.impl_for!/1
(phoenix_html) lib/phoenix_html/safe.ex:15: Phoenix.HTML.Safe.to_iodata/1
(map_type_tester) lib/map_type_tester_web/templates/user/index.html.eex:18: anonymous fn/3 in MapTypeTesterWeb.UserView."index.html"/1
(elixir) lib/enum.ex:1948: Enum."-reduce/3-lists^foldl/2-0-"/3
(map_type_tester) lib/map_type_tester_web/templates/user/index.html.eex:14: MapTypeTesterWeb.UserView."index.html"/1
(phoenix) lib/phoenix/view.ex:310: Phoenix.View.render_within/3
(phoenix) lib/phoenix/view.ex:472: Phoenix.View.render_to_iodata/3
(phoenix) lib/phoenix/controller.ex:776: Phoenix.Controller.render_and_send/4
(map_type_tester) lib/map_type_tester_web/controllers/user_controller.ex:1: MapTypeTesterWeb.UserController.action/2
(map_type_tester) lib/map_type_tester_web/controllers/user_controller.ex:1: MapTypeTesterWeb.UserController.phoenix_controller_pipeline/2
(phoenix) lib/phoenix/router.ex:352: Phoenix.Router.__call__/2
(map_type_tester) lib/map_type_tester_web/endpoint.ex:1: MapTypeTesterWeb.Endpoint.plug_builder_call/2
(map_type_tester) lib/plug/debugger.ex:136: MapTypeTesterWeb.Endpoint."call (overridable 3)"/2
(map_type_tester) lib/map_type_tester_web/endpoint.ex:1: MapTypeTesterWeb.Endpoint.call/2
(phoenix) lib/phoenix/endpoint/cowboy2_handler.ex:65: Phoenix.Endpoint.Cowboy2Handler.init/4
(cowboy) /home/cedric/tutorials/map_type_tester/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
(cowboy) /home/cedric/tutorials/map_type_tester/deps/cowboy/src/cowboy_stream_h.erl:300: :cowboy_stream_h.execute/3
(cowboy) /home/cedric/tutorials/map_type_tester/deps/cowboy/src/cowboy_stream_h.erl:291: :cowboy_stream_h.request_process/3
(stdlib) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
I…cannot quite figure out what’s giving me this error, and would most greatly appreciate any assistance towards getting there.