2017-08-24 8 views
0

Je veux composer un module avec des modules plus petits.La duplication de code dans Elixir et Ecto

C'est un module que j'ai en ce moment:

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 

Mais je veux avoir des choses différentes autres que Product qui ont tous la plupart des mêmes champs que Product sauf pour brand. Donc est-il préférable de créer un module qui a tous les champs sauf brand et alors tous les modules contenant ces champs ont ce module comme un champ?

Voici mon module que tous les modules contiendraient:

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 

Il n'y aura pas de table de base de données pour vegan_things. Mais quelques modules différents qui ont des tables de base de données contiendront un vegan_thing.

Est-ce un bon moyen d'éviter la duplication du code de réécriture de tous les champs de chaque module d'Elixir?

Voici mon changeset actuel:

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 

Je me fonde l'unicité sur un terrain qui serait en vegan_thing et un champ qui est seulement dans product. Puis-je faire quelque chose comme ça?

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 

Ou dois-je mettre le champ name directement product? au lieu de vegan_thing pour pouvoir l'utiliser comme une contrainte unique?

+0

Comme ['Ecto.Schema.embedded_schema/1'] (https://hexdocs.pm/ecto/Ecto.Schema.html#embedded_schema/1)? – mudasobwa

Répondre

2

Les macros peuvent être utilisées pour cette situation:

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 

Pour d'autres fonctions relatives à Ecto.Changeset, puis des modules réguliers et fonctions devraient être bien pour factorisant tout code dupliquée, comme le montre l'exemple ci-dessus où Product.changeset/1 appels Vegan.changeset/2 pour lancer et valider les champs communs.

+0

Merci. Est-ce que je mets le changeset de base dans le module 'Vegan', et une plus petite version dans le module subsuming? – BeniaminoBaggins

+1

Oui, j'ai mis à jour la réponse avec un exemple simple. En particulier, 'Ecto.Changeset.cast' acceptera une structure de schéma ou un' Changeset' comme premier argument, permettant de composer des fonctions de changeset. –