Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
chrislaskey committed Jan 31, 2019
0 parents commit bdab2ca
Show file tree
Hide file tree
Showing 37 changed files with 1,464 additions and 0 deletions.
14 changes: 14 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
### Assoc

# Database

export ASSOC_TEST_POSTGRES_USER=postgres
export ASSOC_TEST_POSTGRES_PASS=postgres
export ASSOC_TEST_POSTGRES_HOST=localhost
export ASSOC_TEST_POSTGRES_DB=assoc_test

### Elixir Version

export ELIXIR_VERSION="1.8.1"
export PATH="$HOME/.kiex/elixirs/elixir-1.8.1/bin:$PATH"
export MIX_ARCHIVES="$HOME/.kiex/mix/archives/elixir-1.8.1"
14 changes: 14 additions & 0 deletions .env.travis
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
### Assoc

# Database

export ASSOC_TEST_POSTGRES_USER=postgres
export ASSOC_TEST_POSTGRES_PASS=
export ASSOC_TEST_POSTGRES_HOST=localhost
export ASSOC_TEST_POSTGRES_DB=assoc_test

### Elixir Version

export ELIXIR_VERSION="1.8.1"
export PATH="$HOME/.kiex/elixirs/elixir-1.8.1/bin:$PATH"
export MIX_ARCHIVES="$HOME/.kiex/mix/archives/elixir-1.8.1"
4 changes: 4 additions & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# The directory Mix will write compiled artifacts to.
/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Ignore package tarball (built via "mix hex.build").
assoc-*.tar

5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Changelog

## [v0.1.0] - 2019-01-30

- Initial Release
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Assoc

> An easy way to manage `many_to_many`, `has_many` and `belongs_to` Ecto associations
3 changes: 3 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
use Mix.Config

import_config "./#{Mix.env()}.exs"
1 change: 1 addition & 0 deletions config/dev.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use Mix.Config
1 change: 1 addition & 0 deletions config/prod.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use Mix.Config
21 changes: 21 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use Mix.Config

# Set the log level
#
# The order from most information to least:
#
# :debug
# :info
# :warn
#
config :logger, level: :info

config :assoc,
ecto_repos: [Assoc.Test.Repo]

config :assoc, Assoc.Test.Repo,
username: System.get_env("ASSOC_TEST_POSTGRES_USER"),
password: System.get_env("ASSOC_TEST_POSTGRES_PASS"),
database: System.get_env("ASSOC_TEST_POSTGRES_DB"),
hostname: System.get_env("ASSOC_TEST_POSTGRES_HOST"),
pool: Ecto.Adapters.SQL.Sandbox
4 changes: 4 additions & 0 deletions lib/assoc.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
defmodule Assoc do
@moduledoc """
"""
end
118 changes: 118 additions & 0 deletions lib/assoc/schema.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
defmodule Assoc.Schema do
@moduledoc """
## Usage
```
defmodule MyApp.User do
use MyApp.Schema
use Assoc.Schema, repo: MyApp.Repo
schema "users" do
field :email, :string
field :name, :string
has_many :user_roles, MyApp.UserRole, on_delete: :delete_all, on_replace: :delete
timestamps()
end
def updatable_associations, do: [
user_roles: MyApp.UserRole
]
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:email, :name])
|> validate_required([:email])
end
end
```
Key points:
- The `use Assoc.Schema` line should come after `use MyApp.Schema`.
- Pass the app's Repo into `use Assoc.Schema, repo: MyApp.Repo`
- Define a `updatable_associations` function. For each updatable association:
- The `key` should be the association name
- The `value` should be the association schema module
- The standard `changeset` function does not change.
- Include all the standard code for updating struct values (e.g. name, email) in `changeset`
- The library will create and use a separate `associations_changeset` to manage the associations
"""

@callback updatable_associations :: List.t

defmacro __using__(repo: repo) do
quote do
@doc """
Preload all schema associations.
## Usage
```
MyApp.User.preload_all(user)
```
## Implementation
Builds a list of keys with `Ecto.Association.NotLoaded` values. Then
feeds the list into `Repo.preload`.
"""
def preload_all(struct) do
keys = struct
|> Map.from_struct
|> Enum.reduce([], fn ({key, value}, acc) ->
case value do
%Ecto.Association.NotLoaded{} -> [key|acc]
_ -> acc
end
end)

unquote(repo).preload(struct, keys)
end

@doc """
Update associations defined in `updatable_associations/0` callback.
"""
def associations_changeset(struct, params \\ %{}) do
struct = preload_associations(struct, updatable_associations())
params = include_existing_associations(struct, params)

struct
|> cast(params, [])
|> put_associations(updatable_associations(), params)
end

@doc """
Preload selected schema associations.
"""
def preload_associations(struct, associations) do
associations = case Keyword.keyword?(associations) do
true -> Keyword.keys(associations)
false -> associations
end

unquote(repo).preload(struct, associations)
end

# Include existing associations in params by merging params into preload struct
defp include_existing_associations(struct, params) do
struct
|> Map.from_struct
|> Map.merge(params)
end

# Dynamically adds `put_assoc` calls to changeset
defp put_associations(changeset, associations, params) do
Enum.reduce(associations, changeset, fn ({key, _}, acc) ->
value = params
|> Assoc.Util.keys_to_atoms()
|> Map.get(key, :omitted)

case value do
:omitted -> acc
value -> put_assoc(acc, key, value)
end
end)
end
end
end
end
Loading

0 comments on commit bdab2ca

Please sign in to comment.