solo relationships, birth events, formatting

This commit is contained in:
Ryan Pandya 2022-11-23 17:42:49 -05:00
parent 5339fdb2b0
commit fd5bcbfc05
10 changed files with 273 additions and 76 deletions

View File

@ -1,6 +1,7 @@
defmodule Friends.Event do defmodule Friends.Event do
use Ecto.Schema use Ecto.Schema
alias Friends.{Relationship,Event} import Ecto.Query
alias Friends.{Relationship, Event}
alias Places.Place alias Places.Place
@repo Friends.Repo @repo Friends.Repo
@ -16,33 +17,157 @@ defmodule Friends.Event do
belongs_to(:relationship, Relationship) belongs_to(:relationship, Relationship)
end end
def changeset(event, params \\ %{}) do
event
|> Ecto.Changeset.cast(params, [
:name,
:date,
:story,
:defining,
:solo,
:place_id,
:relationship_id
])
|> Ecto.Changeset.validate_required([:name, :date, :solo, :relationship_id],
message: "This field is required."
)
|> validate_date_in_past()
|> validate_unique_event()
end
defp validate_unique_event(changeset) do
changeset
end
defp validate_date_in_past(%{changes: %{date: date}} = changeset) do
today = DateTime.utc_now() |> DateTime.to_date()
case date |> Date.diff(today) do
age when age < 0 ->
changeset |> Ecto.Changeset.add_error(:born, "Please enter a date in the past.")
_ ->
changeset
end
end
defp validate_date_in_past(changeset), do: changeset
def new(params \\ %{}) do
%Event{id: nil}
|> struct(params)
end
def get_by_id(id) do
@repo.one(
from(e in Event,
where: e.id == ^id,
preload: [:relationship, :place]
)
)
end
def get(%{relationship_id: rel, date: date, name: name}) do
@repo.one(
from(e in Event,
where: e.relationship_id == ^rel and e.date == ^date and e.name == ^name,
preload: [:relationship, :place]
)
)
end
def get_or_create(params) do
case get(params) do
nil -> create(params) |> commit()
event -> event
end
end
def create(params \\ %{id: nil}) do
Event.new(params)
|> Event.changeset()
|> Map.put(:action, :insert)
end
def update(params) do
Event.get_by_id(params.id |> String.to_integer())
|> Event.changeset(params)
|> Map.put(:action, :update)
end
def commit(changeset) do
changeset
|> @repo.commit!
|> load_preloads
end
def load_preloads(
%Event{
place: %Ecto.Association.NotLoaded{},
relationship: %Ecto.Association.NotLoaded{}
} = model
) do
model
|> @repo.preload([:place, :relationship])
end
def load_preloads(event), do: event
def create_or_update(params) do
case params.id do
"new" ->
params
|> create()
|> commit()
_number ->
params
|> update()
|> commit()
end
end
def meet(friend1, friend2, opts \\ nil) do def meet(friend1, friend2, opts \\ nil) do
relationship = Relationship.get_or_new(friend1, friend2) relationship = Relationship.get_or_new(friend1, friend2)
opts = opts ++ [if opts[:place] do
{ opts =
:place_id, opts ++
Places.Place.get_or_new(opts[:place]).id [
if opts[:place] do
{
:place_id,
Places.Place.get_or_new(opts[:place]).id
}
end
]
{:ok, event} =
%Event{
story: opts[:story],
date: opts[:date],
place_id: opts[:place_id],
relationship_id: relationship.id
} }
end] |> @repo.insert
{:ok, event} = %Event{
story: opts[:story],
date: opts[:date],
place_id: opts[:place_id],
relationship_id: relationship.id
} |> @repo.insert
event event
end end
def person(event) do
Friends.Friend.get_by_id(event.relationship.friend_id)
end
def people(event) do def people(event) do
event.relationship |> Relationship.members if event.solo, do: event.person, else: event.relationship |> Relationship.members()
end end
def age(event) do def age(event) do
years = Date.diff(Date.utc_today, event.date) years =
|> div(365) Date.diff(Date.utc_today(), event.date)
|> div(365)
if years == 0 do if years == 0 do
months = Date.diff(Date.utc_today, event.date) months = Date.diff(Date.utc_today(), event.date) |> rem(365) |> div(12)
|> rem(365) |> div(12)
{months, :month} {months, :month}
else else
{years, :year} {years, :year}
@ -52,5 +177,4 @@ defmodule Friends.Event do
def print_age(age) do def print_age(age) do
Friends.Helpers.pluralize(age |> elem(0), age |> elem(1)) Friends.Helpers.pluralize(age |> elem(0), age |> elem(1))
end end
end end

View File

@ -1,7 +1,7 @@
defmodule Friends.Friend do defmodule Friends.Friend do
use Ecto.Schema use Ecto.Schema
alias Friends.{Relationship, Friend} alias Friends.{Relationship, Friend, Event}
import Helpers import Helpers
import Ecto.Query import Ecto.Query
@ -46,7 +46,9 @@ defmodule Friends.Friend do
:user_id, :user_id,
:address_id :address_id
]) ])
|> Ecto.Changeset.validate_required([:name, :email, :phone, :born], message: "This field is required.") |> Ecto.Changeset.validate_required([:name, :email, :phone, :born],
message: "This field is required."
)
|> Ecto.Changeset.validate_format(:name, ~r/\w+\ \w+/, message: "Please enter your full name.") |> Ecto.Changeset.validate_format(:name, ~r/\w+\ \w+/, message: "Please enter your full name.")
|> Ecto.Changeset.validate_format( |> Ecto.Changeset.validate_format(
:email, :email,
@ -64,15 +66,20 @@ defmodule Friends.Friend do
end end
defp validate_birthdate(%{changes: %{born: born}} = changeset) do defp validate_birthdate(%{changes: %{born: born}} = changeset) do
today = DateTime.utc_now |> DateTime.to_date today = DateTime.utc_now() |> DateTime.to_date()
case born |> Date.diff(today) |> div(-365) do case born |> Date.diff(today) |> div(-365) do
age when age < 0 -> age when age < 0 ->
changeset |> Ecto.Changeset.add_error(:born, "Please enter a date in the past.") changeset |> Ecto.Changeset.add_error(:born, "Please enter a date in the past.")
age when age > 90 -> age when age > 90 ->
changeset |> Ecto.Changeset.add_error(:born, "Are you sure you're #{age} years old?") changeset |> Ecto.Changeset.add_error(:born, "Are you sure you're #{age} years old?")
_ -> changeset
_ ->
changeset
end end
end end
defp validate_birthdate(changeset), do: changeset defp validate_birthdate(changeset), do: changeset
def all() do def all() do
@ -152,7 +159,8 @@ defmodule Friends.Friend do
"new" -> "new" ->
params params
|> create() |> create()
|> generate_slug |> generate_slug()
|> create_birth_event()
|> commit() |> commit()
_number -> _number ->
@ -162,17 +170,17 @@ defmodule Friends.Friend do
end end
end end
def get_relationships(friend) do def get_relationships(friend, include_self \\ false) do
friend friend
|> relations |> relations(include_self)
|> Enum.map(&relation(friend, &1)) |> Enum.map(&relation(friend, &1))
end end
def get_events(friend) do def get_events(friend) do
friend friend
|> get_relationships |> get_relationships(:all)
|> Enum.map(& &1.events) |> Enum.flat_map(& &1.events)
|> List.flatten() |> Enum.dedup()
end end
def age(friend) do def age(friend) do
@ -250,4 +258,16 @@ defmodule Friends.Friend do
{nil, nil} {nil, nil}
end end
end end
def create_birth_event(%Friend{id: id} = friend) do
solo_relationship = Relationship.get_or_new(friend, friend)
Event.get_or_create(%{
name: "Born",
date: friend.born,
solo: true,
defining: true,
relationship_id: solo_relationship.id
})
end
end end

View File

@ -26,8 +26,17 @@ defmodule Friends.Places.Place do
end end
def get_or_create(place) do def get_or_create(place) do
place case @repo.one(
|> Friends.Places.Place.validate() from(
|> Friends.Repo.insert!() p in Friends.Places.Place,
where: p.name == ^place.name
)
) do
nil ->
place
|> Friends.Places.Place.validate()
|> @repo.insert!()
found -> found
end
end end
end end

View File

@ -1,9 +1,10 @@
defmodule Friends.Relationship do defmodule Friends.Relationship do
use Ecto.Schema use Ecto.Schema
import Ecto.Query import Ecto.Query
alias Friends.{Relationship,Friend} alias Friends.{Relationship, Friend}
@repo Friends.Repo @repo Friends.Repo
@default_type 3
schema "relationships" do schema "relationships" do
field(:friend_id, :id) field(:friend_id, :id)
@ -22,6 +23,7 @@ defmodule Friends.Relationship do
def types do def types do
# Tuple: name of the type, associated color, and what that person "is" to the other # Tuple: name of the type, associated color, and what that person "is" to the other
{ {
{:self, :hidden, :self},
{:acquaintances, :info, nil}, {:acquaintances, :info, nil},
{:family, :primary, :relative}, {:family, :primary, :relative},
{:friends, :secondary, :friend}, {:friends, :secondary, :friend},
@ -68,56 +70,61 @@ defmodule Friends.Relationship do
) )
end end
def all() do def all() do
preloads = [] preloads = []
@repo.all(from(r in Friends.Relationship, preload: ^preloads)) @repo.all(from(r in Friends.Relationship, where: r.type != 0, preload: ^preloads))
end end
def new(friend1, friend2, type \\ 2) do def new(friend1, friend2, type \\ @default_type) do
id1 = friend1.id id1 = friend1.id
id2 = friend2.id id2 = friend2.id
{:ok, relationship} = @repo.insert(
%Relationship{ rel_type = if id1 == id2, do: 0, else: type
relationship =
@repo.insert(%Relationship{
friend_id: id1, friend_id: id1,
relation_id: id2, relation_id: id2,
type: type type: rel_type
} })
)
relationship relationship
end end
def get(friend1, friend2) do def get(friend1, friend2) do
id1 = friend1.id id1 = friend1.id
id2 = friend2.id id2 = friend2.id
rel = @repo.one(
from(r in Relationship, rel =
where: r.friend_id == ^id1 and r.relation_id == ^id2, @repo.one(
preload: [:events] from(r in Relationship,
where: r.friend_id == ^id1 and r.relation_id == ^id2,
preload: [:events]
)
) )
)
if rel == nil do if rel == nil do
@repo.one( @repo.one(
from(r in Relationship, from(r in Relationship,
where: r.friend_id == ^id2 and r.relation_id == ^id1, where: r.friend_id == ^id2 and r.relation_id == ^id1,
preload: [:events] preload: [:events]
) )
) )
else else
rel rel
end end
end end
def get_or_new(a,b) do def get_or_create(a, b) do
case get(a, b) do case get(a, b) do
nil -> new(a,b) nil -> new(a, b)
relationship -> relationship relationship -> relationship
end end
end end
def get_by_slugs([slug1, slug2]) do def get_by_slugs([slug1, slug2]) do
friend1 = slug1 |> Friend.get_by_slug friend1 = slug1 |> Friend.get_by_slug()
friend2 = slug2 |> Friend.get_by_slug friend2 = slug2 |> Friend.get_by_slug()
get(friend1, friend2) get(friend1, friend2)
end end
@ -131,22 +138,33 @@ defmodule Friends.Relationship do
def age(relationship) do def age(relationship) do
relationship.events relationship.events
|> Enum.map(fn(event) -> |> Enum.map(fn event ->
Date.diff(Date.utc_today, event.date) Date.diff(Date.utc_today(), event.date)
end) |> Enum.sort |> List.last |> div(365) end)
|> Enum.sort()
|> List.last()
|> div(365)
end end
def load_events(%Relationship{ def load_events(
events: %Ecto.Association.NotLoaded{}} = model) do %Relationship{
events: %Ecto.Association.NotLoaded{}
} = model
) do
model model
|> @repo.preload([:events]) |> @repo.preload([:events])
end end
def load_events(%Relationship{} = r), do: r def load_events(%Relationship{} = r), do: r
def load_preloads(%Relationship{
events: %Ecto.Association.NotLoaded{}} = model) do def load_preloads(
%Relationship{
events: %Ecto.Association.NotLoaded{}
} = model
) do
model model
|> @repo.preload([:events]) |> @repo.preload([:events])
end end
def load_preloads(%Relationship{} = r), do: r
def load_preloads(%Relationship{} = r), do: r
end end

View File

@ -2,6 +2,7 @@ defmodule FriendsWeb.FriendsLive.Components do
use FriendsWeb, :live_component use FriendsWeb, :live_component
use Phoenix.HTML use Phoenix.HTML
import Helpers import Helpers
import FriendsWeb.LiveHelpers
alias Friends.Friend alias Friends.Friend
alias FriendsWeb.Components.{Autocomplete, Map} alias FriendsWeb.Components.{Autocomplete, Map}
alias Phoenix.LiveView.JS alias Phoenix.LiveView.JS
@ -89,12 +90,13 @@ defmodule FriendsWeb.FriendsLive.Components do
<%= for event <- @friend |> Friends.Friend.get_events do %> <%= for event <- @friend |> Friends.Friend.get_events do %>
<ul> <ul>
<li> <li>
<%= event.name %> <b><%= event.name %></b> |
<span><%= event.date |> format_date %></span>
</li> </li>
</ul> </ul>
<% end %> <% end %>
<%= if @friend |> Friends.Friend.get_events |> Enum.empty? do %> <%= if @friend |> Friends.Friend.get_events |> Enum.empty? do %>
<div class="italic">None yet.</div> <div class="italic">No events on record yet.</div>
<% end %> <% end %>
</div> </div>
""" """
@ -102,7 +104,7 @@ defmodule FriendsWeb.FriendsLive.Components do
def show_page(:relationships, assigns) do def show_page(:relationships, assigns) do
~H""" ~H"""
<div id="relationships" class="flex md:flex-row flex-col gap-8"> <div id="relationships" class="flex md:flex-row flex-col gap-8 p-8">
<%= for relation <- @friend |> relations do %> <%= for relation <- @friend |> relations do %>
<% relationship = relation(@friend, relation) %> <% relationship = relation(@friend, relation) %>
<div id={"relation-#{relation.id}"} class="card card-compact w-96 bg-base-100 shadow-xl"> <div id={"relation-#{relation.id}"} class="card card-compact w-96 bg-base-100 shadow-xl">
@ -119,7 +121,7 @@ defmodule FriendsWeb.FriendsLive.Components do
</div> </div>
<% end %> <% end %>
<%= if @friend |> relations |> Enum.empty? do %> <%= if @friend |> relations |> Enum.empty? do %>
<div class="italic p-4">No relationships on record yet.</div> <div class="italic">No relationships on record yet.</div>
<% end %> <% end %>
</div> </div>
""" """

View File

@ -1,7 +1,5 @@
defmodule FriendsWeb.FriendsLive.Friend do defmodule FriendsWeb.FriendsLive.Friend do
use FriendsWeb, :live_view use FriendsWeb, :live_view
alias FriendsWeb.FriendsLive.Components
alias FriendsWeb.Router.Helpers, as: Routes alias FriendsWeb.Router.Helpers, as: Routes
alias Friends.Friend alias Friends.Friend

View File

@ -22,7 +22,8 @@ defmodule FriendsWeb.FriendsLive.Show do
|> assign(:latlon, latlon |> Poison.encode!()) |> assign(:latlon, latlon |> Poison.encode!())
|> title(friend.name <> " - " <> (live_action |> titlecase)) |> title(friend.name <> " - " <> (live_action |> titlecase))
|> assign(:changeset, %Friend{} |> Friend.changeset()) |> assign(:changeset, %Friend{} |> Friend.changeset())
|> assign(:action, editable)} |> assign(:editable, editable)
|> assign(:action, :moot)}
else else
{:ok, socket |> redirect(to: Routes.friends_show_path(socket, :overview, friend.slug))} {:ok, socket |> redirect(to: Routes.friends_show_path(socket, :overview, friend.slug))}
end end

View File

@ -6,7 +6,7 @@
<%= if @editable do %> <%= if @editable do %>
<div class="form-control flex flex-row mb-4"> <div class="form-control flex flex-row mb-4">
<.link navigate={Routes.friends_edit_path(FriendsWeb.Endpoint, :overview, @friend.slug)} class="btn btn-block md:btn-wide text-white">edit</.link> <.link navigate={Routes.friends_edit_path(FriendsWeb.Endpoint, @live_action, @friend.slug)} class="btn btn-block md:btn-wide text-white"><%=@live_action |> get_edit_text%></.link>
</div> </div>
<% end %> <% end %>
</article> </article>

View File

@ -16,23 +16,44 @@ defmodule FriendsWeb.LiveHelpers do
display_phone(phone) display_phone(phone)
end end
end end
def display_phone(phone) do def display_phone(phone) do
""" """
TODO: Actually implement this TODO: Actually implement this
""" """
has_plus = phone |> String.starts_with?("+") has_plus = phone |> String.starts_with?("+")
case phone |> String.length do
case phone |> String.length() do
10 -> 10 ->
country = "+1 " country = "+1 "
[area, first, sec1, sec2] = phone |> to_charlist |> Enum.chunk_every(3) |> Enum.map(&(&1 |> to_string))
[area, first, sec1, sec2] =
phone |> to_charlist |> Enum.chunk_every(3) |> Enum.map(&(&1 |> to_string))
"#{country}(#{area}) #{first}-#{sec1}#{sec2}" "#{country}(#{area}) #{first}-#{sec1}#{sec2}"
IO.inspect "#{country}(#{area}) #{first}-#{sec1}#{sec2}"
11 when has_plus -> 11 when has_plus ->
phone phone
12 when has_plus ->
phone
end end
end end
def get_edit_text(live_action) do
case live_action do
:timeline -> "add a moment"
:relationships -> "add a relationship"
_ -> "edit"
end
end
def format_date(date) do
date
|> Calendar.strftime("%b %d, %Y")
end
def assign_current_user(socket, user_token) do def assign_current_user(socket, user_token) do
user = user =
case user_token do case user_token do

View File

@ -74,16 +74,20 @@ defmodule Helpers do
birthday(friend) |> Date.diff(Date.utc_today()) birthday(friend) |> Date.diff(Date.utc_today())
end end
def relations(friend) do def relations(friend, include_self \\ false) do
[friend.relationships, friend.reverse_relationships] list =
|> List.flatten() [friend.relationships, friend.reverse_relationships]
|> List.flatten()
|> Enum.dedup()
if include_self, do: list, else: list |> Enum.filter(&(&1.id != friend.id))
end end
def relation(friend, friend2) do def relation(friend, friend2) do
Friends.Relationship.get(friend, friend2) Friends.Relationship.get(friend, friend2)
end end
def events(relationship) do def events(%Friends.Relationship{} = relationship) do
relationship.events relationship.events
end end
end end