Ecto، غلاف قاعدة البيانات غير المساومة لتطبيقات Elixir المتزامنة

Ecto

يجعل Elixir ببنائه البسيط والنظيف بناء تطبيقات متزامنة قابلة للتطوير والصيانة أمرًا سهلاً. Ecto عبارة عن غلاف قاعدة بيانات يرقى إلى مستوى التوقعات العالية التي حددتها سمعة Elixir. توفر لغتها الخاصة بالمجال طريقة ممتعة للتفاعل مع قواعد البيانات وإنشاء تطبيقات متزامنة تتسامح مع الأخطاء في Elixir بسهولة. في هذه المقالة يرشدنا مهندس برمجيات Toptal المستقل بوريس باروسو عبر Ecto ومكوناته الأربعة الرئيسية: Repo و Schema و Changeset و Query.

Ecto هي لغة خاصة بالمجال لكتابة الاستفسارات والتفاعل مع قواعد البيانات بلغة Elixir. يدعم الإصدار الأخير (2.0) PostgreSQL و MySQL.( سيتوفر دعم MSSQL و SQLite و MongoDB في المستقبل).

هل سئمت من كل لهجات SQL؟ تحدث إلى قاعدة البيانات الخاصة بك من خلال Ecto.

مكونات Ecto

يتكون Ecto من أربعة مكونات رئيسية:

  1. Ecto.repo. يحدد المستودعات التي هي أغلفة حول مخزن البيانات. باستخدامه يمكننا إدراج وإنشاء وحذف والاستعلام عن الريبو. مطلوب محول وبيانات اعتماد للتواصل مع قاعدة البيانات.
  2. Ecto.Schema. تُستخدم المخططات لتعيين أي مصدر بيانات في بنية إكسير.
  3. Ecto.Changeset. توفر التغييرات طريقة للمطورين لتصفية وإخراج المعلمات الخارجية بالإضافة إلى آلية لتتبع التغييرات والتحقق من صحتها قبل تطبيقها على البيانات.
  4. Ecto.Query. يوفر استعلام SQL يشبه DSL لاسترداد المعلومات من المستودع. الاستعلامات في Ecto آمنة وتتجنب المشاكل الشائعة مثل حقن SQL بينما لا تزال قابلة للتكوين مما يسمح للمطورين ببناء الاستعلامات قطعة تلو الأخرى بدلاً من الكل مرة واحدة.

في هذا البرنامج التعليمي سوف تحتاج إلى:

  • تثبيت Elixir  (دليل التثبيت لـ 1.2 أو أحدث)
  • تثبيت PostgreSQL
  • مستخدم محدد بإذن لإنشاء قاعدة بيانات (ملاحظة: سنستخدم المستخدم  “postgres” بكلمة المرور  “postgres”  كمثال خلال هذا البرنامج التعليمي.)

تثبيت وتكوين Ecto

بالنسبة للمبتدئين دعنا ننشئ تطبيقًا جديدًا مع مشرف باستخدام Mix. Mix هي أداة بناء تأتي مع Elixir والتي توفر مهام لإنشاء وتجميع واختبار تطبيقك وإدارة تبعياته وغير ذلك الكثير.

mix new cart --sup

سيؤدي هذا إلى إنشاء عربة دليل بملفات المشروع الأولية:

* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/ecto_tut.ex
* creating test
* creating test/test_helper.exs
* creating test/ecto_tut_test.exs

نحن نستخدم الخيار – sup لأننا نحتاج إلى شجرة مشرف تحافظ على الاتصال بقاعدة البيانات. بعد ذلك نذهب إلى دليل cart باستخدام cd cart ونفتح ملف mix.exs ونستبدل محتوياته:

defmodule Cart.Mixfile do
  use Mix.Project

  def project do
    [app: :cart,
     version: "0.0.1",
     elixir: "~> 1.2",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     deps: deps]
  end

  def application do
    [applications: [:logger, :ecto, :postgrex],
     mod: {Cart, []}]
  end

  # Type "mix help deps" for more examples and options
  defp deps do
    [{:postgrex, ">= 0.11.1"},
     {:ecto, "~> 2.0"}]
  end
end

في def application do يتعين علينا إضافة تطبيقات: :postgrex, :ecto بحيث يمكن استخدامها داخل تطبيقنا. يتعين علينا أيضًا إضافة هذه على أنها تبعيات عن طريق إضافة أقسام defp do postgrex  (وهو محول قاعدة البيانات) و ecto. بمجرد تحرير الملف قم بتشغيل في وحدة التحكم:

mix deps.get

سيؤدي هذا إلى تثبيت جميع التبعيات وإنشاء ملف mix.lock يخزن جميع التبعيات والتبعيات الفرعية للحزم المثبتة (على غرار Gemfile.lock في المجمع).

إكتو ريبو (Ecto.Repo)

Ecto، غلاف قاعدة البيانات غير المساومة لتطبيقات Elixir المتزامنة

سننظر الآن في كيفية تحديد الريبو في تطبيقنا. يمكن أن يكون لدينا أكثر من ريبو مما يعني أنه يمكننا الاتصال بأكثر من قاعدة بيانات واحدة. نحتاج إلى تكوين قاعدة البيانات في الملف config/config.exs:

use Mix.Config
config :cart, ecto_repos: [Cart.Repo]

نحن فقط نضع الحد الأدنى حتى نتمكن من تشغيل الأمر التالي. مع السطر: :cart, cart_repos: [Cart.Repo] نخبر Ecto عن المستودعات التي نستخدمها. هذه ميزة رائعة لأنها تتيح لنا الحصول على العديد من عمليات إعادة الشراء أي يمكننا الاتصال بقواعد بيانات متعددة.

الآن قم بتشغيل الأمر التالي:

mix ecto.gen.repo
==> connection
Compiling 1 file (.ex)
Generated connection app
==> poolboy (compile)
Compiled src/poolboy_worker.erl
Compiled src/poolboy_sup.erl
Compiled src/poolboy.erl
==> decimal
Compiling 1 file (.ex)
Generated decimal app
==> db_connection
Compiling 23 files (.ex)
Generated db_connection app
==> postgrex
Compiling 43 files (.ex)
Generated postgrex app
==> ecto
Compiling 68 files (.ex)
Generated ecto app
==> cart
* creating lib/cart
* creating lib/cart/repo.ex
* updating config/config.exs
Don't forget to add your new repo to your supervision tree
(typically in lib/cart.ex):

    supervisor(Cart.Repo, [])

And to add it to the list of ecto repositories in your configuration files (so Ecto tasks work as expected):

    config :cart,
      ecto_repos: [Cart.Repo]

هذا الأمر يولد الريبو. إذا قرأت الإخراج فسيخبرك بإضافة مشرف وإعادة الشراء في تطبيقك. لنبدأ مع المشرف. سنقوم بتعديل lib/cart.ex

defmodule Cart do
  use Application

  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    children = [
      supervisor(Cart.Repo, [])
    ]

    opts = [strategy: :one_for_one, name: Cart.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

في هذا الملف نحدد المشرف supervisor(Cart.Repo, []) ونضيفه إلى قائمة الأطفال (في الإكسير القوائم تشبه المصفوفات). نحدد الأطفال الخاضعين للإشراف باستراتيجية strategy: :one_for_one  مما يعني أنه في حالة فشل إحدى العمليات الخاضعة للإشراف سيقوم المشرف بإعادة تشغيل هذه العملية فقط إلى حالتها الافتراضية. يمكنك معرفة المزيد عن المشرفين هنا. إذا نظرت إلى lib/cart/repo.ex فسترى أن هذا الملف قد تم إنشاؤه بالفعل مما يعني أن لدينا ريبو لتطبيقنا.

defmodule Cart.Repo do
  use Ecto.Repo, otp_app: :cart
end

الآن دعنا نعدل ملف التكوين config / config.exs:

use Mix.Config
config :cart, ecto_repos: [Cart.Repo]

config :cart, Cart.Repo,
  adapter: Ecto.Adapters.Postgres,
  database: "cart_dev",
  username: "postgres",
  password: "postgres",
  hostname: "localhost"

بعد تحديد جميع إعدادات قاعدة البيانات الخاصة بنا يمكننا الآن إنشاؤها عن طريق تشغيل:

mix ecto.create

يقوم هذا الأمر بإنشاء قاعدة البيانات وبذلك نكون قد أنهينا التكوين بشكل أساسي. نحن الآن جاهزون لبدء الترميز ولكن دعنا نحدد نطاق تطبيقنا أولاً.

بناء فاتورة (invoice) بالعناصر المضمنة

بالنسبة لتطبيقنا التجريبي سنقوم ببناء أداة بسيطة للفواتير. بالنسبة لمجموعات التغييرات (النماذج) سيكون لدينا invoice item invoiceItem. InvoiceItem ينتمي إلى الفاتورة والبند. يمثل هذا الرسم البياني كيف سترتبط نماذجنا ببعضها البعض:

الرسم التخطيطي بسيط جدا. لدينا جدول فواتير يحتوي على العديد من invoice_items حيث نقوم بتخزين جميع التفاصيل وأيضًا عناصر الجدول التي تحتوي على العديد من invoice_items. يمكنك أن ترى أن نوع invoice_id و item_id في جدول invoice_items هو UUID. نحن نستخدم UUID لأنه يساعد في تشويش المسارات في حالة رغبتك في عرض التطبيق عبر واجهة برمجة تطبيقات وتجعل المزامنة أسهل نظرًا لأنك لا تعتمد على رقم تسلسلي. لنقم الآن بإنشاء الجداول باستخدام مهام Mix.

Ecto.migration

Ecto.migration

عمليات الترحيل هي ملفات تُستخدم لتعديل مخطط قاعدة البيانات. يمنحك Ecto.Migration مجموعة من الأساليب لإنشاء الجداول وإضافة الفهارس وإنشاء القيود والأشياء الأخرى المتعلقة بالمخطط. تساعد عمليات الترحيل حقًا في إبقاء التطبيق متزامنًا مع قاعدة البيانات. لنقم بإنشاء برنامج نصي للترحيل لجدولنا الأول:

mix ecto.gen.migration create_invoices

سيؤدي هذا إلى إنشاء ملف مشابه لـ priv/repo/migrations/20160614115844_create_invoices.exs حيث سنقوم بتعريف الترحيل الخاص بنا. افتح الملف الذي تم إنشاؤه وقم بتعديل محتوياته لتكون على النحو التالي:

defmodule Cart.Repo.Migrations.CreateInvoices do
  use Ecto.Migration

  def change do
    create table(:invoices, primary_key: false) do
      add :id, :uuid, primary_key: true
      add :customer, :text
      add :amount, :decimal, precision: 12, scale: 2
      add :balance, :decimal, precision: 12, scale: 2
      add :date, :date

      timestamps
    end
  end
end

def change do في الدالة الداخلية هل نحدد المخطط الذي سينشئ SQL لقاعدة البياناتcreate table(:invoices, primary_key: false) do سوف يقوم بإنشاء جدول فواتير. لقد قمنا بتعيين Primary_key: false ولكننا سنضيف حقل معرف من نوع UUID حقل العميل من نوع النص حقل التاريخ لنوع التاريخ. ستنشئ داله timestamps الحقلين inserted_at و updated_at اللذين يملأهما Ecto تلقائيًا بالوقت الذي تم فيه إدراج السجل ووقت تحديثه على التوالي. انتقل الآن إلى وحدة التحكم وقم بتشغيل الترحيل:

mix ecto.migrate

لقد أنشأنا invoices الجدول مع جميع الحقول المحددة. دعونا ننشئ جدول العناصر:

mix ecto.gen.migration create_items

الآن قم بتحرير نص الترحيل الذي تم إنشاؤه:

defmodule Cart.Repo.Migrations.CreateItems do
  use Ecto.Migration

  def change do
    create table(:items, primary_key: false) do
      add :id, :uuid, primary_key: true
      add :name, :text
      add :price, :decimal, precision: 12, scale: 2

      timestamps
    end
  end
end

الشيء الجديد هنا هو الحقل العشري الذي يسمح بالأرقام المكونة من 12 رقمًا 2 منها للجزء العشري من الرقم. لنبدأ الترحيل مرة أخرى:

mix ecto.migrate

لقد أنشأنا الآن جدول العناصر وأخيراً دعنا ننشئ جدول invoice_items:

mix ecto.gen.migration create_invoice_items

تحرير migration:

defmodule Cart.Repo.Migrations.CreateInvoiceItems do
  use Ecto.Migration

  def change do
    create table(:invoice_items, primary_key: false) do
      add :id, :uuid, primary_key: true
      add :invoice_id, references(:invoices, type: :uuid, null: false)
      add :item_id, references(:items, type: :uuid, null: false)
      add :price, :decimal, precision: 12, scale: 2
      add :quantity, :decimal, precision: 12, scale: 2
      add :subtotal, :decimal, precision: 12, scale: 2

      timestamps
    end

    create index(:invoice_items, [:invoice_id])
    create index(:invoice_items, [:item_id])
  end
end

كما ترى يحتوي هذا الترحيل على بعض الأجزاء الجديدة. أول شيء ستلاحظه هو add :invoice_id, references(:invoices, type: :uuid, null: false). يؤدي هذا إلى إنشاء حقل invoice_id بقيد في قاعدة البيانات يشير إلى جدول invoices. لدينا نفس النمط لحقل item_id. الشيء الآخر المختلف هو طريقة إنشاء الفهرس: create index(:invoice_items, [:invoice_id]) ينشئ الفهرس invoice_items_invoice_id_index.

Ecto.Schema و Ecto.changeset

في Ecto تم إهمال Ecto.Model لصالح استخدام Ecto.Schema لذلك سنسمي مخططات الوحدات بدلاً من النماذج. فلنقم بإنشاء مجموعات التغييرات. سنبدأ بأبسط عنصر من مجموعة التغييرات وننشئ الملف lib/cart/item.ex

defmodule Cart.Item do
  use Ecto.Schema
  import Ecto.Changeset

  alias Cart.InvoiceItem

  @primary_key {:id, :binary_id, autogenerate: true}
  schema "items" do
    field :name, :string
    field :price, :decimal, precision: 12, scale: 2
    has_many :invoice_items, InvoiceItem

    timestamps
  end

  @fields ~w(name price)

  def changeset(data, params \\ %{}) do
    data
    |> cast(params, @fields)
    |> validate_required([:name, :price])
    |> validate_number(:price, greater_than_or_equal_to: Decimal.new(0))
  end
end

في الجزء العلوي نقوم بحقن كود في مجموعة التغييرات باستخدام Ecto.Schema. نحن نستخدم أيضًا استيراد Ecto.Changeset لاستيراد الوظائف من Ecto.Changeset. كان بإمكاننا تحديد الطرق المحددة للاستيراد ولكن دعونا نجعلها بسيطة. يسمح لنا الاسم المستعار Cart.InvoiceItem بالكتابة مباشرة داخل مجموعة التغييرات InvoiceItem كما سترى بعد قليل.

Ecto.Schema

Ecto.Schema

يحدد @primary_key {:id, :binary_id, autogenerate: true}  أن مفتاحنا الأساسي سيتم إنشاؤه تلقائيًا. نظرًا لأننا نستخدم نوع UUID فإننا نحدد المخطط باستخدام schema "items" do وداخل الكتلة نحدد كل حقل وعلاقات. لقد عرّفنا الاسم على أنه سلسلة والسعر على أنه رقم عشري ، وهو مشابه جدًا للترحيل. بعد ذلك ، فإن الماكرو has_many :invoice_items, InvoiceItem يشير إلى علاقة بين Item و InvoiceItem. نظرًا لأننا قمنا بتسمية الحقل item_id في جدول invoice_items فنحن لسنا بحاجة إلى تكوين المفتاح الخارجي. أخيرًا ستقوم طريقة الطوابع الزمنية بتعيين الحقلين inserted_at و updated_at.

Ecto.Changeset

Ecto.Changeset

تستقبل داله def changeset(data, params \\ %{}) do بنية Elixir مع المعلمات التي سنمر بها عبر دوال مختلفة. يلقي cast(params, @fields) القيم في النوع الصحيح. على سبيل المثال يمكنك تمرير السلاسل فقط في المعلمات وسيتم تحويلها إلى النوع الصحيح المحدد في المخطط. validate_required([:name, :price]) للتحقق من وجود حقلي الاسم والسعر

validate_number(:price, greater_than_or_equal_to: Decimal.new(0)) يتحقق من أن الرقم أكبر من أو يساوي 0 أو في هذه الحالة Decimal.new(0). في Elixir تتم العمليات العشرية بشكل مختلف حيث يتم تنفيذها كبنية.

كان هذا كثيرًا لتستوعبه لذلك دعونا نلقي نظرة على هذا في وحدة التحكم بأمثلة حتى تتمكن من فهم المفاهيم بشكل أفضل:

iex -S mix

سيؤدي هذا إلى تحميل وحدة التحكم. -S mix يقوم بتحميل المشروع الحالي في iex REPL.

iex(0)> item = Cart.Item.changeset(%Cart.Item{}, %{name: "Paper", price: "2.5"})
#Ecto.Changeset<action: nil, changes: %{name: "Paper", price: #Decimal<2.5>},
 errors: [], data: #Cart.Item<>, valid?: true>

يؤدي هذا إلى إرجاع بنية Ecto.Changeset صالحة بدون أخطاء. الآن دعونا نحفظه:

iex(1)> item = Cart.Repo.insert!(item)
%Cart.Item{__meta__: #Ecto.Schema.Metadata<:loaded, "items">,
 id: "66ab2ab7-966d-4b11-b359-019a422328d7",
 inserted_at: #Ecto.DateTime<2016-06-18 16:54:54>,
 invoice_items: #Ecto.Association.NotLoaded<association :invoice_items is not loaded>,
 name: "Paper", price: #Decimal<2.5>,
 updated_at: #Ecto.DateTime<2016-06-18 16:54:54>}

لا نعرض SQL للإيجاز. في هذه الحالة تقوم بإرجاع Cart.Item Structure مع تعيين جميع القيم ويمكنك أن ترى أن inserted_at و updated_at يحتويان على الطوابع الزمنية الخاصة بهما وأن حقل المعرف يحتوي على قيمة UUID. دعونا نرى بعض الحالات الأخرى:

iex(3)> item2 = Cart.Item.changeset(%Cart.Item{price: Decimal.new(20)}, %{name: "Scissors"})         
#Ecto.Changeset<action: nil, changes: %{name: "Scissors"}, errors: [],
 data: #Cart.Item<>, valid?: true>
iex(4)> Cart.Repo.insert(item2)

الآن قمنا بتعيين عنصر Scissors بطريقة مختلفة وتحديد السعر مباشرة %Cart.Item{price: Decimal.new(20)}. نحتاج إلى تعيين نوعه الصحيح على عكس العنصر الأول الذي مررنا فيه للتو سلسلة على أنها سعر. كان من الممكن أن نجتاز عددًا عشريًا وكان من الممكن تحويله إلى نوع عشري. إذا مررنا على سبيل المثال %Cart.Item{price: 12.5} عند إدراج العنصر فسيتم طرح استثناء يفيد بأن النوع غير مطابق.

iex(4)>  invalid_item = Cart.Item.changeset(%Cart.Item{}, %{name: "Scissors", price: -1.5})
#Ecto.Changeset<action: nil,
 changes: %{name: "Scissors", price: #Decimal<-1.5>},
 errors: [price: {"must be greater than or equal to %{number}",
   [number: #Decimal<0>]}], data: #Cart.Item<>, valid?: false>

لإنهاء وحدة التحكم اضغط على Ctrl + C مرتين. يمكنك أن ترى أن عمليات التحقق من الصحة تعمل وأن السعر يجب أن يكون أكبر من أو يساوي الصفر (0). كما ترون لقد حددنا كل مخطط Ecto.Schema وهو الجزء المتعلق بكيفية تعريف بنية الوحدة وتغيير مجموعة Ecto.Changeset وهي جميع عمليات التحقق من الصحة والإرسال. دعنا نتابع وننشئ الملف lib/cart/invoice_item.ex:

defmodule Cart.InvoiceItem do
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:id, :binary_id, autogenerate: true}
  schema "invoice_items" do
    belongs_to :invoice, Cart.Invoice, type: :binary_id
    belongs_to :item, Cart.Item, type: :binary_id
    field :quantity, :decimal, precision: 12, scale: 2
    field :price, :decimal, precision: 12, scale: 2
    field :subtotal, :decimal, precision: 12, scale: 2

    timestamps
  end

  @fields ~w(item_id price quantity)
  @zero Decimal.new(0)

  def changeset(data, params \\ %{}) do
    data
    |> cast(params, @fields)
    |> validate_required([:item_id, :price, :quantity])
    |> validate_number(:price, greater_than_or_equal_to: @zero)
    |> validate_number(:quantity, greater_than_or_equal_to: @zero)
    |> foreign_key_constraint(:invoice_id, message: "Select a valid invoice")
    |> foreign_key_constraint(:item_id, message: "Select a valid item")
    |> set_subtotal
  end

  def set_subtotal(cs) do
    case {(cs.changes[:price] || cs.data.price), (cs.changes[:quantity] || cs.data.quantity)} do
      {_price, nil} -> cs
      {nil, _quantity} -> cs
      {price, quantity} ->
        put_change(cs, :subtotal, Decimal.mult(price, quantity))
    end
  end
end

مجموعة التغييرات هذه أكبر ولكن يجب أن تكون على دراية بمعظمها بالفعل. belongs_to :invoice, Cart.Invoice, type: :binary_id يعرّف علاقة “belongs to” مع مجموعة التغييرات Cart.Invoice التي سننشئها قريبًا. ينتمي العنصر التالي إلى: ينشئ العنصر علاقة مع جدول العناصر. لقد حددناzero Decimal.new (0). في هذه الحالة يعد zero بمثابة ثابت يمكن الوصول إليه داخل الوحدة النمطية. تحتوي داله changeset على أجزاء جديدة أحدها foreign_key_constraint(:invoice_id, message: "Select a valid invoice"). سيسمح هذا بإنشاء رسالة خطأ بدلاً من إنشاء استثناء عندما لا يتم استيفاء القيد. وأخيرًا ستحسب الداله set_subtotal المجموع الفرعي. نجتاز مجموعة التغييرات ونعيد مجموعة تغييرات جديدة مع حساب الإجمالي الفرعي إذا كان لدينا السعر والكمية.

الآن لنقم بإنشاء Cart.Invoice. لذلك قم بإنشاء وتعديل الملف lib / cart / invoice.ex ليحتوي على ما يلي:

defmodule Cart.Invoice do
  use Ecto.Schema
  import Ecto.Changeset

  alias Cart.{Invoice, InvoiceItem, Repo}

  @primary_key {:id, :binary_id, autogenerate: true}
  schema "invoices" do
    field :customer, :string
    field :amount, :decimal, precision: 12, scale: 2
    field :balance, :decimal, precision: 12, scale: 2
    field :date, Ecto.Date
    has_many :invoice_items, InvoiceItem, on_delete: :delete_all

    timestamps
  end

  @fields ~w(customer amount balance date)

  def changeset(data, params \\ %{}) do
    data
    |> cast(params, @fields)
    |> validate_required([:customer, :date])
  end

  def create(params) do
    cs = changeset(%Invoice{}, params)
    |> validate_item_count(params)
    |> put_assoc(:invoice_items, get_items(params))

    if cs.valid? do
      Repo.insert(cs)
    else
      cs
    end
  end

  defp get_items(params) do
    items = params[:invoice_items] || params["invoice_items"]
    Enum.map(items, fn(item)-> InvoiceItem.changeset(%InvoiceItem{}, item) end)
  end

  defp validate_item_count(cs, params) do
    items = params[:invoice_items] || params["invoice_items"]

    if Enum.count(items) <= 0 do
      add_error(cs, :invoice_items, "Invalid number of items")
    else
      cs
    end
  end

end

هناك بعض الاختلافات في مجموعة تغييرات cart.invoice. الأول داخل المخططات: has_many :invoice_items, InvoiceItem, on_delete: :delete_all تعني أنه عندما نحذف فاتورة سيتم حذف جميع عناصر_الفاتورة المرتبطة بها. ضع في اعتبارك مع ذلك أن هذا ليس قيدًا محددًا في قاعدة البيانات.

دعونا نجرب طريقة الإنشاء في وحدة التحكم لفهم الأشياء بشكل أفضل. ربما تكون قد أنشأت العناصر (“paper” ، “scissors”) التي سنستخدمها هنا:

iex(0)> item_ids = Enum.map(Cart.Repo.all(Cart.Item), fn(item)-> item.id end)
iex(1)> {id1, id2} = {Enum.at(item_ids, 0), Enum.at(item_ids, 1) }

لقد جلبنا جميع العناصر باستخدام Cart.Repo.all وباستخدام وظيفة Enum.map نحصل فقط على item.id لكل عنصر. في السطر الثاني نقوم فقط بتعيين id1 و id2 مع العنصرين الأول والثاني على التوالي:

iex(2)> inv_items = [%{item_id: id1, price: 2.5, quantity: 2},
 %{item_id: id2, price: 20, quantity: 1}]
iex(3)> {:ok, inv} = Cart.Invoice.create(%{customer: "James Brown", date: Ecto.Date.utc, invoice_items: inv_items})

تم إنشاء الفاتورة مع invoice_items الخاصة بها ويمكننا جلب جميع الفواتير الآن.

iex(4)> alias Cart.{Repo, Invoice}
iex(5)> Repo.all(Invoice)

يمكنك أن ترى أنها تعيد الفاتورة ولكننا نرغب أيضًا في رؤية invoice_items:

iex(6)> Repo.all(Invoice) |> Repo.preload(:invoice_items)

باستخدام وظيفة Repo.preload يمكننا الحصول على invoice_items. لاحظ أن هذا يمكن أن يعالج الاستعلامات بشكل متزامن. في حالتي بدا الاستعلام كما يلي:

iex(7)> Repo.get(Invoice, "5d573153-b3d6-46bc-a2c0-6681102dd3ab") |> Repo.preload(:invoice_items)

المصدر

منشور ذات صلة
ممارسات DevOps 7 Minutes

أفضل ممارسات DevOps التي تحتاج إلى معرفتها

جاسم ناظري

الأتمتة هي جانب حيوي من جوانب عمل ممارسات DevOps. تستفيد الفرق من القدرة على استبدال المهام اليدوية مثل تحديد تغييرات معينة في إنشاءات التعليمات البرمجية أو تقليل الاختناقات المحتملة في العمليات.

ماهو CSS؟ 5 Minutes

(Cascading Style Sheets (CSS

آيات عامر

ما هو CSS؟ هي آلية بسيطة لإضافة نمط (على سبيل المثال، الخطوط والألوان والتباعد) إلى […]

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

السلة