olivermt
Hello!
I am a guy who really don’t like declarative stuff and I don’t think there is enough plumbing in a regular Phoenix project that I think Ash has a void that I need to fill. But I watched the elixirconf video and realized I was maybe being a bit close minded. It seems it is indeed trying to solve not just plumbing/boiler plate, but also how you go about architecting a project. If I go from my negative stance of this way of “magic included” framework coding to either “oh cool less boilerplate” or “oh cool, a new way to think about how to build software” I will consider this a success.
The premise:
I am following a fairly advanced weightlifting program and the current plateau barrier breaking segments of the program are getting very hard to model sensibly with excel macros. I have been using excel macros as a crutch for logging weights and reps on my phone.
Essentially the program is a “endless” program meant for somewhat advanced lifters who just wanna hit the gym X times a week and get the most ROI. It works in the following way:
- Test week for two weeks, find your 8-12rep range (if you are beginner, if not you prolly know it already)
- Progression weeks are 5-8 to reps, if you make 8 reps you bump the weight
- Since this is a “I have a job, three kids and just need to minmax my gym effort” program, you bump it the minimal amount. This program prefers you to use machines because they are often much easier to scale in this way and there is way less ceremony in messing around with tiny plates of 0.25kg etc.
- If you plateau, you can regress down to 2 top sets instead of 1 top set. Then you only progress if you manage 5 + 5 reps. As in if you don’t manage to break 180kg leg press x 8, you regress down to trying to do 5 + 5 reps instead of the single top set.
After this? Rince and repeat. Unless you are an ex powerlifter who decided to come back after a year or two of break, this program should have you running for at least a couple of years until you stop seeing meaningful progress.
The tech / architecture I need to achieve this:
- Logging in (will use auth0 as free tier is generous and Ash seems to have Auth0 stuff)
- Set up your program
- How many days (I run 3-split on mine now)
- Which exercise per day
- Repeating exercises must be supported (as in I do leg press both day 1 and day 3) and weight increase should be weekly, not per day. So if I make 8 reps day 1 but only 6 reps day 3, it does not bump the weight until I make 8 reps both days.
- Exercise needs to be able to set rep-bump-weight (leg press machine has different intervals than shoulder press machine etc)
- Manual overrides are probably needed because of things I have foreseen. Also maybe to reset a weight if you have an injury or if you were busy moving houses over the summer while having a baby and you havent been able to see the inside of a gym for two months. This may or may not have been me over the summer.
- Stats (main reason I log in the first place, gotta have those sweet gains graphs)
- Mobile first (mobile only) liveview with tailwind.
This is a non-exhaustive list and I will update it (with notes and timestamps) as I go deeper in my ventures here.
I am going to be doing this over the course of a lot of nights as I am in paternity leave and I have very limited coding time.
So today was spent mainly to write this spec and note down what I need to do get going.
I hope this “blog” series on the forum can be two things:
- A nice intro to Ash framework for other people who wonder what the buzz is.
- A nice intro to how a 15 year veteran architect / CTO who has coded elixir for almost 10 years goes about modeling this toy project. I will be modeling it as if someone paid me to make some solid software, but with a small budget. I am not going to Enterprise this little baby of mine.
I got as far today as:
- Making @kip and @zachdaniel get together on slack to make ash_double_entry default (or at least more fully support) using ex_money so that the framework does not expose such a big foot gun to people doing something else than dollars. Any money involving project should always default to using ex_money and the underlying ex_cldr

- Realizing that it feels super weird to do mix new with a supervisor instead of mix phx.new which has been my default more or less since a few days after it was first released.
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #ai
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #hex
- #security










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
zachdaniel
Looking forward to seeing how this goes! It will be a win win for Ash no matter what your results end up being, since we’ll have the context and areas to improve!
kip
To be fair, @zachdaniel has always been amenable to collaboration and we’ve discussed it before. I took an action to drive the next step and then real life got in the way. I hope now the timing is better for all concerned.
sodapopcan
I actually do like declarative stuff and I’m big on DDD and Ash has always looked good to me (I’ve read through a lot of the docs) but I still haven’t tried it. I watched the talk last night and it got me a bit more excited again, especially when I saw code that looked my code that I’m generally really happy with and was told it could still be better
With some reflection there are few reasons I still haven’t tried it:
In any event, I look forward to this, and the workout focus of this could do me some good too
Btw, I love your presentation style @zachdaniel! I can’t put my finger on it but you held my attention the entire talk.
dimitarvp
This could be double interesting if OP makes sure to have some sort of milestones – e.g. GitHub releases – with traceable steps and not a huge number of commits / diffs in-between, so any onlookers can retrace his steps and see the meaningful changes step by step.
(Or, as a good CTO already knows –
– to make sure commits/PRs are more or less atomic.)
I have been tempted by Ash for at least a year but health and real life are constantly in the way so this would be a very welcome opportunity to observe passively every now and then.
So – good luck! Interested in the journey.
olivermt
Part 2!
I rounded off with how weird it was to start with mix new and a supervisor tree. Apparently that feeling of weirdness was correct, because the guides for AshPhoenix says to start with a mix phx.new indeed
I thought it was a bit weird that Ash hid and abstracted that much. This guide: Get Started with Ash and Phoenix — ash_phoenix v2.3.23 for those who are curious.
So: I have now done more due dilligence and read more than just the core Ash and Auth0 guides. The guide for AshPhoenix feels a lot more at home to me since I know Phoenix very well. I do wonder why Ash has such a hangup on using uuids. I am not a big fan of using uuids. If you need something opaque for the end user that isn’t a digit (like you don’t want to show your fancy new customer you just sold your software to that his company has a PK of 3) I have had great success with using ints internally and just displaying them externally with a HashedInt (hashids | Hex). Uuids has their place, like if you have needs where PKs needs to be generated outside your app and still come in with somewhat collision free guarantees etc but the downsides of uuids in postgres are pretty big imo. Clunky to work with when writing queries and it takes away some benefits of stable sorting etc. See running list of questions at the end.
The liveview usage segment intrigues me a bit, I do find typing out all the changeset blabla boilerplate in my service modules (context modules for you who like that nomenclature) to be a bit tedious, so I like that that is all taken care of. Including all the form helper stuff from lv 0.18+.
I am ready and I upgrade phoenix installer to the newest, do a mix phx.new growth (perfect name for a muscle building app) and immediately go in to sort the deps alphabetically before adding the ash ones. Gotta have the house in order, right???
I wondered why ash wants me to use their create repo command but realize now that it is so the extension installer can run. Not sure I am a big fan of that. It should just be emitted by the migration generators. It’s not like I’ll be running ash create against a prod instance anyways, so now I have to manually remember to fix that before I deploy this.
Also, the formatter documentation is out of date (told Zach on slack) as a default mix phx.new includes the html formatter plugin now and the example at Get Started with Ash and Phoenix — ash_phoenix v2.3.23 does not show it. Some newbs might be missing out on the awesomeness due to this, so beware
(Ok before I had time to mix deps.get and set up my Repo file, zach has already pushed a fix for this that will be include in next release, not bad!)
Repo is now set up and I am ready to start specifying my domain. One of the very few things I dislike (a lot) about the phoenix generators is that I think the Context design they produce is promoting sloppy modules. I think it is many orders of magnitude better than when noobs put all their business logic in the controller, but if you generate a couple of schemas you end up with a 800 loc context that is hard to navigate. I have been mandating a pattern of FooSchema (preferably anemic, no code that is not around validation, and no external module calls) coupled with a FooService. FooService deals only with Foo, or in some special cases maybe FooCloseRelative. Think User and UserRole. Any cross-domain communication should be done with a service talking to a service.
Ash resources are anything but anemic, but each section of the resource promotes a clean and anemic-style definition set. It also means that a Registry stays very terse and you never end up with the gazillion-LOC contexts that a generator based phoenix app gives you.
I set the default config required and map up a Growth.Workout registry. I start to map my first entity and I hit the first uuid-snag. I don’t want an uuid, I want an integer. I think the docs here should either showcase both side-by-side or at least have a documentation link directly to the attributes. I had to go hunting in the docs to find how to specify an integer. The doc search shows no results on “primary_key” (I would expect it to at least show the already existing “uuid_primary_key”, but no). I did find “integer_primary_key” by searching for the uuid one and clicking through. Ash is an elixir library after all, so as expected the docs are clear and informative when I just find the right spot
It is time for me to run
mix ash_postgres.createto see what all the fuzz is about! … aaand drumroll wut?Where are my extensions?
https://github.com/omt-tech/growth/blob/main/lib/growth/repo.ex#L5-L7
I was expecting the uuid-ossp and citext extensions to be active?
Anyways, at this point I realize that I should probably put in place the user system before I start mapping up too much of the programs and stuff because almost everything I want to model up is tied to the user. So I have now pushed up an initial commit at GitHub - omt-tech/growth · GitHub including my first api + resource and I plan to swap over to the User work to be able to log in with Auth0 tomorrow or the day after.
If my text seems jumbled it’s because I have been writing it on and off throughout the day, my lil six month old girl Ellinor does not find coding as exciting as I do apparently.
Questions (for @zachdaniel or whomever else wants to chip in):
zachdaniel
Great stuff!
UUIDs
UUID is mostly just a sane starting point, but you can use your own primary keys as well:
EDIT: one thing to keep in mind is that generated
belongs_toattributes default to:uuid. You will get a warning at compile time if you try to do something like have abelongs_torelationship from a:uuidtype to an:integertype, but basically for compilation-related reasons, we can’t magically assume the type of abelongs_torelationships source attribute to be the type of the field it refers to. So we just default to:uuid. You can configure the default here:and read more about it here: Relationships — ash v3.29.3
ash_postgres.create
mix ash_postgres.createis a thin wrapper aroundmix ecto.createthat finds the repos to use by looking at all the repos in use by your app. The migration generation happens when you domix ash_postgres.generate_migrations.With that said, we’ve got the work done and will be pushing some new mix tasks across the documentation, that automatically pick up what extensions you have and run any corresponding tasks. They are like built-in aliases with extra features.
In the context of
ash_postgres(the only one who has implemented the hooks for these new tasks) you have:mix ash.setup- creates your dbmix ash.codegen- generates migrationsmix ash.migrate- runs migrationsmix ash.tear_down- drops your dbdimitarvp
Nice, I subscribed to the repo’s PRs and releases. Also left you a comment on the 2nd commit.
outlog
this is great stuff - have had a keen eye on Ash for ages - but pragmatism/lack of time/risk aversion always end up postponing the dive in..
olivermt
You cant introspect the primary key of the remote schema at compile time?
zachdaniel
We can, but not in such a way that we can modify the current module (without inducing compile time dependencies that could result in deadlocks). So instead we check at compile time and validate using a compiler step that happens after modules have been compiled (after_verify).
Even if we could, if you were do do something like this (don’t):
where the attribute on
Foois a string and the attribute onBaris a uuid. This is valid in some cases (pretty sure not with postgres, though).