Generate Ecto model term from raw data?

I want to create Ecto model term from raw data.

ex)

defmodule Test do
  ...

  schema "tests" do
    field :number, :decimal
    belongs_to :relationship, Relationship
  end
end

# it returns `number: 1` and it's not a decimal.
%Test{number: 1, relationship_id: 1}
=> %Test{
  __meta__: #Ecto.Schema.Metadata<:loaded, "tests">,
  number: 1,
  relationship: #Ecto.Association.NotLoaded<association :relationship is not loaded>,
  relationship_id: 1,
}

# What I want
some_function(Test, %{number: 1, relationship_id: 1})
=> %Test{
  __meta__: #Ecto.Schema.Metadata<:loaded, "tests">,
  number: #Decimal<1>,
  relationship: #Ecto.Association.NotLoaded<association :relationship is not loaded>,
  relationship_id: 1,
}

Is there a good way to do that except I explicitly convert 1 to Decimal.new(1)?

Would Ecto.Changeset.cast/4 together with Ecto.Changeset.apply_changes/1 work for you?

def some_function(module, attrs) do
   %module{}
   |> Ecto.Changeset.cast(attrs, Map.keys(attrs))
   |> Ecto.Changeset.apply_changes()
end

Maybe you can do something other than Map.keys(attrs) in cast, that’s just an example and it’s dangerous.

Maybe

# ...
|> Ecto.Changeset.cast(attrs, keys(module))
# ...

defp keys(Test), do: [:number]

Or

def some_function(module, attrs) do
  %module{}
  |> module.changeset(attrs)
  |> Ecto.Changeset.apply_changes()
end

I wouldn’t cast foreign keys like :relationship_id in changeset though since that gives malicious users ability to get access to other “relationships” in this case (assuming changeset is called with data submitted by a user).


I wonder if this would work

%Test{number: 1, relationship_id: 1}
|> Ecto.Changeset.change()
|> Ecto.Changeset.apply_changes()
#=> %Test{number: #Decimal<1>, relationship_id: 1}
2 Likes