Ecto: Inserting associated rows transactionally

Not sure why I can’t seem to find a very clear example or description of what I’m trying to do, and I’m afraid I’m just temporarily stuck in “Rails head”. But I can’t seem to find a definitive “best practices” example of how to create a set of associated records transactionally in Ecto.

Case in point: Playing around with Ueberauth, and I have a User record which has_many OAuth records, which stores the identifying information for that OAuth source (Facebook, Twitter, etc). The idea is that one User may have associated multiple OAuth sources, and can log in with any of them.

So, in the case where this is a brand new login, say from Facebook, I need to create the OAuth record AND the User record, preferably at the same time. The OAuth record belongs_to User (has the user_id foreign key), so User has to be saved first.

Do I have to roll my own transaction around the two inserts? Or can I just set the user association in OAuth to the User changeset, and have Ecto be smart enough to create the dependent record first?

I would think this would be a pretty common question, particularly for people coming from ActiveRecord, but for whatever reason, I can’t seem to find the magic Google incantation to find a good answer…

…Paul

Paul,

it seems to me like you are looking for Ecto.Multi. It’s good good docs so I’ll just refer you to it:
https://hexdocs.pm/ecto/Ecto.Multi.html

but please do ask if you have additional questions

2 Likes

Ecto is able of inserting entire association graphs (since 2.0, since 1.0 except belongs_to associations) in the correct order inside a transaction, filling foreign keys where necessary. So the simplest solution to your question is:

user = Repo.insert!(%User{authentications: [%Authentication{provider: "facebook"}]})
```

When using changesets, you'll find functions like `put_assoc` (used when programatically manipulating associations), and `cast_assoc` (used when you want to handle associations coming in external params) handy.

When you need more control, either explicit "by-hand" transaction with `Repo.transaction/2` or `Ecto.Multi` is the way to go.
2 Likes

Ecto.Multi is especially awesome as you can compose the operations then perform it as fast as possible inside a transaction. :slight_smile:

2 Likes

Thanks, I’ll look closer at put_assoc and cast_assoc. I’m just surprised I haven’t seen too many tutorial-like examples of doing this; I figured it was probably handled somehow.

…Paul