Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pagination error "paginate_products/1 is undefined or private." #163

Closed
Kapeusz opened this issue May 11, 2020 · 5 comments
Closed

Pagination error "paginate_products/1 is undefined or private." #163

Kapeusz opened this issue May 11, 2020 · 5 comments

Comments

@Kapeusz
Copy link

Kapeusz commented May 11, 2020

I am fairly new to Elixir and Phoenix. I followed the instructions, but I cannot access the "/admin/products" page because of the error:

Request: GET /admin/products
** (exit) an exception was raised:
    ** (UndefinedFunctionError) function Shopify.Inventory.paginate_products/1 is undefined or private. Did you mean one of:

      * change_product/1
      * get_product!/1
      * list_products/0
      * update_product/2

I don't know where to start with solving this problem. Does anyone have an idea what is wrong or what I could have done wrong?

@cpjolicoeur
Copy link
Member

cpjolicoeur commented May 11, 2020

You'd have to describe which commands you ran first.

What generator commands did you run to generate the torch files? Also, which version of Torch and Phoenix are you using?

@Kapeusz
Copy link
Author

Kapeusz commented May 11, 2020

I am using Phoenix 1.4 and torch 3.1. I followed the Installation guide.
First, I added torch 3.1 to mix.exs and Plug.Static to my endpoint.ex. Then I copied and pasted code to config.exs. After that setup I ran mix torch.install and later

mix torch.gen.html Inventory Product products--no-schema --no-context --web Admin avdate:date name:string price:float quantity:integer.

Later I added to my router.ex:

    scope "/admin", ShopifyWeb.Admin, as: :admin do
      pipe_through :browser
      resources "/categories", CategoryController
    end

and to endpoint.ex

    plug(
      Plug.Static,
      at: "/torch",
      from: {:torch, "priv/static"},
      gzip: true,
      cache_control_for_etags: "public, max-age=86400",
      headers: [{"access-control-allow-origin", "*"}]
    )

@cpjolicoeur
Copy link
Member

I see. So, by passing the --no-context flag, torch did not generate any the context file inventory.ex for you, which includes that paginate method.

You'd either need to manually add this method in, or you can run the generator again without the --no-context flag and manually handle the file diff yourself (to keep your existing context file and only add the torch generated content on top of it.

When using the --no-schema flag like you did, most of the generated methods are just "stubs" that you need to fill out yourself (presumably because you already had them defined since you had an existing schema). You could just copy the new paginate_products/1 method (and other paginate and filter_config methods yourself.

Or, a final alternative would be to use a new context for the admin side of things vs re-using the existing context you had in place.

@Kapeusz
Copy link
Author

Kapeusz commented May 11, 2020

I ran same generator again without the --no-context and added paginate_product. Copied and pasted paginate_products and other methods on top of inventory.ex. Now it raises such error:

== Compilation error in file lib/shopify/inventory.ex ==
** (CompileError) lib/shopify/inventory.ex:205: definitions with multiple clauses and 
default values require a header. Instead of:

    def foo(:first_clause, b \\ :default) do ... end
    def foo(:second_clause, b) do ... end

one should write:

    def foo(a, b \\ :default)
    def foo(:first_clause, b) do ... end
    def foo(:second_clause, b) do ... end

def create_product/1 has multiple clauses and defines defaults in one or more clauses 
    lib/shopify/inventory.ex:205: (module)
    (stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
    (elixir) lib/kernel/parallel_compiler.ex:198: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/6

My inventory.ex:

defmodule Shopify.Inventory do
  @moduledoc """
  The Inventory context.
  """
  alias Shopify.Departments

  import Ecto.Query, warn: false
  import Torch.Helpers, only: [sort: 1, paginate: 4]
  import Filtrex.Type.Config
  alias Shopify.Repo

  alias Shopify.Inventory.Product

@pagination [page_size: 15]
@pagination_distance 5

@doc """
Paginate the list of products using filtrex
filters.

## Examples

    iex> list_products(%{})
    %{products: [%Product{}], ...}
"""
@spec paginate_products(map) :: {:ok, map} | {:error, any}
def paginate_products(params \\ %{}) do
  params =
    params
    |> Map.put_new("sort_direction", "desc")
    |> Map.put_new("sort_field", "inserted_at")

  {:ok, sort_direction} = Map.fetch(params, "sort_direction")
  {:ok, sort_field} = Map.fetch(params, "sort_field")

  with {:ok, filter} <- Filtrex.parse_params(filter_config(:products), params["product"] || %{}),
       %Scrivener.Page{} = page <- do_paginate_products(filter, params) do
    {:ok,
      %{
        products: page.entries,
        page_number: page.page_number,
        page_size: page.page_size,
        total_pages: page.total_pages,
        total_entries: page.total_entries,
        distance: @pagination_distance,
        sort_field: sort_field,
        sort_direction: sort_direction
      }
    }
  else
    {:error, error} -> {:error, error}
    error -> {:error, error}
  end
end

defp do_paginate_products(filter, params) do
  raise "TODO"
end

@doc """
Returns the list of products.

## Examples

    iex> list_products()
    [%Product{}, ...]

"""
def list_products do
  raise "TODO"
end

@doc """
Gets a single product.

Raises `Ecto.NoResultsError` if the Product does not exist.

## Examples

    iex> get_product!(123)
    %Product{}

    iex> get_product!(456)
    ** (Ecto.NoResultsError)

"""
def get_product!(id), do: raise "TODO"

@doc """
Creates a product.

## Examples

    iex> create_product(%{field: value})
    {:ok, %Product{}}

    iex> create_product(%{field: bad_value})
    {:error, %Ecto.Changeset{}}

"""
def create_product(attrs \\ %{}) do
  raise "TODO"
end

@doc """
Updates a product.

## Examples

    iex> update_product(product, %{field: new_value})
    {:ok, %Product{}}

    iex> update_product(product, %{field: bad_value})
    {:error, %Ecto.Changeset{}}

"""
def update_product(%Product{} = product, attrs) do
  raise "TODO"
end

@doc """
Deletes a Product.

## Examples

    iex> delete_product(product)
    {:ok, %Product{}}

    iex> delete_product(product)
    {:error, %Ecto.Changeset{}}

"""
def delete_product(%Product{} = product) do
  raise "TODO"
end

@doc """
Returns an `%Ecto.Changeset{}` for tracking product changes.

## Examples

    iex> change_product(product)
    %Ecto.Changeset{source: %Product{}}

"""
def change_product(%Product{} = product, _attrs \\ %{}) do
  raise "TODO"
end

defp filter_config(:products) do
  raise "TODO"
end

  @doc """
  Returns the list of products.

  ## Examples

      iex> list_products()
      [%Product{}, ...]

  """
  def list_products do
    Product
    |> preload(:category)
    |> Repo.all()
  end

  @doc """
  Gets a single product.

  Raises `Ecto.NoResultsError` if the Product does not exist.

  ## Examples

      iex> get_product!(123)
      %Product{}

      iex> get_product!(456)
      ** (Ecto.NoResultsError)

  """
  def get_product!(id) do
    Product
    |> Repo.get!(id)
    |> Repo.preload(:comments)
    |> Repo.preload(:category)
  end

  @doc """
  Creates a product.

  ## Examples

      iex> create_product(%{field: value})
      {:ok, %Product{}}

      iex> create_product(%{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def create_product(attrs \\ %{}) do
    %Product{}
    |> Product.changeset(attrs)
    |> Repo.insert()
  end

  @doc """
  Updates a product.

  ## Examples

      iex> update_product(product, %{field: new_value})
      {:ok, %Product{}}

      iex> update_product(product, %{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def update_product(%Product{} = product, attrs) do
    product
    |> Product.changeset(attrs)
    |> Repo.update()
  end

  @doc """
  Deletes a product.

  ## Examples

      iex> delete_product(product)
      {:ok, %Product{}}

      iex> delete_product(product)
      {:error, %Ecto.Changeset{}}

  """
  def delete_product(%Product{} = product) do
    Repo.delete(product)
  end

  @doc """
  Returns an `%Ecto.Changeset{}` for tracking product changes.

  ## Examples

      iex> change_product(product)
      %Ecto.Changeset{source: %Product{}}

  """
  def change_product(%Product{} = product) do
    Product.changeset(product, %{})
  end

end

I am sorry for raising the issue by probably newbie mistakes...

@cpjolicoeur
Copy link
Member

Sure, see how many of the functions are duplicated now? For example, you already had/have a change_product/1 and delete_product/1 near the bottom of your file from your code that existed before you ran the generator.

But, when you copy/pasted the generated code you pasted everything including the stubbed functions:

@doc """
Deletes a Product.

## Examples

    iex> delete_product(product)
    {:ok, %Product{}}

    iex> delete_product(product)
    {:error, %Ecto.Changeset{}}

"""
def delete_product(%Product{} = product) do
  raise "TODO"
end

@doc """
Returns an `%Ecto.Changeset{}` for tracking product changes.

## Examples

    iex> change_product(product)
    %Ecto.Changeset{source: %Product{}}

"""
def change_product(%Product{} = product, _attrs \\ %{}) do
  raise "TODO"
end

You'll want to remove these duplicated functions that you pasted at the top and keep the functions that you already had defined. Really, you just wanted to paste in the functions that didnt already exist in your file previously. Namely, the following:

  • paginate_products/1
  • do_paginate_products/2
  • filter_config/1

Note that, since you ran the generator with --no-schema the do_paginate_products/2 and filter_config/1 methods are just stubs. You'll need to actually fill them out. Most likely, based on what I see, you will want the following (or something similar):

defp do_paginate_products(filter, params) do
  Product
  |> Filtrex.query(filter)
  |> order_by(^sort(params))
  |> paginate(Repo, params, @pagination)
end

and:

defp filter_config(:products) do
  defconfig do
    date :avdate
    text :name 
    # TODO: add config for `price` of type float
    number :quantity 
  end
end

rafaltrojanowski added a commit to rafaltrojanowski/kickstart that referenced this issue Dec 17, 2020
1. Run: mix torch.gen.html Blog Post posts --no-schema --no-context
--web Admin title:string body:text published_at:datetime
published:boolean views:integer slug:string
2. See: mojotech/torch#163 (comment)
3. See: mojotech/torch#100 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants