Current section
12 Versions
Jump to
Current section
12 Versions
Compare versions
16
files changed
+533
additions
-254
deletions
| @@ -31,7 +31,9 @@ Declarative [`Ecto`](https://github.com/elixir-ecto/ecto) `embedded_schema`s for | |
| 31 31 | ```elixir |
| 32 32 | def deps do |
| 33 33 | [ |
| 34 | - {:flint, "~> 0.5"} |
| 34 | + {:flint, "~> 0.6"}, |
| 35 | + # If you want access to the `Typed` extension to add generated typespecs |
| 36 | + {:typed_ecto_schema, "~> 0.4", runtime: false} |
| 35 37 | ] |
| 36 38 | end |
| 37 39 | ``` |
| @@ -349,7 +351,6 @@ You can configure the default options set by `Flint`. | |
| 349 351 | * `embeds_one!`: The default arguments when using `embeds_one!`. Defaults to `[on_replace: :delete]` |
| 350 352 | * `embeds_many`: The default arguments when using `embeds_many` or `embeds_many!`. Defaults to `[on_replace: :delete]` |
| 351 353 | * `embeds_many!`: The default arguments when using `embeds_many!`. Defaults to `[on_replace: :delete]` |
| 352 | - * `:enum`: The default arguments for an `Ecto.Enum` field. Defaults to `[embed_as: :dumped]`. |
| 353 354 | * `:aliases`: [Aliases](#aliases) |
| 354 355 | |
| 355 356 | You can also configure any aliases you want to use for schema validations. |
| @@ -358,13 +359,13 @@ You can also configure any aliases you want to use for schema validations. | |
| 358 359 | |
| 359 360 | `Flint` takes advantage of the distinction `Ecto` makes between an `embedded_schema`'s embedded and dumped representations. |
| 360 361 | |
| 361 | - For example, by default in `Flint`, `Ecto.Enum`s that are `Keyword` (rather than just lists of atoms) will have their keys |
| 362 | + For example, `Flint` provides the `Flint.Types.Enum` type, which are `Ecto.Enum`s where, when given values that are `Keyword` (rather than just lists of atoms) will have their keys |
| 362 363 | be the embedded representation and will have the values be the dumped representation. |
| 363 364 | |
| 364 365 | ```elixir |
| 365 366 | defmodule Book do |
| 366 367 | use Flint.Schema, schema: [ |
| 367 | - field(:genre, Ecto.Enum, values: [biography: 0, science_fiction: 1, fantasy: 2, mystery: 3]) |
| 368 | + field(:genre, Flint.Types.Enum, values: [biography: 0, science_fiction: 1, fantasy: 2, mystery: 3]) |
| 368 369 | ] |
| 369 370 | end |
| @@ -1,6 +1,6 @@ | |
| 1 1 | {<<"links">>,[{<<"GitHub">>,<<"https://github.com/acalejos/flint">>}]}. |
| 2 2 | {<<"name">>,<<"flint">>}. |
| 3 | - {<<"version">>,<<"0.5.1">>}. |
| 3 | + {<<"version">>,<<"0.6.0">>}. |
| 4 4 | {<<"description">>, |
| 5 5 | <<"Declarative Ecto embedded schemas for data validation, coercion, and manipulation.">>}. |
| 6 6 | {<<"elixir">>,<<"~> 1.14">>}. |
| @@ -17,6 +17,11 @@ | |
| 17 17 | {<<"optional">>,false}, |
| 18 18 | {<<"requirement">>,<<"~> 2.2">>}, |
| 19 19 | {<<"repository">>,<<"hexpm">>}], |
| 20 | + [{<<"name">>,<<"typed_ecto_schema">>}, |
| 21 | + {<<"app">>,<<"typed_ecto_schema">>}, |
| 22 | + {<<"optional">>,true}, |
| 23 | + {<<"requirement">>,<<"~> 0.4">>}, |
| 24 | + {<<"repository">>,<<"hexpm">>}], |
| 20 25 | [{<<"name">>,<<"jason">>}, |
| 21 26 | {<<"app">>,<<"jason">>}, |
| 22 27 | {<<"optional">>,true}, |
| @@ -30,13 +35,15 @@ | |
| 30 35 | {<<"files">>, |
| 31 36 | [<<"lib">>,<<"lib/type.ex">>,<<"lib/changeset.ex">>,<<"lib/schema.ex">>, |
| 32 37 | <<"lib/flint.ex">>,<<"lib/flint">>,<<"lib/flint/types">>, |
| 33 | - <<"lib/flint/types/union.ex">>,<<"lib/flint/extension">>, |
| 34 | - <<"lib/flint/extension/field.ex">>,<<"lib/flint/extension/dsl.ex">>, |
| 35 | - <<"lib/flint/extensions">>,<<"lib/flint/extensions/pre_transforms.ex">>, |
| 38 | + <<"lib/flint/types/enum.ex">>,<<"lib/flint/types/union.ex">>, |
| 39 | + <<"lib/flint/extension">>,<<"lib/flint/extension/field.ex">>, |
| 40 | + <<"lib/flint/extension/dsl.ex">>,<<"lib/flint/extensions">>, |
| 41 | + <<"lib/flint/extensions/pre_transforms.ex">>, |
| 42 | + <<"lib/flint/extensions/typed.ex">>, |
| 36 43 | <<"lib/flint/extensions/post_transforms.ex">>, |
| 37 44 | <<"lib/flint/extensions/ecto_validations.ex">>, |
| 38 | - <<"lib/flint/extensions/when.ex">>,<<"lib/flint/extensions/json.ex">>, |
| 39 | - <<"lib/flint/extensions/embedded.ex">>, |
| 45 | + <<"lib/flint/extensions/block.ex">>,<<"lib/flint/extensions/when.ex">>, |
| 46 | + <<"lib/flint/extensions/json.ex">>,<<"lib/flint/extensions/embedded.ex">>, |
| 40 47 | <<"lib/flint/extensions/accessible.ex">>,<<"lib/extension.ex">>, |
| 41 48 | <<".formatter.exs">>,<<"mix.exs">>,<<"README.md">>,<<"LICENSE.md">>]}. |
| 42 49 | {<<"build_tools">>,[<<"mix">>]}. |
| @@ -4,69 +4,6 @@ defmodule Flint.Changeset do | |
| 4 4 | that are collected when using the `Flint.Schema` macros to perform transformations and validations. |
| 5 5 | """ |
| 6 6 | |
| 7 | - @doc """ |
| 8 | - Uses the quoted expressions from the `Flint.Schema.field` and `Flint.Schema.field!` |
| 9 | - `do` blocks to validate the changeset. |
| 10 | - |
| 11 | - You can optionally pass bindings to be added to the evaluation context. |
| 12 | - """ |
| 13 | - def validate_do_blocks(changeset, bindings \\ []) do |
| 14 | - module = changeset.data.__struct__ |
| 15 | - env = Module.concat(module, Env) |> apply(:env, []) |
| 16 | - |
| 17 | - all_validations = |
| 18 | - module.__schema__(:blocks) |
| 19 | - |
| 20 | - for {field, block} <- all_validations, reduce: changeset do |
| 21 | - changeset -> |
| 22 | - bindings = bindings ++ Enum.into(changeset.changes, []) |
| 23 | - |
| 24 | - block |
| 25 | - |> Enum.with_index() |
| 26 | - |> Enum.reduce(changeset, fn |
| 27 | - {{quoted_condition, quoted_err}, index}, chngset -> |
| 28 | - try do |
| 29 | - {invalid?, _bindings} = Code.eval_quoted(quoted_condition, bindings, env) |
| 30 | - |
| 31 | - invalid? = |
| 32 | - if is_function(invalid?) do |
| 33 | - case Function.info(invalid?, :arity) do |
| 34 | - {:arity, 0} -> |
| 35 | - apply(invalid?, []) |
| 36 | - |
| 37 | - {:arity, 1} when not is_nil(field) -> |
| 38 | - apply(invalid?, [Ecto.Changeset.fetch_change!(changeset, field)]) |
| 39 | - |
| 40 | - _ -> |
| 41 | - raise ArgumentError, |
| 42 | - "Anonymous functions in validation clause must be either 0-arity or an input value for the field must be provided." |
| 43 | - end |
| 44 | - else |
| 45 | - invalid? |
| 46 | - end |
| 47 | - |
| 48 | - {err_msg, _bindings} = Code.eval_quoted(quoted_err, bindings, env) |
| 49 | - |
| 50 | - if invalid? do |
| 51 | - Ecto.Changeset.add_error(chngset, field, err_msg, |
| 52 | - validation: :block, |
| 53 | - clause: index + 1 |
| 54 | - ) |
| 55 | - else |
| 56 | - chngset |
| 57 | - end |
| 58 | - rescue |
| 59 | - _ -> |
| 60 | - Ecto.Changeset.add_error( |
| 61 | - chngset, |
| 62 | - field, |
| 63 | - "Error evaluating expression in Clause ##{index + 1} of `do:` block" |
| 64 | - ) |
| 65 | - end |
| 66 | - end) |
| 67 | - end |
| 68 | - end |
| 69 | - |
| 70 7 | @doc """ |
| 71 8 | Given a `Flint` (or `Ecto`) schema and params (can be a map, struct of the given schema, or an existing changeset), |
| 72 9 | applies all steps of the `Flint.Changeset` to generate a new changeset. |
| @@ -106,8 +43,19 @@ defmodule Flint.Changeset do | |
| 106 43 | ) |
| 107 44 | end |
| 108 45 | |
| 46 | + extension_names = |
| 47 | + module.__schema__(:extensions) |
| 48 | + |> Enum.map(fn |
| 49 | + {ext, _opts} when is_atom(ext) -> ext |
| 50 | + ext when is_atom(ext) -> ext |
| 51 | + end) |
| 52 | + |
| 109 53 | changeset |
| 110 54 | |> Ecto.Changeset.validate_required(required_fields) |
| 111 | - |> validate_do_blocks(bindings) |
| 55 | + |> then( |
| 56 | + &Enum.reduce(extension_names, &1, fn extension, chst -> |
| 57 | + extension.changeset(chst, bindings) |
| 58 | + end) |
| 59 | + ) |
| 112 60 | end |
| 113 61 | end |
| @@ -161,6 +161,8 @@ defmodule Flint.Extension do | |
| 161 161 | |
| 162 162 | By default, `Flint` will enable the following extensions: |
| 163 163 | |
| 164 | + * `Flint.Extensions.Block` |
| 165 | + * `Flint.Extensions.Typed` |
| 164 166 | * `Flint.Extensions.PreTransforms`, |
| 165 167 | * `Flint.Extensions.When`, |
| 166 168 | * `Flint.Extensions.EctoValidations`, |
| @@ -177,6 +179,9 @@ defmodule Flint.Extension do | |
| 177 179 | many_extension_kinds: [:extensions], |
| 178 180 | default_extensions: [extensions: Flint.Extension.Dsl] |
| 179 181 | |
| 182 | + @callback changeset(Ecto.Changeset.t()) :: Ecto.Changeset.t() |
| 183 | + @callback changeset(Ecto.Changeset.t(), keyword()) :: Ecto.Changeset.t() |
| 184 | + |
| 180 185 | @doc false |
| 181 186 | defmacro embedded_schema(do: {:__block__, _, contents}) do |
| 182 187 | Module.put_attribute(__CALLER__.module, :embedded_schema, contents) |
| @@ -187,20 +192,50 @@ defmodule Flint.Extension do | |
| 187 192 | Module.put_attribute(__CALLER__.module, :embedded_schema, [block]) |
| 188 193 | end |
| 189 194 | |
| 195 | + @doc false |
| 196 | + def __embedded_schema__(env, extension) do |
| 197 | + mods = |
| 198 | + env.macros |
| 199 | + |> Enum.find_value( |
| 200 | + fn {_mod, exports} -> Keyword.has_key?(exports, :embedded_schema) end, |
| 201 | + fn {mod, exports} -> |
| 202 | + {mod, Keyword.take(exports, [:embedded_schema])} |
| 203 | + end |
| 204 | + ) |
| 205 | + |
| 206 | + Module.put_attribute(env.module, :__embedded_schema_super__, {extension, mods}) |
| 207 | + |
| 208 | + mods |
| 209 | + end |
| 210 | + |
| 211 | + @doc false |
| 212 | + def __context__(env, extension) do |
| 213 | + Module.get_attribute(env.module, :__embedded_schema_super__) |
| 214 | + |> Keyword.fetch!(extension) |
| 215 | + end |
| 216 | + |
| 190 217 | defmacro __using__(opts) do |
| 191 218 | Module.put_attribute(__CALLER__.module, :embedded_schema, []) |
| 192 219 | |
| 193 220 | quote do |
| 194 221 | import Flint.Extension |
| 222 | + @behaviour Flint.Extension |
| 195 223 | unquote_splicing(super(opts)) |
| 196 224 | |
| 225 | + @impl true |
| 226 | + def changeset(changeset, bindings \\ []) do |
| 227 | + changeset |
| 228 | + end |
| 229 | + |
| 197 230 | defmacro __using__(opts) do |
| 198 231 | quote do |
| 199 232 | end |
| 200 233 | end |
| 201 234 | |
| 202 235 | defoverridable __using__: 1 |
| 236 | + defoverridable Flint.Extension |
| 203 237 | |
| 238 | + @doc false |
| 204 239 | def template_schema() do |
| 205 240 | @embedded_schema |
| 206 241 | end |
| @@ -9,15 +9,19 @@ defmodule Flint do | |
| 9 9 | if opts[:except] && opts[:only], |
| 10 10 | do: raise(ArgumentError, "Cannot specify both `:only` and `:except` options.") |
| 11 11 | |
| 12 | - defaults = [ |
| 13 | - Flint.Extensions.PreTransforms, |
| 14 | - Flint.Extensions.When, |
| 15 | - Flint.Extensions.EctoValidations, |
| 16 | - Flint.Extensions.PostTransforms, |
| 17 | - Flint.Extensions.Accessible, |
| 18 | - Flint.Extensions.Embedded, |
| 19 | - Flint.Extensions.JSON |
| 20 | - ] |
| 12 | + defaults = |
| 13 | + [ |
| 14 | + Flint.Extensions.Block, |
| 15 | + if(Code.ensure_loaded?(TypedEctoSchema), do: Flint.Extensions.Typed), |
| 16 | + Flint.Extensions.PreTransforms, |
| 17 | + Flint.Extensions.When, |
| 18 | + Flint.Extensions.EctoValidations, |
| 19 | + Flint.Extensions.PostTransforms, |
| 20 | + Flint.Extensions.Accessible, |
| 21 | + Flint.Extensions.Embedded, |
| 22 | + Flint.Extensions.JSON |
| 23 | + ] |
| 24 | + |> Enum.filter(& &1) |
| 21 25 | |
| 22 26 | cond do |
| 23 27 | opts[:only] -> |
Loading more files…