josefrichter
Hi, I have a function in context that reads value from other table before inserting a new record. Specifically, it checks class capacity before adding new enrollment into it.
See:
defmodule Enrol.Classes do
def create_enrollment(%{"class_id" => class_id} = attrs \\ %{}) do
# check if class capacity is not exhausted
# and inserts only then, otherwise rolls back
Repo.transaction(fn ->
capacity = Repo.one!(from c in Class, where: c.id == ^class_id, select: c.capacity)
count = Repo.one(from e in Enrollment, where: e.class_id == ^class_id, select: count(e.class_id))
case count < capacity do
true ->
# maybe call insert_enrollment(attrs) from here?
%Enrollment{}
|> Enrollment.changeset(attrs)
|> Repo.insert()
false ->
IO.puts "Class capacity EXCEEDED, rollback"
Repo.rollback(:class_full)
end
end) # end transaction
end
# default generic function
def create_enrollment(attrs \\ %{}) do
%Enrollment{}
|> Enrollment.changeset(attrs)
|> Repo.insert()
end
# alternative (just renamed)
def insert_enrollment(attrs \\ %{}) do
%Enrollment{}
|> Enrollment.changeset(attrs)
|> Repo.insert()
end
end
When writing unit tests for this context, this would mean creating a class fixture first, so that I can read its capacity, before testing insertion of the enrollment. I feel like the unit test should be more granular, and just check whether plain vanilla insertion works.
So my questions are
- should I split my function so that it does the checks and then calls the plain vanilla create function?
- should I test these two separately? and does it make sense to test each of them?
- do I need to rename the second one to
insert_enrollment? because they have the same arguments, co callingcreate_enrollmentin the first one would basically result in recursive call and infinite loop, right?
Thank you very much!
Trending in Questions
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
Hi everyone.
My team and I have been working on a fairly modest app based around video streaming and chat, but we’ve landed a customer t...
New
Hello,
I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind.
However, when I launch mix phx.server, I get an error...
New
I really like the adapter patterns that ecto, nebulex, waffle, etc. use and would love find something similar for a key management servic...
New
Hello folks!
So at work, we are seeing some situations where we have to define some “fixed” strings that are used across the codebase in...
New
How Can I Optimise Compile Time Dependencies
I have been building an elixir application for about 2 years now. Many modules and files ha...
New
Other Trending Topics
A little off-topic, but I feel like people here have a good head on their shoulders.
I used to be quite good at making software. Was luc...
New
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
I love Elixir. It’s one of 2 programming languages I’ve ever fallen in love with.
But I don’t use it anymore.
Serverless was the promis...
New
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
- #ai
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #blog-post
- #elixirconf-us
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #elixirconf-eu
- #api
- #forms
- #security
- #metaprogramming










Showing Posts 1 to 7- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
csadewa
hmmm, your question is more of code structuring, and there’s room for opinion & preference on it, so i would answer just like how i would prefer to do it:
insert_enrollmentis a bit dubious (for me anyway) as it’s really standard code from ecto, i usually trust ecto to do it’s job.create_enrollmentwould be better as it’s more standard.just for example, here’s how business logic code might be:
josefrichter
Cool, thank you! where would your
enroll_student/2go? If it doesn’t belong to the context, but probably also not to controller, where should it actually be?csadewa
oh, you could actually create a new module within context layer, at above i call it:
Though what i usually do is actually move the data module inside, and use main context as logic module, so for example:
Enrol.EnrollmentEnrol.Enrollment.Enrollment,Enrol.Enrollment.ClassEnrol.Enrollment.Schema.Enrollments,Enrol.Enrollment.Schema.Classstefanchrobot
I would suggest writing the use cases for your app and translating them into tests. It’s much better to have the unit of the test be a business use case then a context, module, function or a table.
I don’t know the details of your domain and app, so I’m going to make a bunch of assumptions. I’d suggest something like this:
AdminUIandStudentUIare test helpers that model the actions that admins and students can perform in your app. It’s up to you to decide what’s the best way to implement those: you can use the API if you have one, use context or insert straight into the DB if that makes sense.This way, the tests are going to be disconnected from the implementation and you’ll have much greater freedom in changing things if needed.
josefrichter
Yes, those I was going to write next!
I was just wondering whether the logic should be separated from the basic CRUD functions in context, and whether it makes sense to actually test those basic CRUD functions. A generator gives you those functions including tests, so I guess it doesn’t hurt to keep them, I just wasn’t sure whether the context functions are generally to be left untouched, and context-related logic be put into separate modules as suggested above.
stefanchrobot
The generator is unable to generate anything more useful since it doesn’t have any understanding of the domain.
I have an app where the tests follow that pattern, but the more I look at them, the more I think they are a liability than help. I think I’m going to nuke them.
josefrichter
shortly before midnight, but all tests written and passing now
thank you all for help!