Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions lib/bike_brigade/locations.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,28 @@ 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 """
Gets a single community_fridge.

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
Expand All @@ -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
Expand Down
20 changes: 17 additions & 3 deletions lib/bike_brigade/locations/community_fridge.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@

<.input type="textarea" field={f[:description]} label="Description" />

<.live_component
module={LiveLocation}
id="location-form"
field={f[:location]}
label="Location"
/>

<div>
<label class="block text-sm font-semibold leading-6 text-zinc-800">Photo</label>
<input
Expand Down
38 changes: 37 additions & 1 deletion lib/bike_brigade_web/live/community_fridge_live/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@ defmodule BikeBrigadeWeb.CommunityFridgeLive.Index do
alias BikeBrigade.Locations
alias BikeBrigade.Locations.CommunityFridge

# Toronto city hall as fallback centre
@default_coords %Geo.Point{coordinates: {-79.3832, 43.6532}}

@impl Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok,
socket
|> 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
Expand All @@ -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
Expand All @@ -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
10 changes: 10 additions & 0 deletions lib/bike_brigade_web/live/community_fridge_live/index.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
Community fridges are neighbourhood food-sharing locations. Dispatchers can manage fridge details here.
</:subtitle>
</.header>
<.map
id="community-fridges-map"
coords={@map_coords}
initial_layers={@map_layers}
class="w-full h-64 mb-4"
/>

<div class="flex justify-end">
<.button patch={~p"/community_fridges/new"}>
New Community Fridge
Expand All @@ -13,6 +20,9 @@
<:col :let={community_fridge} label="Name">
{community_fridge.name}
</:col>
<:col :let={community_fridge} label="Address">
{community_fridge.location}
</:col>
<:col :let={community_fridge} label="Description">
{community_fridge.description}
</:col>
Expand Down
67 changes: 30 additions & 37 deletions test/bike_brigade/locations/community_fridge_test.exs
Original file line number Diff line number Diff line change
@@ -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
20 changes: 20 additions & 0 deletions test/bike_brigade_web/live/community_fridge_live_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
9 changes: 2 additions & 7 deletions test/support/fixtures.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down