【问题标题】:Code duplication in Elixir and EctoElixir 和 Ecto 中的代码重复
【发布时间】:2017-08-27 16:51:44
【问题描述】:

我想用更小的模块组成一个模块。

这是我现在拥有的一个模块:

defmodule Api.Product do
  use Ecto.Schema
  import Ecto.Changeset
  import Api.Repo
  import Ecto.Query

  @derive {Poison.Encoder, only: [:name, :brand, :description, :image, :rating, :number_of_votes]}
  schema "products" do
    field :name, :string
    field :brand, :string
    field :description, :string
    field :image, :string
    field :rating, :integer
    field :number_of_votes, :integer
    field :not_vegan_count, :integer
  end

  def changeset(product, params \\ %{}) do
    product
    |> cast(params, [:name, :brand, :description, :image, :rating, :number_of_votes, :not_vegan_count])
    |> validate_required([:name, :description, :brand])
    |> unique_constraint(:brand, name: :unique_product)
  end

  def delete_all_from_products do
    from(Api.Product) |> delete_all
  end

  def insert_product(conn, product) do
    changeset = Api.Product.changeset(%Api.Product{}, product)
    errors = changeset.errors
    valid = changeset.valid?
    case insert(changeset) do
      {:ok, product} ->
        {:success, product}
      {:error, changeset} ->
        {:error, changeset}
    end
  end

  def get_product_by_name_and_brand(name, brand) do
    Api.Product |> Ecto.Query.where(name: ^name) |> Ecto.Query.where(brand: ^brand) |> all
  end

  def get_products do
    Api.Product |> all
  end
end

但我想要除Product 之外的其他东西,它们都具有与Product 相同的大部分字段,除了brand。因此,最好创建一个包含除brand 之外的所有字段的模块,然后包含这些字段的所有模块都将该模块作为字段?

这是所有模块都包含的我的模块:

defmodule Api.VeganThing do
  use Ecto.Schema
  import Ecto.Changeset
  import Api.Repo
  import Ecto.Query

  @derive {Poison.Encoder, only: [:name, :description, :image, :rating, :number_of_votes]}
  schema "vegan_things" do
    field :name, :string
    field :description, :string
    field :image, :string
    field :rating, :integer
    field :number_of_votes, :integer
    field :not_vegan_count, :integer
  end
end

vegan_things 将没有数据库表。但是一些具有数据库表的不同模块将包含vegan_thing

这是避免在 Elixir 的每个模块中重写每个字段的代码重复的好方法吗?

这是我当前的变更集:

defmodule Api.Repo.Migrations.CreateProducts do
  use Ecto.Migration

  def change do
    create table(:products) do
      add :name, :string
      add :brand, :string
      add :description, :string
      add :image, :string
      add :rating, :integer
      add :number_of_votes, :integer
      add :not_vegan_count, :integer
    end

    create unique_index(:products, [:name, :brand], name: :unique_product)
  end
end

所以我将唯一性基于vegan_thing 中的一个字段和一个仅在product 中的字段。我可以这样做吗?

defmodule Api.Repo.Migrations.CreateProducts do
  use Ecto.Migration

  def change do
    create table(:products) do
      add :name, :string
      add :vegan_thing, :vegan_thing
    end

    create unique_index(:products, [:vegan_thing.name, :brand], name: :unique_product)
  end
end

或者我必须将name 字段直接放在product 中吗?而不是 vegan_thing 能够将其用作唯一约束?

【问题讨论】:

标签: ecto elixir


【解决方案1】:

宏可以用于这种情况:

  defmodule Vegan do
    defmacro vegan_schema name, fields do
      quote do
        schema unquote(name) do
          unquote(fields)
          field :name, :string
          field :description, :string
          field :image, :string
          field :rating, :integer
          field :number_of_votes, :integer
          field :not_vegan_count, :integer
        end
      end
    end

    def changeset(struct_or_changeset, params) do
      struct_or_changeset
      |> Ecto.Changeset.cast(params, [:name, :description, :rating])
      |> Ecto.Changeset.validate_required([:name, :description])
    end
  end

  defmodule Product do
    use Ecto.Schema
    require Vegan

    @derive {Poison.Encoder, only: [:name, :brand, :description, :image, :rating, :number_of_votes]}
    Vegan.vegan_schema "products" do
      field :brand, :string
    end

    def changeset(params) do
      %Product{}
      |> Vegan.changeset(params)
      |> Ecto.Changeset.cast(params, [:brand])
      |> Ecto.Changeset.validate_required([:brand])
    end
  end

对于处理Ecto.Changeset 的其他函数,常规模块和函数应该可以很好地分解出任何重复的代码,如上面的示例所示,其中Product.changeset/1 调用Vegan.changeset/2 来强制转换和验证公共字段。

【讨论】:

  • 谢谢。我是否将基本变更集放在 Vegan 模块中,并将其较小版本放在包含模块中?
  • 是的,我已经用一个简单的例子更新了答案。特别是,Ecto.Changeset.cast 将接受模式结构或Changeset 作为第一个参数,允许组合变更集函数。
猜你喜欢
  • 2017-01-12
  • 2016-10-26
  • 2015-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-20
  • 1970-01-01
相关资源
最近更新 更多