Skip to content

Commit 26d8697

Browse files
committed
Updates livebook_helpers to latest version
Adds data schema as a dependancy as latest livebook version removes the mix runtime. Sadje.
1 parent 6a3f900 commit 26d8697

File tree

4 files changed

+37
-31
lines changed

4 files changed

+37
-31
lines changed

.tool-versions

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
elixir 1.13.4-otp-24
1+
elixir 1.14.1-otp-24
22
erlang 24.3.4

livebooks/readme.livemd

+33-27
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
# DataSchema
44

5+
## Dependencies
6+
7+
```elixir
8+
Mix.install([{:data_schema, path: "./"}])
9+
```
510

611
Data schemas are declarative descriptions of how to create a struct from some input data. You can set up different schemas to handle different kinds of input data. By default we assume the incoming data is a map, but you can configure schemas to work with any arbitrary data input including XML and json.
712

@@ -35,22 +40,22 @@ There are 5 kinds of struct fields we could want:
3540
2. `list_of` - The value will be a list of casted values created from the source data.
3641
3. `has_one` - The value will be created from a nested data schema (so will be a struct)
3742
4. `has_many` - The value will be created by casting a list of values into a data schema.
38-
(You end up with a list of structs defined by the provided schema). Similar to has_many in ecto
43+
(You end up with a list of structs defined by the provided schema). Similar to has_many in ecto
3944
5. `aggregate` - The value will be a casted value formed from multiple bits of data in the source.
4045

4146
Available options are:
4247

4348
* `:optional?` - specifies whether or not the field in the struct should be included in
44-
the `@enforce_keys` for the struct. By default all fields are required but you can mark
45-
them as optional by setting this to `true`. This will also be checked when creating a
46-
struct with `DataSchema.to_struct/2` returning an error if the required field is null.
49+
the `@enforce_keys` for the struct. By default all fields are required but you can mark
50+
them as optional by setting this to `true`. This will also be checked when creating a
51+
struct with `DataSchema.to_struct/2` returning an error if the required field is null.
4752
* `:empty_values` - allows you to define what values should be used as "empty" for a
48-
given field. If either the value returned from the data accessor or the casted value are
49-
equivalent to any element in this list, that field is deemed to be empty. Defaults to `[nil]`,
50-
meaning nil is always considered "empty".
53+
given field. If either the value returned from the data accessor or the casted value are
54+
equivalent to any element in this list, that field is deemed to be empty. Defaults to `[nil]`,
55+
meaning nil is always considered "empty".
5156
* `:default` - specifies a 0 arity function that will be called to produce a default value for a field
52-
when casting. This function will only be called if a field is found to be empty AND optional.
53-
If it's empty and not optional we will error.
57+
when casting. This function will only be called if a field is found to be empty AND optional.
58+
If it's empty and not optional we will error.
5459

5560
For example:
5661

@@ -63,23 +68,28 @@ defmodule Sandwich do
6368
)
6469
end
6570
```
66-
And:
71+
72+
And:
73+
6774
```elixir
6875
defmodule Sandwich do
6976
require DataSchema
7077

7178
DataSchema.data_schema(field: {:list, "list", &{:ok, &1}, optional?: true, empty_values: [[]]})
7279
end
7380
```
74-
And:
81+
82+
And:
7583

7684
```elixir
7785
defmodule Sandwich do
7886
require DataSchema
87+
7988
@options [optional?: true, empty_values: [nil], default: &DateTime.utc_now/0]
8089
DataSchema.data_schema(field: {:created_at, "inserted_at", &{:ok, &1}, @options})
8190
end
8291
```
92+
8393
To see this better let's look at a very simple example. Assume our input data looks like this:
8494

8595
```elixir
@@ -93,7 +103,6 @@ source_data = %{
93103
}
94104
```
95105

96-
97106
And now let's assume the struct we wish to make is this one:
98107

99108
```elixir
@@ -105,7 +114,6 @@ And now let's assume the struct we wish to make is this one:
105114
}
106115
```
107116

108-
109117
We can describe the following schemas to enable this:
110118

111119
```elixir
@@ -137,7 +145,6 @@ defmodule BlogPost do
137145
end
138146
```
139147

140-
141148
Then to transform some input data into the desired struct we can call `DataSchema.to_struct/2` which works recursively to transform the input data into the struct defined by the schema.
142149

143150
```elixir
@@ -161,7 +168,6 @@ DataSchema.to_struct(source_data, BlogPost)
161168
}
162169
```
163170

164-
165171
So imagine the input data came from an API response:
166172

167173
```elixir
@@ -171,7 +177,6 @@ with {:ok, %{status: 200, response_body: body}} <- Http.get("https://www.my_thin
171177
end
172178
```
173179

174-
175180
## Different Source Data Types
176181

177182
As we mentioned before we want to be able to handle multiple different kinds of source data in our schemas. For each type of source data we want to be able to specify how you access the data for each field type. We do that by providing a "data accessor" (a module that implements the `DataSchema.DataAccessBehaviour`) when we create the schema. We do this by providing a `@data_accessor` on the schema. By default if you do not provide this module attribute we use `DataSchema.MapAccessor`. That means the above example is equivalent to doing the following:
@@ -236,7 +241,6 @@ defmodule DataSchema.MapAccessor do
236241
end
237242
```
238243

239-
240244
To save repeating `@data_accessor DataSchema.MapAccessor` on all of your schemas you could use a `__using__` macro like so:
241245

242246
```elixir
@@ -260,7 +264,6 @@ defmodule DraftPost do
260264
end
261265
```
262266

263-
264267
This means should we want to change how we access data (say we wanted to use `Map.fetch!` instead of `Map.get`) we would only need to change the accessor used in one place - inside the `__using__` macro. It also gives you a handy place to provide other functions for the structs that get created, perhaps implementing a default Inspect protocol implementation for example:
265268

266269
```elixir
@@ -284,7 +287,6 @@ defmodule MapSchema do
284287
end
285288
```
286289

287-
288290
This could help ensure you never log sensitive fields by requiring you to explicitly implement an inspect protocol for a struct in order to see the fields in it.
289291

290292
### XML Schemas
@@ -318,7 +320,6 @@ defmodule XpathAccessor do
318320
end
319321
```
320322

321-
322323
As we can see our accessor uses the library [Sweet XML](https://github.com/kbrw/sweet_xml) to access the XML. That means if we wanted to change the library later we would only need to alter this one module for all of our schemas to benefit from the change.
323324

324325
Our source data looks like this:
@@ -338,7 +339,6 @@ source_data = """
338339
"""
339340
```
340341

341-
342342
Let's define our schemas like so:
343343

344344
```elixir
@@ -373,7 +373,6 @@ defmodule BlogPost do
373373
end
374374
```
375375

376-
377376
And now we can transform:
378377

379378
```elixir
@@ -405,7 +404,6 @@ DataSchema.to_struct(source_data, BlogPost)
405404
}
406405
```
407406

408-
409407
## to_runtime_schema/1
410408

411409
Accepts a the module of a compile time schema and will expand it into a runtime schema
@@ -440,6 +438,7 @@ DataSchema.to_struct(data, Foo)
440438
# => Outputs the following:
441439
%Foo{a_rocket: "enables space travel"}
442440
```
441+
443442
## to_struct/5
444443

445444
Creates a struct or map from the provided arguments. This function can be used to define
@@ -464,6 +463,7 @@ fields = [
464463
DataSchema.to_struct(input, Run, fields, DataSchema.MapAccessor)
465464
{:ok, %Run{time: "10:00"}}
466465
```
466+
467467
Creating a map:
468468

469469
```elixir
@@ -476,6 +476,7 @@ fields = [
476476
DataSchema.to_struct(input, %{}, fields, DataSchema.MapAccessor)
477477
{:ok, %{time: "10:00"}}
478478
```
479+
479480
## data_schema/1
480481

481482
A macro that creates a data schema. By default all struct fields are required but you
@@ -490,40 +491,44 @@ There are 5 kinds of struct fields we can have:
490491
2. `list_of` - The value will be a list of casted values created from the source data.
491492
3. `has_one` - The value will be created from a nested data schema (so will be a struct)
492493
4. `has_many` - The value will be created by casting a list of values into a data schema.
493-
(You end up with a list of structs defined by the provided schema). Similar to has_many in ecto
494+
(You end up with a list of structs defined by the provided schema). Similar to has_many in ecto
494495
5. `aggregate` - The value will a casted value formed from multiple bits of data in the source.
495496

496497
### Options
497498

498499
Available options are:
499500

500-
- `:optional?` - specifies whether or not the field in the struct should be included in
501+
* `:optional?` - specifies whether or not the field in the struct should be included in
501502
the `@enforce_keys` for the struct. By default all fields are required but you can mark
502503
them as optional by setting this to `true`. This will also be checked when creating a
503504
struct with `DataSchema.to_struct/2` returning an error if the required field is null.
504-
- `:empty_values` - allows you to define what values should be used as "empty" for a
505+
* `:empty_values` - allows you to define what values should be used as "empty" for a
505506
given field. If either the value returned from the data accessor or the casted value are
506507
equivalent to any element in this list, that field is deemed to be empty. Defaults to `[nil]`.
507-
- `:default` - specifies a 0 arity function that will be called to produce a default value for a field
508+
* `:default` - specifies a 0 arity function that will be called to produce a default value for a field
508509
when casting. This function will only be called if a field is found to be empty AND optional.
509510
If it's empty and not optional we will error.
510511

511512
For example:
513+
512514
```elixir
513515
defmodule Sandwich do
514516
require DataSchema
515517

516518
DataSchema.data_schema(field: {:type, "the_type", &{:ok, String.upcase(&1)}, optional?: true})
517519
end
518520
```
521+
519522
And:
523+
520524
```elixir
521525
defmodule Sandwich do
522526
require DataSchema
523527

524528
DataSchema.data_schema(field: {:list, "list", &{:ok, &1}, optional?: true, empty_values: [[]]})
525529
end
526530
```
531+
527532
And:
528533

529534
```elixir
@@ -533,6 +538,7 @@ defmodule Sandwich do
533538
DataSchema.data_schema(field: {:created_at, "inserted_at", &{:ok, &1}, @options})
534539
end
535540
```
541+
536542
### Examples
537543

538544
See the guides for more in depth examples but below you can see how we create a schema

mix.exs

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ defmodule DataSchema.MixProject do
2424
end
2525

2626
defp create_livebook(_) do
27-
Mix.Task.run("create_livebook_from_module", ["DataSchema", "livebooks/readme"])
27+
Mix.Task.run("create_livebook_from_module", ["DataSchema", "livebooks/readme", "[{:data_schema, path: \"./\"}]"])
2828
end
2929

3030
defp elixirc_paths(:test), do: ["lib", "test/support"]
@@ -52,7 +52,7 @@ defmodule DataSchema.MixProject do
5252
{:benchee, ">= 0.0.0", only: [:dev, :test]},
5353
# This will be used for benchmarking.
5454
{:ecto, ">= 0.0.0", only: [:dev, :test]},
55-
{:livebook_helpers, "~> 0.0.1", only: [:dev, :docs]},
55+
{:livebook_helpers, "~> 0.0.8", only: [:dev, :docs]},
5656
{:ex_doc, ">= 0.0.0", only: :docs, runtime: false}
5757
]
5858
end

mix.lock

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"earmark_parser": {:hex, :earmark_parser, "1.4.19", "de0d033d5ff9fc396a24eadc2fcf2afa3d120841eb3f1004d138cbf9273210e8", [:mix], [], "hexpm", "527ab6630b5c75c3a3960b75844c314ec305c76d9899bb30f71cb85952a9dc45"},
66
"ecto": {:hex, :ecto, "3.7.1", "a20598862351b29f80f285b21ec5297da1181c0442687f9b8329f0445d228892", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d36e5b39fc479e654cffd4dbe1865d9716e4a9b6311faff799b6f90ab81b8638"},
77
"ex_doc": {:hex, :ex_doc, "0.28.0", "7eaf526dd8c80ae8c04d52ac8801594426ae322b52a6156cd038f30bafa8226f", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "e55cdadf69a5d1f4cfd8477122ebac5e1fadd433a8c1022dafc5025e48db0131"},
8-
"livebook_helpers": {:hex, :livebook_helpers, "0.0.4", "a3c501726e8bfdf542506a198dd06654665a178e4402a8bc9dc2664593892759", [:mix], [], "hexpm", "17bf83eddc5b62993e8a841c29c7fe1cc7b96d13316a5db86eab832ad55daeda"},
8+
"livebook_helpers": {:hex, :livebook_helpers, "0.0.8", "52fb2fc58522801f3fbdf13100088083af7db6c3dd948ce7796761a16306c2b7", [:mix], [], "hexpm", "dcbaa2146f66f68ed7a6fde76f9e46118791b814e7753851fe526fa83bcbff82"},
99
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
1010
"makeup_elixir": {:hex, :makeup_elixir, "0.15.2", "dc72dfe17eb240552857465cc00cce390960d9a0c055c4ccd38b70629227e97c", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "fd23ae48d09b32eff49d4ced2b43c9f086d402ee4fd4fcb2d7fad97fa8823e75"},
1111
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},

0 commit comments

Comments
 (0)