-
-
Save seanwash/c7ffb8753e2f7e2da873502328c8c83f to your computer and use it in GitHub Desktop.
An example of pattern matching with guard clauses.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Dk.Utils.Access do | |
@moduledoc """ | |
A collection of functions for working with deeply nested data. | |
""" | |
def deep_update_in(list, [], fun) when is_list(list) do | |
Enum.map(list, &deep_update_in(&1, [], fun)) | |
end | |
def deep_update_in(the_end, [], fun) do | |
fun.(the_end) | |
end | |
def deep_update_in(map, [key | rest], fun) when is_map(map) do | |
Map.update(map, key, nil, &deep_update_in(&1, rest, fun)) | |
end | |
def deep_update_in(list, path, fun) when is_list(list) do | |
Enum.map(list, &deep_update_in(&1, path, fun)) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Dk.Presenter.ProductPresenter do | |
@moduledoc """ | |
This module is responsible for augmenting a Product or a list of Products with presentational | |
fields based off of the given user's account type and other outside factors. | |
## Usage | |
Presenters should be used after a collection of products have been compiled, but before sending | |
that data off to a view for serialization or Eex templates for building pages. | |
""" | |
alias Dk.Repo | |
alias Dk.Accounts.Users | |
alias Dk.Pricing | |
alias Dk.Formatter | |
alias Dk.Utils.Access | |
def augment_in(parent, key, user \\ nil, opts \\ []) | |
@doc """ | |
Augments a nested list of products located at the given path within a %Scrivener.Page{}. | |
struct. | |
## Examples | |
iex> augment_in( | |
%Scrivener.Page{entries: [%Store{products: %Product{}}]}, | |
[:products] | |
) | |
%Scrivener.Page{} | |
""" | |
@spec augment_in(struct, list, struct, keyword) :: struct | |
def augment_in(%Scrivener.Page{entries: entries} = page, path, user, opts) do | |
Map.put( | |
page, | |
:entries, | |
Access.deep_update_in(entries, path, fn product -> augment(product, user, opts) end) | |
) | |
end | |
@doc """ | |
Augments a nested list of products located at the given path within the given list of structs. | |
## Examples | |
iex> augment_in( | |
[%Campaign{issues: [%Issue{issue_products: [%IssueProduct{product: %Product{}}]}]}, ...], | |
[:issues, :issue_products, :product]] | |
) | |
%Campaign{} | |
""" | |
@spec augment_in(list, list, struct, keyword) :: struct | |
def augment_in(parent, path, user, opts) when is_list(parent) do | |
Access.deep_update_in(parent, path, fn product -> augment(product, user, opts) end) | |
end | |
@doc """ | |
Augments a nested list of products located at the given path within the given struct. | |
## Examples | |
iex> augment_in( | |
%Campaign{issues: [%Issue{issue_products: [%IssueProduct{product: %Product{}}]}]}, | |
[:issues, :issue_products, :product]] | |
) | |
%Campaign{} | |
""" | |
@spec augment_in(struct, list, struct, keyword) :: struct | |
def augment_in(parent, path, user, opts) when is_map(parent) and is_list(path) do | |
Access.deep_update_in(parent, path, fn product -> augment(product, user, opts) end) | |
end | |
@doc """ | |
Augments a single product found in the given struct. | |
## Examples | |
iex> augment_in( | |
%IssueProduct{product: %Product{}}, | |
:product | |
) | |
%IssueProduct{} | |
""" | |
@spec augment_in(struct | map, atom, struct, keyword) :: struct | map | |
def augment_in(parent, key, user, opts) when is_map(parent) and is_atom(key) do | |
product = | |
Map.get(parent, key) | |
|> augment(user, opts) | |
Map.put(parent, key, product) | |
end | |
def augment(entries, user \\ nil, opts \\ []) | |
@doc """ | |
Augments each product contained in the given list. | |
## Examples | |
iex> augment([%Product{}, %Product{}], %User{}) | |
[%Product{}, %Product{}] | |
""" | |
@spec augment(list, struct, keyword) :: list | |
def augment(products, user, opts) when is_list(products) do | |
products | |
|> Enum.map(&augment(&1, user, opts)) | |
end | |
@doc """ | |
Augments the entries belonging to the given %Scrivener.Page{} struct. | |
## Examples | |
iex> augment(%Scrivener.Page{entries: [%Product{}, %Product{}]}, %User{}) | |
%Scrivener.Page{} | |
""" | |
@spec augment(struct, struct, keyword) :: struct | |
def augment(%Scrivener.Page{entries: entries} = page, user, opts) do | |
augmented_entries = | |
entries | |
|> Enum.map(&augment(&1, user, opts)) | |
Map.put(page, :entries, augmented_entries) | |
end | |
@doc """ | |
Adds various virtual attributes to the given Product map based on the current user. | |
## Examples | |
iex> augment(%Product{}, %User{}) | |
%Product{} | |
""" | |
@spec augment(struct | map, struct, keyword) :: struct | map | |
def augment(product, user, opts) when is_map(product) do | |
product | |
|> modify_purchase_prices() | |
|> inject_trade_price(user) | |
|> inject_product_favorite(user) | |
|> inject_distance_in_miles(opts) | |
end | |
defp modify_purchase_prices(product) do | |
%{ | |
product | |
| base_price_in_cents: Pricing.base_price_in_cents_for_product(product), | |
sale_price_in_cents: Pricing.sale_price_in_cents_for_product(product) | |
} | |
end | |
defp inject_trade_price(product, user) do | |
with true <- user && Users.user_eligible_for_trade_discount?(user), | |
trade_price = | |
Pricing.user_price_in_cents_for_product( | |
product, | |
user, | |
trade_user_discount_percent: product.store.trade_user_discount_percent | |
), | |
true <- | |
trade_price > 0 && | |
trade_price < Pricing.base_purchase_price_in_cents_for_product(product) do | |
product | |
|> Map.put(:sale_price_in_cents, trade_price) | |
|> Map.put(:trade_user_discount_applied, true) | |
else | |
_ -> | |
Map.put(product, :trade_user_discount_applied, false) | |
end | |
end | |
defp inject_product_favorite(product, nil) do | |
Map.put(product, :favorite, nil) | |
end | |
defp inject_product_favorite(product, user) do | |
favorite = Dk.Engagement.find_favorite(product.id, user.id) | |
Map.put(product, :favorite, favorite) | |
end | |
defp inject_distance_in_miles(product, opts) do | |
location = opts[:location] || nil | |
if is_nil(location) do | |
Map.put(product, :distance_in_miles, nil) | |
else | |
do_inject_distance_in_miles(product, location) | |
end | |
end | |
defp do_inject_distance_in_miles(product, point_b) do | |
point_a = product.addresses |> List.last() |> Map.get(:point) | |
distance = | |
case point_a do | |
nil -> | |
nil | |
_ -> | |
[lng, lat] = String.split(point_b, ",") | |
point_b = %Geo.Point{coordinates: {lng, lat}, srid: 4326} | |
calculate_distance(point_a, point_b) | |
end | |
Map.put(product, :distance_in_miles, distance) | |
end | |
defp calculate_distance(point_a, point_b) do | |
result = | |
Repo.query("SELECT ST_Distance(ST_GeogFromText($1), ST_GeogFromText($2))", [ | |
Geo.WKT.encode(point_a), | |
Geo.WKT.encode(point_b) | |
]) | |
case result do | |
{:error, _reason} -> | |
nil | |
{:ok, result} -> | |
result.rows | |
|> List.first() | |
|> List.first() | |
|> Formatter.Distance.meters_to_formatted_miles() | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment