I started talking about this here on GH: Add default argument to changeset generator in schema by srcrip · Pull Request #5721 · phoenixframework/phoenix · GitHub
I shared an example of how common this pattern is on gh: Code search results · GitHub
I’m talking about default attributes on changesets like this:
def changeset(thing, attrs \\ %{}) do
...
end
This is an extremely common pattern. I understand the idea right now is pushing people away from calling the Schema modules directly, and so the preference is towards defining the default attrs in the changeset function wrappers in contexts, but my counter point to that is:
- you can easily end up calling the same changeset in multiple contexts, in which case the default there comes in handy
- I just think it runs counter to the idea of letting people develop these patterns for themselves. its an extremely convenient default with almost no downsides.
I do this in every project, so I mean for myself I can just edit my own generators, but I really think this change would be preferrrable.
You lost me a bit on this one. This is true if you put all of your schemas under a MyApp.Schemas
module, but not when using the context pattern that the generators encourage. One context should never “reach into” another one without going through its surface API. This is exactly how people keep ending up with cyclical compile time dependencies and is what the Boundary library aims to prevent. While we do have these these references in schemas when defining relationships, this is a bit of a special case and should not be considered the norm.
While it’s clear many have, I’ve honestly never considered this before and have never put a default on the changeset itself.
Nah, I’d prefer explicitness in the lower-level modules and I view the schema modules as such. Nothing prevents you from running a global find-and-replace and have your project be the way you want it to but I’d be against this being a default. The default values are a higher-level concern (the only notable exception I can think of is in the DB where you don’t want to deal with NULL
and have a meaningful defaulit / sentinel value).
I’m more likely to do this sort of thing, so that for inserts the caller doesn’t have the extra noise of creating an empty schema struct.
def changeset(thing \\ %__MODULE__{}, params) do ...
5 Likes
My context functions are always:
def change_record(record \\ %Record{}, attrs \\ %{})
This is nice when using them in the web layer to build a changeset for an empty form, specifically so I don’t have to explicitly reference the struct at that level.
I used to be bothered about creating the empty struct in contexts but now not so much. It’s not like it’s a huge win or anything but it’s good signal when changeset
is used for both create and update.