Skip to content

Commit

Permalink
Merge pull request #303 from wyeworks/T-127/publication-many-countrie…
Browse files Browse the repository at this point in the history
…s-and-publishers

[T-127] Publication Has Many Countries and Publishers
  • Loading branch information
andres-vidal authored Jun 26, 2024
2 parents 8efe71e + 32d4612 commit 57988a7
Show file tree
Hide file tree
Showing 30 changed files with 2,699 additions and 450 deletions.
12 changes: 12 additions & 0 deletions backend/lib/richard_burton/author.ex
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ defmodule RichardBurton.Author do

def link_fingerprint(changeset = %Ecto.Changeset{valid?: false}), do: changeset

@spec search(binary(), :fuzzy | :prefix) :: any()
def search(term, :prefix) when is_binary(term) do
from(a in Author, where: ilike(a.name, ^"#{term}%"))
|> Repo.all()
Expand All @@ -97,4 +98,15 @@ defmodule RichardBurton.Author do
keywords when is_list(keywords) -> keywords
end
end

def nest(authors) when is_binary(authors) do
authors |> String.split(",") |> Enum.map(&%{"name" => String.trim(&1)})
end

def flatten(authors) when is_list(authors), do: Enum.map_join(authors, ", ", &get_name/1)
def flatten(authors), do: authors

def get_name(%Author{name: name}), do: name
def get_name(%{"name" => name}), do: name
def get_name(%{name: name}), do: name
end
128 changes: 123 additions & 5 deletions backend/lib/richard_burton/country.ex
Original file line number Diff line number Diff line change
@@ -1,16 +1,134 @@
defmodule RichardBurton.Country do
@moduledoc """
Utilities for standardized country manipulation
Schema for countries
"""
use Ecto.Schema
import Ecto.Changeset

def validate_country(changeset) do
validate_change(changeset, :country, fn :country, country ->
if Countries.exists?(:alpha2, country) do
alias RichardBurton.Country
alias RichardBurton.Repo
alias RichardBurton.Publication
alias RichardBurton.Util

@derive {Jason.Encoder, only: [:code]}
schema "countries" do
field(:code, :string)

many_to_many(:publications, Publication, join_through: "publication_countries")

timestamps()
end

@doc false
def changeset(country, attrs \\ %{})

@doc false
def changeset(country, attrs = %Country{}) do
changeset(country, Map.from_struct(attrs))
end

@doc false
def changeset(country, attrs) do
country
|> cast(attrs, [:code])
|> validate_required([:code])
|> validate_code()
|> unique_constraint(:code)
end

def validate_code(changeset) do
validate_change(changeset, :code, fn :code, code ->
if Countries.exists?(:alpha2, code) do
[]
else
[country: {"Invalid ISO-3361-1 alpha2 country code", [validation: :alpha2]}]
[code: {"Invalid ISO-3361-1 alpha2 country code: #{code}", [validation: :alpha2]}]
end
end)
end

def validate_countries(changeset = %Ecto.Changeset{}) do
changeset
|> validate_required([:countries])
|> validate_change(:countries, fn :countries, countries ->
case validate_countries(countries) do
{:ok} -> []
{:error, message} -> [countries: {message, [validation: :alpha2]}]
end
end)
end

def validate_countries(countries) when is_binary(countries) do
invalid =
countries
|> nest()
|> Enum.map(&changeset(%Country{}, &1))
|> Enum.reject(fn cset -> cset.valid? end)

message = "Invalid countries: #{Enum.map_join(invalid, ", ", &get_change(&1, :code))}"

case invalid do
[] -> {:ok}
_ -> {:error, message}
end
end

@spec fingerprint(binary() | maybe_improper_list()) :: binary()
def fingerprint(countries) when is_binary(countries) do
countries
|> nest()
|> Enum.map(fn %{"code" => code} -> %Country{code: code} end)
|> fingerprint()
end

def fingerprint(countries) when is_list(countries) do
countries
|> Enum.map(fn %Country{code: code} -> code end)
|> Enum.sort()
|> Enum.join()
|> Util.create_fingerprint()
end

def maybe_insert!(attrs) do
%__MODULE__{}
|> changeset(attrs)
|> Repo.maybe_insert!([:code])
end

def all do
Repo.all(Country)
end

def link(changeset = %{valid?: true}) do
countries =
changeset
|> get_change(:countries)
|> Enum.map(&apply_changes/1)
|> Enum.map(&maybe_insert!/1)

put_assoc(changeset, :countries, countries)
end

def link(changeset = %{valid?: false}), do: changeset

def link_fingerprint(changeset = %Ecto.Changeset{valid?: true}) do
countries_fingerprint =
changeset
|> get_field(:countries)
|> fingerprint

put_change(changeset, :countries_fingerprint, countries_fingerprint)
end

def link_fingerprint(changeset = %Ecto.Changeset{valid?: false}), do: changeset

def nest(countries) when is_binary(countries) do
countries |> String.split(",") |> Enum.map(&%{"code" => String.trim(&1)})
end

def flatten(countries) when is_list(countries), do: Enum.map_join(countries, ", ", &get_code/1)
def flatten(countries), do: countries

def get_code(%Country{code: code}), do: code
def get_code(%{"code" => code}), do: code
def get_code(%{code: code}), do: code
end
27 changes: 16 additions & 11 deletions backend/lib/richard_burton/flat_publication.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule RichardBurton.FlatPublication do
import Ecto.Query

alias RichardBurton.FlatPublication
alias RichardBurton.Publication
alias RichardBurton.Publisher
alias RichardBurton.Repo
alias RichardBurton.TranslatedBook
alias RichardBurton.Validation
Expand All @@ -16,8 +16,8 @@ defmodule RichardBurton.FlatPublication do
@external_attributes [
:title,
:year,
:country,
:publisher,
:countries,
:publishers,
:authors,
:original_title,
:original_authors
Expand All @@ -27,27 +27,32 @@ defmodule RichardBurton.FlatPublication do
schema "flat_publications" do
field(:title, :string)
field(:year, :integer)
field(:country, :string)
field(:countries, :string)
field(:authors, :string)
field(:publisher, :string)
field(:publishers, :string)
field(:original_title, :string)
field(:original_authors, :string)

field(:countries_fingerprint, :string)
field(:translated_book_fingerprint, :string)
field(:publishers_fingerprint, :string)
end

@doc false
def changeset(flat_publication, attrs) do
%Publication{}
|> Publication.changeset(Publication.Codec.nest(attrs))

flat_publication
|> cast(attrs, @external_attributes)
|> validate_required(@external_attributes)
|> Country.validate_country()
|> Country.validate_countries()
|> Country.link_fingerprint()
|> Publisher.link_fingerprint()
|> TranslatedBook.link_fingerprint()
end

def all() do
Repo.all(FlatPublication)
end

def validate(attrs) do
%FlatPublication{} |> changeset(attrs) |> validate_changeset()
end
Expand All @@ -62,8 +67,8 @@ defmodule RichardBurton.FlatPublication do
[
:title,
:year,
:country,
:publisher,
:countries_fingerprint,
:publishers_fingerprint,
:translated_book_fingerprint
],
&{&1, get_field(changeset, &1)}
Expand Down
54 changes: 42 additions & 12 deletions backend/lib/richard_burton/publication.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,28 @@ defmodule RichardBurton.Publication do

require Ecto.Query

alias RichardBurton.Country
alias RichardBurton.Publication
alias RichardBurton.Publisher
alias RichardBurton.Repo
alias RichardBurton.TranslatedBook
alias RichardBurton.Validation
alias RichardBurton.Country

@external_attributes [:country, :publisher, :title, :year, :translated_book]
@external_attributes [:countries, :publishers, :title, :year, :translated_book]

@derive {Jason.Encoder, only: @external_attributes}
schema "publications" do
field(:country, :string)
field(:publisher, :string)
field(:title, :string)
field(:year, :integer)
field(:translated_book_fingerprint, :string)
field(:countries_fingerprint, :string)
field(:publishers_fingerprint, :string)

belongs_to(:translated_book, TranslatedBook)

many_to_many(:countries, Country, join_through: "publication_countries")
many_to_many(:publishers, Publisher, join_through: "publication_publishers")

timestamps()
end

Expand All @@ -39,15 +43,23 @@ defmodule RichardBurton.Publication do
@doc false
def changeset(publication, attrs) do
publication
|> cast(attrs, [:title, :year, :country, :publisher])
|> cast(attrs, [:title, :year])
|> cast_assoc(:translated_book, required: true)
|> validate_required([:title, :year, :country, :publisher])
|> Country.validate_country()
|> TranslatedBook.link_fingerprint()
|> cast_assoc(:countries, required: true)
|> cast_assoc(:publishers, required: true)
|> validate_length(:countries, min: 1)
|> validate_required([:title, :year])
|> unique_constraint(
[:title, :year, :country, :publisher, :translated_book_fingerprint],
[
:title,
:year,
:publishers_fingerprint,
:countries_fingerprint,
:translated_book_fingerprint
],
name: "publications_composite_key"
)
|> link_fingerprints()
end

def all do
Expand All @@ -57,13 +69,17 @@ defmodule RichardBurton.Publication do
end

def preload(data) do
Repo.preload(data, translated_book: [:authors, original_book: [:authors]])
Repo.preload(data, [
:countries,
:publishers,
translated_book: [:authors, original_book: [:authors]]
])
end

def insert(attrs) do
%Publication{}
|> changeset(attrs)
|> TranslatedBook.link()
|> link_assocs()
|> Repo.insert()
|> case do
{:ok, publication} ->
Expand All @@ -75,7 +91,21 @@ defmodule RichardBurton.Publication do
end

def validate(attrs) do
Validation.validate(changeset(%Publication{}, attrs), &TranslatedBook.link/1)
Validation.validate(changeset(%Publication{}, attrs), &link_assocs/1)
end

defp link_fingerprints(changeset) do
changeset
|> TranslatedBook.link_fingerprint()
|> Country.link_fingerprint()
|> Publisher.link_fingerprint()
end

defp link_assocs(changeset) do
changeset
|> Country.link()
|> TranslatedBook.link()
|> Publisher.link()
end

def insert_all(attrs_list) do
Expand Down
Loading

0 comments on commit 57988a7

Please sign in to comment.