JackMaarek

JackMaarek

Hello guys,
Sorry for disturbing you again but I’m facing an issues that I cannot understand on my own.

I’m trying to create a image gallery api that handles images associated with categories and tags,
right now I’m focused on the association between the images and the categories and when I’m trying to send a image with a category id the image enitty is created but not associated with the category:

 phoenix_1  | [info] POST /api/image
phoenix_1  | [debug] Processing with ApiAppWeb.ImageController.create/2
phoenix_1  |   Parameters: %{"image" => %{"category_id" => 1, "description" => "This is a test image", "image" => "mylovelywife.png", "name" => "i1"}}
phoenix_1  |   Pipelines: [:api]
phoenix_1  | [debug] QUERY OK db=2.9ms queue=0.1ms idle=9349.5ms
phoenix_1  | INSERT INTO "image" ("description","image","name","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" ["This is a test image", "mylovelywife.png", "i1", ~N[2020-04-20 23:37:57], ~N[2020-04-20 23:37:57]]
phoenix_1  | [info] Sent 201 in 7ms

Here is my schema for my categories:

schema "category" do
    field :name, :string
    has_many :image, ApiApp.Images.Image

    timestamps()
  end

  @doc false
  def changeset(categories, attrs) do
    categories
    |> cast(attrs, [:name])
    |> validate_required([:name])
    |> unique_constraint(:name)
    |> validate_length(:name, max: 60, count: :codepoints)
  end

And the schema for the images :

 schema "image" do
    field :description, :string
    field :image, :string
    field :name, :string
    belongs_to :category, ApiApp.Images.Categories, foreign_key: :category_id

    timestamps()
  end

  @doc false
  def changeset(image, attrs) do
    image
    |> cast(attrs, [:name, :description, :image])
    |> validate_required([:name, :description, :image])
    |> foreign_key_constraint(:category_id,
         name: :image_category_id_fkey,
         message: "Category not found!"
       )
  end

Also in my create_image method I’ve putted the changest’s cast_assoc method:

  def create_image(attrs \\ %{}) do
    %Image{}
    |> Image.changeset(attrs)
    |> cast_assoc(:category, with: &Category.changeset/2)
    |> Repo.insert()
  end

If anyone got an idea of what I’m doing wrong It would be great to hear !
Thanks a lot for your time and help guys.

Showing Posts 1 to 10

axelson

axelson

Scenic Core Team

I think you might want to change your parameters from:
%{"image" => %{"category_id" => 1, "description" => "This is a test image", "image" => "mylovelywife.png", "name" => "i1"}}
to:
%{"image" => %{"category" => %{"id" => 1}, "description" => "This is a test image", "image" => "mylovelywife.png", "name" => "i1"}}

cast_assoc expects the embed to have nested params.

JackMaarek

JackMaarek OP

Thanks for your help mate !

You were right when the params are nested it throws back an error again:

Request: POST /api/image
phoenix_1  | ** (exit) an exception was raised:
phoenix_1  |     ** (UndefinedFunctionError) function Category.changeset/2 is undefined (module Category is not available)
phoenix_1  |         Category.changeset(%ApiApp.Images.Categories{__meta__: #Ecto.Schema.Metadata<:built, "category">, id: nil, image: #Ecto.Association.NotLoaded<association :image is not loaded>, inserted_at: nil, name: nil, updated_at: nil}, %{"id" => 1})
phoenix_1  |         (ecto 3.4.0) lib/ecto/changeset/relation.ex:126: Ecto.Changeset.Relation.do_cast/6
phoenix_1  |         (ecto 3.4.0) lib/ecto/changeset/relation.ex:315: Ecto.Changeset.Relation.single_change/5
phoenix_1  |         (ecto 3.4.0) lib/ecto/changeset/relation.ex:110: Ecto.Changeset.Relation.cast/5
phoenix_1  |         (ecto 3.4.0) lib/ecto/changeset.ex:785: Ecto.Changeset.cast_relation/4
phoenix_1  |         (api_app 0.1.0) lib/api_app/images.ex:248: ApiApp.Images.create_image/1
phoenix_1  |         (api_app 0.1.0) lib/api_app_web/controllers/image_controller.ex:15: ApiAppWeb.ImageController.create/2
phoenix_1  |         (api_app 0.1.0) lib/api_app_web/controllers/image_controller.ex:1: ApiAppWeb.ImageController.action/2
phoenix_1  |         (api_app 0.1.0) lib/api_app_web/controllers/image_controller.ex:1: ApiAppWeb.ImageController.phoenix_controller_pipeline/2
phoenix_1  |         (phoenix 1.4.16) lib/phoenix/router.ex:288: Phoenix.Router.__call__/2
phoenix_1  |         (api_app 0.1.0) lib/api_app_web/endpoint.ex:1: ApiAppWeb.Endpoint.plug_builder_call/2
phoenix_1  |         (api_app 0.1.0) lib/api_app_web/endpoint.ex:1: ApiAppWeb.Endpoint.call/2
phoenix_1  |         (phoenix 1.4.16) lib/phoenix/endpoint/cowboy2_handler.ex:42: Phoenix.Endpoint.Cowboy2Handler.init/4
phoenix_1  |         (cowboy 2.7.0) /backend/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2
phoenix_1  |         (cowboy 2.7.0) /backend/deps/cowboy/src/cowboy_stream_h.erl:320: :cowboy_stream_h.execute/3
phoenix_1  |         (cowboy 2.7.0) /backend/deps/cowboy/src/cowboy_stream_h.erl:302: :cowboy_stream_h.request_process/3
phoenix_1  |         (stdlib 3.12) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

But I do not understand why as Category.changeset is defined in my schema…

nickdichev

nickdichev

Are you using the right module name? From what I can see in your errors it looks like that module might live somewhere like ApiApp.Image.Category (not sure on this just guessing) but you’re directly calling Category.changeset/2

You either need to use the full module name there or alias it if you want to use the shortened form.

JackMaarek

JackMaarek OP

Yes you’re right I was just making an update about it the right module to use is the Categories module and not category.

But now the problem comes from the fallback controller…

 [info] POST /api/image
phoenix_1  | [debug] Processing with ApiAppWeb.ImageController.create/2
phoenix_1  |   Parameters: %{"image" => %{"category" => %{"id" => 1}, "description" => "This is a test image", "image" => "mylovelywife.png", "name" => "i1"}}
phoenix_1  |   Pipelines: [:api]
phoenix_1  | [info] Sent 500 in 1ms
phoenix_1  | [info] Converted error :function_clause to 500 response
phoenix_1  | [error] #PID<0.910.0> running ApiAppWeb.Endpoint (connection #PID<0.909.0>, stream id 1) terminated
phoenix_1  | Server: localhost:4000 (http)
phoenix_1  | Request: POST /api/image
phoenix_1  | ** (exit) an exception was raised:
phoenix_1  |     ** (FunctionClauseError) no function clause matching in ApiAppWeb.FallbackController.call/2
phoenix_1  |         (api_app 0.1.0) lib/api_app_web/controllers/fallback_controller.ex:9: ApiAppWeb.FallbackController.call(%Plug.Conn{adapter: {Plug.Cowboy.Conn, :...}, assigns: %{}, before_send: [#Function<0.24098476/1 in Plug.Telemetry.call/2>], body_params: %{"image" => %{"category" => %{"id" => 1}, "description" => "This is a test image", "image" => "mylovelywife.png", "name" => "i1"}}, cookies: %Plug.Conn.Unfetched{aspect: :cookies}, halted: false, host: "localhost", method: "POST", owner: #PID<0.910.0>, params: %{"image" => %{"category" => %{"id" => 1}, "description" => "This is a test image", "image" => "mylovelywife.png", "name" => "i1"}}, path_info: ["api", "image"], path_params: %{}, port: 4000, private: %{ApiAppWeb.Router => {[], %{}}, :phoenix_action => :create, :phoenix_controller => ApiAppWeb.ImageController, :phoenix_endpoint => ApiAppWeb.Endpoint, :phoenix_format => "json", :phoenix_layout => {ApiAppWeb.LayoutView, :app}, :phoenix_router => ApiAppWeb.Router, :phoenix_view => ApiAppWeb.ImageView, :plug_session_fetch => #Function<1.32542428/1 in Plug.Session.fetch_session/1>}, query_params: %{}, query_string: "", remote_ip: {172, 25, 0, 1}, req_cookies: %Plug.Conn.Unfetched{aspect: :cookies}, req_headers: [{"accept", "*/*"}, {"content-length", "114"}, {"content-type", "application/json"}, {"host", "localhost:4000"}, {"user-agent", "curl/7.64.1"}], request_path: "/api/image", resp_body: nil, resp_cookies: %{}, resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}, {"x-request-id", "FgevKmnwQSUnG-UAAAzR"}], scheme: :http, script_name: [], secret_key_base: :..., state: :unset, status: nil}, {:error, #Ecto.Changeset<action: :insert, changes: %{description: "This is a test image", image: "mylovelywife.png", name: "i1"}, errors: [categories_id: {"can't be blank", [validation: :required]}], data: #ApiApp.Images.Image<>, valid?: false>})
phoenix_1  |         (api_app 0.1.0) lib/api_app_web/controllers/image_controller.ex:1: ApiAppWeb.ImageController.action/2
phoenix_1  |         (api_app 0.1.0) lib/api_app_web/controllers/image_controller.ex:1: ApiAppWeb.ImageController.phoenix_controller_pipeline/2
phoenix_1  |         (phoenix 1.4.16) lib/phoenix/router.ex:288: Phoenix.Router.__call__/2
phoenix_1  |         (api_app 0.1.0) lib/api_app_web/endpoint.ex:1: ApiAppWeb.Endpoint.plug_builder_call/2
phoenix_1  |         (api_app 0.1.0) lib/api_app_web/endpoint.ex:1: ApiAppWeb.Endpoint.call/2
phoenix_1  |         (phoenix 1.4.16) lib/phoenix/endpoint/cowboy2_handler.ex:42: Phoenix.Endpoint.Cowboy2Handler.init/4
phoenix_1  |         (cowboy 2.7.0) /backend/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2
phoenix_1  |         (cowboy 2.7.0) /backend/deps/cowboy/src/cowboy_stream_h.erl:320: :cowboy_stream_h.execute/3
phoenix_1  |         (cowboy 2.7.0) /backend/deps/cowboy/src/cowboy_stream_h.erl:302: :cowboy_stream_h.request_process/3
phoenix_1  |         (stdlib 3.12) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

Here is the image scheme updated:

defmodule ApiApp.Images.Image do
  use Ecto.Schema
  import Ecto.Changeset

  alias ApiApp.Images.Categories

  schema "image" do
    field :description, :string
    field :image, :string
    field :name, :string
    belongs_to :category, Categories

    timestamps()
  end

  @doc false
  def changeset(image, attrs) do
    image
    |> cast(attrs, [:name, :description, :image, :category_id])
    |> validate_required([:name, :description, :image, :category_id])
    |> foreign_key_constraint(:category_id,
         name: :image_category_id_fkey,
         message: "Category not found!"
       )
  end
end

Even with "categories": {"id": 1}

nickdichev

nickdichev

Can you share the contents of api_app_web/controllers/image_controller.ex?

JackMaarek

JackMaarek OP

Yeah for sure !

here you go :

defmodule ApiAppWeb.ImageController do
  use ApiAppWeb, :controller

  alias ApiApp.Images
  alias ApiApp.Images.Image

  action_fallback ApiAppWeb.FallbackController

  def index(conn, _params) do
    image = Images.list_image()
    render(conn, "index.json", image: image)
  end

  def create(conn, %{"image" => image_params}) do
    with {:ok, %Image{} = image} <- Images.create_image(image_params) do
      conn
      |> put_status(:created)
      |> put_resp_header("location", Routes.image_path(conn, :show, image))
      |> render("show.json", image: image)
    end
  end

  def show(conn, %{"id" => id}) do
    image = Images.get_image!(id)
    render(conn, "show.json", image: image)
  end

  def update(conn, %{"id" => id, "image" => image_params}) do
    image = Images.get_image!(id)

    with {:ok, %Image{} = image} <- Images.update_image(image, image_params) do
      render(conn, "show.json", image: image)
    end
  end

  def delete(conn, %{"id" => id}) do
    image = Images.get_image!(id)

    with {:ok, %Image{}} <- Images.delete_image(image) do
      send_resp(conn, :no_content, "")
    end
  end
end

and the method create_image:

  def create_image(attrs \\ %{}) do
    %Image{}
    |> Image.changeset(attrs)
    |> Ecto.Changeset.cast_assoc(:category, with: &Categories.changeset/2)
    |> Repo.insert()
  end

Thanks a lot for helping me mate !

JackMaarek

JackMaarek OP

Oh and here is the fallbackController :

defmodule ApiAppWeb.FallbackController do
  @moduledoc """
  Translates controller action results into valid `Plug.Conn` responses.

  See `Phoenix.Controller.action_fallback/1` for more details.
  """
  use ApiAppWeb, :controller

  def call(conn, {:error, :not_found}) do
    conn
    |> put_status(:not_found)
    |> put_view(ApiAppWeb.ErrorView)
    |> render(:"404")
  end
end
nickdichev

nickdichev

So, in your controller you have create/2 where you catch the happy path in the with clause. What I think is happening here is that you are actually getting a changeset error that isn’t being handled. The reason I think this is the case is because of the fallback controller function definition error.

You don’t have an else clause for the with so create/2 is just returning the {:error, changeset} of Repo.insert() in create_image/1. But, {:error, changeset} isn’t a valid %Plug.Conn{} struct – the expected return of your controller functions!

Since you’re not returning a %Plug.Conn{}, Phoenix tries to help you out and call your defined fallback controller ApiAppWeb.FallbackController. You are likely missing the function call(conn, {:error, changeset}) there (you might want to pattern match against the changeset against %Ecto.Changeset{} to avoid a catch all match…)

So you have a couple options to debug from here. You can define the fallback controller call/2 and inspect in there, or put an inspect in your changeset pipeline to see what’s going wrong. (The error is actually in the previous error you posted, at the very end of the line)

I know this is a lot so let me know if you have any questions! Also here’s the documentation on the fallback controller with good examples!

JackMaarek

JackMaarek OP

First of all thanks a lot for this great explanation I think I’m getting it a little bit more.

If I want to pattern match through the changeset against Ecto.Changeset{} the documentation doesn’t specify how to format ecto response in order to debug it..

I have created a second call function :

 def call(conn, {:error, changeset}) do
    IO.puts(Plug.Conn.Status.code/1)
    IO.puts(changeset)
  end

In order to get which status code is sent back from the error but it still trying to call Plug.Conn.Status.code/0
And also getting the changeset pattern used by Ecto and the printed output was:

#Ecto.Changeset<action: :insert, changes: %{category: #Ecto.Changeset<action: :insert, changes: %{}, errors: [name: {"can't be blank", [validation: :required]}], data: #ApiApp.Images.Categories<>, valid?: false>, description: "This is a test image", image: "mylovelywife.png", name: "i1"}, errors: [category_id: {"can't be blank", [validation: :required]}]

Thanks again for everything mate !

JackMaarek

JackMaarek OP

I do not understand what happened but it seems that the association is working fine now.. I just recompile the project and it worked.

So that’s great, and thanks to help of this wonderful community that motivates the envy of persisting !

Now my second step is to see the associated images on GET /categories route !

Where Next? Top

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 94592 917
New
cblavier
Hey there, It’s been more than a year since we started using LiveView as our main UI library and building a whole library of UI componen...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
heathen
Quite interesting article Google brought me. Didn’t find any mentions about it here. What do you think in general? Would you use togethe...
New
AstonJ
Since we have deprecated our Erlang sections (as we have dedicated Erlang Forums now) let’s add this thread for those who’d like to post ...
New
maennchen
:warning: Security advisory: Decimal DoS vulnerability A vulnerability has been published for decimal where very large exponents can cau...
New
Null-logic-0
What IDE or editor are you using for Elixir development? Personally, I use Zed, and I really like it, but sometimes I wish there were a ...
New

Other Trending Topics Top

marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
webofbits
Aludel - LLM Evaluation Workbench Aludel is an embeddable Phoenix LiveView dashboard for evaluating and comparing LLM prompts across mult...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews