Kishan Kumar Gupta
Kishan Kumar Gupta

Reputation: 151

value `"user_id"` in `where` cannot be cast to type :id in query:

I am trying to work on a Role Based Access control Pheonix API. In which the user will sign in and a session will be created. After logging in he can create a post the post created will be assigned to that user_id. I have encountered this problem the user_id is coming like a string instead of the value inside it.

I have tried String.to_integer(user_id) but it is giving an error because user_id is coming like a string, not a variable storing an id.

My router


scope "/", PxblogWeb do
    pipe_through :browser

    get "/", PageController, :index

    resources "/users", UserController do
      resources "/posts", PostController
    end

    resources "/sessions", SessionController, only: [:new, :create, :delete]
  end

  scope "/" do
    resources "/sessions", SessionController, only: [:new, :create, :delete]
  end

  # Other scopes may use custom stacks.
  scope "/api", PxblogWeb do
    pipe_through :api

    get "/users/read", UserController, :index
  end

new.html.eex

<h1>New Post</h1>
<%= render "form.html", Map.put(assigns, :action, Routes.user_post_path(@conn, :create, :user_id)) %>
<span><%= link "Back", to: Routes.user_post_path(@conn, :index, :user_id) %></span>

post_controller.ex

plug :assign_user
  plug :authorize_user when action in [:new, :create, :update, :edit, :delete]


  defp assign_user(conn, _opts) do
    case conn.params do
      %{"user_id" => user_id} ->
        case Repo.get(Pxblog.Post.User, user_id) do
          nil  -> invalid_user(conn)
          user -> assign(conn, :user, user)
        end
      _ -> invalid_user(conn)
    end
  end

  defp invalid_user(conn) do
    conn
    |> put_flash(:error, "Invalid user!")
    |> redirect(to: Routes.page_path(conn, :index))
    |> halt
  end

  defp authorize_user(conn, _) do
    user = get_session(conn, :current_user)
    if user && (Integer.to_string(user.id) == conn.params["user_id"] || Pxblog.RoleChecker.is_admin?(user)) do
      conn
    else
      conn
      |> put_flash(:error, "You are not authorized to modify that post!")
      |> redirect(to: Routes.page_path(conn, :index))
      |> halt()
    end
  end

  def index(conn, _params) do
    posts = Repo.all(assoc(conn.assigns[:user], :posts))
    render(conn, "index.html", posts: posts)
  end

  def new(conn, _params) do
    changeset =
      conn.assigns[:user]
      |> build_assoc(:posts)
      |> Pxblog.Learn.Post.changeset(%{})
    render(conn, "new.html", changeset: changeset)
  end

  def create(conn, %{"post" => post_params}) do
    changeset =
      conn.assigns[:user]
      |> build_assoc(:posts)
      |> Pxblog.Learn.Post.changeset(post_params)
    case Repo.insert(changeset) do
      {:ok, _post} ->
        conn
        |> put_flash(:info, "Post created successfully.")
        |> redirect(to: Routes.user_post_path(conn, :index, conn.assigns[:user]))
      {:error, changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end

session_controller.ex

defmodule PxblogWeb.SessionController do
    use PxblogWeb, :controller

    alias Pxblog.Post.User
    alias Pxblog.Post
    alias Pxblog.Repo

    import Comeonin.Bcrypt, only: [checkpw: 2, dummy_checkpw: 0]

    plug :scrub_params, "user" when action in [:create]

    def new(conn, _params) do
      render conn, "new.html", changeset: Pxblog.Post.User.changeset(%Pxblog.Post.User{}, %{})
    end

    def create(conn, %{"user" => %{"username" => username, "password" => password}})
    when not is_nil(username) and not is_nil(password) do
      user = Repo.get_by(Pxblog.Post.User, username: username)
      sign_in(user, password, conn)
    end

    def create(conn, _) do
      failed_login(conn)
    end

    defp failed_login(conn) do
      dummy_checkpw()
      conn
      |> put_session(:current_user, nil)
      |> put_flash(:error, "Invalid username/password combination!")
      |> redirect(to: Routes.page_path(conn, :index))
      |> halt()
    end

    defp sign_in(user, _password, conn) when is_nil(user) do
      failed_login(conn)
    end

    defp sign_in(user, password, conn) do
      if checkpw(password, user.password_digest) do
        conn
        |> put_session(:current_user, %{id: user.id, username: user.username, role_id: user.role_id})
        |> put_flash(:info, "Sign in successful!")
        |> redirect(to: Routes.page_path(conn, :index))
      else
        failed_login(conn)
      end
    end

    def delete(conn, _params) do
      conn
      |> delete_session(:current_user)
      |> put_flash(:info, "Signed out successfully!")
      |> redirect(to: Routes.page_path(conn, :index))
    end

end

Upvotes: 0

Views: 935

Answers (1)

7stud
7stud

Reputation: 48589

The error in your title suggests that somewhere there is a string that looks like:

"some string user_id"

instead of:

"some string #{user_id}" 

And where in the error message indicates a db query, so:

"...where id=user_id" 

instead of

"...where id=#{user_id}"

=====

In your new action, you have:

 def new(conn, _params) do
    changeset =
      conn.assigns[:user]
      |> build_assoc(:posts)
      |> Pxblog.Learn.Post.changeset(%{})
    render(conn, "new.html", changeset: changeset)
  end

The render() call makes the variable @changeset available in the template (as well as @conn by elixir magic). Here's your template:

<h1>New Post</h1>
<%= render "form.html", 
    Map.put(assigns, :action, 
            Routes.user_post_path(@conn, :create, :user_id)
           ) 
 %>
<span><%= link "Back", 
 to: Routes.user_post_path(@conn, :index, :user_id) %>
</span>

1) You don't use @changeset in the template, so specifying changeset: changeset in render() was unnecessary.

2) I think that each of the :user_id atoms should be a variable instead, which contains the actual user_id--a variable that you should create in the action and specify in render(), like you did with changeset.

When you interpolate an atom into a string (rather than a variable), you get:

iex(2)> "...where user=#{:user_id}"
"...where user=user_id"

And the error message is saying that the substring "user_id" cannot be converted to an :id type. I think Phoenix must do a database query to construct the user_post_path.

If you do:

$ iex -S mix

then:

iex(1)> h <YourApp>Web.Router.Helpers.user_post_path

def user_post_path(conn_or_endpoint, action, user_id)              
def user_post_path(conn_or_endpoint, action, user_id, params)          
def user_post_path(conn_or_endpoint, action, user_id, id, params)  

iex displays the actual defs that were created for your path helper. I think that if the argument for user_id was supposed to be the atom :user_id, then those functions would pattern match on the atom, like this:

def user_post_path(conn_or_endpoint, action, :user_id)              
def user_post_path(conn_or_endpoint, action, :user_id, params)          
def user_post_path(conn_or_endpoint, action, :user_id, id, params)  

Upvotes: 1

Related Questions