# `MishkaBlog.Resources.Preparations.ReaderLocks`
[🔗](https://github.com/mishka-group/mishka_blog/blob/v0.0.1/lib/mishka_blog/resources/__preparations/reader_locks.ex#L5)

What a reader may open, as the filter of the read that fetches the records: the answer
`MishkaCmsCore.Accounts.Checks.AccessPermissions` gives for a record's lock, worked out by the
database in the statement that reads the rows, so that no lock is read beforehand.

The reader is the names its actor holds (`of/1`): a guest holds none, a user's are loaded with the
actor. A lock opens for a reader when it names nothing, or when a name in it is covered by a name
the reader holds, as `AccessPermissions.match?/3` asks it of a request from the dashboard:

  * a held name with no `*` segment covers the same name and nothing else;
  * a held name with a `*` segment covers a name of at least its number of segments whose segments
    agree with its own, a `*` standing for any one segment and, as its last segment, for the rest
    of the name, however many segments that is. `*` alone covers every name, `members:*` every
    name below `members`;
  * a name `admin:api:…` counts for nobody, whoever holds what.

The held names with no `*` are compared as they are and the others as anchored regular
expressions. `fragment/1` is used for that, and for nothing else of the matching, because Ash has
no operator that tests each element of an array against a list of names and patterns. Everything
else is an Ash expression.

## The page that serves a record

A record is also kept from a reader the runtime page that serves its address is locked to. The
pages are those of the site that show the kind of record (`page_open/3`: `extra["record"]` is
`"blog:post"`, `"blog:category"`, `"blog:tag"`, `"blog:author"` or `"blog:team"`), active and not
archived, and the kind opens for the reader when no page shows it or when one of them names no
permission or names one the reader holds, as the page itself lets them in. It is a subquery of the
read that does not depend on the row, so the database runs it once for the statement and no read
is made beforehand; a site that locks no page opens every kind. The fragment names the table and
the columns of the pages, which `MishkaBlog.ReaderLocksTest` keeps to the resource's.

## Records that inherit

A post opens with its own lock, else with those of the categories it is filed under, each as the
nearest category above it, or itself, that names some; it opens when none of them is locked or the
reader may open one. A category opens with its own lock, else the nearest above it that names
some. A comment opens with its post.

The categories under a lock are found by a recursive query over the site's categories, which Ash
has no operator for either. It is a subquery of the read that does not depend on the row, so the
database runs it once for the statement, and it starts from the categories that name permissions:
a site that locks nothing walks none. The fragments name the tables of the categories and of their
posts, which `MishkaBlog.ReaderLocksTest` keeps to the resources'.

# `t`

```elixir
@type t() :: %MishkaBlog.Resources.Preparations.ReaderLocks{
  exact: [String.t()],
  patterns: [String.t()]
}
```

# `category`

```elixir
@spec category(t(), Ash.Expr.t()) :: Ash.Expr.t()
```

Whether a category opens for the reader, in `site`: its own lock when it names permissions, else
those of the nearest category above it that does.

# `comment`

```elixir
@spec comment(t(), Ash.Expr.t()) :: Ash.Expr.t()
```

Whether a comment opens for the reader, in `site`: whoever may open its post. A comment whose post
is gone opens, as it does for the posts' read.

# `of`

```elixir
@spec of(map() | nil) :: t()
```

The names `actor` holds, as what a lock is matched against. `nil` holds none.

# `opens`

```elixir
@spec opens(t()) :: Ash.Expr.t()
```

Whether a record whose lock is its `permissions` opens for the reader: it names nothing, or names
something the reader holds.

# `own_site`

```elixir
@spec own_site() :: Ash.Expr.t()
```

The site of the record itself, for a record asked for by itself.

# `page_open`

```elixir
@spec page_open(t(), String.t(), Ash.Expr.t()) :: Ash.Expr.t()
```

Whether the runtime page that serves a record of `record` (`"blog:post"`, `"blog:category"`,
`"blog:tag"`, `"blog:author"` or `"blog:team"`) is open to the reader, in `site` (see `site/1`):
true when no page shows the kind, or when a page that does names no permission or one the reader
holds.

# `post`

```elixir
@spec post(t(), Ash.Expr.t()) :: Ash.Expr.t()
```

Whether a post opens for the reader, in `site` (see `site/1`): its own lock when it names
permissions, else those of the categories it is filed under. It opens when none of them is locked
or when the reader may open one of them.

# `site`

```elixir
@spec site(String.t()) :: Ash.Expr.t()
```

The site a read is made in, as the id the categories are walked in: the one a list's query runs
for.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
