Select "belongs_to" another table, in the database

Folks,
I’m currently having another problem.
Topic open yesterday.

I was able to solve it by inserting it manually.
But another question arose.
I have 2 “models” as follows.

1 Model =

defmodule RelatDemo.Disp do
  use RelatDemo.Web, :model

  schema "disps" do
    field :disp, :float
    field :date, Ecto.DateTime
    belongs_to :city, RelatDemo.Cidade

    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:dispon, :date])
    |> validate_required([:dispon, :date])
  end
end

2 Model =

defmodule RelatDemo.Local do
  use RelatDemo.Web, :model

  schema "locais" do
    field :city, :string

    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:city])
    |> validate_required([:city])
  end
end

NOTE: belongs_to is associated to :city

I needed the code form.html.eex to list the entire column of a table.
More or less …

<%= 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 %>

  <div class="form-group">
    <%= label f, :city, class: "control-label" %>
    <%= select(f, :city, []) %>
    <%= error_tag f, :city %>
  </div>
<% end %>

I can not get anything.

Error:

protocol Phoenix.HTML.Safe not implemented for #Ecto.Association.NotLoaded

What should I do to solve this problem?

1 Like

It seems like you need to preload something (city?) either like this or like [this] (https://hexdocs.pm/ecto/Ecto.Query.html#preload/3)

2 Likes

Maybe I can help by giving you an example of how things work:

User -> has_one -> Profile
Profile -> belongs_to -> User

Here are the schemas:

# schema/user.ex
defmodule Schema.User do
  @moduledoc false
  use Ecto.Schema
  import Ecto.Changeset

  @required_params [:email, :password]

  schema "users" do
    has_many :orders, Schema.Order
    has_one :profile, Schema.User.Profile

    field :email, :string
    field :password, :string, virtual: true
    field :encrypted_password, :string
    timestamps()
  end

  def changeset(user,  params \\ %{}) do
    user
    |> cast(params, @required_params)
    |> cast_assoc(:profile)
    |> validate_required(@required_params)
    |> unique_constraint(:email, name: :unique_email)
    |> validate_format(:email, ~r/\S+@\S+\.\S+/)
    |> validate_length(:password, min: 6)
  end
end
# schema/user/profile.ex
defmodule Schema.User.Profile do
  @moduledoc false
  use Ecto.Schema
  import Ecto.Changeset

  @required_params [:first_name, :last_name, :gender]
  @optional_fields [:phone, :image]

  schema "user_profile" do
    belongs_to :user, Schema.User

    field :first_name, :string
    field :last_name, :string
    field :gender, :string
    field :phone, :string
    field :image, :string
    timestamps()
  end

  def changeset(profile,  params \\ %{}) do
    profile
    |> cast(params, @required_params ++ @optional_fields)
    |> validate_required(@required_params)
  end
end

You can insert into database using changesets from schemas:

user_changeset = Schema.User.changeset(
    %Schema.User{}, 
    %{
        :email => "contact@alexandrubagu.info", 
        :password => "123456"
    }
)

profile_changeset = Schema.User.Profile.changeset(
    %Schema.User.Profile{}, 
    %{
        :first_name => "Alexandru", 
        :last_name => "Bagu",
        :gender => "male",
    }
)
user_changeset = Schema.User.hash_password(user_changeset)
user_changeset = Ecto.Changeset.put_assoc(user_changeset, :profile, profile_changeset)
Persistence.Repo.insert(user_changeset)

In your controller you need to preload associations:

product = Persistence.Repo.get(Schema.User, 1)  |> Persistence.Repo.preload(:profile)

or using Ecto.query:

def all do
    query = from u in Schema.User,
            join: p in assoc(p, :profile),
            preload: [profile: p]

    Persistence.Repo.all(query)
end

The following code is from controller:

defmodule Frontend.UserController do
  def register(conn, %{"user" => params}) do
    changeset = Schema.User.changeset(%Schema.User{}, params)
    if changeset.valid? do
      result = changeset
      |> Schema.User.hash_password
      |> Persistence.Repo.insert

      case result do
        {:ok, user} ->
          conn
          |> Guardian.Plug.sign_in(user)
          |> redirect(to: page_path(conn, :index))
        {:error, changeset} -> 
          conn
          |> assign(:changeset, %{changeset | action: :insert})
          |> render("register.html")
      end
    else
      conn
        |> assign(:changeset, %{changeset | action: :insert})
        |> render("register.html")
    end
  end
end


If you want to use relationship in forms here is an example


<%= form_for @changeset, user_path(@conn, :register), fn f -> %>
    <%= inputs_for f, :profile, fn fp -> %>
        <div class="form-group">
            <%= label fp, :gender, gettext "Gender" %>
            <%= select fp, :gender, ["Mr": 1, "Mrs": 0], class: "form-control styled", id: "gender" %>
            <%= error_tag fp, :gender %>
        </div>
        <div class="form-group">
            <%= label fp, :first_name, gettext "First name" %>
            <%= text_input fp, :first_name, class: "form-control" %>
            <%= error_tag fp, :first_name %>
        </div>
        <div class="form-group">
            <%= label fp, :last_name, gettext "Last name" %>
            <%= text_input fp, :last_name, class: "form-control" %>
            <%= error_tag fp, :last_name %>
        </div>
    <% end %>

    <div class="form-group">
        <%= label f, :email, gettext "Email" %>
        <%= text_input f, :email, class: "form-control" %>
        <%= error_tag f, :email %>
    </div>
    <div class="form-group">
        <%= label f, :password, gettext "Password" %>
        <%= password_input f, :password, class: "form-control" %>
        <%= error_tag f, :password %>
    </div>
    <button type="submit" class="btn btn btn-primary"><%= gettext "Register" %></button>
<% end %>

I hope I give you an answer for your problem.

3 Likes

Thank you all for the contribution.

Reviewed for @kelvinst
I needed to add inside the “controller” the following parameter.

Staying like this:

  def new(conn, _params) do
    changeset = Disp.changeset(%Disp{})
    render(conn, "new.html", changeset: changeset, cities: Repo.all(from(c in Local, select: {c.city, c.id})))
  end

Problem solved

2 Likes