Skip to content

Commit

Permalink
add crud Catalog Product to pento
Browse files Browse the repository at this point in the history
  • Loading branch information
Tinix committed Jan 17, 2024
1 parent d234a4e commit ff276be
Show file tree
Hide file tree
Showing 11 changed files with 581 additions and 0 deletions.
104 changes: 104 additions & 0 deletions lib/pento/catalog.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
defmodule Pento.Catalog do
@moduledoc """
The Catalog context.
"""

import Ecto.Query, warn: false
alias Pento.Repo

alias Pento.Catalog.Product

@doc """
Returns the list of products.
## Examples
iex> list_products()
[%Product{}, ...]
"""
def list_products do
Repo.all(Product)
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: Repo.get!(Product, id)

@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{data: %Product{}}
"""
def change_product(%Product{} = product, attrs \\ %{}) do
Product.changeset(product, attrs)
end
end
21 changes: 21 additions & 0 deletions lib/pento/catalog/product.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defmodule Pento.Catalog.Product do
use Ecto.Schema
import Ecto.Changeset

schema "products" do
field :name, :string
field :description, :string
field :unit_price, :float
field :sku, :integer

timestamps(type: :utc_datetime)
end

@doc false
def changeset(product, attrs) do
product
|> cast(attrs, [:name, :description, :unit_price, :sku])
|> validate_required([:name, :description, :unit_price, :sku])
|> unique_constraint(:sku)
end
end
93 changes: 93 additions & 0 deletions lib/pento_web/live/product_live/form_component.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
defmodule PentoWeb.ProductLive.FormComponent do
use PentoWeb, :live_component

alias Pento.Catalog

@impl true
def render(assigns) do
~H"""
<div>
<.header>
<%= @title %>
<:subtitle>Use this form to manage product records in your database.</:subtitle>
</.header>
<.simple_form
for={@form}
id="product-form"
phx-target={@myself}
phx-change="validate"
phx-submit="save"
>
<.input field={@form[:name]} type="text" label="Name" />
<.input field={@form[:description]} type="text" label="Description" />
<.input field={@form[:unit_price]} type="number" label="Unit price" step="any" />
<.input field={@form[:sku]} type="number" label="Sku" />
<:actions>
<.button phx-disable-with="Saving...">Save Product</.button>
</:actions>
</.simple_form>
</div>
"""
end

@impl true
def update(%{product: product} = assigns, socket) do
changeset = Catalog.change_product(product)

{:ok,
socket
|> assign(assigns)
|> assign_form(changeset)}
end

@impl true
def handle_event("validate", %{"product" => product_params}, socket) do
changeset =
socket.assigns.product
|> Catalog.change_product(product_params)
|> Map.put(:action, :validate)

{:noreply, assign_form(socket, changeset)}
end

def handle_event("save", %{"product" => product_params}, socket) do
save_product(socket, socket.assigns.action, product_params)
end

defp save_product(socket, :edit, product_params) do
case Catalog.update_product(socket.assigns.product, product_params) do
{:ok, product} ->
notify_parent({:saved, product})

{:noreply,
socket
|> put_flash(:info, "Product updated successfully")
|> push_patch(to: socket.assigns.patch)}

{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign_form(socket, changeset)}
end
end

defp save_product(socket, :new, product_params) do
case Catalog.create_product(product_params) do
{:ok, product} ->
notify_parent({:saved, product})

{:noreply,
socket
|> put_flash(:info, "Product created successfully")
|> push_patch(to: socket.assigns.patch)}

{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign_form(socket, changeset)}
end
end

defp assign_form(socket, %Ecto.Changeset{} = changeset) do
assign(socket, :form, to_form(changeset))
end

defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
end
47 changes: 47 additions & 0 deletions lib/pento_web/live/product_live/index.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
defmodule PentoWeb.ProductLive.Index do
use PentoWeb, :live_view

alias Pento.Catalog
alias Pento.Catalog.Product

@impl true
def mount(_params, _session, socket) do
{:ok, stream(socket, :products, Catalog.list_products())}
end

@impl true
def handle_params(params, _url, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end

defp apply_action(socket, :edit, %{"id" => id}) do
socket
|> assign(:page_title, "Edit Product")
|> assign(:product, Catalog.get_product!(id))
end

defp apply_action(socket, :new, _params) do
socket
|> assign(:page_title, "New Product")
|> assign(:product, %Product{})
end

defp apply_action(socket, :index, _params) do
socket
|> assign(:page_title, "Listing Products")
|> assign(:product, nil)
end

@impl true
def handle_info({PentoWeb.ProductLive.FormComponent, {:saved, product}}, socket) do
{:noreply, stream_insert(socket, :products, product)}
end

@impl true
def handle_event("delete", %{"id" => id}, socket) do
product = Catalog.get_product!(id)
{:ok, _} = Catalog.delete_product(product)

{:noreply, stream_delete(socket, :products, product)}
end
end
44 changes: 44 additions & 0 deletions lib/pento_web/live/product_live/index.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<.header>
Listing Products
<:actions>
<.link patch={~p"/products/new"}>
<.button>New Product</.button>
</.link>
</:actions>
</.header>

<.table
id="products"
rows={@streams.products}
row_click={fn {_id, product} -> JS.navigate(~p"/products/#{product}") end}
>
<:col :let={{_id, product}} label="Name"><%= product.name %></:col>
<:col :let={{_id, product}} label="Description"><%= product.description %></:col>
<:col :let={{_id, product}} label="Unit price"><%= product.unit_price %></:col>
<:col :let={{_id, product}} label="Sku"><%= product.sku %></:col>
<:action :let={{_id, product}}>
<div class="sr-only">
<.link navigate={~p"/products/#{product}"}>Show</.link>
</div>
<.link patch={~p"/products/#{product}/edit"}>Edit</.link>
</:action>
<:action :let={{id, product}}>
<.link
phx-click={JS.push("delete", value: %{id: product.id}) |> hide("##{id}")}
data-confirm="Are you sure?"
>
Delete
</.link>
</:action>
</.table>

<.modal :if={@live_action in [:new, :edit]} id="product-modal" show on_cancel={JS.patch(~p"/products")}>
<.live_component
module={PentoWeb.ProductLive.FormComponent}
id={@product.id || :new}
title={@page_title}
action={@live_action}
product={@product}
patch={~p"/products"}
/>
</.modal>
21 changes: 21 additions & 0 deletions lib/pento_web/live/product_live/show.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defmodule PentoWeb.ProductLive.Show do
use PentoWeb, :live_view

alias Pento.Catalog

@impl true
def mount(_params, _session, socket) do
{:ok, socket}
end

@impl true
def handle_params(%{"id" => id}, _, socket) do
{:noreply,
socket
|> assign(:page_title, page_title(socket.assigns.live_action))
|> assign(:product, Catalog.get_product!(id))}
end

defp page_title(:show), do: "Show Product"
defp page_title(:edit), do: "Edit Product"
end
29 changes: 29 additions & 0 deletions lib/pento_web/live/product_live/show.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<.header>
Product <%= @product.id %>
<:subtitle>This is a product record from your database.</:subtitle>
<:actions>
<.link patch={~p"/products/#{@product}/show/edit"} phx-click={JS.push_focus()}>
<.button>Edit product</.button>
</.link>
</:actions>
</.header>

<.list>
<:item title="Name"><%= @product.name %></:item>
<:item title="Description"><%= @product.description %></:item>
<:item title="Unit price"><%= @product.unit_price %></:item>
<:item title="Sku"><%= @product.sku %></:item>
</.list>

<.back navigate={~p"/products"}>Back to products</.back>

<.modal :if={@live_action == :edit} id="product-modal" show on_cancel={JS.patch(~p"/products/#{@product}")}>
<.live_component
module={PentoWeb.ProductLive.FormComponent}
id={@product.id}
title={@page_title}
action={@live_action}
product={@product}
patch={~p"/products/#{@product}"}
/>
</.modal>
16 changes: 16 additions & 0 deletions priv/repo/migrations/20240117042735_create_products.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
defmodule Pento.Repo.Migrations.CreateProducts do
use Ecto.Migration

def change do
create table(:products) do
add :name, :string
add :description, :string
add :unit_price, :float
add :sku, :integer

timestamps(type: :utc_datetime)
end

create unique_index(:products, [:sku])
end
end
Loading

0 comments on commit ff276be

Please sign in to comment.