slug contains a nil value

Viewed 81

I followed this episode on elixircasts page. I have problem with creating new records. When I try to add a new book I face such an error

cannot convert Bookstore.Inventory.Book to param, key :slug contains a nil value

I don't know where is the problem with my code. I have written the same code to create slugs for my authors' pages and it works. I would be grateful for pointing where I made a mistake. I am using {:slugify, "~> 1.3"}.

inventory/book.ex

      @derive {Phoenix.Param, key: :slug}
  schema "books" do
    field(:description, :string)
    field(:image_url, BookstoreWeb.Uploaders.ImageUploader.Type)
    field(:isbn, :string)
    field(:original_price, :float)
    field(:title, :string)
    field(:slug, :string)
    field(:year_published, :integer)

    belongs_to(:category, Bookstore.Genres.Category)
    belongs_to(:author, Bookstore.Writers.Author)

    timestamps()
  end

  @doc false
  def changeset(book, attrs) do
    book
    |> cast(attrs, [
      :author_id,
      :category_id,
      :isbn,
      :title,
      :description,
      :year_published,
      :original_price,
      :slug
    ])
    |> validate_required([:isbn, :title, :description, :year_published, :original_price])
    |> build_slug()
    |> cast_attachments(attrs, [:image_url])
  end

  def build_slug(changeset) do
    id = get_field(changeset, :id)
    title = get_field(changeset, :title)

    if id && title do
      name = Enum.join([id, title], "")
      slug = Slug.slugify(name)
      put_change(changeset, :slug, slug)
    else
      changeset
    end
  end

inventory.ex

  def get_book!(slug),
    do:
      Repo.get_by!(Book, slug: slug)
      |> Repo.preload(:category)
      |> Repo.preload(:author)

  def create_book(attrs \\ %{}) do
    %Book{}
    |> Book.changeset(attrs)
    |> Repo.insert()
  end
1 Answers

By default, Ecto lets the database autogenerate primary keys (see docs), which means the id will only be generated once the book has been saved to the database (after the call to the Repo), and cannot be retrieved before. Your get_field(changeset, :id) call within build_slug would return nil, your else branch would be evaluated and the book will be saved with a nil slug.

In the link you shared, the slug is built only from the title, which is the typical approach.

If you then want to access it with URLs such as /books/123-my-title, it is possible to parse your param using something like String.split(id_and_slug, "-", parts: 2).

Related