Digging - Phoenix Models

cover Digging Phoenix Models

I'll start a new - well, and first too - series of posts called "Digging". Whenever I face an interesting and enriching digging through code I'll just write down the steps I took, in quite a verbose way, and what I brought back. Hope you enjoy it or, at least, find it useful!

Motivation

I wanted to give support to better interact with changes on models on the Addict lib.

Dig!

Phoenix brings the ability to generate stuff via mix phoenix.generate.model. Let's generate a model and see the output, as of Phoenix 0.14.1:

$ mix phoenix.generate model 
* creating priv/repo/migrations/20150725010851_create_user.exs
* creating web/models/user.ex
* creating test/models/user_test.exs

Inspecting the contents of web/models/user.ex:

Everything on the schema definition is pretty straight forward.
@required_fields and @optional_fields are also self describing.

And then we have changeset/2. From the signature it seems that it receives the model we generated and some params.
Wild guessing is that it should apply the paramsto the model. Onto de function body!

So it feeds the model into a... cast. Wat? Where's this cast defined? What does it do? And what does it return? It seems it makes some computations on the required_fields and optional_fields for our model. It has to be imported via use YourApp.Web, :model so let's dig through that one:

So it comes from Ecto.Model. Let's go to ecto in github and check it out! Searching for "def cast" shows us that the cast/4 is defined on Ecto.Changeset. There are a couple of function signatures for cast/4, and, better yet, great documentation! Checking the docs in iex for Ecto.Changeset and Ecto.Changeset.cast is a breeze:

iex(5)> h Ecto.Changeset  
# ---8<---- docs ---8<----
iex(5)> h Ecto.Changeset.cast  
# ---8<---- docs ---8<----

Lot's of good information here. We now know what a Changeset is - "Changesets allow filtering, casting and validation of model changes."; the fields of the Changeset; Ecto.Changeset.cast/4 job is that it "Converts the given params into a changeset for model keeping only the set of required and optional keys."; the type and structure of each parameter;

We just needed to check the docs to get a good understanding of what a Changeset is, what the function of cast/4 - pun intended - is for and how it operates.

Naming is hard. Writing the documentation is a bit easier and totally worth it.

In summary

Going back to our initial YourApp.User module, we'll want to invoke changeset/2 every time we wish to update it:

Digging into cast/4

Reading good documentation helps us improve our own documentation by osmosis. IMO, on open source projects, and specially libraries like this, we should always strive to have in mind the next person who'll be reading the code. What seems obvious to the original developer, may not be so obvious to the next one looking at it.

Besides documentation, the thing that really matters is the code. Reading good code can bring you something new to your perspective. I'll denote some nice findings from analysing this function's code.

Pattern matching

%{__struct__: module} is a great example of how to use pattern matching to actually decompose a structure.

Type handling

when is_list(required) and is_list(optional) is a great way to both control the code flow - only applies when these parameters are lists - and to semantically expose the code. Definitely better to read than a if or a when.

Enum.map_reduce

I feel dumb. I knew Enum.map/2 and Enum.reduce/3. I didn't know Enum.map_reduce/3.

iex> h Enum.map_reduce

                      def map_reduce(collection, acc, fun)

(...)

Examples

┃ iex> Enum.map_reduce([1, 2, 3], 0, fn(x, acc) -> {x * 2, x + acc} end)
┃ {[2, 4, 6], 6}

Handy! Note to self, go read Enum module documentation again.

Digged!

Hope you enjoyed! See you on the next digging :)

Check my other Elixir posts here