Phoenix with EctoDripper, a tutorial (I guess)

Fabian Zitter
7 min readJun 26, 2018

The Phoenix 1.3 release and their change in the way generators work, the code it scaffolds for us and most importantly how we, as developers, should think about our phoenix-applications has changed a lot for me in the last few month. I actually feel like my understanding of elixir and especially phoenix has gone from “I have no ideas what I’m doing guys, but I would like to know more about this thing” to “guys, this makes sense.”

I can’t emphasize enough how much finally cleaning up the (my) misconception of the web folder has done for me in terms of thinking about using phoenix as my transport layer, instead of putting everything in there and get frustrated when the whole thing starts to bloat up like my old rails apps used to bloat. “Damn you elixir and phoenix!” I thought, “you were supposed to defeat the bloat, not join it!”

If you have similar notions about rails, phoenix or any other framework, don’t worry friend, it is us who is using it wrong, not the frameworks fault. We have been putting — stuffing — our apps with business logic like it’s not our business (logic), hid functionality away in tightly coupled modules and threw out our likes to tweets that were condemning our framework of choice for not supporting big applications as they should.

Finally, one of the frameworks I use has stepped up and did the education I needed in form of generators that will actually generate something that I am trying to embrace now: Domain Driven Design.

Some actual code in this tutorial

Enough rambling already, show me the goods you say? Of course you came here to learn about that package you just downloaded into your awesome application!

Or not… I know you probably didn’t know about the package before, first of all because it didn’t exist, and second because I wrote it — and I am pretty sure you have no idea who I am, unless we have met.

EctoDripper is a small package I accidentally wrote while working in a project using Absinthe to create a GraphQL API. I wanted to share not only my code, but also explain the way I am using it, because I know the frustration when you find a tool and then are lost in how it is supposed to help you in any way — it is usually me who is screwing the usage up, but sometimes the problem is simply that I can not find the resources I need to make use of it the way I would like to.

Anyways, lets start already. Phoenix Context documentation uses the Accounts context for their introduction, lets stick to that and go through the motions. We will use the generator for the sake of scaffolding, but I would like to use this opportunity to point out that we are in no way tied to the way the generator structures files and modules, it is simply a suggestion.

[You can find the code for this tutorial here]

Up up and scaffold away

Lets start with creating our phoenix app and the contexts — we will skip all view layers (no html, no json) because we want to focus on database queries using ecto:

mix phx.new dripper_example --no-brunch --no-html && cd dripper_example

Now we create our contexts (contextes? conti?)

mix phx.gen.context Accounts User users name:string username:string:uniquemix phx.gen.context Accounts Credential credentials email:string:unique user_id:references:usersmix ecto.create && mix ecto.migrate

Lets go and fix our relations in the schemas

Excellent, that breaks our tests!

It breaks our tests

The problem is that we set the user_id as required, but the generated tests do not care about relations. Lets fix that really quick, we are going to change our fixture in the testfile so it uses a user — that is not how I usually do that, mind you. I would usually use ExMachina to set up factories, but lets go quick and dirty here. Find the describe "credentials" line and replace the block with this code

What we did here is add a build_valid_attrs/1 function to add a user_id to the valid attributes and also replace @valid_attrs in the create test with that function. Lets run our tests again.

Now everything is green

Alright. Time to do real work!

Lets take a second and think about a real world example that could come up and we can solve. Usually whenever you are querying for a user, you would query for a user name. So that’s a good starting point. Lets start out with writing tests, so we know we are not screwing this up

touch test/dripper_example/accounts/find_user_test.exs

This is a quick implementation to make this test pass (I put the find_user function into its own module, so it is easier to follow this tutorial, I would actually not do this in a real app):

To make this work, add {:ecto_dripper, "~> 0.1.0"} to your mix.exs file and run deps.get . Done.

What’s going on here

Good god, you are right… That seems an awefull lot like magic and the reason we all love phoenix is because it does not do (much) magic, isn’t it? Don’t worry, the underlying code is really not all that complicated, and you could write it out instead of using the convenience matchers instead, but to be honest I got tired of that pretty fast, so I put the basic operators into EctoDripper as a convenience function:

[:x, :==], [:x, :!=], [:x, :>=], [:x, :<=], [:x, :<], [:x, :>]

And they do exactly what you would think they do, they check for equality, non equality, greater than, greater than equal etc… With the help of meta programming, it creates the methods for you.

Now this is a very simple query and it doesn’t really show what the point behind EctoDripper is, because is it only using a built in matcher, we could have done the same in a few lines of code, using the ecto DSL. However, I consider this a win, since we tucked our query away into it’s own module, didn’t have to write out anything and got a pretty readable code… But lets explore a little more!

Down and dirty with a more complex example

I now want to build on what I already have and go completely crazy! Lets query for a username AND the email he put into his credential — there goes my real life example! Phew, aren’t we some daredevils!
In order to do that, lets add a test that asserts we get the right user for a username-email combination, and not to get the user for a wrong combination.

The magic word here is “combination”. I want my code to find a user based on a combination of queries, but conserve the original purpose to find a user based on only one of those possible arguments. In other words, I want to be able to find a user by his username, or by username AND email, depending on what arguments I pass:

AccountUsers.find_user(%{username: "abc"})
# => %User{username: "abc"}
AccountUsers.find_user(%{username: "abc", email: "abc@email.com"})
# => %User{username: "abc", credential: %Credential{email: "abc@email.com"}}
AccountUsers.find_user(%{username: "xyz", email: "abc@email.com"})
# => nil

Lets update our Query modules and make the test pass:

Add query_email/1

In this case, there is no built in function for a join, and I am not planing on doing that ever, it would require people to learn a DSL on top of the ecto DSL, that would be stupid. Instead we can create the query ourselves and tell EctoDripper which function to use to resolve query_email .

Then why don’t we just write the whole thing with ecto?

Because there is actually some code hidden away, code I deem not worth reasoning about over and over again, because it is always the same.

This is nice. Don’t you think?

this is nice

I think it is! It is nice, because we can use both query functions in our find_user/1 without breaking the original functionality of finding a user based on his username, we save a few lines of boilerplate code, create something like a “Query Index” on top of the module, and are still explicit in the one function that defines the important logic i.e. the query!
Composable Queries, quick and easy.

Now, in conclusion I ask you to see this code as an example using some quick and dirty techniques and code to get to the topic faster. There are a few things in there I would not consider best practice and I deliberately did not comment on WHERE to put your query module, because that is a whole other beast and a discussion better lead by people who actually know what they are talking about ;) The gist is, I found it very useful to put queries in dedicated modules, instead of the schemas, as I have seen others suggest. It makes for clearer boundaries if you have 2 different query combinations that do ALMOST the same, but not quite, like “find something for an admin user” VS “find something for a regular user.”

If you would like to do the same without EctoDripper, you can do so by applying the concepts from this post about composable queries or this one I found this morning!

Anyways, there are a few more options to EctoDripper I haven’t gone into yet, but I will wait to see if there are any users before jumping that gun :)

Thanks for reading!

--

--