Computing a virtual field from multiple fields in schema

868 views
Skip to first unread message

benjamin...@gmail.com

unread,
May 7, 2018, 3:27:03 PM5/7/18
to elixir-ecto
Hi,

Is there a way to compute virtual fields with other non virtual fields in a schema when loading from the database?

In my case I need to convert an amount and currency into a Money struct. The only way I see is to store a map in a single field, but I would like to keep this in multiple columns for SQL order, select and index.

I'm thinking about something similar to this:

```
defmodule Myapp.Cost do
  use Ecto.Schema
  alias Myapp.{Money, Product, Purchase}

  schema "cost" do
    belongs_to :product,          Product

    has_many :purchases,      Purchase

    field :amount,                     :integer,        null: false
    field :currency,                   :string,           null: false,   size: 3
    field :cost                           Money.Type, virtual: true, from: [:amount, :currency]

    timestamps()
  end
end

defmodule Myapp.Money.Type do
  @behaviour Ecto.Type
  def type, do: :any

  # Not sure if needed as it's a virtual field?
  def cast(_), do: :error

  # This is how I imagine computing the virtual field could work
  def load([amount, currency]) when is_integer(amount) and is_binary(currency)  do
    money = Money.from_integer(amount, currency)
    {:ok, money}
  end
  def load(_), do: :error

  # Not sure if needed as it's a virtual field?
  def dump(_), do: :error
end```
```
iex> Myapp.Repo.get_by(Myapp.Cost, id: 1)
%Myapp.Cost{id: 1, amount: 100, currency: "USD", cost: %Money{amount: 100, currency: :USD}}

iex> Myapp.Repo.get_by(Myapp.Purchase, id: 1) |> Myapp.Repo.preload(:cost)
%Myapp.Purchase{id: 1, cost: %Myapp.Cost{id: 1, amount: 100, currency: "USD", cost: %Money{amount: 100, currency: :USD}}}
```

Ivan Yurov

unread,
May 11, 2018, 12:11:28 AM5/11/18
to elixir-ecto
You can simply use something like select_merge:
```
select_merge(query, [c], %{cost: fragment("type_conversion_and_concatenation_function_that_your_db_provides(?, ?)", c.amount, c.currency)}
```
And your virtual field will be populated. But this will only work in simple cases that aren't involving application logic.

benjamin...@gmail.com

unread,
May 12, 2018, 12:14:47 AM5/12/18
to elixir-ecto
Thanks a lot, Ivan Yurov for your response but as you say yourself it only works in simple cases and that is not really what I'm looking for
I would argue that this could make Ecto way more powerful if we could do these kinds of post hooks.

Ivan Youroff

unread,
May 12, 2018, 2:24:39 AM5/12/18
to elixi...@googlegroups.com
I see what you mean, but I believe Elixir people would argue that this kind of computed attributes is something from OOP world, not FP. This might be implemented as a function that would inject this attribute inside of representation layer: JaSerializer/Absinthe or HTML.

--
You received this message because you are subscribed to the Google Groups "elixir-ecto" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-ecto+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-ecto/3c474307-6a49-4901-94b0-45cbfc8f5fda%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Kind regards,
Ivan Yurov
Reply all
Reply to author
Forward
0 new messages