rsmml
Hello there, dear community!
I have been dealing the last days with a function that save multiple records at once using Ecto.Multi. At the moment on my code I have 2 functions that successfully works (yeey
) using Multi too.
The logic of this function is to take the Attributes comming after submiting a form. The aim of this form is to allow users to Sign-up for a Sports Club: This form could send:
A - The data to insert 1 new user. (Already working
).
B - The data to insert 2 new users, a minor user + the “Legal guardian” (Already working
).
C - (The one I am currently working on) That will insert X amount of users (group of users like 2 parents and 2 children for instance).
Starting from function A.
def create_membership(attrs, club_id, plan_ids) do
# This will store the new user changeset
membership_cs = Membership.create_changeset(attrs, plan_ids)
multi = Multi.insert(Multi.new(), :insert_membership, membership_cs)
# list of events to record on the activity logs table.
~w(register_membership accept_articles join_club)a
|> Enum.reduce(multi, fn event, multi ->
Multi.run(multi, {:insert_activity_log, event}, fn repo, %{insert_membership: membership} ->
membership = Repo.preload(membership, :club)
Users.update_user(membership.user, %{membership_id: membership.id})
log_params = %{event: event, actor: :membership, data: %{club_name: membership.club.name}}
%ActivityLog{}
|> ActivityLog.create_changeset(membership.club, membership, log_params)
|> repo.insert()
end)
end)
|> Repo.transaction()
end
Function B.
This will record 2 new users, a minor user and an adult user. The logic the for the sport clubs is also to save the users on a family group.
def create_membership_with_group(attrs, club_id, plan_ids) do
membership_cs = Membership.create_changeset_minor(attrs, plan_ids)
membership_guardian = Membership.create_changeset(attrs, []) #not passing plan_ids since the guardian is not an active member of the club, only pays for the kid.
multi =
Multi.insert(Multi.new(), :insert_membership, membership_cs)
|> Multi.run(:insert_guardian, fn repo, %{insert_membership: membership} ->
Users.update_user(membership.user, %{membership_id: membership.id})
membership_number = generate_membership_number(membership.membership_number)
repo.insert(
Ecto.Changeset.put_change(
membership_guardian,
:membership_number,
membership_number
)
)
end)
# After the new 2 memberships are created, we create a new group, and we insert both memberships.
|> Multi.run(:create_group, fn repo, %{insert_guardian: guardian} ->
repo.insert(Group.create_changeset(attrs, guardian))
end)
|> Multi.run(:update_group, fn repo, %{insert_membership: membership, create_group: group} ->
repo.update(Group.add_memberships_changeset([membership], group))
end)
# List of events to record on the activity-logs table.
~w(register_membership accept_articles join_club)a
|> Enum.reduce(multi, fn event, multi ->
Multi.run(multi, {:insert_activity_log, event}, fn repo, %{insert_membership: membership} ->
membership = Repo.preload(membership, :club)
Users.update_user(membership.user, %{membership_id: membership.id})
log_params = %{event: event, actor: :membership, data: %{club_name: membership.club.name}}
%ActivityLog{}
|> ActivityLog.create_changeset(membership.club, membership, log_params)
|> repo.insert()
end)
end)
|> Repo.transaction()
end
Function C.
This will record X new users (minimum 2 as the example above, but it could be up to 12 new users), The logic of the membership group is to have between 1 Parent and 1 children (But in this case the parent is an active members of the club, so it has plans), and 2 Parents and 10 children. The logic the for the sport clubs here is also to save all the new memberships into a family group, as Funciton B.
My attempt:
def create_membership_with_family_group(attrs, club_id, plan_ids) do
# This creates the changeset for the Payer of the Group (User on the form)
membership_cs = Membership.create_changeset(attrs, plan_ids)
# This Returns a list of Changesets for the rest of the members of the group (without the payer).
membership_fm = []
membership_fm = for _group_member <- attrs["group_members"] do
group_member = Membership.create_changeset_family_group_members(attrs, [])
membership_fm ++ group_member
end
# The boths variables have the required data, which I checked with the inspector.
# Here is where my code starts to sink.
# Since I have now X amount of memberships, I should do a loop over the list of memberships_fm.
# On my logic I should insert the first changeset, membership_cs, and then loop over the rest and insert them too. I am trying to follow the logic from Function B but I am confused on the loop part.
multi_payer = Multi.insert(Multi.new(), :insert_membership, membership_cs)
multi =
membership_fm
|> Enum.reduce(multi_payer, fn group_member, multi_payer ->
Multi.insert(Multi.new(), :insert_membership, membership_cs)
|> Multi.run(:insert_family_group, fn repo, %{insert_membership: membership} ->
Users.update_user(membership.user, %{membership_id: membership.id})
membership_number = generate_membership_number(membership.membership_number)
# Here where I think I should start to loop over membership_fm.
repo.insert(
Ecto.Changeset.put_change(
group_member,
:membership_number,
membership_number
)
)
end
end)
|> Multi.run(:create_group, fn repo, %{insert_membership: payer_group} ->
repo.insert(Group.create_changeset(attrs, payer_group))
end)
|> Multi.run(:update_group, fn repo, %{insert_family_group: memberships, create_group: group} ->
# Here I insert the list of memberships into the group already created with the payer.
repo.update(Group.add_memberships_changeset([memberships], group))
end)
end)
# List of events to record on the activity-logs table.
~w(register_membership accept_articles join_club)a
|> Enum.reduce(multi, fn event, multi ->
Multi.run(multi, {:insert_activity_log, event}, fn repo, %{insert_membership: membership} ->
membership = Repo.preload(membership, :club)
Users.update_user(membership.user, %{membership_id: membership.id})
log_params = %{event: event, actor: :membership, data: %{club_name: membership.club.name}}
%ActivityLog{}
|> ActivityLog.create_changeset(membership.club, membership, log_params)
|> repo.insert()
end)
end)
|> Repo.transaction()
end
I hope it’s enough information, if needed I can post more on the thread. I will truly appreciate your time to read and give me a feedback.
Thanks a lot! ![]()
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #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
- #blog-post
- #elixirconf-us
- #elixir-ls
- #ai
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 6- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
dimitarvp
When I need to iterate on stuff I sometimes use Ecto.Multi — Ecto v3.14.0. In your case you should also notice that you’re not making a unique key per each insert.
Instead of using
:insert_membershipyou should either use a tuple like{:insert_membership, something_unique_here}or just a key with a number contained inside it likeString.to_atom("insert_membership_#{key}").That’s where I’d start.
stefanchrobot
Since you seem to be doing only DB operations in the transaction, I would suggest trying doing this with
Repo.transaction. In cases where theMultiis not passed between contexts, I found working directly with the transactions easier.To make this work, I usually follow these rules:
withto code the happy path and have a fallback for the error path,Something like:
rsmml
Thanks @stefanchrobot and @dimitarvp Both answers helped me to go through a solution. Indeed the
String.to_atom("insert_membership_#{key}")was a key part of it.My solution:
At the moment of updating the
created group, I have to pattern match with the key I used withString.to_atom("insert_family_group_#{index}"). I couldn’t find a way to make the key “dynamic” so that I pattern match like %{insert_family_group_“#{index}”: membership, …} for instanceI tried doing something like
but
membershipandgroupare not defined. Is there any way to solve that?Thanks for the replies, and sorry for the late reply
stefanchrobot
The key can be any term, so you can do
update_group = {:update_group, index}. This should help you remove the repetitive code.dimitarvp
That’s a super long function. Break it apart a little bit.
rsmml
Hey guys @dimitarvp @stefanchrobot . Thanks again for the reply! Now the code is way much shorter, and thanks @stefanchrobot for the tip, it helped a lot to get rid of the repetitive code
Thanks!
