Skip to content

Commit

Permalink
Merge pull request #18 from curiosum-dev/fix/ignore_docs_other_than_f…
Browse files Browse the repository at this point in the history
…or_function_in_get_function_doc

Replace get_module_docs with get_function_docs
  • Loading branch information
szsoppa authored May 31, 2023
2 parents 2b0898f + a044a3a commit 8651acc
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 15 deletions.
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ _Note: Official documentation for contexted library is [available on hexdocs][he

[hexdoc]: https://hexdocs.pm/contexted

---
---

<br/>

Expand Down Expand Up @@ -62,7 +62,7 @@ Add the following to your `mix.exs` file:
```elixir
defp deps do
[
{:contexted, "~> 0.1.2"}
{:contexted, "~> 0.1.3"}
]
end
```
Expand All @@ -74,11 +74,13 @@ Then run `mix deps.get`.
## Step by step overview

To describe a sample usage of this library, let's assume that your project has three contexts:

- `Account`
- `Subscription`
- `Blog`

Our goal, as the project grows, is to:

1. Keep contexts separate and not create any cross-references. For this to work, we'll raise errors during compilation whenever such a cross-reference happens.
2. Divide each context into smaller parts so that it is easier to maintain. In this case, we'll refer to each of these parts as **Subcontext**. It's not a new term added to the Phoenix framework but rather a term proposed to emphasize that it's a subset of Context. For this to work, we'll use delegates.
3. Not repeat ourselves with common business logic operations. For this to work, we'll be using CRUD functions generator, since these are the most common.
Expand All @@ -89,7 +91,8 @@ Our goal, as the project grows, is to:

It's very easy to monitor cross-references between context modules with the `contexted` library.

First, add `contexted` as one of the compilers in *mix.exs*:
First, add `contexted` as one of the compilers in _mix.exs_:

```elixir
def project do
[
Expand All @@ -100,7 +103,8 @@ def project do
end
```

Next, define a list of contexts available in the app inside *config.exs*:
Next, define a list of contexts available in the app inside _config.exs_:

```elixir
config :contexted, contexts: [
# list of context modules goes here, for instance:
Expand All @@ -124,6 +128,7 @@ Read more about `Contexted.Tracer` and its options in [docs](https://hexdocs.pm/
To divide big Context into smaller Subcontexts, we can use `delegate_all/1` macro from `Contexted.Delegator` module.

Let's assume that the `Account` context has `User`, `UserToken` and `Admin` resources. Here is how we can split the context module:

```elixir
# Users subcontext

Expand Down Expand Up @@ -153,7 +158,7 @@ end

defmodule App.Account do
import Contexted.Delegator

delegate_all App.Account.Users
delegate_all App.Account.UserTokens
delegate_all App.Account.Admins
Expand All @@ -163,11 +168,13 @@ end
From now on, you can treat the `Account` context module as the API for the "outside" world.

Instead of calling:

```elixir
App.Account.Users.find_user(1)
```

You will simply do:

```elixir
App.Account.find_user(1)
```
Expand All @@ -179,6 +186,7 @@ App.Account.find_user(1)
Both docs and specs are attached as metadata of module once it's compiled and saved as `.beam`. In reference to the example of `App.Account` context, it's possible that `App.Account.Users` will not be saved in `.beam` file before the `delegate_all` macro is executed. Therefore, first, all of the modules have to be compiled, and saved to `.beam` and only then we can create `@doc` and `@spec` of each delegated function.

As a workaround, in `Contexted.Tracer.after_compiler/1` all of the contexts `.beam` files are first deleted and then recompiled. This is an opt-in functionality, as it extends compilation time, and may produce warnings. If you want to enable it, set the following config value:

```elixir
config :contexted,
enable_recompilation: true
Expand All @@ -195,6 +203,7 @@ Read more about `Contexted.Delegator` and its options in [docs](https://hexdocs.
In most web apps CRUD operations are very common. Most of these, have the same pattern. Why not autogenerate them?

Here is how you can generate common CRUD operations for `App.Account.Users`:

```elixir
defmodule App.Account.Users do
use Contexted.CRUD,
Expand All @@ -204,6 +213,7 @@ end
```

This will generate the following functions:

```elixir
iex> App.Accounts.Users.__info__(:functions)
[
Expand Down
4 changes: 2 additions & 2 deletions lib/contexted/delegator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ defmodule Contexted.Delegator do
module
end

functions_docs = ModuleAnalyzer.get_module_docs(module)
functions_specs = ModuleAnalyzer.get_module_specs(module)
functions_docs = ModuleAnalyzer.get_functions_docs(module)
functions_specs = ModuleAnalyzer.get_functions_specs(module)

# Get the module's public functions
functions =
Expand Down
18 changes: 11 additions & 7 deletions lib/contexted/module_analyzer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ defmodule Contexted.ModuleAnalyzer do
@doc """
Fetches the `@doc` definitions for all functions within the given module.
"""
@spec get_module_docs(module()) :: [tuple()]
def get_module_docs(module) do
@spec get_functions_docs(module()) :: [tuple()]
def get_functions_docs(module) do
case Code.fetch_docs(module) do
{:docs_v1, _, _, _, _, _, functions_docs} ->
functions_docs
Enum.filter(functions_docs, fn
{{:function, _, _}, _, _, _, _} -> true
{_, _, _, _, _} -> false
end)

_ ->
[]
Expand All @@ -20,8 +23,8 @@ defmodule Contexted.ModuleAnalyzer do
@doc """
Fetches the `@spec` definitions for all functions within the given module.
"""
@spec get_module_specs(module()) :: [tuple()]
def get_module_specs(module) do
@spec get_functions_specs(module()) :: [tuple()]
def get_functions_specs(module) do
case Code.Typespec.fetch_specs(module) do
{:ok, specs} -> specs
_ -> []
Expand Down Expand Up @@ -51,8 +54,9 @@ defmodule Contexted.ModuleAnalyzer do
"""
@spec get_function_doc([tuple()], atom(), non_neg_integer()) :: String.t() | nil
def get_function_doc(functions_docs, name, arity) do
Enum.find(functions_docs, fn {{:function, func_name, func_arity}, _, _, _, _} ->
func_name == name && func_arity == arity
Enum.find(functions_docs, fn
{{:function, func_name, func_arity}, _, _, _, _} ->
func_name == name && func_arity == arity
end)
|> case do
{_, _, _, %{"en" => doc}, _} ->
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule Contexted.MixProject do
app: :contexted,
description:
"Contexted is an Elixir library designed to streamline the management of complex Phoenix contexts in your projects, offering tools for module separation, subcontext creation, and auto-generating CRUD operations for improved code maintainability.",
version: "0.1.2",
version: "0.1.3",
elixir: "~> 1.14",
start_permanent: Mix.env() == :prod,
deps: deps(),
Expand Down

0 comments on commit 8651acc

Please sign in to comment.