从另一个字段推断字段值
Infer a field value from another field
我想根据另一个字段的值设置结构的字段值。
这是我到达的方法,我一直等到 changeset
被调用,然后将其插入验证器之间。这是做这种事的correct/expected方法吗?
这样做的一个问题是,如果有人写 fruit = %Fruit(%{name: :lemon})
,他们无法访问 fruit.tastes
,直到他们保存并加载记录(或至少调用一些围绕 [=11 的包装器) =] 和 apply_changeset
)。但这不是什么大不了的事,只是一个很好的选择。我不只是使用 Fruit.tastes(fruit)
函数的原因是因为口味必须在数据库中可索引。
defmodule Food.Fruit do
use Ecto.Schema
import Ecto.Changeset
@required_attributes [:name]
@optional_attributes []
@valid_names [:lemon, :banana]
@valid_tastes [:sour, :sweet]
schema "fruits" do
field :name, :string #user specified
field :tastes, :string #inferred from name
timestamps()
end
def changeset(fruit, attrs) do
fruit
|> cast(attrs, @required_attribtues ++ @optional_attributes)
|> validate_required(@required_attributes)
|> validate_inclusion(:name, @valid_names)
|> infer_colour
# these are debately needed, we should set things correctly in infer_colour
# but perhaps doesn't hurt to check to ward against future stupid.
|> validate_required(:tastes)
|> validate_inclusion(:tastes, @valid_tastes)
end
# name is invalid, so we can't infer any value
def infer_colour(%Ecto.Changeset{errors: [{:name, _} | _]} = changeset), do: changeset
# name isn't error'd so we can infer a value
def infer_colour(%Ecto.Changeset{} = changeset) do
case fetch_field(changeset, :name) do
# match on {:data, value} or {:changes, value} so if the user passes a
# custom :tastes to Fruit.changeset we default to overwriting it
# with our correct value (though cast(...) should be stripping :tastes if present)
{_, :lemon} -> change(changeset, %{tastes: :sour})
{_, :apple} -> change(changeset, %{tastes: :sweet})
{_. name} -> add_error(changeset, :tastes, "unknown :tastes for #{name}")
:error -> add_error(changeset, :tastes, "could not infer :tastes, no :name found")
end
end
end
我认为您的解决方案没有任何问题,但您是否考虑过在投射之前在 attrs 中添加 :taste,例如:
defmodule Food.Fruit do
use Ecto.Schema
import Ecto.Changeset
@required_attributes [:name]
@optional_attributes []
@valid_names [:lemon, :banana]
@valid_tastes [:sour, :sweet]
schema "fruits" do
field :name, :string #user specified
field :tastes, :string #inferred from name
timestamps()
end
def changeset(fruit, attrs) do
attrs = resolve_taste(attrs)
fruit
|> cast(attrs, @required_attribtues ++ @optional_attributes)
|> validate_required(@required_attributes)
|> validate_inclusion(:name, @valid_names)
|> validate_required(:tastes)
|> validate_inclusion(:tastes, @valid_tastes)
end
def resolve_taste(%{name: :lemon}=a),
do: Map.put_new(a, :tastes, :sour)
def resolve_taste(%{name: :apple}=a),
do: Map.put_new(a, :tastes, :sweet)
def resolve_taste(any), do: any
end
应该仍然可以正确验证,并且您无需担心口味是否发生变化。
我想根据另一个字段的值设置结构的字段值。
这是我到达的方法,我一直等到 changeset
被调用,然后将其插入验证器之间。这是做这种事的correct/expected方法吗?
这样做的一个问题是,如果有人写 fruit = %Fruit(%{name: :lemon})
,他们无法访问 fruit.tastes
,直到他们保存并加载记录(或至少调用一些围绕 [=11 的包装器) =] 和 apply_changeset
)。但这不是什么大不了的事,只是一个很好的选择。我不只是使用 Fruit.tastes(fruit)
函数的原因是因为口味必须在数据库中可索引。
defmodule Food.Fruit do
use Ecto.Schema
import Ecto.Changeset
@required_attributes [:name]
@optional_attributes []
@valid_names [:lemon, :banana]
@valid_tastes [:sour, :sweet]
schema "fruits" do
field :name, :string #user specified
field :tastes, :string #inferred from name
timestamps()
end
def changeset(fruit, attrs) do
fruit
|> cast(attrs, @required_attribtues ++ @optional_attributes)
|> validate_required(@required_attributes)
|> validate_inclusion(:name, @valid_names)
|> infer_colour
# these are debately needed, we should set things correctly in infer_colour
# but perhaps doesn't hurt to check to ward against future stupid.
|> validate_required(:tastes)
|> validate_inclusion(:tastes, @valid_tastes)
end
# name is invalid, so we can't infer any value
def infer_colour(%Ecto.Changeset{errors: [{:name, _} | _]} = changeset), do: changeset
# name isn't error'd so we can infer a value
def infer_colour(%Ecto.Changeset{} = changeset) do
case fetch_field(changeset, :name) do
# match on {:data, value} or {:changes, value} so if the user passes a
# custom :tastes to Fruit.changeset we default to overwriting it
# with our correct value (though cast(...) should be stripping :tastes if present)
{_, :lemon} -> change(changeset, %{tastes: :sour})
{_, :apple} -> change(changeset, %{tastes: :sweet})
{_. name} -> add_error(changeset, :tastes, "unknown :tastes for #{name}")
:error -> add_error(changeset, :tastes, "could not infer :tastes, no :name found")
end
end
end
我认为您的解决方案没有任何问题,但您是否考虑过在投射之前在 attrs 中添加 :taste,例如:
defmodule Food.Fruit do
use Ecto.Schema
import Ecto.Changeset
@required_attributes [:name]
@optional_attributes []
@valid_names [:lemon, :banana]
@valid_tastes [:sour, :sweet]
schema "fruits" do
field :name, :string #user specified
field :tastes, :string #inferred from name
timestamps()
end
def changeset(fruit, attrs) do
attrs = resolve_taste(attrs)
fruit
|> cast(attrs, @required_attribtues ++ @optional_attributes)
|> validate_required(@required_attributes)
|> validate_inclusion(:name, @valid_names)
|> validate_required(:tastes)
|> validate_inclusion(:tastes, @valid_tastes)
end
def resolve_taste(%{name: :lemon}=a),
do: Map.put_new(a, :tastes, :sour)
def resolve_taste(%{name: :apple}=a),
do: Map.put_new(a, :tastes, :sweet)
def resolve_taste(any), do: any
end
应该仍然可以正确验证,并且您无需担心口味是否发生变化。