Other than defining a method in the application model module (that accepts the model as a parameter), is it possible to define a computed field in an ecto schema or model?
For example, say we have a field ‘title’ (Mr., Mrs., Dr. etc.) and a field ‘name’, and we’d like a display field ‘display_name’ - which includes the concatenated ‘title’ (if present), with ‘name’. (e.g. Dr. James Gordon).
The concatenated field should also be lazy evaluated, so that there is no impact on Repo.all(model).
thanks @sztosz - I’d actually read this thread quickly, before posting here. The OP on that thread also raised the serialization issue, ensuring that the computed field appears in JSON as well. The after_load callback has been removed from Ecto 2.0. The ‘Store’ implementation suggested there looks like a valid approach. https://github.com/elixir-ecto/ecto/issues/405#issuecomment-231563686
I would probably add virtual: true field to the schema and a function that maybe populates it.
schema "users" do
field :title, :string
field :name, :string
field :display_name, :string, virtual: true
end
@spec maybe_populate_display_name(%User{}) :: %User{}
def maybe_populate_display_name(%User{title: nil, name: nil} = user) do
user # that's why it's maybe
end
def maybe_populate_display_name(%User{title: nil, name: name} = user) do
%{user | display_name: name}
end
def maybe_populate_display_name(%User{title: title, name: nil} = user) do
%{user | display_name: title} # because i'm so silly
end
def maybe_populate_display_name(%User{title: title, name: name} = user) do
%{user | display_name: title <> " " <> name} # however I would prefer iolist here
end
Actually, I think @outlog’s approach below is better.
You use Phoenix views even when you serve JSON API. Or other kind of API, or you can actually use them in different ways you want and they are very convenient way of providing sort of decorator to your structs.
I’m confused, this seems like the right idea, but how is this called? Do we have to use User.maybe_populate_display_name(user) every-time we want to populated a user struct to do user.display_name? I’m thinking that, with the exception of the requirement of lazy loading it may be best to create it in the changeset and store it in the db. Thoughts?
Current convention is to encapsulate all calls to the Repo inside the context module. So internally all those context get functions would need to use maybe_populate_display_name/1 - everybody else is supposed to be using the context (Accounts) functions - not the Repo functions; so it really isn’t that big of an issue.