-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[seed] Record site visits in the database (initial slow implementation)
- Loading branch information
Showing
13 changed files
with
210 additions
and
18 deletions.
There are no files selected for viewing
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
defmodule Core.Metrics.Visit do | ||
# @related [test](/test/core/metrics/visit_test.exs) | ||
@moduledoc """ | ||
Counts the number of visits to the site per day. Intentionally doesn't capture any user info. | ||
Inspired by: https://dashbit.co/blog/homemade-analytics-with-ecto-and-elixir | ||
""" | ||
|
||
use Core.Schema | ||
alias Core.Metrics.Visit.Query | ||
|
||
@type t() :: %__MODULE__{} | ||
|
||
@primary_key false | ||
schema "visits" do | ||
field :counter, :integer, default: 0 | ||
field :date, :date, primary_key: true | ||
field :path, :string, primary_key: true | ||
end | ||
|
||
@doc "Returns a new `Visit` for today at `path` and counter `count`" | ||
@spec new(binary(), non_neg_integer()) :: t() | ||
def new(path, count), | ||
do: %__MODULE__{date: Date.utc_today(), path: path, counter: count} | ||
|
||
@spec opts_for_upsert(non_neg_integer()) :: keyword() | ||
def opts_for_upsert(count), | ||
do: [on_conflict: Query.upsert(count), conflict_target: [:date, :path]] | ||
|
||
defmodule Query do | ||
import Ecto.Query | ||
alias Core.Metrics.Visit | ||
|
||
def display_order(query \\ base()), | ||
do: query |> exclude(:order_by) |> order_by([visits: visits], asc: [visits.date, visits.path]) | ||
|
||
def sum(query \\ base()), | ||
do: query |> select([visits: visits], sum(visits.counter)) | ||
|
||
def upsert(count), | ||
do: from(visits in Visit, update: [inc: [counter: ^count]]) | ||
|
||
def with_path(query \\ base(), path), | ||
do: query |> where([visits: visits], visits.path == ^path) | ||
|
||
defp base, | ||
do: from(_ in Visit, as: :visits) |> display_order() | ||
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
defmodule Core.Schema do | ||
# @related [test](/test/core/schema_test.exs) | ||
|
||
@type list_or_t_or_nil(type) :: [type] | type | nil | ||
|
||
defmacro __using__(_) do | ||
quote do | ||
use Ecto.Schema | ||
@primary_key {:id, :binary_id, autogenerate: true} | ||
@foreign_key_type :binary_id | ||
@timestamp_opts [type: :utc_datetime_usec] | ||
end | ||
end | ||
|
||
@doc """ | ||
When given a struct, returns the value of its `:id` key; otherwise returns the given value with the assumption | ||
that it is an ID. | ||
""" | ||
@spec id(map() | struct() | binary()) :: binary() | ||
def id(%_{id: id}), do: id | ||
def id(%{id: id}), do: id | ||
def id(%{"id" => id}), do: id | ||
def id(id) when is_binary(id), do: id | ||
def id(other), do: raise("Expected a map or struct with `id` key or a binary, got: #{inspect(other)}") | ||
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
11 changes: 11 additions & 0 deletions
11
priv/repo/migrations/20230623150834_create_visits_table.exs
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
defmodule Core.Repo.Migrations.CreateVisitsTable do | ||
use Ecto.Migration | ||
|
||
def change do | ||
create table(:visits, primary_key: false) do | ||
add :counter, :integer, default: 0 | ||
add :date, :date, primary_key: true | ||
add :path, :string, primary_key: true | ||
end | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
defmodule Core.Metrics.VisitTest do | ||
# @related [subject](lib/core/metrics/visit.ex) | ||
use Test.DataCase, async: true | ||
|
||
test "schema" do | ||
assert_schema Core.Metrics.Visit, "visits", | ||
counter: :integer, | ||
date: :date, | ||
path: :string | ||
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
defmodule Core.SchemaTest do | ||
# @related [subject](/lib/core/schema.ex) | ||
|
||
defmodule SampleStruct do | ||
defstruct ~w[id]a | ||
end | ||
|
||
use Test.SimpleCase, async: true | ||
|
||
describe "id" do | ||
test "returns the id value when given a struct with an :id key" do | ||
assert Core.Schema.id(%SampleStruct{id: "1234"}) == "1234" | ||
end | ||
|
||
test "returns the id value when given a map with an :id key" do | ||
assert Core.Schema.id(%{id: "1234"}) == "1234" | ||
assert Core.Schema.id(%{"id" => "1234"}) == "1234" | ||
end | ||
|
||
test "returns the argument when given a binary" do | ||
assert Core.Schema.id("1234") == "1234" | ||
end | ||
|
||
test "raises when given something else" do | ||
assert_raise RuntimeError, "Expected a map or struct with `id` key or a binary, got: [:a, :b]", fn -> | ||
Core.Schema.id([:a, :b]) | ||
end | ||
end | ||
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
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
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