README.md

# Grimoire

## Work in Progress
This library is a proof of concept under heavy development. Use at your own peril.

## TLDR Intro
Grimoire is a framework for automating parts of development and refactoring.

## How it works
**Code generation, removal, and updating**

You create a Map defining your database schema (plus metadata) and Grimoire uses it to generate code such as migrations, Ecto schemas, Absinthe Type definitions, etc.

Schema maps are treated as persistant. When your data changes, you create a new version of the schema. Grimoire makes a diff and keeps the generated code up to date.

**Customized**

No two projects are identical. You might use a whole different file structure, you probably want to hide certain fields from your graphql api, or any number of other nuanced changes. You might even want to manage non-elixir files!

Grimoire allows you to define your own template and path functions. Think of it more as a set of tools, rather than a prepackaged solution.

## Some problems it solves
In a normal phoenix app with GraphQL, I found myself duplicating data in at least 3 places:
- Ecto Migrations
- Ecto Schema/Structs
- Absinthe object types

For (almost) every database table, I would need to put (almost) every field in each of those places.
When my data changes, I have to hunt down every instance of the old data in my code, and update it.

#### Automation is great, but has challenges:
- `phx.gen create` creates files, but does not update them.
- I use my own file structure instead of the way phx.gen does it.
- I don't want my passwords exposed to the API
- Syntax and requirements change slightly depending on what I'm doing.
- Nothing is ever *that* simple.

## Solution: Highest level theory
1. You build a schema map which describes the CURRENT shape of your database graph.
2. Grimoire can make a diff which shows the changes between the previous and present schemas.
3. You build template functions which take data from the schema/diff and return a string.
4. You build path functions which determine WHERE to write the templates.
5. A config map holds your schemas, template functions, and path functions.
6. Pass the config map into various grimoire functions to create/update files.
7. As your data changes naturally, grimoire makes sure everything stays up to date.
8. Put everything together via Mix.Tasks

## The Schema
Treat it as a persistant structure and create new schemas instead of mutating it.
For example:

`schema1.ex`
```elixir
...
%{
  users: %{
    name: :string
  }
}
...
```
`> mix grimoire.update`
`Updated (some files)`
` `
`> mix grimoire.new`
`Created schema2.ex`

Now add a line to the newest schema.
`schema2.ex`
```elixir
...
%{
  users: %{
    name: :string,
    email: :string
  }
}
...
```

To hide a table, column, or field from a specific template, we can simply add some metadata.
You can put anything in :__meta__ and use it later.
`schema3.ex`
```elixir
...
%{
  users: %{
    __meta__: %{domain: :accounts}
    name: :string,
    email: %{
      t: :string,
      __meta__: %{
        hide: [:gql]
      }
    }
  }
}
...
```


## Tutorial
### Use some built in defaults to generate ecto migrations and structs.

`mix phx.new app`

`cd app`

add grimoire to the mix.exs deps

`mix deps.get`

create a file at mix/tasks/grimoire/new.ex
```elixir
defmodule Mix.Tasks.Grimoire.New do
    use Mix.Task
    alias Grimoire.Utilities.{Time, Files}

    @impl Mix.Task
    def run([]), do: Strings.print_error("""
        Please give a schema name
        e.g.
        > mix grimoire.new my_schema
    """)
    def run([schema_name]) do
        dir = Path.join(["lib", "app", "schemas"])
        if Files.name_is_unique(schema_name, dir) do
            file_name = "#{Time.timestamp()}_#{schema_name}.ex"
            mod_name = "App.Schemas." <> Macro.camelize(schema_name)
            Grimoire.new(dir, file_name, mod_name)
        else
            Strings.print_error("Your schema name must be unique.")
        end
    end

end
```

create a file at mix/tasks/grimoire/update.ex
```elixir
defmodule Mix.Tasks.Grimoire.Update do
    alias Grimoire.Render.Lists
    alias Grimoire.Defaults.Logic.Migration
    use Mix.Task

    @impl Mix.Task
    def run(_args) do
        config = %{
            app_name: :app,
            schemas: Path.join(["lib", "app", "schemas"]),
            paths: %{},
            templates: %{}
        } |> Grimoire.Config.parse

        Lists.write_tables(config, :default_struct, :default_struct)
        Migration.handle_premigration(config)
        Migration.handle_mainmigration(config)
    end

end
```

If you used a different app name, update those files.

now run `mix grimoire.new my_schema` to generate your first schema.

It's a blank map for now, so add some tables and columns.

Now run `mix grimoire.update` and watch it create some files.

If you run `mix grimoire.new` again, it will create another schema, copying the map from the previous one. You only really need to do this if you ran the migrations it created.

## Customizing templates
@TODO
(sorry)
## Customizing paths
@TODO
(sorry)
## Customizing actions
@TODO
(sorry)