Вывести значение поля из другого поля - PullRequest
0 голосов
/ 19 ноября 2018

Я хочу установить значение поля структуры в зависимости от значения другого поля.

Это метод, к которому я пришел, когда я жду, пока не вызовут changeset, и вставлю его между валидаторами. Это правильный / ожидаемый способ сделать что-то подобное?

Одна из проблем, возникающих при этом, заключается в том, что если кто-то пишет fruit = %Fruit(%{name: :lemon}), он не может получить доступ к fruit.tastes, пока не сохранит и не загрузит запись (или, по крайней мере, не вызовет какую-нибудь оболочку для changeset и 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

1 Ответ

0 голосов
/ 19 ноября 2018

Я не думаю, что у вашего решения есть какие-либо проблемы, но вы рассматривали добавление: вкус в attrs перед кастомом, например:

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

должен по-прежнему корректно проверяться, и вам не нужно беспокоитьсяесли вкус меняется или нет.

...