Skip to content

Commit

Permalink
Merge pull request #16 from renatomassaro/add-after_read-callbacks
Browse files Browse the repository at this point in the history
Add support for `after_read` callbacks
  • Loading branch information
renatomassaro authored Dec 17, 2024
2 parents e734106 + 52607d5 commit 9997f52
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 5 deletions.
27 changes: 25 additions & 2 deletions lib/feeb/db/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,20 @@ defmodule Feeb.DB.Schema do
|> Map.new()
|> Map.keys()

after_read_fields =
Enum.reduce(normalized_schema, [], fn {field, {_, opts, _}}, acc ->
if after_read = opts[:after_read] do
[{field, after_read} | acc]
else
acc
end
end)

@schema normalized_schema
@modded_fields modded_fields
@sorted_cols sorted_cols
@virtual_cols virtual_cols
@after_read_fields after_read_fields

if is_nil(Module.get_attribute(__MODULE__, :derived_fields)) do
@derived_fields []
Expand All @@ -90,6 +100,7 @@ defmodule Feeb.DB.Schema do
def __schema__, do: @schema
def __cols__, do: @sorted_cols
def __virtual_cols__, do: @virtual_cols
def __after_read_fields__, do: @after_read_fields
def __table__, do: @table
def __context__, do: @context
def __modded_fields__, do: @modded_fields
Expand Down Expand Up @@ -260,6 +271,7 @@ defmodule Feeb.DB.Schema do
schema = model.__schema__()
table_fields = model.__cols__()
virtual_fields = model.__virtual_cols__()
after_read_fields = model.__after_read_fields__()
fields_to_populate = if fields == [:*], do: table_fields, else: fields

# TODO: Test this a lot...
Expand Down Expand Up @@ -290,16 +302,17 @@ defmodule Feeb.DB.Schema do
values =
fields_to_populate
|> Enum.zip(row)
|> Enum.map(fn {field, v} ->
|> Enum.map(fn {field, raw_value} ->
{type_module, opts, _mod} = Map.fetch!(schema, field)
{field, type_module.load!(v, opts, {model, field})}
{field, type_module.load!(raw_value, opts, {model, field})}
end)

model
|> struct(values)
|> Map.put(:__meta__, %{origin: :db})
|> add_missing_values(table_fields, fields_to_populate)
|> add_virtual_fields(virtual_fields, schema)
|> trigger_after_read_callbacks(after_read_fields)
end

def cast_value!(schema_mod, schema, field, raw_value) do
Expand Down Expand Up @@ -376,4 +389,14 @@ defmodule Feeb.DB.Schema do
Map.put(acc, field_name, value)
end)
end

defp trigger_after_read_callbacks(struct, []), do: struct

defp trigger_after_read_callbacks(struct, after_read_fields) do
Enum.reduce(after_read_fields, struct, fn {field, callback}, acc ->
old_value = Map.get(struct, field)
new_value = apply(struct.__struct__, callback, [old_value, struct])
Map.put(acc, field, new_value)
end)
end
end
3 changes: 2 additions & 1 deletion priv/test/migrations/test/241020150201_friends.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
CREATE TABLE friends (
id INTEGER PRIMARY KEY,
name TEXT
name TEXT,
sibling_count INTEGER
) STRICT, WITHOUT ROWID;
16 changes: 15 additions & 1 deletion test/db/schema_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ defmodule DB.SchemaTest do

describe "generated: __cols__/0" do
test "includes all non-virtual fields in order" do
assert [:id, :name] == Friend.__cols__()
assert [:id, :name, :sibling_count] == Friend.__cols__()

assert [
:boolean,
Expand Down Expand Up @@ -123,4 +123,18 @@ defmodule DB.SchemaTest do
assert monica.repo_config == expected_repo_config
end
end

describe "after_read" do
test "columns with after_read are post-processed", %{shard_id: shard_id} do
DB.begin(@context, shard_id, :read)

joey = DB.one({:friends, :get_by_name}, "Joey")
rachel = DB.one({:friends, :get_by_name}, "Rachel")
pheebs = DB.one({:friends, :get_by_name}, "Phoebe")

assert joey.sibling_count == 7
assert rachel.sibling_count == 2
assert pheebs.sibling_count == 1
end
end
end
2 changes: 1 addition & 1 deletion test/db/sqlite_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ defmodule Feeb.DB.SQLiteTest do
test "returns an entry if found", %{c: c} do
{:ok, stmt} = SQLite.prepare(c, "SELECT * FROM friends WHERE id = ?")
:ok = SQLite.bind(c, stmt, [1])
assert {:ok, [1, "Phoebe"]} == SQLite.one(c, stmt)
assert {:ok, [1, "Phoebe", nil]} == SQLite.one(c, stmt)
end

test "returns nil if not found", %{c: c} do
Expand Down
14 changes: 14 additions & 0 deletions test/support/db/schemas/friend.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defmodule Sample.Friend do
{:id, :integer},
{:name, :string},
{:divorce_count, {:integer, virtual: :get_divorce_count}},
{:sibling_count, {:integer, nullable: true, after_read: :get_sibling_count}},
{:repo_config, {:map, virtual: :get_repo_config}}
]

Expand Down Expand Up @@ -38,4 +39,17 @@ defmodule Sample.Friend do
0
end
end

def get_sibling_count(_, %{name: name}) do
case name do
"Joey" ->
7

"Rachel" ->
2

_ ->
1
end
end
end

0 comments on commit 9997f52

Please sign in to comment.