diff --git a/lib/bike_brigade/locations.ex b/lib/bike_brigade/locations.ex
index e4568a08..64985f4c 100644
--- a/lib/bike_brigade/locations.ex
+++ b/lib/bike_brigade/locations.ex
@@ -21,7 +21,10 @@ defmodule BikeBrigade.Locations do
Returns the list of community_fridges.
"""
def list_community_fridges do
- Repo.all(from cf in CommunityFridge, order_by: [desc: cf.active, asc: cf.name])
+ CommunityFridge
+ |> from(order_by: [desc: :active, asc: :name])
+ |> Repo.all()
+ |> Repo.preload(:location)
end
@doc """
@@ -29,13 +32,17 @@ defmodule BikeBrigade.Locations do
Raises `Ecto.NoResultsError` if the Community fridge does not exist.
"""
- def get_community_fridge!(id), do: Repo.get!(CommunityFridge, id)
+ def get_community_fridge!(id) do
+ CommunityFridge
+ |> Repo.get!(id)
+ |> Repo.preload(:location)
+ end
@doc """
Creates a community_fridge.
"""
def create_community_fridge(attrs \\ %{}) do
- %CommunityFridge{}
+ %CommunityFridge{location: nil}
|> CommunityFridge.changeset(attrs)
|> Repo.insert()
end
@@ -45,6 +52,7 @@ defmodule BikeBrigade.Locations do
"""
def update_community_fridge(%CommunityFridge{} = community_fridge, attrs) do
community_fridge
+ |> Repo.preload(:location)
|> CommunityFridge.changeset(attrs)
|> Repo.update()
end
diff --git a/lib/bike_brigade/locations/community_fridge.ex b/lib/bike_brigade/locations/community_fridge.ex
index c801e49e..aa536b6a 100644
--- a/lib/bike_brigade/locations/community_fridge.ex
+++ b/lib/bike_brigade/locations/community_fridge.ex
@@ -19,16 +19,30 @@ defmodule BikeBrigade.Locations.CommunityFridge do
field :active, :boolean, default: true
field :pair_preferred, :boolean, default: false
- belongs_to :location, Location
+ belongs_to :location, Location, on_replace: :update
timestamps()
end
def changeset(community_fridge, params) do
community_fridge
- |> cast(params, @fields ++ [:location_id])
+ |> cast(params, @fields)
+ |> maybe_cast_location(params)
|> validate_required([:name])
|> unique_constraint(:location_id)
- |> assoc_constraint(:location)
+ end
+
+ # Only associate a location when an address is present in the params (new location),
+ # or the fridge already has a location that may need updating (edit scenario).
+ defp maybe_cast_location(changeset, params) do
+ location_params = Map.get(params, "location") || Map.get(params, :location, %{})
+ address = Map.get(location_params, "address") || Map.get(location_params, :address)
+ has_existing = not is_nil(changeset.data.location_id)
+
+ if address not in [nil, ""] || has_existing do
+ cast_assoc(changeset, :location)
+ else
+ changeset
+ end
end
end
diff --git a/lib/bike_brigade_web/live/community_fridge_live/form_component.ex b/lib/bike_brigade_web/live/community_fridge_live/form_component.ex
index 3f821db7..4d41ff3a 100644
--- a/lib/bike_brigade_web/live/community_fridge_live/form_component.ex
+++ b/lib/bike_brigade_web/live/community_fridge_live/form_component.ex
@@ -2,6 +2,7 @@ defmodule BikeBrigadeWeb.CommunityFridgeLive.FormComponent do
use BikeBrigadeWeb, :live_component
alias BikeBrigade.{Locations, MediaStorage}
+ alias BikeBrigadeWeb.Components.LiveLocation
@impl true
def mount(socket) do
diff --git a/lib/bike_brigade_web/live/community_fridge_live/form_component.html.heex b/lib/bike_brigade_web/live/community_fridge_live/form_component.html.heex
index 2c21fcfd..801babaa 100644
--- a/lib/bike_brigade_web/live/community_fridge_live/form_component.html.heex
+++ b/lib/bike_brigade_web/live/community_fridge_live/form_component.html.heex
@@ -12,6 +12,13 @@
<.input type="textarea" field={f[:description]} label="Description" />
+ <.live_component
+ module={LiveLocation}
+ id="location-form"
+ field={f[:location]}
+ label="Location"
+ />
+
assign(:page, :community_fridges)
|> assign(:page_title, "Community Fridges")
|> assign(:community_fridge, nil)
- |> assign(:community_fridges, Locations.list_community_fridges())}
+ |> assign(:community_fridges, Locations.list_community_fridges())
+ |> assign(:map_layers, [])
+ |> assign(:map_coords, @default_coords)}
end
@impl Phoenix.LiveView
@@ -23,6 +28,8 @@ defmodule BikeBrigadeWeb.CommunityFridgeLive.Index do
socket
|> assign(:page_title, "Community Fridges")
|> assign(:community_fridge, nil)
+ |> assign(:map_layers, fridge_markers(socket.assigns.community_fridges))
+ |> assign(:map_coords, map_center(socket.assigns.community_fridges))
end
defp apply_action(socket, :new, _params) do
@@ -36,4 +43,33 @@ defmodule BikeBrigadeWeb.CommunityFridgeLive.Index do
|> assign(:page_title, "Edit Fridge")
|> assign(:community_fridge, Locations.get_community_fridge!(id))
end
+
+ defp fridge_markers(community_fridges) do
+ for %{id: id, name: name, location: location} <- community_fridges,
+ not is_nil(location),
+ not is_nil(location.coords) do
+ %{
+ id: "fridge-#{id}",
+ type: :marker,
+ data: %{
+ lat: lat(location),
+ lng: lng(location),
+ icon: "warehouse",
+ color: "#1c64f2",
+ tooltip: name
+ }
+ }
+ end
+ end
+
+ defp map_center(community_fridges) do
+ community_fridges
+ |> Enum.find_value(fn %{location: location} ->
+ location && location.coords
+ end)
+ |> case do
+ nil -> @default_coords
+ coords -> coords
+ end
+ end
end
diff --git a/lib/bike_brigade_web/live/community_fridge_live/index.html.heex b/lib/bike_brigade_web/live/community_fridge_live/index.html.heex
index dd8e8ee9..5986223a 100644
--- a/lib/bike_brigade_web/live/community_fridge_live/index.html.heex
+++ b/lib/bike_brigade_web/live/community_fridge_live/index.html.heex
@@ -4,6 +4,13 @@
Community fridges are neighbourhood food-sharing locations. Dispatchers can manage fridge details here.
+<.map
+ id="community-fridges-map"
+ coords={@map_coords}
+ initial_layers={@map_layers}
+ class="w-full h-64 mb-4"
+/>
+
<.button patch={~p"/community_fridges/new"}>
New Community Fridge
@@ -13,6 +20,9 @@
<:col :let={community_fridge} label="Name">
{community_fridge.name}
+ <:col :let={community_fridge} label="Address">
+ {community_fridge.location}
+
<:col :let={community_fridge} label="Description">
{community_fridge.description}
diff --git a/test/bike_brigade/locations/community_fridge_test.exs b/test/bike_brigade/locations/community_fridge_test.exs
index c8b3dc46..e2e4004c 100644
--- a/test/bike_brigade/locations/community_fridge_test.exs
+++ b/test/bike_brigade/locations/community_fridge_test.exs
@@ -1,69 +1,62 @@
defmodule BikeBrigade.Locations.CommunityFridgeTest do
use BikeBrigade.DataCase
- alias BikeBrigade.Locations.Location
+ alias BikeBrigade.Locations
alias BikeBrigade.Locations.CommunityFridge
-
- setup do
- location_data = fixture(:location)
-
- {:ok, location} =
- %Location{}
- |> Location.changeset(location_data)
- |> Repo.insert()
-
- %{location: location}
- end
+ alias BikeBrigade.Repo.Seeds.Toronto
describe "CommunityFridge changeset" do
- test "changeset with valid attributes", %{location: location} do
+ test "valid with nested location params" do
attrs = %{
name: "Downtown Community Fridge",
description: "A fridge for the community",
- photo: "https://example.com/photo.jpg",
active: true,
pair_preferred: false,
- location_id: location.id
+ location: Toronto.random_location()
}
- changeset = CommunityFridge.changeset(%CommunityFridge{}, attrs)
+ changeset = CommunityFridge.changeset(%CommunityFridge{location: nil}, attrs)
assert changeset.valid?
end
- test "changeset with minimal required attributes", %{location: location} do
- attrs = %{
- name: "Community Fridge",
- location_id: location.id
- }
-
- changeset = CommunityFridge.changeset(%CommunityFridge{}, attrs)
+ test "valid without a location" do
+ changeset =
+ CommunityFridge.changeset(%CommunityFridge{location: nil}, %{name: "Bare Fridge"})
assert changeset.valid?
end
- test "changeset requires name", %{location: location} do
- attrs = %{
- description: "A fridge without a name",
- location_id: location.id
- }
-
- changeset = CommunityFridge.changeset(%CommunityFridge{}, attrs)
+ test "requires name" do
+ changeset =
+ CommunityFridge.changeset(%CommunityFridge{location: nil}, %{
+ location: Toronto.random_location()
+ })
refute changeset.valid?
assert "can't be blank" in errors_on(changeset).name
end
- test "changeset rejects empty name", %{location: location} do
- attrs = %{
- name: "",
- location_id: location.id
- }
-
- changeset = CommunityFridge.changeset(%CommunityFridge{}, attrs)
+ test "rejects empty name" do
+ changeset =
+ CommunityFridge.changeset(%CommunityFridge{location: nil}, %{
+ name: "",
+ location: Toronto.random_location()
+ })
refute changeset.valid?
assert "can't be blank" in errors_on(changeset).name
end
+
+ test "updating location updates the associated record in place" do
+ fridge = fixture(:community_fridge)
+ original_location_id = fridge.location.id
+
+ new_location = Toronto.random_location()
+ {:ok, updated} = Locations.update_community_fridge(fridge, %{location: new_location})
+
+ assert updated.location.id == original_location_id
+ assert updated.location.address == new_location.address
+ end
end
end
diff --git a/test/bike_brigade_web/live/community_fridge_live_test.exs b/test/bike_brigade_web/live/community_fridge_live_test.exs
index b9cc4e38..0cb5f01e 100644
--- a/test/bike_brigade_web/live/community_fridge_live_test.exs
+++ b/test/bike_brigade_web/live/community_fridge_live_test.exs
@@ -15,6 +15,26 @@ defmodule BikeBrigadeWeb.CommunityFridgeLiveTest do
assert html =~ fridge.name
end
+ test "shows address column for fridge with a location", ctx do
+ fridge = fixture(:community_fridge, %{name: "Located Fridge"})
+ {:ok, _view, html} = live(ctx.conn, ~p"/community_fridges")
+
+ assert html =~ fridge.location.address
+ end
+
+ test "renders map element on index page", ctx do
+ {:ok, _view, html} = live(ctx.conn, ~p"/community_fridges")
+
+ assert html =~ "community-fridges-map"
+ end
+
+ test "map includes pin for fridge with coordinates", ctx do
+ fridge = fixture(:community_fridge, %{name: "Mapped Fridge"})
+ {:ok, _view, html} = live(ctx.conn, ~p"/community_fridges")
+
+ assert html =~ "fridge-#{fridge.id}"
+ end
+
test "can create a new community fridge", ctx do
{:ok, view, _html} = live(ctx.conn, ~p"/community_fridges")
diff --git a/test/support/fixtures.ex b/test/support/fixtures.ex
index e392f86e..d96147d3 100644
--- a/test/support/fixtures.ex
+++ b/test/support/fixtures.ex
@@ -193,15 +193,10 @@ defmodule BikeBrigade.Fixtures do
end
def fixture(:community_fridge, attrs) do
- attrs =
- attrs
- |> Map.put_new_lazy(:location_id, fn -> fixture(:persisted_location, %{}).id end)
+ attrs = Map.put_new_lazy(attrs, :location, fn -> Toronto.random_location() end)
{:ok, community_fridge} =
- %{
- name: Faker.Company.name(),
- active: true
- }
+ %{name: Faker.Company.name(), active: true}
|> Map.merge(attrs)
|> Locations.create_community_fridge()