2017-08-24 4 views
0

Ich möchte ein Modul aus kleineren Modulen zusammensetzen.Code-Duplizierung in Elixir und Ecto

Dies ist ein Modul ich jetzt haben:

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 

Aber ich will andere verschiedene Dinge haben, als Product, die alle die meisten die gleichen Felder wie Product Ausnahme brand haben. Daher ist es am besten, ein Modul zu erstellen, das alle Felder außer brand enthält und dann alle Module, die diese Felder enthalten, dieses Modul als Feld haben?

Hier ist mein Modul, das alle Module enthalten würde:

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 

Es wird für vegan_things keine Datenbanktabelle sein. Aber einige verschiedene Module, die Datenbanktabellen haben, enthalten eine vegan_thing.

Ist dies ein guter Weg, um die Code-Duplizierung zu vermeiden, jedes Feld in jedem Modul in Elixir neu zu schreiben?

Hier ist meine aktuelle changeset:

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 

So die Einzigartigkeit auf einem Feld Ich bin stützen, die in vegan_thing und ein Feld sein würde, die in product nur. Kann ich so etwas tun?

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 

Oder muss ich in product direkt das name Feld setzen? anstelle von vegan_thing in der Lage sein, es als eine einzigartige Einschränkung zu verwenden?

+0

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

Antwort

2

Makros für diese Situation kann verwendet werden: mit Ecto.Changeset, dann regelmäßig Module und Funktionen den Umgang

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 

Für andere Funktionen sollten für Ausklammern jeden duplizierten Code in Ordnung sein, wie oben, wo Product.changeset/1 Anrufe im gezeigten Beispiel Vegan.changeset/2 zum Umwandeln und Validieren der allgemeinen Felder.

+0

Danke. Lege ich das Basis-Changeset in 'Vegan'-Modul und eine kleinere Version davon in das Subsuming-Modul? – BeniaminoBaggins

+1

Ja, ich habe die Antwort mit einem einfachen Beispiel aktualisiert. Insbesondere akzeptiert 'Ecto.Changeset.cast' eine Schema-Struktur oder ein' Changeset' als erstes Argument, wodurch Änderungsmengen-Funktionen zusammengesetzt werden können. –

Verwandte Themen