diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b511ab..0bf3aae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +> NOTE: Make sure to see the [Upgrading Versions](guides/howtos/Upgrading Versions.md) guide if you're having an issue after upgrading. + ## [0.9.0] - 2023-08-07 ### Added - Warnings when there are extra triggers / function in the database -- Supprot for auto-deleting triggers and functions when the `ECTO_WATCH_CLEANUP` environment variable is set to `cleanup` +- Support for auto-deleting triggers and functions when the `ECTO_WATCH_CLEANUP` environment variable is set to `cleanup` ## [0.8.1] - 2023-08-07 diff --git a/README.md b/README.md index 1a3b1f6..4a7a734 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ (Thanks to [Erlang Solutions](https://www.erlang-solutions.com) for sponsoring this project) +[HexDocs documentation](https://hexdocs.pm/ecto_watch) + EctoWatch allows you to easily setup notifications of database changes *directly* from PostgreSQL. Often in Elixir applications a `Phoenix.PubSub.broadcast` is inserted into the application code to notify the rest of the application about inserts, updates, or deletions (e.g. `Accounts.insert_user`/`Accounts.update_user`/`Accounts.delete_user`). This has a few potential problems: @@ -96,164 +98,7 @@ Once subscribed, messages can be handled like so (LiveView example are given her end ``` -## Tracking specific columns and using labels - -You can also setup the database to trigger only on specific column changes on `:updated` watchers. When doing this a `label` required: - -```elixir - # setup - {EctoWatch, - repo: MyApp.Repo, - pub_sub: MyApp.PubSub, - watchers: [ - # ... - {User, :updated, trigger_columns: [:email, :phone], label: :user_contact_info_updated}, - # ... - ]} - - # subscribing - EctoWatch.subscribe(:user_contact_info_updated) - # or... - EctoWatch.subscribe(:user_contact_info_updated, package.id) - - # handling messages - def handle_info({:user_contact_info_updated, %{id: id}}, socket) do -``` - -A label is required for two reasons: - - * When handling the message it makes it clear that the message isn't for general schema updates but is for specific columns - * The same schema can be watched for different sets of columns, so the label is used to differentiate between them. - -You can also use labels in general without tracking specific columns: - -```elixir - # setup - {EctoWatch, - repo: MyApp.Repo, - pub_sub: MyApp.PubSub, - watchers: [ - # ... - {User, :updated, label: :user_update}, - # ... - ]} - - # subscribing - EctoWatch.subscribe(:user_update) - # or... - EctoWatch.subscribe(:user_update, package.id) - - # handling messages - def handle_info({:user_update, %{id: id}}, socket) do -``` - -## Getting additional values - -If you would like to get more than just the `id` from the record, you can use the `extra_columns` option. - -> [!IMPORTANT] -> The `extra_columns` option should be used with care because: -> -> * The `pg_notify` function has a limit of 8000 characters and wasn't created to send full-records on updates. -> * If many updates are done in quick succession to the same record, subscribers will need to process all of the old results before getting to the newest one. -> -> Thus using `extra_columns` with columns that change often may not be what you want. -> -> One use-case where using `extra_columns` may be particularly useful is if you want to receive updates about the deletion of a record and you need to know one of it's foreign keys. E.g. in a blog, if a `Comment` is deleted you might want to get the `post_id` to refresh any caches related to comments. - -```elixir - # setup - {EctoWatch, - repo: MyApp.Repo, - pub_sub: MyApp.PubSub, - watchers: [ - # ... - {Comment, :deleted, extra_columns: [:post_id]}, - # ... - ]} - - # subscribing - EctoWatch.subscribe({Comment, :deleted}) - - # handling messages - def handle_info({{Comment, :deleted}, %{id: id, post_id: post_id}}, socket) do - Posts.refresh_cache(post_id) -``` - -## Watching without a schema - -Since ecto supports working with tables withoun needed a schema, you may also want to create EctoWatch watchers without needing to create a schema like so: - -```elixir - # setup - {EctoWatch, - repo: MyApp.Repo, - pub_sub: MyApp.PubSub, - watchers: [ - { - %{ - table_name: "comments", - primary_key: :ID, - columns: [:title, :body, :author_id, :post_id], - association_columns: [:author_id, :post_id] - }, :updated, extra_columns: [:post_id] - } - ]} -``` - -Everything works the same as with a schema, though make sure to specify your association columns if you want to subscribe to an association column. - -Supported keys for configuring a table without a schema: - - * `schema_prefix` (optional, defaults to `public`) - * `table_name` (required) - * `primary_key` (optional, defaults to `id`) - * `columns` (optional, defaults to `[]`) - * `association_columns` (optional, defaults to `[]`) - -## Notes - -### Dealing with leftover PostgreSQL triggers - -Because of the nature of delegating to a trigger in PostgreSQL, you can end up with leftover triggers and functions in the database (e.g. if you remove a watcher or change a watcher's label). If you have `EctoWatch` in your application tree (even with an empty list of watchers) it will output (error-level) logs to warn you about any extra triggers and functions. If you would like to clean these up you can start your application with the `ECTO_WATCH_CLEANUP` environment variable set to `cleanup` and `EctoWatch` will delete any triggers and functions which wouldn't be created by the current watcher configuration. - -### Why only send the id and not the full record? - -The main reason: The `pg_notify` function has a limit of 8000 characters and wasn't created to send full-records on updates. - -Also if many updates are done in quick succession to the same record, subscribers will need to process all of the old results before getting to the newest one. For example if a LiveView is a subscriber it may get 10 updates about a record to the browser. If the LiveView has to make a query then it will be more likely to get the latest data. Since LiveView doesn't send updates when nothing has changed in the view for the user, this will mean less traffic to the browsers. - -### Why not send the values inside of a schema struct? - -If an application were to take the extra data from an event and pass it to some other part of the app, it may seem like the missing fields were actually missing from the database. Since the data sent due to `extra_columns` isn't a complete load of the record, it doesn't make sense to send the whole struct. - -### Scaling of queries - -If you have many processes which are subscribed to updates and each process makes a DB query on receiving the message this could lead to many queries. You may solve this by creating a GenServer which listens for messages and then makes a single query to the database (e.g. every X milliseconds) to get all the records that need to be updated, passing them on via another `PubSub` message. - -This may be added later as a feature of `EctoWatch`. - -### Sometimes you may not want updates whenever there's an update from the database - -If you have a task or a migration that needs to update the database **without** triggering updates in the rest of the application there are a few solutions (see [this StackOverflow question](https://stackoverflow.com/questions/37730870/how-to-disable-postgresql-triggers-in-one-transaction-only). One solutions is to set `session_replication_role` to `replica` temporarily in a transaction: - -``` -BEGIN -SET session_replication_role = replica; --- do changes here -- -SET session_replication_role = DEFAULT; -COMMIT -``` - -Disabling the triggers can lock the table in a transaction and so should be used with caution. Disabling the triggers outside of a transaction may cause updates to be missed. - -## Potential TODOs - - * Support features of `CREATE TRIGGER`: - * allow specifying a condition for when the trigger should fire - * Creating a batch-processing GenServer to reduce queries to the database. - * Make watchers more generic (?). Don't need dependency on PubSub, but could make it an adapter or something - * Allow for local broadcasting of Phoenix.PubSub messages +There are a lot of features to check out! Check out the [HexDocs documentation](https://hexdocs.pm/ecto_watch) for all of the details! ## Installation diff --git a/guides/howtos/Upgrading Versions.md b/guides/howtos/Upgrading Versions.md new file mode 100644 index 0000000..d86e05f --- /dev/null +++ b/guides/howtos/Upgrading Versions.md @@ -0,0 +1,72 @@ +As `EctoWatch` is pre-1.0.0, breaking changes may occur in minor versions. This guide is here to help you understand version releases with breaking changes and how to change your code to work with the new version. + +## Upgrading to 0.6.0 + +In this version the broadcast messages from `EctoWatch` changed from a 4-element tuple to a 3-element tuple. Before the primary key value and the `extra_columns` data were two separate elements in the tuple: + +```elixir +def handle_info({:inserted, Comment, id, %{post_id: post_id}}, socket) do + # ... +``` + +With version 0.6.0, they are combined into a single map: + +```elixir +def handle_info({:inserted, Comment, %{id: id, post_id: post_id}}, socket) do + # ... +``` + +## Upgrading to 0.8.0 + +In this version became is no longer required to specify the update type (`:inserted`/`:updated`/`:deleted`) for watchers with labels. Before you would have: + +```elixir +# watcher config: +watchers: [ + {Comment, :updated, trigger_columns: [:title, :body], label: :title_or_body_updated}, + +# subscribe: +EctoWatch.subscribe(:title_or_body_updated, :updated) +# or +EctoWatch.subscribe(:title_or_body_updated, :updated, comment_id) +# or +EctoWatch.subscribe(:title_or_body_updated, :updated, {:post_id, post_id}) + +# handler: +def handle_info({:inserted, Comment, %{id: id}}, socket) do + # ... +``` + +With versios 0.8.0 the update type is implied by the label, so you can subscribe simply by doing: + +```elixir +# subscribe: +EctoWatch.subscribe(:title_or_body_updated) +# or +EctoWatch.subscribe(:title_or_body_updated, comment_id) +# or +EctoWatch.subscribe(:title_or_body_updated, {:post_id, post_id}) +``` + +Also, to keep the subscribe function consistent, the normal case of subscribing and handling to a watcher that doesn't have a label requires a tuple of the ecto schema + update type: + +```elixir +# watcher config: +watchers: [ + {Comment, :updated}, + +EctoWatch.subscribe({Comment, :updated}) + +# handler (NOTE the flipped order of schema and update type): +def handle_info({{Comment, :inserted}, %{id: id}}, socket) do + # ... +``` + +You can think of the first argument of `subscribe` or the first element of the tuple as an lookup identifier for the watcher which is either `{ecto_schema(), update_type()}` or a label atom. So handling a label would just be: + + +```elixir +def handle_info({:title_or_body_updated, %{id: id}}, socket) do + # ... +``` + diff --git a/guides/introduction/Getting Started.md b/guides/introduction/Getting Started.md new file mode 100644 index 0000000..5d86a5e --- /dev/null +++ b/guides/introduction/Getting Started.md @@ -0,0 +1,74 @@ +To use EctoWatch, you need to add it to your supervision tree and specify watchers for Ecto schemas and update types. It would look something like this in your `application.ex` file (after `MyApp.Repo` and `MyApp.PubSub`): + +```elixir + alias MyApp.Accounts.User + alias MyApp.Accounts.Package + + {EctoWatch, + repo: MyApp.Repo, + pub_sub: MyApp.PubSub, + watchers: [ + {User, :inserted}, + {User, :updated}, + {User, :deleted}, + {Package, :inserted}, + {Package, :updated} + ]} +``` + +This will setup: + + * triggers in PostgreSQL during application startup + * an Elixir process for each watcher which listens for notifications and broadcasts them via `Phoenix.PubSub` + +Then any process (e.g. a GenServer, a LiveView, a Phoenix channel, etc...) can subscribe to messages like so: + +```elixir + EctoWatch.subscribe({User, :inserted}) + EctoWatch.subscribe({User, :updated}) + EctoWatch.subscribe({User, :deleted}) + + EctoWatch.subscribe({Package, :inserted}) + EctoWatch.subscribe({Package, :updated}) +``` + +(note that if you are subscribing in a LiveView `mount` callback you should subscribe inside of a `if connected?(socket) do` to avoid subscribing twice). + +You can also subscribe to individual records: + +```elixir + EctoWatch.subscribe({User, :updated}, user.id) + EctoWatch.subscribe({User, :deleted}, user.id) +``` + +... OR you can subscribe to records by an association column (but the given column must be in the `extra_columns` list for the watcher! See below for more info on the `extra_columns` option): + +```elixir + EctoWatch.subscribe({User, :updated}, {:role_id, role.id}) + EctoWatch.subscribe({User, :deleted}, {:role_id, role.id}) +``` + +Once subscribed, messages can be handled like so (LiveView example are given here but `handle_info` callbacks can be used elsewhere as well): + +```elixir + def handle_info({{User, :inserted}, %{id: id}}, socket) do + user = Accounts.get_user(id) + socket = stream_insert(socket, :users, user) + + {:noreply, socket} + end + + def handle_info({{User, :updated}, %{id: id}}, socket) do + user = Accounts.get_user(id) + socket = stream_insert(socket, :users, user) + + {:noreply, socket} + end + + def handle_info({{User, :deleted}, %{id: id}}, socket) do + socket = stream_delete_by_dom_id(socket, :songs, "users-#{id}") + + {:noreply, socket} + end +``` + diff --git a/guides/introduction/Getting additional values.md b/guides/introduction/Getting additional values.md new file mode 100644 index 0000000..6da7c40 --- /dev/null +++ b/guides/introduction/Getting additional values.md @@ -0,0 +1,31 @@ + +If you would like to get more than just the `id` from the record, you can use the `extra_columns` option. + +> The `extra_columns` option should be used with care because: +> +> * The `pg_notify` function has a limit of 8000 characters and wasn't created to send full-records on updates. +> * If many updates are done in quick succession to the same record, subscribers will need to process all of the old results before getting to the newest one. +> +> Thus using `extra_columns` with columns that change often may not be what you want. +> +> One use-case where using `extra_columns` may be particularly useful is if you want to receive updates about the deletion of a record and you need to know one of it's foreign keys. E.g. in a blog, if a `Comment` is deleted you might want to get the `post_id` to refresh any caches related to comments. + +```elixir + # setup + {EctoWatch, + repo: MyApp.Repo, + pub_sub: MyApp.PubSub, + watchers: [ + # ... + {Comment, :deleted, extra_columns: [:post_id]}, + # ... + ]} + + # subscribing + EctoWatch.subscribe({Comment, :deleted}) + + # handling messages + def handle_info({{Comment, :deleted}, %{id: id, post_id: post_id}}, socket) do + Posts.refresh_cache(post_id) +``` + diff --git a/guides/introduction/Notes.md b/guides/introduction/Notes.md new file mode 100644 index 0000000..7b80612 --- /dev/null +++ b/guides/introduction/Notes.md @@ -0,0 +1,34 @@ +### Dealing with leftover PostgreSQL triggers + +Because of the nature of delegating to a trigger in PostgreSQL, you can end up with leftover triggers and functions in the database (e.g. if you remove a watcher or change a watcher's label). If you have `EctoWatch` in your application tree (even with an empty list of watchers) it will output (error-level) logs to warn you about any extra triggers and functions. If you would like to clean these up you can start your application with the `ECTO_WATCH_CLEANUP` environment variable set to `cleanup` and `EctoWatch` will delete any triggers and functions which wouldn't be created by the current watcher configuration. + +### Why only send the id and not the full record? + +The main reason: The `pg_notify` function has a limit of 8000 characters and wasn't created to send full-records on updates. + +Also if many updates are done in quick succession to the same record, subscribers will need to process all of the old results before getting to the newest one. For example if a LiveView is a subscriber it may get 10 updates about a record to the browser. If the LiveView has to make a query then it will be more likely to get the latest data. Since LiveView doesn't send updates when nothing has changed in the view for the user, this will mean less traffic to the browsers. + +### Why not send the values inside of a schema struct? + +If an application were to take the extra data from an event and pass it to some other part of the app, it may seem like the missing fields were actually missing from the database. Since the data sent due to `extra_columns` isn't a complete load of the record, it doesn't make sense to send the whole struct. + +### Scaling of queries + +If you have many processes which are subscribed to updates and each process makes a DB query on receiving the message this could lead to many queries. You may solve this by creating a GenServer which listens for messages and then makes a single query to the database (e.g. every X milliseconds) to get all the records that need to be updated, passing them on via another `PubSub` message. + +This may be added later as a feature of `EctoWatch`. + +### Sometimes you may not want updates whenever there's an update from the database + +If you have a task or a migration that needs to update the database **without** triggering updates in the rest of the application there are a few solutions (see [this StackOverflow question](https://stackoverflow.com/questions/37730870/how-to-disable-postgresql-triggers-in-one-transaction-only). One solutions is to set `session_replication_role` to `replica` temporarily in a transaction: + +``` +BEGIN +SET session_replication_role = replica; +-- do changes here -- +SET session_replication_role = DEFAULT; +COMMIT +``` + +Disabling the triggers can lock the table in a transaction and so should be used with caution. Disabling the triggers outside of a transaction may cause updates to be missed. + diff --git a/guides/introduction/Tracking specific columns and using labels.md b/guides/introduction/Tracking specific columns and using labels.md new file mode 100644 index 0000000..e99ad52 --- /dev/null +++ b/guides/introduction/Tracking specific columns and using labels.md @@ -0,0 +1,49 @@ +You can also setup the database to trigger only on specific column changes on `:updated` watchers. When doing this a `label` required: + +```elixir + # setup + {EctoWatch, + repo: MyApp.Repo, + pub_sub: MyApp.PubSub, + watchers: [ + # ... + {User, :updated, trigger_columns: [:email, :phone], label: :user_contact_info_updated}, + # ... + ]} + + # subscribing + EctoWatch.subscribe(:user_contact_info_updated) + # or... + EctoWatch.subscribe(:user_contact_info_updated, package.id) + + # handling messages + def handle_info({:user_contact_info_updated, %{id: id}}, socket) do +``` + +A label is required for two reasons: + + * When handling the message it makes it clear that the message isn't for general schema updates but is for specific columns + * The same schema can be watched for different sets of columns, so the label is used to differentiate between them. + +You can also use labels in general without tracking specific columns: + +```elixir + # setup + {EctoWatch, + repo: MyApp.Repo, + pub_sub: MyApp.PubSub, + watchers: [ + # ... + {User, :updated, label: :user_update}, + # ... + ]} + + # subscribing + EctoWatch.subscribe(:user_update) + # or... + EctoWatch.subscribe(:user_update, package.id) + + # handling messages + def handle_info({:user_update, %{id: id}}, socket) do +``` + diff --git a/guides/introduction/Watching without a schema.md b/guides/introduction/Watching without a schema.md new file mode 100644 index 0000000..0b92c8e --- /dev/null +++ b/guides/introduction/Watching without a schema.md @@ -0,0 +1,29 @@ +Since ecto supports working with tables withoun needed a schema, you may also want to create EctoWatch watchers without needing to create a schema like so: + +```elixir + # setup + {EctoWatch, + repo: MyApp.Repo, + pub_sub: MyApp.PubSub, + watchers: [ + { + %{ + table_name: "comments", + primary_key: :ID, + columns: [:title, :body, :author_id, :post_id], + association_columns: [:author_id, :post_id] + }, :updated, extra_columns: [:post_id] + } + ]} +``` + +Everything works the same as with a schema, though make sure to specify your association columns if you want to subscribe to an association column. + +Supported keys for configuring a table without a schema: + + * `schema_prefix` (optional, defaults to `public`) + * `table_name` (required) + * `primary_key` (optional, defaults to `id`) + * `columns` (optional, defaults to `[]`) + * `association_columns` (optional, defaults to `[]`) + diff --git a/guides/other/Potental TODOs.md b/guides/other/Potental TODOs.md new file mode 100644 index 0000000..d707f9c --- /dev/null +++ b/guides/other/Potental TODOs.md @@ -0,0 +1,6 @@ + * Support features of `CREATE TRIGGER`: + * allow specifying a condition for when the trigger should fire + * Creating a batch-processing GenServer to reduce queries to the database. + * Make watchers more generic (?). Don't need dependency on PubSub, but could make it an adapter or something + * Allow for local broadcasting of Phoenix.PubSub messages + diff --git a/lib/ecto_watch.ex b/lib/ecto_watch.ex index 16a95f7..fc90816 100644 --- a/lib/ecto_watch.ex +++ b/lib/ecto_watch.ex @@ -1,5 +1,7 @@ defmodule EctoWatch do - @moduledoc false + @moduledoc """ + A library to allow you to easily get notifications about database changes directly from PostgreSQL. + """ alias EctoWatch.Helpers alias EctoWatch.WatcherServer @@ -7,6 +9,8 @@ defmodule EctoWatch do use Supervisor + @since "0.8.0" + @deprecated "subscribe/3 was removed in version 0.8.0. See the updated documentation" def subscribe(schema_mod_or_label, update_type, id) when is_atom(schema_mod_or_label) do if Helpers.ecto_schema_mod?(schema_mod_or_label) do raise ArgumentError, @@ -63,6 +67,26 @@ defmodule EctoWatch do end end + @doc """ + Subscribe to notifications from watchers. + + Examples: + + iex> EctoWatch.subscribe({Comment, :updated}) + + When subscribing to a watcher with the `label` option specified as `:comment_updated_custom`: + + iex> EctoWatch.subscribe(:comment_updated_custom) + + You can subscribe to notifications just from specific primary key values: + + iex> EctoWatch.subscribe({Comment, :updated}, user_id) + + Or you can subscribe to notifications just from a specific foreign column (**the column must be in the watcher's `extra_columns` list): + + iex> EctoWatch.subscribe({Comment, :updated}, {:post_id, post_id}) + """ + @spec subscribe(identifier(), term()) :: :ok | {:error, term()} def subscribe(identifier, id \\ nil) do if is_atom(identifier) && id in ~w[inserted updated deleted]a do if Helpers.ecto_schema_mod?(identifier) do diff --git a/lib/ecto_watch/helpers.ex b/lib/ecto_watch/helpers.ex index b66db7a..e498d6c 100644 --- a/lib/ecto_watch/helpers.ex +++ b/lib/ecto_watch/helpers.ex @@ -1,7 +1,5 @@ defmodule EctoWatch.Helpers do - @moduledoc """ - General helpers useful throughout the `ecto_watch` library - """ + @moduledoc false def label(schema_mod_or_label) do if ecto_schema_mod?(schema_mod_or_label) do diff --git a/lib/ecto_watch/options.ex b/lib/ecto_watch/options.ex index decbd87..e6cb425 100644 --- a/lib/ecto_watch/options.ex +++ b/lib/ecto_watch/options.ex @@ -1,7 +1,5 @@ defmodule EctoWatch.Options do - @moduledoc """ - Logic for processing the `EctoWatch` options passed by the end user's config - """ + @moduledoc false alias EctoWatch.Options.WatcherOptions diff --git a/lib/ecto_watch/options/watcher_options.ex b/lib/ecto_watch/options/watcher_options.ex index baa7f2d..4ebab34 100644 --- a/lib/ecto_watch/options/watcher_options.ex +++ b/lib/ecto_watch/options/watcher_options.ex @@ -1,10 +1,8 @@ defmodule EctoWatch.Options.WatcherOptions do + @moduledoc false + alias EctoWatch.Helpers - @moduledoc """ - Logic for processing the `EctoWatch` postgres notification watcher options - which are passed in by the end user's config - """ defstruct [:schema_definition, :update_type, :label, :trigger_columns, :extra_columns] def validate_list(list) do @@ -12,10 +10,8 @@ defmodule EctoWatch.Options.WatcherOptions do end defmodule SchemaDefinition do - @moduledoc """ - Generic representation of an app schema. Contains important details about a postgres table, - whether it's create from an Ecto schema module or from a map. - """ + @moduledoc false + defstruct [ :schema_prefix, :table_name, diff --git a/lib/ecto_watch/watcher_server.ex b/lib/ecto_watch/watcher_server.ex index e2a0f5f..c31c401 100644 --- a/lib/ecto_watch/watcher_server.ex +++ b/lib/ecto_watch/watcher_server.ex @@ -1,6 +1,8 @@ defmodule EctoWatch.WatcherServer do @moduledoc """ - GenServer for the individiual change watchers which are configured by end users + Internal GenServer for the individiual change watchers which are configured by end users + + Used internally, but you'll see it in your application supervision tree. """ alias EctoWatch.Helpers diff --git a/lib/ecto_watch/watcher_supervisor.ex b/lib/ecto_watch/watcher_supervisor.ex index dd0595d..557f3ce 100644 --- a/lib/ecto_watch/watcher_supervisor.ex +++ b/lib/ecto_watch/watcher_supervisor.ex @@ -1,6 +1,8 @@ defmodule EctoWatch.WatcherSupervisor do @moduledoc """ - Supervisor for postgres notification watchers (`EctoWatch.WatcherServer`) + Internal Supervisor for postgres notification watchers (`EctoWatch.WatcherServer`) + + Used internally, but you'll see it in your application supervision tree. """ alias EctoWatch.WatcherServer diff --git a/lib/ecto_watch/watcher_trigger_validator.ex b/lib/ecto_watch/watcher_trigger_validator.ex index f10315d..30bbb2a 100644 --- a/lib/ecto_watch/watcher_trigger_validator.ex +++ b/lib/ecto_watch/watcher_trigger_validator.ex @@ -1,7 +1,9 @@ defmodule EctoWatch.WatcherTriggerValidator do @moduledoc """ - Task run as part of the EctoWatch supervision tree to check for a match between the triggers + Intenal task run as part of the EctoWatch supervision tree to check for a match between the triggers that are in the database and the triggers that were started via the configuration. + + Used internally, but you'll see it in your application supervision tree. """ alias EctoWatch.WatcherServer diff --git a/lib/test_repo.ex b/lib/test_repo.ex index 99595fb..83b0709 100644 --- a/lib/test_repo.ex +++ b/lib/test_repo.ex @@ -1,5 +1,5 @@ defmodule EctoWatch.TestRepo do - @moduledoc "Repo for tests" + @moduledoc false use Ecto.Repo, otp_app: :ecto_watch, diff --git a/mix.exs b/mix.exs index 76d7520..8d61b8d 100644 --- a/mix.exs +++ b/mix.exs @@ -1,6 +1,8 @@ defmodule EctoWatch.MixProject do use Mix.Project + @source_url "https://github.com/cheerfulstoic/ecto_watch" + def project do [ app: :ecto_watch, @@ -11,7 +13,11 @@ defmodule EctoWatch.MixProject do licenses: ["MIT"], package: package(), start_permanent: Mix.env() == :prod, - deps: deps() + deps: deps(), + + # Docs + name: "EctoWatch", + docs: docs() ] end @@ -19,7 +25,7 @@ defmodule EctoWatch.MixProject do [ maintainers: ["Brian Underwood"], licenses: ["MIT"], - links: %{"GitHub" => "https://github.com/cheerfulstoic/ecto_watch"} + links: %{"GitHub" => @source_url} ] end @@ -43,4 +49,42 @@ defmodule EctoWatch.MixProject do {:credo, "~> 1.7", only: [:dev, :test], runtime: false} ] end + + defp docs do + [ + main: "EctoWatch", + extra_section: "GUIDES", + source_url: @source_url, + skip_undefined_reference_warnings_on: ["CHANGELOG.md"], + extras: extras(), + groups_for_extras: groups_for_extras() + # groups_for_docs: [ + # group_for_function("Test") + # ] + ] + end + + def extras() do + [ + "guides/introduction/Getting Started.md", + "guides/introduction/Tracking specific columns and using labels.md", + "guides/introduction/Getting additional values.md", + "guides/introduction/Watching without a schema.md", + "guides/introduction/Notes.md", + "guides/howtos/Upgrading Versions.md", + "guides/other/Potental TODOs.md", + "CHANGELOG.md" + ] + end + + defp group_for_function(group), do: {String.to_atom(group), &(&1[:group] == group)} + + defp groups_for_extras do + [ + Introduction: ~r/guides\/introduction\/.?/, + # Cheatsheets: ~r/cheatsheets\/.?/, + "How-To's": ~r/guides\/howtos\/.?/, + Other: ~r/guides\/other\/.?/ + ] + end end