Cast_assoc is null?

I’m trying to do belongs_to on my magnets (posts) but it’s also null, cast_assoc is not doing anything. I’m pretty sure I’m just doing something wrong

My magnets (posts) has

belongs_to :user, Magnify.Coherence.User

And when I create a new magnet (post)

def changeset(magnet, attrs) do
    magnet
    |> cast(attrs, [:title, :description, :content, ])
    |> cast_assoc(:user)
    |> validate_required([:title, :description, :content,])
  end 

Anyone have an idea what I’m doing wrong?

1 Like

Magnet already has user_id no need cast assoc user. If you want to create magnet with user changeset cast_assoc(:magnet)

The user id is still null, it’s not adding anything

I added |> changeset cast_assoc(:magnet) but I get

function Magnify.Magnets.Magnet.search/2 is undefined (module Magnify.Magnets.Magnet is not available)

actually cast_assoc :user should work too but maybe belongs to tables doesn’t have that property

|> cast(attrs, [:title, :description, :content, :user_id # add this])

I just did that and tested,

6 is the new item

Are you creating from html form or iex? Can you show code?

From html, is it possble that I have in the wrong place should this be in the controller?

Here is the form

<div class="card">
    <div class="card-body">
        <%= 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, :title, class: "control-label" %>
                            <%= text_input f, :title, class: "form-control" %>
                                <%= error_tag f, :title %>
                    </div>

                    <div class="form-group">
                        <%= label f, :description, class: "control-label" %>
                            <%= text_input f, :description, class: "form-control" %>
                                <%= error_tag f, :description %>
                    </div>

                    <div class="form-group" style="display: block; height: 425px;">
                     <div id="editormd" data-read-only="false" data-line-numbers="true">
                        <%= label f, :content, class: "control-label" %>
                            <%= textarea f, :content, class: "" %>
                                <%= error_tag f, :content %>
                        </div>
                    </div>

    </div>
    <div class="card-actionbar">
        <div class="card-actionbar-row">
            <%= link dgettext("cancel", "Cancel"), to: magnet_path(@conn, :index), class: "btn btn-flat btn-default ink-reaction" %>
            <%= submit dgettext("submit", "Submit"), class: "btn btn-primary" %>
        </div>
    </div>
    <% end %>
</div>

And the schema

defmodule Magnify.Magnets.Magnet do
  use Ecto.Schema
  import Ecto.Changeset
  import Ecto.Query, only: [from: 2]


  schema "magnets" do
    field :content, :string
    field :description, :string
    field :title, :string
    belongs_to :user, Magnify.Coherence.User
    many_to_many :fluxes, Magnify.Magnets.Flux, join_through: "magnets_fluxes"

    timestamps()
  end

  @doc false
  def changeset(magnet, attrs) do
    magnet
    |> cast(attrs, [:title, :description, :content, :user_id])
    |> cast_assoc(:user)
    |> validate_required([:title, :description, :content,])
  end

  def search(query, search_term) do
    wildcard_search = "%#{search_term}%"

    from magnet in query,
         where: ilike(magnet.title, ^wildcard_search),
         or_where: ilike(magnet.description, ^wildcard_search)
  end

end

controller

def new(conn, _params) do
    changeset = Magnets.change_magnet(%Magnet{})
    render(conn, "new.html", changeset: changeset)
  end

  def create(conn, %{"magnet" => magnet_params}) do
    case Magnets.create_magnet(magnet_params) do
      {:ok, magnet} ->
        conn
        |> put_flash(:info, "Magnet created successfully.")
        |> redirect(to: magnet_path(conn, :show, magnet))
      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end

Youd should update magnet_params from controller.

def create(conn, %{"magnet" => magnet_params}) do
 magnet_params = Map.put(magnet_params, "user_id",  conn.assigns.current_user.id)
    case Magnets.create_magnet(magnet_params) do
      {:ok, magnet} ->
        conn
        |> put_flash(:info, "Magnet created successfully.")
        |> redirect(to: magnet_path(conn, :show, magnet))
      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "new.html", changeset: changeset)
    end

That code is wrong, getting a syntax error

1 Like

I fixed wrongly } instead of )

Thanks that was it and I think it’s working I am getting the following error which I’m guessing it has to do with the user id

Postgrex expected a binary of 16 bytes, got 1. Please make sure the value you are passing matches the definition in your table or in your query or convert the value accordingly.

This is my db object

schema "magnets" do
    field :content, :string
    field :description, :string
    field :title, :string
    belongs_to :user, Magnify.Coherence.User
    many_to_many :fluxes, Magnify.Magnets.Flux, join_through: "magnets_fluxes"

    timestamps()
  end

Passing :user_id or any other foreign key into a changeset cast is an invitation for breaking into the system …

Ok so than what is the correct way to do it?

tl;dr

Just don’t put it into the params / changeset. Treat the params as an opaque data structure.

# in your controller

def create(conn, %{"magnet" => magnet_params}) do
    case Magnets.create_magnet(magnet_params, for: conn.assigns.current_user) do
      {:ok, magnet} ->
        conn
        |> put_flash(:info, "Magnet created successfully.")
        |> redirect(to: magnet_path(conn, :show, magnet))
      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
end

# in your business logic module

def create_magnet(attrs, for: %User{id: user_id}) do
  %Magnet{user_id: user_id}
  |> Magnet.changeset(attrs)
  |> Repo.insert()
end

# in the module where you schema / changesets are defined
def changeset(manget, attrs) do
  magnet
  |> cast(attrs, [:title, :description, :content])
  |> validate_required([:title, :description, :content])
end

https://github.com/nccgroup/sobelow/issues/29

1 Like

I guessed so but i saw that example at thechangelog project so that made me think this is right too. Thanks for help

I tried doing that, i.e. having in the schema did " |> cast_assoc(:user)"

but it’s null, did you see my first post

guessed so but i saw that example at thechangelog project so that made me think this is right too.

You might want to warn them about it. As I’ve mentioned before, this approach (casting foreign keys inside the changeset functions) allowed me to gain write access to any record in one shop’s database once.