By Developers, For Developers

Historical errata for Designing Elixir Systems with OTP

PDF PgPaper PgTypeDescriptionFixed onComments
9ERROR

Output should start at 0 after `run/2` called with `f` and `0`.

2019-04-30
10ERROR

iex return value for start function has old return value:
```
er.Api.state(counter_pid)
0
```

2019-04-30
10ERROR

iex(1)> counter_pid = Counter.start(0) #PID<0.112.0>
The return value of `start/1` would be a different pid

2019-04-30
26TYPO

last paragraph may be missing something, starts as “and a predetermined”.

2019-04-30
38ERROR

The description says that some characters take two bytes, but the diagram seems to indicate a character of three bytes (the ellipses).

2019-06-29The é is two bytes, but I'll add a small clarification.
ixTYPO

> Find James at @geg2

James’ twitter user name is wrong. It should be @JEG2

2019-04-30
16TYPO

In a second item of the numbered list, there’s a word “with” used twice as “with with”.

2019-04-30
8SUGGEST

I’ve never taken part in an escape room challenge but the idea of running out of time and ‘calling for our first clue’ doesn’t make sense to me. Were you asking for the last clue? In the next sentence you mention ‘this last bit of information’ so it is probably just a typo.

2019-04-30
53ERROR

We’ll create a lib/core directory to hold the modules with our data layer (and later our core functions).
The path should probably be lib/mastery/core.

2019-04-30
3TYPO

Original text: The next component, a project called Mastery. will be much more complex,

I believe the dot after Mastery (“Mastery.”) is incorrect.

2019-04-30
4TYPO

Original text: At this stage, you’re first goal is to understand

Maybe It should be ? … “At this stage, your first goal is to understand” (your, instead of you’re)

2019-04-30
5TYPO

Original text: Our focus will be primarily in three areas. (and then displays a list of three bullet points)

Other parts of the book use “:” before a list of bullet points, but here is different, it’s using a final hard dot.

2019-04-30
29SUGGEST

At the end of the discussion of structs it says:

“In this book, we’ll use structs primarily for internal interfaces, except when we’re building common infrastructure.”

Could you add a small example, or perhaps a pointer to another part of the book where this principle is put into practice? I found this sentence to be unclear (perhaps, in part, because I come from a world where “infrastructure” refers to machines and networks and the like).

2019-04-30
3DEFER

I was really confused to have been introduced to the names of the layers, but not their definitions, and then transition immediately into building some code.

What was missing for me was a simple description of what each of the layers meant. I think a short bullet chart with one or two sentences about what each layer was for would have been handy. THEN introduce the projects and discuss how each will tie back to those definitions.

2019-09-09We will collect comments like this and prioritize them, getting to as many as we can.
9TYPO

At the top of the page you say, “Crack open lib/counter/lib/counter/server.ex and key this in:”

I think the path should be:

lib/counter/server.ex

2019-04-30
9SUGGEST

The GettingStarted/counter/lib/counter.ex code is split over two pages in the PDF but in the ePub and downloaded code there is an empty line where this happens (after ‘receive do’) and it may be a result of the PDF code’s layout.

However if you run ‘mix format’ against the code with the empty line after ‘receive do’ it removes it.

So, my suggestion for this (and all of the code samples) would be to run it against ‘mix format’ to standardise it.

For better or worse we now have a code formatter and I think that adhering to its rules would be a good thing for sample code.

2019-04-30I don't think we're going to do this. Good comment, but books are a special case and I think we want to control the formatting closely.
15SUGGEST

Towards the bottom of the page you say, “There you have it. Our counter is done and you’ve seen all of our layers. Let’s wrap up, and then we’ll be ready to introduce the layers step by step.”

However this chapter was an introduction to the layers so parhaps say something like, “There you have it. Our counter is done and you’ve seen all of our layers. Let’s wrap up, and then we’ll be ready to go into more detail about each layer in turn.”

2019-04-30
24TYPO

In “Random Access in Lists” paragraph,
Original Text: " To accessing the third element…"
Perhaps it should be " To access the third element…"

2019-04-30
5TYPO

James Twitter Handle is jeg2, not geg2 on the last page of „Online Resources“

2019-04-30
109ERROR

> Let’s write our first cast callback, the one to build a quiz:

This line says we are going to write a cast callback but the following example is for a call callback.

> Boundary/lib/mastery/boundary/quiz_manager.ex
> def handle_call({:build_quiz, quiz_fields}, _from, quizzes) do quiz = Quiz.new(quiz_fields)
> new_quizzes = Map.put(quizzes, quiz.title, quiz)
> {:reply, :ok, new_quizzes}
> end

2019-04-30
110ERROR

> This final cast looks up a quiz and return it to the user.

1. This line refers a a cast but the example before this line was for a call.
2. Perhaps ‘return’ needs an ‘s’ -> “and returns it to the user.”

2019-04-30
25TYPO

The first diagram seems to be the same as the second and is probably the wrong one. In case it isn’t, I found it confusing as an explanation for what you wanted to do to the list.

2019-04-30
24TYPO

In the last line you say that you want to add an item to the list. You then explain how you replace an item in the list.

2019-04-30
82SUGGEST

In the sentence: “We use Map.fetch! because we want to…” you write Map.fetch!, but the first listing on the page (Tests/test/support/quiz_builders.exs) uses Keyword.fetch!. Maybe just write “fetch!” or “Keyword.fetch!”

2019-04-30
45TYPO

The diagram on this page hasn’t been reproduced properly in the ePub or Kindle versions of the book. I realise that this will eventually be sorted out as a publishing issue (along with general image quality issues in these formats too) but it might be an easy fix for the next beta.

2019-04-30you might be seeing the stacked boxes, meaning multiple instances.
25ERROR

The section on lists states: “Streams provide some wonderful properties because Elixir is a lazy language.”
IMO Elixir is strict/(eager), it does provides lazy enumeration API’s.

2019-04-30
48TYPO

name of file should be lib/mastery/core/response.ex not lib/mastery/respone.ex

2019-04-30
16TYPO

“2. A function invoked with with the same inputs” -> “A function invoked with the same inputs”

2019-04-30
87ERROR

The test “a timestamp is added at build time” incorrectly compares DateTime structures with “<”:

assert response.timestamp < DateTime.utc_now()

DateTimes should be compared with the “compare” function, as per the DateTime documentation.

2019-04-30we'll handle code changes in a later beta
6TYPO

point 2 of that page: A function invoked with with …

2019-04-30
1OK

I find the reference to “do fun things with… something” confusing and annoying. I don’t think anyone needs that analogy. You can safely assume your audience is familiar with the concepts described in the book. No need for that extra layer.

2019-04-30
65TYPO

The last paragraph starts with, “Then pick_current_question adds that list to a quiz.”

It’s adding a question however, not a list.

2019-04-30
66TYPO

On the paragraph below the first code block you say, “Moving a template to used or any other field is the same, so we generalize the concept. We remove the quiz from the quiz.templates list and then add it to the specified field of quiz.”

The second sentence should be, I think, “We remove the template from the quiz.templates…”

2019-04-30
9ERROR

“Here’s the magic. The receive message allows us to interact with the server at each iteration of the loop.”

I wouldn’t refer to the receive block as a message. If “block” isn’t a good enough abstraction, I would then refer to it as the receive “mailbox”.

2019-04-30
9SUGGEST

A chapter 1 code example of “Establish Your Boundaries” could be made simpler without losing the point of the discussion.
The Counter.Server.run/1 does not add to the discussion and can simply be removed. Instead, have Counter.Server.listen/1 recurse at the end of each message processed. In addition, have Counter.start/1 spawn the process with its entry point Counter.Server.listen/1 instead of Counter.Server.run/1.
I think the code example is simpler, which makes the Boundary discussion clearer.

2019-06-29Whenever I have to repeat an operation over and over again, I prefer to separate the operation from the loop. The two main reasons for this are that it keeps each chunk of code worrying about just one thing and it also makes testing much easier since you can trigger individual steps as needed.
12SUGGEST

The last sentence of the first paragraph of section “Keep Your Functional Core Separate”.
“If we need to, we can then wrap that core in a process layer to concurrently share our data.”

Something about combining “share our data” and an isolated OTP process in the same sentence feels O-Oish to me.
Probably just me…

2019-06-29 \nGood point. Let me think about this one.
10TYPO

The iex(2) line is missing the “iex(2)>” prompt. The call for Counter.state/1 is partially missing and has “.Api” in it as well. It should be “iex(2)> Counter.state(counter_pid)”

2019-04-30
57TYPO

‘reexamine’ in paragraph 2 should be hyphenated.

2019-04-30
57TYPO

In the 3rd paragraph you say, “… and a checker functions to test results.” This should either be ‘and checker functions’ or ‘and a checker function’

2019-04-30
57TYPO

The last paragraph starts with, “EEX is a module used to compile idiomatic Elixir templates…”. The opening EEX should be EEx.

2019-04-30
57TYPO

In the last paragraph you say, “Though our template looked complex at the surface…”. On the surface is more usual.

2019-04-30
102TYPO

In the second paragraph you say, "… and the external systems our boundary might uses can fail. Uses should be use.

2019-04-30
58TYPO

The second paragraph begins with, “We will need to use the template to generate the question text we put in asked, and we’ll store the template we use to generate a question, as well as the substitutions we’ll choose from.” The substitutions store the chosen values, not the values we can choose from. Maybe change to, “… as well as the substitutions we chose.”

2019-04-30
78TYPO

The first new paragraph on the page starts with: “Fixtures are constructors. They are convenience functions that return complex data project-specific structures for the purposes of tests.” It would read better as: “Fixtures are constructors. They are convenience functions that return complex, project-specific, data structures for the purposes of tests.”

2019-04-30
79OK

At the end of the quiz_builders.exs code block add a final ‘end’.

2019-04-30This is actually part of another listing. We break longer listings into pieces.
1OK

The mnemonic isn’t working for me and seems to be in the way all over the text. It leads to things like diagrams with “Loud” and “Wildebeest” in them. Why? When you break it down to that level “loud” has absolutely nothing to do with “lifecycle.” It was supposed to be a helpful way to remember 6 words, but now it’s everywhere in the text itself.

The mnemonic is neither helpful nor needed. In a book targeting experts the six concepts are already quite familiar. The alternate six words are just a mess when they’re used.

Take page 22 for example. If you read the presumably important first words in the diagram you get “Loud, Wildebeests, Big, Fun, Do.” What?

Compare this to simply labeling them “Lifecycle, Workers, Boundaries, Functions, Data.” I realize that those are also in there, but they’re hiding behind this set of words that are supposed to be helping. It’s clutter.

2019-04-30
10TYPO

Missing iex(2)> in interactive example

2019-04-30
10TYPO

On the runtime example:
- er.Api.state(counter_pid) is used instead of Counter.state(counter_pid)

2019-04-30
23ERROR

“Atoms are quite efficient, taking a single byte, plus a lookup table.” Atoms use 1 word plus a lookup table, not 1 byte.

2019-04-30
33SUGGEST

This paragraph is probably worth the price of admission:
“”We start with a function called square. It takes a point and a size, and transforms that data to the same square format we saw earlier. This technique has far reaching implications for a language built on the actor model7 with heavy distributed computing influences: don’t send the data to the functions because that’s slow. Send the functions to the data!“”

However it leaves me hanging. Despite the footnote, I don’t know what the ‘far reaching implications’ are. And I really want to know. Consider expanding this point. (Great work so far)

2019-06-29We will think about this one.
34TYPO

We emphasizing the need to access lists head first.

2019-04-30
59SUGGEST

This is the worst kind of errata… Joe needs to be referred to in the past.

“Joe Armstrong, one of the creators of Erlang, used to say that we’re always taking the data to the code…”

2019-04-30
28SUGGEST

This also occurs on page 59 but you refer to a “two-tuple” which might be normal but seemed odd to me. A two-element tuple sounds better,

2019-04-30
5TYPO

In: “Just as an artist needs to learn to use the colors on their palate”, palate shouldn’t be palette?

2019-04-30
1-15SUGGEST

I think the structuring of the concepts (Data, Functions, Tests, Boundaries, Process Lifecycle, Process Deployment) valuable
The mnemonic does not work for me as mentionend by others. It confused me and
I think it is hard to learn and although I learned to appreciate mnemonics if you need to know something by heart
I am not convinced, that it is necessary for illustrating the concepts mentioned.

2019-04-30
41SUGGEST

As we describe the problem, think about the nouns, those will be Data.. (p 41.),
this reminds me of the early days in OO-Design, it did not work out there very well…
I’d rather like to think of facts and keep the state of the facts as mentioned in the Immutability drives everything chapter

2019-04-30
41ffSUGGEST

Explanation of data use (Prefer Flat Data to Deep Ones) is good.
The modules with the data in chapter 3 are just given. For me on first reading it is a too big leap as to why you arrived at this structure, or not yet really fully explained yet.
I would prefer a sample mastery quiz game for illustration of the needed data.

2019-06-29This section has improved a lot, since this comment was made. I suspect it's better now.
3426TYPO

Looks like some text is missing or there is an unintentional paragraph break. The end of the page is:

"The defstruct macro adds the struct function to User with two arities. The zero arity function creates a default struct and the second takes a list of key- value pairs.

and a predetermined list of attributes, :name and :email. One capability of structs that’s often missed is the @enforce_keys module attribute. You can use it to"

2019-04-30
118115SUGGEST

“We alias the only two functions we need” should probably be “We alias the only two modules we need”

2019-04-30
124121SUGGEST

I believe the API layer is reaching too deep into the GenServers.
Also some behaviour feels out of place.

- Out of place:

in Boundary/lib/mastery.ex:

def start_quiz_manager(opts), do: QuizManager.start_link(opts)

This would require you to have a start_link function in the QuizManager module, which also feels more naturally.

- Reaching too deep:

Boundary/lib/mastery.ex:

def build_quiz(fields) do
with :ok <- QuizValidator.errors(fields),
:ok <GenServer.call(QuizManager, {:build_quiz, fields}), do: :ok, else: (error> error)
end

Would make more sense if it looked like this:

def build_quiz(fields) do
with :ok <- QuizValidator.errors(fields) do
QuizManager.build_quiz(fields)
end
end

2019-06-29I agree. Fixed.
84SUGGEST

The second paragraph in the Prime Tests With Named Setups section begins with, “In version 1.3, Elixir released a describe block.” It would be clearer if it said something like, “In version 1.3 of Elixir the ExUnit.Case module added a describe block.” It’s a bit more explicit and lets the reader easily go and read about it in the docs if they want to.

2019-04-30
24TYPO

In the last line of the last paragraph you say, “Let’s say you want to replace an item to a list”. It should be “Let’s say you want to replace an item in a list:”

2019-05-18
30SUGGEST

“Strings Are Bitstrings”

Isn’t it more accurate to say “Strings are Binarys”? Isn’t a binary called a bitstring when the total size of all the values isn’t a multiple of 8?

2019-05-18
30SUGGEST

Section: “String Traps”
Sentence: “For example, the BEAM shares very long strings across processes, and lets them go after all references are cleared.”

I’ve tried searching the Erlang / BEAM online docs for references to this as I’d like to nail down when does a string become a “very long string”. Most helpful if a link is provided to the relevant Erlang docs.

2019-06-29Anyone know where to find this documentation? If so I will add it
46TYPO

In the “Our Flow” aside, there is this sentence: “We made mistakes, refactored our
data, refactored our functions and them made more mistakes.” The word “them” should be “then”.

2019-05-18
44TYPO

Correcting my previous report; I was looking at B1 still, but in B2 the typo is on page 44 not 46:

“We made mistakes, refactored our data, refactored our functions and them made more mistakes.” Should be “then,” not “them.” In the “Our Flow” section at the top of the page.

2019-05-18
144TYPO

starup should be startup ( shutdown and starup policies)

2019-05-18
146ERROR

select_question/1 and pid/1 are not implemented so the example id broken.

In the iex session:

Mastery.select_question user1

(FunctionClauseError) no function clause matching in GenServer.whereis/1

The following arguments were given to GenServer.whereis/1:

# 1
{:simple_addition, “mather_of_the_universe@example.com”}

2019-06-29
39SUGGEST

Section: New Facts Don’t Invalidate Old Facts, 1st paragraph
Sentence: “Multiple processes can access the same variables without having to deal with the data changing out from under them.”
Something I’m not groking here. If Elixir processes are nominally isolated from each other, how can they access the same variables (memory locations)?

2019-06-29It's not strictly true that Erlang/Elixir processes have complete isolation.
48SUGGEST

Section Start With the Right Data, 4th paragraph:
“Finally, we applied all of those lessons using a functional component. We
used templates with functions to generate questions, and we used functions
to check each question we created. When we were done, we had a rough
skeleton to build on.”
Seems to me the project structs were specified, not actually used, or implemented. Chapter 4 holds the promise of actually implementing and using those specifications.

2019-05-18
55SUGGEST

Section “Edit to a Single Purpose”, Paragraph 6, Text: “Since struct! takes some fields as a KeywordDict , we’ll conform to that API.”

I can’t find information on KeywordDict searching both Elixir & Erlang references.

2019-05-18
77TYPO

2nd paragraph, last sentence.
Change “We’ll use test/test_helper.ex” to “We’ll use test/test_helper.exs”.

2019-05-18
77TYPO

3rd paragraph, last sentence.
Lowercase USING

2019-05-18
77-78SUGGEST

Shouldn’t references to “Mastery.QuizBuilder” simply be “QuizBuilder” ?

2019-05-18I think it's right. We're just showing the reduced ceremony.
78TYPO

Section “Provide Test Data With Fixtures”, Line: “Recall that our quizzes have the following structure:”

The example defstruct is missing key/value: “last_response: nil”

2019-05-18
9ERROR

In the paragraph starting with: “Here’s the magic.” there is a sentence “The state message simply sends a message back to the server.”
I think it should be stated that the message is sent back to the client sending the {:state, pid} message

2019-06-24
48DEFER

An object oriented veteran like me tries to picture a class diagram model showing the relationships between created data structures (or the ERD diagram if it was supposed to be the database design). I can follow the relationships based on the text but some kind of diagram maybe would help to see better what is going on and how the data will cooperate.

2019-09-09We'll put it on the list and if there's time we'll do it.
88TYPO

It seems to me that the passage starting with “Keep in mind that this approach is not the only one that we could have chosen…” has been revised in the next couple of paragraphs, but the original version has accidentally been kept.

2019-06-29
103TYPO

case expression is missing it’s input

2019-06-29The input is piped in with the pipe operator (`|>`).
9SUGGEST

In general, I think it would be nice if the code aligned with what “mix format” would do by default; eg.

diff —git a/lib/counter.ex b/lib/counter.ex
index 1c15cf1..578d750 100644
—- a/lib/counter.ex
+ b/lib/counter.ex
@@ –1,9 +1,9 @@
defmodule Counter do
def start(initial_count) do
- spawn( fn() -> Counter.Server.run(initial_count) end )
+ spawn(fn -> Counter.Server.run(initial_count) end)
end

def tick(pid) do
- send pid, {:tick}
+ send(pid, {:tick})
end
end

Applies beyond this particular example, of course :)

2019-06-19We're not going to fix that. Fixing each example individually is tough and running mix format reshapes the code in ways I am not willing to do. \n \nI know some will disagree, including some of our reviewers, but in my mind, it's ultimately a good call.
10ERROR

Macros for eg. GenServer are just used for injecting default callback implementations, avoiding some boilerplate, but the last paragraph states: “In Elixir, OTP uses the magic of macros to build all of this, the recursive loop, the message passing and more. It hides many of the messy details from you.”

2019-06-24
88OK

Discussion of function eventually_match/2, paragraph 6.
“We want to make sure that this exact generator will eventually generate a specific number we’re calling answer.”

It appears you want to replace ‘answer’ with ‘number’.

2019-06-29
26ERROR

Error in response

So a User is actually a map with a struct field. Let’s look at the functions User
supports. In IEx, type “User.” and then type tab, twice.
iex(5)> User.struct
struct/0 struct/1
iex(6)> User.struct
%User{age: nil, name: nil} <— this should be %User{email: nil, name: nil}
iex(7)> User.struct name: “James”
%User{age: nil, name: “James”} <— this should be %User{email: nil, name: “James”}

2019-06-24
27SUGGEST

`````
def speak(%Animal{type: “dog”}), do: “Woof”
def speak(%Animal{type: “cat”}), do: “Meow”
````````

Could clear some confusion by stating this needs to be defined in the module, otherwise you get:
`
iex(23)> def speak(%Animal{type: “dog”}), do: “Woof”

(ArgumentError) cannot invoke def/2 outside module
(elixir) lib/kernel.ex:5222: Kernel.assert_module_scope/3
(elixir) lib/kernel.ex:3982: Kernel.define/4
(elixir) expanding macro: Kernel.def/2
iex:23: (file)
`

2019-06-24
xTYPO

In the first paragraph of the “Wildebeest Driven Design” section of “Introduction”, the first sentence:

“James came up with…to remember the layers: Data, functions, tests, boundaries, layers, workers.”

should be:

“James came up with…to remember the layers: Data, functions, tests, boundaries, lifecycles, workers.”

2019-06-24
118113TYPO

The title “Test Drive the Quiz Manager” at the top of the page should be “Test Drive the Quiz Session”.

2019-07-31
146141ERROR

In the paragraph after the select_question/1 and answer_question/2 function listing, first sentence, you state that these functions call pid(name) to look up process names. It should say that these functions call via(name).

2019-07-31
122ERROR

In the section about the boundary layer >> “Build the API layer” the validator for the Mastery.Examples.Math.build_quiz for the field :title fails because the validator is expecting a string whereas the example is using an atom. This is also reflected in the code example.

2019-09-09Sorry, but I haven't been able to reproduce this in the final version of the book.
12OK

If you’re like us, you’ve found a valuable companion in Elixir, with some
characteristics you believe can help you with some of these challenges, even
if you don’t fully understand it.
This: characteristics you believe can help you with some of these challenges
Should probably be: characteristics WE believe can help you with some of these challenges

But to be honest this sentence doesn’t ring.

2019-07-31
46DEFER

When comparing tuples with lists, the book uses the fact that you can use the “get_in” and “put_in” functions with lists as an advantage over tuples, because it makes writing and reading equally complex.

However, you could also use those functions with tuples, you just have to switch from “Access.at” to “Access.elem” and the code works the same.

It’s a small thing, but it may have a negative impact depending on the reader.

PS.: I love your books and I’m really enjoying this one so far! Keep being awesome!

2019-09-09This is a fair point and obviously correct, but we worry that asides like this detract from the main discussion. So, we hear you, but we're choosing not to go into the difference here.
7SUGGEST

“Recall that our public API had two functions…” Was this already mentioned somewhere? AFAICT there is no mention of the public API before this sentence.

2019-07-31
34TYPO

first line of the second text block (first complete para on the page)

…. leave Elixir is an SQL database.

should be

…. leave Elixir is a SQL database.

2019-09-09
164ERROR

The start_quiz/2 function does not work as written as add_template is not defined in the Proctor module. I was able to get it to work with the following:

defp start_quiz(quiz, now) do
Logger.info “Starting quiz #{quiz.fields.title}…”
QuizManager.build_quiz(quiz.fields)
Enum.each(quiz.templates, &QuizManager.add_template(quiz.fields.title, &1))
timeout = DateTime.diff(quiz.end_at, now, :millisecond)
Process.send_after(self(), {:end_quiz, quiz.fields.title}, timeout)
end

2019-09-09If you download our code, you'll see a helper function that does the forwarding.
46TYPO

In the field listing: name, atom, instruction, etc.
‘instruction’ should be ‘instructions’ in order to be consistent with the defstruct definition that appears later in page 47.

2019-09-09
139TYPO

Erroneous backslash in iex console input, after …“name: TestSupervisor])”. Should remove backslash so that reader knows to hit enter and get the message from start_link.

2019-09-09
148SUGGEST

The lookup function is:

> def pid({_title, _email}=name) do …

But we referred to the function in previous examples as via. One or the other isn’t quite right.

2019-09-09via() creates the tuples needed to feed to GenServer functions, to access a process via the Registry. pid() is a different function for retrieving the PID directly.
200ERROR

I had this problem with my code so I got the source code from the pragprog.com zip file.

After having run mix test in the root folder (mastery) in the BoundaryTests I get this

…………….

Finished in 0.2 seconds
16 tests, 0 failures

Randomized with seed 685609

Then after cd-ing into the mastery_persistence folder running mix test results in failed tests as can be seen below. Opening a psql session on mastery_test database and running delete from responses solves this error. However, there should be a clean way to have the mix test command clean the database after the test run.

1) test an error in the function rolls back the save (MasteryPersistenceTest)
test/mastery_persistence_test.exs:41
Assertion with failed code: assert Repo.aggregate(Response, :count, :id) 0
left: 9
right: 0
stacktrace:
test/mastery_persistence_test.exs:43: (test)

.

2) test simple reporting (MasteryPersistenceTest)
test/mastery_persistence_test.exs:51
Assertion with failed code: assert MasteryPersistence.report(response.quiz_title()) %{response.email() => 2, “other_#{response.email()}” => 1}
left: %{“other_student@example.com” => 1, “student@example.com” => 2, “yes_mathter@example.com” => 9}
right: %{“other_student@example.com” => 1, “student@example.com” => 2}
stacktrace:
test/mastery_persistence_test.exs:59: (test)

3) test responses are recorded (MasteryPersistenceTest)
test/mastery_persistence_test.exs:28
Assertion with failed code: assert Repo.aggregate(Response, :count, :id) 0
left: 9
right: 0
stacktrace:
test/mastery_persistence_test.exs:29: (test)

Finished in 0.1 seconds
4 tests, 3 failures

Randomized with seed 384668

2019-09-09Sorry, but I haven't been able to recreate this.
164TYPO

> We process a handle_info where we basically take advantage of two functions we’ve already built, start_quizzes and reply_with_next_quiz_timeout.

Function that’s used and called is build_reply_with_timeout not reply_with_next_quiz_timeout.

2019-09-23
26ERROR

There is an error in the iex console
iex(4)> map.STRUCT

(KeyError) key :STRUCT not found in: %User{email: nil, name: nil}

Followed by the corrected command
iex(4)> map.struct
User

2019-09-23
147SUGGEST

It seems like some code is missing. This section should probably suggest updating the following functions in the Mastery module to

def select_question(name) do
QuizSession.select_question(name)
end

def answer_question(name, answer) do
QuizSession.answer_question(name, answer)
end

166ERROR

Missing implementation of add_template in proctor.ex

181TYPO

Bullet list under the heading “Integrate MasteryPersistence Into Mastery” -

2nd bullet 3rd word: “perstence” instead of “persistence”

145ERROR

The description of restart option of child_spec is wrong, they are :permanent, :temporary and :transient, they got confused with the supervisor strategies (:one_for_one, :one_for_all, …)

6SUGGEST

On page 6, the path mentioned /lib/counter/core.ex. But, this is not the same on a windows machine. After running mix new counter, we have a /counter/lib/counter.ex file. You may need to clarify the path for windows and unix-based OSs regarding the path. To get /counter/core.ex, it has to be created manually. The way it is written is as if this has been generated for you.

7ERROR

In the Clock example, the print statement when running the program should start with:
The clock is ticking with 0
The clock is ticking with 1…

In the PDF B6 version, page 7 shows the print out starting with “The clock is ticking with 1”, but that can’t be the case since the run statement is run(f, 0) and the first call to your_hearts_desire is before the increment while count is initialized to 0.

You guys should be testing your own code.

111TYPO

In the Math module, the quiz_fields function return a title that is an atom. This makes the QuizValidator.validate_title on page 115 (PDF) fail since it validates the title to be a string when applied on page 124 (PDF) as:

iex(3)> Mastery.build_quiz Math.quiz_fields

(FunctionClauseError) no function clause matching in Mastery.Boundary.QuizValidator.validate_title/1

The following arguments were given to Mastery.Boundary.QuizValidator.validate_title/1:

# 1
:simple_addition

Attempted function clauses (showing 1 out of 1):

def validate_title(title) when is_binary(title)

(mastery) lib/mastery/boundary/quiz_validator.ex:12: Mastery.Boundary.QuizValidator.validate_title/1
(mastery) lib/mastery/boundary/validator.ex:19: Mastery.Boundary.Validator.check_required_field/5
(mastery) lib/mastery/boundary/quiz_validator.ex:6: Mastery.Boundary.QuizValidator.errors/1
(mastery) lib/mastery.ex:11: Mastery.build_quiz/1

122SUGGEST

The QuizManager have API-functions for build_quiz, add_template and lookup_quiz_by_title yet only lookup_quiz_by_title is used in the Mastery module. The others call GenServer.call directly.

123SUGGEST

The QuizSession have API-functions for select_question and answer_question but they are not used by the Mastery module. Instead there are calls to GenServer.call directly.

92SUGGEST

All the setup functions in chapter 5 merge in the values they set up in the context. This is not needed since ExUnit handles that for you.
See: ex_unit docs: ExUnit.Callbacks.html#module-context

Do:
defp quiz(_context) do
{:ok, quiz: build_quiz_with_two_templates()}
end

Dont:
defp quiz(context) do
{:ok, Map.put(context, :quiz, build_quiz_with_two_templates())}
end

149ERROR

If the Math quiz title is an atom the QuizValidator will fail. If it is a String the output would be different.

iex(4)> title = Math.quiz.title
:simple_addition

90SUGGEST

Maybe it should be mentioned, that the downside of the stream method for random generators is, that without setting the test timeout explicitly or with using —trace, eventually_match/2 will run the full 60 seconds, before it fails, when no match is found.

148TYPO

The printed Mastery module code does not reflect the archived code.

The API implementation for answer_question/2 and select_question/1 calls the GenServer in QuizSession directly, whereas the function in the archive call the QuizSession API, as it is intended.

When following the print version, this leads to exceptions, as we had to pass the session through Mastery.Boundary.QuizSession.via/1

167TYPO

“We will need to revise the QuizManager to return the active sessions for a quiz and also adapt QuizSession to end the sessions for a given title as our scheduler terminates them.”

The QuizManager needs to be able to remove a quiz, not return active sessions (that is a job for the QuizSession)

182ERROR

The mastery_persistence dependency in mastery’s mix.exs has incorrect path, it should be:

{:mastery_persistence, path: “mastery_persistence”}

189TYPO

We’d just replace the SessionServer with a SessionChannel, and that Channel module would accept the exact same messages, though we might not choose to wrap those handle calls in a public API.

There is no module called SessionServer in Mastery, it’s called QuizSession (Mastery.Boundary.QuizSession)

201ERROR

> Finally, we make sure the function in fact records responses with a simple
> aggregate call to our Repo that counts distinct :id values.

This is not what the code does in the example. It reads the Response rows from the database and make sure it matches the response email from the setup.

148ERROR

In “Touch Up the API Layer”, the select_question and answer_question functions do need to be modified. In both, the first argument to GenServer.call/3 need to be wrapped into a :via tuple:

session —> QuizSession.via(session)

166ERROR

The implementation of add_template used in start_quiz is missing from the book.

def add_template(quiz, template_fields) do
QuizManager.add_template(quiz.fields.title, template_fields)
end

176ERROR

With Elixir 1.9.4, use Mix.Config is deprecated in favor of import Config.

117TYPO

Int the Validator module, function “require” maybe missing the “d” letter because “require” is a macro in Elixir I suppose?
When I type these in my editor it understand that it is a macro and not a custom function.

Thank you for the book, it is amazing :)

117ERROR

The check_field function returns a list of errors in all cases but one:
defp check_field(:ok, _errors, _field_name), do: :ok
This is a bug because later when combining functions:
[]
|> required(…) #returns an error
|> optional(…) #returns OK
If the required portion returns an error, but the optional one doesn’t, it will ignore the first error.

329ERROR

When writing test for MasteryPersistance it’s needed to change the Sandbox mode to manual. Otherwise transaction will be committed and data leaked between tests.

Adding Ecto.Adapters.SQL.Sandbox.mode(MasteryPersistance.Repo, :manual) to the test_helpers will solve this problem.

28TYPO

The two function definitions at the top of the page seem to be invalid, as the function name is missing.

6060TYPO

I think in the first code snippet 2 and 3 lines should be indented 2 spaces

6060TYPO

2 and 3 lines of first code snippet should be indented

169ERROR

After working through the Chapter on Workers I ran into an issue where the code blows up with an ArgumentError. I thought I had merely made a mistake somewhere, but after pulling down the sample code and running it in code/Workers I encountered the exact same ArgumentError. The StackTrace is included:

❯ iex -S mix
Erlang/OTP 21 [erts-10.2.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]

Compiling 14 files (.ex)
Generated mastery app
Interactive Elixir (1.8.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> alias Mastery.Examples.Math
Mastery.Examples.Math
iex(2)> alias Mastery.Boundary.QuizSession
Mastery.Boundary.QuizSession
iex(3)> now = DateTime.utc_now()
#DateTime<2020-03-08 02:20:00.597349Z>
iex(4)> five_seconds_from_now = DateTime.add(now, 5)
#DateTime<2020-03-08 02:20:05.597349Z>
iex(5)> one_minute_from_now = DateTime.add(now, 60)
#DateTime<2020-03-08 02:21:00.597349Z>
iex(6)> Mastery.schedule_quiz(Mastery.Examples.Math.quiz_fields(), [Math.template_fields], five_seconds_from_now, one_minute_from_now)

18:23:44.059 [info] Starting quiz simple_addition…

18:23:44.079 [error] GenServer Mastery.Boundary.Proctor terminating

(ArgumentError) argument error
:erlang.send_after(–163462, #PID<0.227.0>, {:end_quiz, :simple_addition})
(mastery) lib/mastery/boundary/proctor.ex:76: Mastery.Boundary.Proctor.start_quiz/2
(elixir) lib/enum.ex:769: Enum.“each/2-lists^foreach/1-0”/2
(elixir) lib/enum.ex:769: Enum.each/2
(mastery) lib/mastery/boundary/proctor.ex:66: Mastery.Boundary.Proctor.start_quizzes/2
(mastery) lib/mastery/boundary/proctor.ex:36: Mastery.Boundary.Proctor.handle_call/3
(stdlib) gen_server.erl:661: :gen_server.try_handle_call/4
(stdlib) gen_server.erl:690: :gen_server.handle_msg/6
Last message (from #PID<0.229.0>): {:schedule_quiz, , start_at: #DateTime<2020-03-08 02:20:05.597349Z>, templates: [[name: :single_digit_addition, category: :addition, instructions: “Add the numbers”, raw: "<= left %> + <%= right ", generators:{left: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], right: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}, checker: #Function<0.89711312/2 in Mastery.Examples.Math.template_fields/0>]]}}
State: []
Client #PID<0.229.0> is alive

(stdlib) gen.erl:169: :gen.do_call/4
(elixir) lib/gen_server.ex:986: GenServer.call/3
(mastery) lib/mastery.ex:29: Mastery.schedule_quiz/4
(stdlib) erl_eval.erl:680: :erl_eval.do_apply/6
(elixir) src/elixir.erl:258: :elixir.eval_forms/4
(iex) lib/iex/evaluator.ex:257: IEx.Evaluator.handle_eval/5
(iex) lib/iex/evaluator.ex:237: IEx.Evaluator.do_eval/3
(iex) lib/iex/evaluator.ex:215: IEx.Evaluator.eval/3

(exit) exited in: GenServer.call(Mastery.Boundary.Proctor, {:schedule_quiz, , start_at: #DateTime<2020-03-08 02:20:05.597349Z>, templates: [[name: :single_digit_addition, category: :addition, instructions: “Add the numbers”, raw: “<%= left %> + <%= right %>”, generators:{left: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], right: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}, checker: #Function<0.89711312/2 in Mastery.Examples.Math.template_fields/0>]]}}, 5000)
(EXIT) an exception was raised:
(ArgumentError) argument error
:erlang.send_after(–163462, #PID<0.227.0>, {:end_quiz, :simple_addition})
(mastery) lib/mastery/boundary/proctor.ex:76: Mastery.Boundary.Proctor.start_quiz/2
(elixir) lib/enum.ex:769: Enum.“each/2-lists^foreach/1-0”/2
(elixir) lib/enum.ex:769: Enum.each/2
(mastery) lib/mastery/boundary/proctor.ex:66: Mastery.Boundary.Proctor.start_quizzes/2
(mastery) lib/mastery/boundary/proctor.ex:36: Mastery.Boundary.Proctor.handle_call/3
(stdlib) gen_server.erl:661: :gen_server.try_handle_call/4
(stdlib) gen_server.erl:690: :gen_server.handle_msg/6
(elixir) lib/gen_server.ex:989: GenServer.call/3
(mastery) lib/mastery.ex:29: Mastery.schedule_quiz/4

211206TYPO

In the description of the notify_stopped function the book states:

> If there’s a pid, we send a tuple with the :stopping_quiz atom and

But the code sample sends a :stopped atom.

Categories: