I see - I was not aware you could have associations without foreign key constraints but makes sense from the abstraction layer. This should definitely should be opt-in then.
I like José's first suggestion of having a flag in the association. It seems this is desirable for an association (ex you've set a foreign key constraint on it), it always would be desirable for that association. The second idea of `Ecto.empty_assocs(struct)` would be repetitive in those cases. Also if you wanted it on some associations and not others for that struct, there would have to be some way to specify that ex a second parameter and it might be open to errors if that parameter is not used consistently for the same struct.
A few naming ideas:
1. `has_many(:comments, Comment, empty_on_insert: true)` - would also be allowed on has_one and many_to_many. has_one's "empty" would be nil whereas the others would be Ecto.Association.NotLoaded. It could be possible to allow on belongs_to as well but it could do better in that it could check the corresponding id column to see if it's nil or not.
2. `has_many(:comments, Comment, default_on_insert: [])` - the nice thing about this is if the user does not provide it we could say it defaults to Ecto.Association.NotLoaded. I don't think this is better than 1) personally.
3. `has_many(:comments, Comment, default: [])` - this would shares a option with `field` but this could arguably be either a downside or an upside. has_many also already has a `defaults` option so might be best to not have another so close.
4. `has_many(:comments, Comment, defaults_to_empty: true)` - similar to 1) but closer naming to embed's defaults_to_struct. It may be ambiguous/confusing what happens in select/update cases though
This might be moot but I still don't understand the point with transactions. Here's what should happen today:
```
{post1, post2} = Repo.transaction!(fn ->
post1 = Repo.insert!(%Post{})
_comment1 = Repo.insert!(%Comment{post_id:
post1.id, text: "post1 comment"})
post2 =
%Post{}
|> Ecto.Changeset.change()
|> Ecto.Changeset.put_assoc(:comments, [%Comment{text: "post2 comment"}])
|> Repo.insert!()
_comment2 = Repo.insert!(%Comment{post_id:
post2.id, text: "post2 another comment"})
{post1, post2}
end)
# Current behavior passes
assert %{} = post1.comments
assert [_] = post2.comments
```
Why is post2.comments allowed to be unaware of the second comment but post1.comments is not allowed to be unaware of its comment?