diff --git a/README.md b/README.md index 2806470..7cf6d28 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ _Note: Official documentation for contexted library is [available on hexdocs][he [hexdoc]: https://hexdocs.pm/contexted ---- +---
@@ -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 ``` @@ -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. @@ -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 [ @@ -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: @@ -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 @@ -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 @@ -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) ``` @@ -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 @@ -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, @@ -204,6 +213,7 @@ end ``` This will generate the following functions: + ```elixir iex> App.Accounts.Users.__info__(:functions) [ diff --git a/lib/contexted/delegator.ex b/lib/contexted/delegator.ex index 03f1502..54a3a2c 100644 --- a/lib/contexted/delegator.ex +++ b/lib/contexted/delegator.ex @@ -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 = diff --git a/lib/contexted/module_analyzer.ex b/lib/contexted/module_analyzer.ex index e812402..c17a0ca 100644 --- a/lib/contexted/module_analyzer.ex +++ b/lib/contexted/module_analyzer.ex @@ -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) _ -> [] @@ -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 _ -> [] @@ -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}, _} -> diff --git a/mix.exs b/mix.exs index adb0bf0..241f09a 100644 --- a/mix.exs +++ b/mix.exs @@ -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(),