portaldacalheta.pt
  • Основен
  • Agile Talent
  • Дизайнерски Живот
  • Възходът На Дистанционното
  • Рентабилност И Ефективност
Back-End

Запознайте се с Ecto, безкомпромисна обвивка за бази данни за едновременни приложения на Elixir



Екто е специфичен за домейн език за писане на заявки и взаимодействие с бази данни в Еликсирен език . Най-новата версия (2.0) поддържа PostgreSQL и MySQL. (поддръжката за MSSQL, SQLite и MongoDB ще бъде налична в бъдеще). В случай, че сте нов за Еликсир или имате малко опит с него, бих ви препоръчал да прочетете Kleber Virgilio Correia’s Първи стъпки с езика за програмиране Elixir .



Уморени ли сте от всички диалекти на SQL? Говорете с вашата база данни чрез Ecto. Tweet

Ecto се състои от четири основни компонента:

  • Ecto.Repo. Определя хранилища, които са обвивки около хранилище за данни. Използвайки го, можем да вмъкваме, създаваме, изтриваме и заявяваме репо. За комуникация с базата данни са необходими адаптер и идентификационни данни.
  • Ecto.Schema. Схемите се използват за картографиране на всеки източник на данни в структура на Elixir.
  • Ecto.Changeset. Променливите предоставят на разработчиците начин за филтриране и излъчване на външни параметри, както и механизъм за проследяване и валидиране на промените, преди те да бъдат приложени към данни.
  • Ecto.Query. Предоставя DSL-подобна SQL заявка за извличане на информация от хранилище. Заявките в Ecto са защитени, като се избягват често срещани проблеми като SQL Injection, като същевременно се композират, което позволява на разработчиците да създават заявки парче по парче, вместо всички наведнъж.

За този урок ще ви трябва:



  • Инсталиран еликсир ( ръководство за инсталиране за 1.2 или по-нова версия)
  • Инсталиран PostgreSQL
  • Потребител, дефиниран с разрешение за създаване на база данни (Забележка: Ще използваме потребителя „postgres“ с парола „postgres“ като пример в този урок.)

Инсталиране и конфигуриране

Като начало, нека създадем ново приложение с ръководител, използвайки 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 deps do постгрекс (който е адаптерът на базата данни) и екто . След като редактирате файла, стартирайте в конзолата:

mix deps.get

Това ще инсталира всички зависимости и ще създаде файл mix.lock който съхранява всички зависимости и подзависимости на инсталираните пакети (подобно на Gemfile.lock в пакета).

Ecto.Repo

Сега ще разгледаме как да дефинираме репо в нашето приложение. Можем да имаме повече от едно репо, което означава, че можем да се свържем с повече от една база данни. Трябва да конфигурираме базата данни във файла config/config.exs:

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

Ние просто задаваме минимума, за да можем да изпълним следващата команда. С реда :cart, cart_repos: [Cart.Repo] казваме на Ecto кои репо сделки използваме. Това е страхотна функция, тъй като ни позволява да имаме много репозитории, т.е.можем да се свържем с множество бази данни.

s корпорация, обложена като c корпорация

Сега изпълнете следната команда:

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, []) и го добавя към списъка с деца (в Elixir списъците са подобни на масиви). Определяме децата, контролирани със стратегията 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

Тази команда създава базата данни и с това по същество завършихме конфигурацията. Вече сме готови да започнем да кодираме, но нека първо определим обхвата на нашето приложение.

Съставяне на фактура с вградени елементи

За нашето демонстрационно приложение ще създадем прост инструмент за фактуриране. За набори от промени (модели) ще имаме Фактура , Вещ и InvoiceItem . InvoiceItem принадлежи на Фактура и Вещ . Тази диаграма представя как нашите модели ще бъдат свързани помежду си:

Диаграмата е доста проста. Имаме маса фактури че има много invoice_items където съхраняваме всички детайли, а също и маса елементи че има много invoice_items . Можете да видите, че типът за invoice_id и item_id в invoice_items таблицата е UUID. Използваме UUID, защото помага да се замъглят маршрутите, в случай че искате да изложите приложението през API и улеснява синхронизирането, тъй като не зависи от пореден номер. Сега нека създадем таблиците, използвайки микс задачи.

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

Създадохме таблицата invoice s с всички дефинирани полета. Нека създадем елементи маса:

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

Редактирайте миграцията:

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 с ограничение в базата данни, което препраща към фактури маса. Имаме еднакъв модел за 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

В горната част инжектираме код в набора от промени, използвайки use Ecto.Schema. Също така използваме import Ecto.Changeset за импортиране на функционалност от Ecto.Changeset . Можехме да посочим кои конкретни методи да импортираме, но нека го улесним. alias Cart.InvoiceItem ни позволява да пишем директно в набора от промени InvoiceItem , както ще видите след малко.

Ecto.Schema

@primary_key {:id, :binary_id, autogenerate: true} указва, че нашият първичен ключ ще бъде автоматично генериран. Тъй като използваме UUID тип, дефинираме схемата с schema 'items' do и вътре в блока дефинираме всяко поле и взаимоотношения. Ние определихме име като низ и цена като десетична, много подобна на миграцията. След това макросът has_many :invoice_items, InvoiceItem показва връзка между Вещ и InvoiceItem . Тъй като по конвенция ние кръстихме полето item_id в invoice_items таблица, не е необходимо да конфигурираме външния ключ. И накрая времеви марки метод ще зададе вмъкнат_ат и updated_at полета.

Ecto.Changeset

def changeset(data, params \ %{}) do функция получава структура на еликсир с параметри, които ние ще тръба чрез различни функции. 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

Това връща Ecto.Changeset struct, която е валидна без грешки. Сега нека го запазим:

iex(1)> item = Cart.Repo.insert!(item) %Cart.Item{__meta__: #Ecto.Schema.Metadata, id: '66ab2ab7-966d-4b11-b359-019a422328d7', inserted_at: #Ecto.DateTime, invoice_items: #Ecto.Association.NotLoaded, name: 'Paper', price: #Decimal, updated_at: #Ecto.DateTime}

Не показваме SQL за краткост. В този случай връща Количка struct с всички зададени стойности, Можете да видите това вмъкнат_ат и updated_at съдържат техните времеви марки и документ за самоличност полето има UUID стойност. Нека да видим някои други случаи:

iex(3)> item2 = Cart.Item.changeset(%Cart.Item{price: Decimal.new(20)}, %{name: 'Scissors'}) #Ecto.Changeset 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

За да прекратите конзолата, натиснете два пъти 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.data.price), (cs.changes[: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 :item създава връзка с таблицата с елементи. Определихме @zero Decimal.new(0). В такъв случай, @ нула е като константа, която може да бъде достъпна вътре в модула. Функцията за промяна има нови части, една от които е foreign_key_constraint(:invoice_id, message: 'Select a valid invoice'). Това ще позволи да се генерира съобщение за грешка, вместо да се генерира изключение, когато ограничението не е изпълнено. И накрая, методът set_subtotal ще изчисли междинната сума. Предаваме набора от промени и връщаме нов набор от промени с изчислената междинна сума, ако имаме както цената, така и количеството.

Сега нека създадем Количка, фактура . Така че създайте и редактирайте файла 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

Количка, фактура changeset има някои разлики. Първият е вътре схеми : has_many :invoice_items, InvoiceItem, on_delete: :delete_all означава, че когато изтрием фактура, всички свързани с нея invoice_items ще бъдат изтрити. Имайте предвид обаче, че това не е ограничение, дефинирано в базата данни.

Нека опитаме метода create в конзолата, за да разберем нещата по-добре. Може да сте създали елементите („Хартия“, „Ножици“), които ще използваме тук:

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 съответно с първия и втория item_ids:

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)

Ecto.Query

Досега показахме как да създаваме нови елементи и нови фактури с взаимоотношения. Но какво ще кажете за заявки? Е, нека ви запозная Ecto.Query което ще ни помогне да отправяме запитвания към базата данни, но първо се нуждаем от повече данни, за да обясним по-добре.

iex(1)> alias Cart.{Repo, Item, Invoice, InvoiceItem} iex(2)> Repo.insert(%Item{name: 'Chocolates', price: Decimal.new('5')}) iex(3)> Repo.insert(%Item{name: 'Gum', price: Decimal.new('2.5')}) iex(4)> Repo.insert(%Item{name: 'Milk', price: Decimal.new('1.5')}) iex(5)> Repo.insert(%Item{name: 'Rice', price: Decimal.new('2')}) iex(6)> Repo.insert(%Item{name: 'Chocolates', price: Decimal.new('10')})

Сега трябва да имаме 8 артикула и има повтарящ се „Шоколад“. Може да искаме да знаем кои елементи се повтарят. Така че нека опитаме тази заявка:

iex(7)> import Ecto.Query iex(8)> q = from(i in Item, select: %{name: i.name, count: (i.name)}, group_by: i.name) iex(9)> Repo.all(q) 19:12:15.739 [debug] QUERY OK db=2.7ms SELECT i0.'name', count(i0.'name') FROM 'items' AS i0 GROUP BY i0.'name' [] [%{count: 1, name: 'Scissors'}, %{count: 1, name: 'Gum'}, %{count: 2, name: 'Chocolates'}, %{count: 1, name: 'Paper'}, %{count: 1, name: 'Milk'}, %{count: 1, name: 'Test'}, %{count: 1, name: 'Rice'}]

Можете да видите, че в заявката искахме да върнем карта с името на елемента и колко пъти се появява в таблицата с елементи. Като алтернатива обаче, по-вероятно бихме се заинтересували да видим кои са най-продаваните продукти. Така че, нека създадем няколко фактури. Първо, нека улесним живота си, като създадем карта за достъп до item_id:

iex(10)> l = Repo.all(from(i in Item, select: {i.name, i.id})) iex(11)> items = for {k, v} '8fde33d3-6e09-4926-baff-369b6d92013c', 'Gum' => 'cb1c5a93-ecbf-4e4b-8588-cc40f7d12364', 'Milk' => '7f9da795-4d57-4b46-9b57-a40cd09cf67f', 'Paper' => '66ab2ab7-966d-4b11-b359-019a422328d7', 'Rice' => 'ff0b14d2-1918-495e-9817-f3b08b3fa4a4', 'Scissors' => '397b0bb4-2b04-46df-84d6-d7b1360b6c72', 'Test' => '9f832a81-f477-4912-be2f-eac0ec4f8e8f'}

Както можете да видите, ние създадохме карта с помощта на разбиране

iex(12)> line_items = [%{item_id: items['Chocolates'], quantity: 2}]

Трябва да добавим цената в invoice_items params, за да създадете фактура, но би било по-добре просто да предадете идентификатора на артикула и цената да се попълни автоматично. Ще направим промени в Количка, фактура модул за постигане на това:

defmodule Cart.Invoice do use Ecto.Schema import Ecto.Changeset import Ecto.Query # We add to query # .... # schema, changeset and create functions don't change # The new function here is items_with_prices defp get_items(params) do items = items_with_prices(params[:invoice_items] || params['invoice_items']) Enum.map(items, fn(item)-> InvoiceItem.changeset(%InvoiceItem{}, item) end) end # new function to get item prices defp items_with_prices(items) do item_ids = Enum.map(items, fn(item) -> item[:item_id] || item['item_id'] end) q = from(i in Item, select: %{id: i.id, price: i.price}, where: i.id in ^item_ids) prices = Repo.all(q) Enum.map(items, fn(item) -> item_id = item[:item_id] || item['item_id'] % end) end

Първото нещо, което ще забележите, е, че сме добавили Ecto.Query , което ще ни позволи да направим запитване към базата данни. Новата функция е defp items_with_prices(items) do който търси в артикулите и намира и определя цената за всеки артикул.

Първо, defp items_with_prices(items) do получава списък като аргумент. С item_ids = Enum.map(items, fn(item) -> item[:item_id] || item['item_id'] end) преглеждаме всички елементи и получаваме само item_id . Както можете да видите, ние имаме достъп или с atom :item_id или низ „item_id“, тъй като картите могат да имат и двете като ключове. Запитването q = from(i in Item, select: %{id: i.id, price: i.price}, where: i.id in ^item_ids) ще намери всички елементи, които са в item_ids и ще върне карта с item.id и item.price. След това можем да изпълним заявката prices = Repo.all(q) което връща списък с карти. След това трябва да прегледаме елементите и да създадем нов списък, който ще добави цената. Enum.map(items, fn(item) -> прелиства всеки артикул, намира цената Enum.find(prices, fn(p) -> p[:id] == item_id end)[:price] || 0 и създава нов списък с item_id, количество и цена. И с това вече не е необходимо да добавяте цената във всеки от invoice_items.

Вмъкване на още фактури

Както си спомняте, по-рано създадохме карта елементи което ни дава достъп до документ за самоличност като се използва името на артикула, т.е. items['Gum'] „Cb1c5a93-ecbf-4e4b-8588-cc40f7d12364“. Това улеснява създаването invoice_items . Нека създадем още фактури. Стартирайте конзолата отново и изпълнете:

Iex -S mix iex(1)> Repo.delete_all(InvoiceItem); Repo.delete_all(Invoice)

Изтриваме всички invoice_items и фактури, за да има празен лист:

iex(2)> li = [%{item_id: items['Gum'], quantity: 2}, %{item_id: items['Milk'], quantity: 1}] iex(3)> Invoice.create(%{customer: 'Mary Jane', date: Ecto.Date.utc, invoice_items: li}) iex(4)> li2 = [%{item_id: items['Chocolates'], quantity: 2}| li] iex(5)> Invoice.create(%{customer: 'Mary Jane', date: Ecto.Date.utc, invoice_items: li2}) iex(5)> li3 = li2 ++ [%{item_id: items['Paper'], quantity: 3 }, %{item_id: items['Rice'], quantity: 1}, %{item_id: items['Scissors'], quantity: 1}] iex(6)> Invoice.create(%{customer: 'Juan Perez', date: Ecto.Date.utc, invoice_items: li3})

Сега имаме 3 фактури; първият с 2 артикула, вторият с 3 артикула и третият с 6 артикула. Сега бихме искали да знаем кои продукти са най-продаваните артикули? За да отговорим на това, ще създадем заявка, за да намерим най-добре продаваните артикули по количество и по междинна сума (цена х количество).

defmodule Cart.Item do use Ecto.Schema import Ecto.Changeset import Ecto.Query alias Cart.{InvoiceItem, Item, Repo} # schema and changeset don't change # ... def items_by_quantity, do: Repo.all items_by(:quantity) def items_by_subtotal, do: Repo.all items_by(:subtotal) defp items_by(type) do from i in Item, join: ii in InvoiceItem, on: ii.item_id == i.id, select: %{id: i.id, name: i.name, total: sum(field(ii, ^type))}, group_by: i.id, order_by: [desc: sum(field(ii, ^type))] end end

Внасяме Ecto.Query и тогава ние alias Cart.{InvoiceItem, Item, Repo} така че не е нужно да добавяме Количка в началото на всеки модул. Първата функция items_by_quantity извиква items_by функция, предавайки :quantity параметър и извикване на Repo.all за изпълнение на заявката. Функцията items_by_subtotal е подобна на предишната функция, но предава :subtotal параметър. Сега да обясним items_by :

дефиниране на преговорната сила на доставчиците
  • from i in Item, този макрос избира модула Item
  • join: ii in InvoiceItem, on: ii.item_id == i.id, създава присъединяване при условието “items.id = invoice_items.item_id”
  • select: %{id: i.id, name: i.name, total: sum(field(ii, ^type))}, генерираме карта с всички полета, които искаме, първо избираме идентификатора и името от Item и правим операторна сума. Полето (тип ii, ^) използва полето на макроса за динамичен достъп до поле
  • group_by: i.id, Групираме по items.id
  • order_by: [desc: sum(field(ii, ^type))] и накрая подредете по сумата в низходящ ред

Досега сме написали заявката в стила на списъка, но можем да я пренапишем в макро стил:

defp items_by(type) do Item |> join(:inner, [i], ii in InvoiceItem, ii.item_id == i.id) |> select([i, ii], %{id: i.id, name: i.name, total: sum(field(ii, ^type))}) |> group_by([i, _], i.id) |> order_by([_, ii], [desc: sum(field(ii, ^type))]) end

Предпочитам да пиша заявки под формата на списък, тъй като го намирам за по-четлив.

Заключение

Покрихме добра част от това, което можете да направите в приложение с Ecto. Разбира се, има много повече неща, които можете да научите от Ecto docs . С Ecto можете да създавате едновременни, устойчиви на грешки приложения с малко усилия, които могат лесно да се мащабират благодарение на виртуалната машина Erlang. Ecto осигурява основата за съхранение във вашите приложения Elixir и предоставя функции и макроси за лесно управление на вашите данни.

В този урок разгледахме Ecto.Schema , Ecto.Changeset , Ecto.Migration , Ecto.Query , и Ecto.Repo . Всеки от тези модули ви помага в различни части на вашето приложение и прави кода по-ясен и по-лесен за поддръжка и разбиране.

Ако искате да проверите кода на урока, можете да го намерите тук на GitHub.

Ако ви е харесал този урок и се интересувате от повече информация, бих препоръчал Феникс (за списък на страхотни проекти), Страхотен еликсир , и тази беседа който сравнява ActiveRecord с Ecto.

Анти-образци в дистанционна работа

Наука За Данни И Бази Данни

Анти-образци в дистанционна работа
Стилове на шрифтове за уеб и печат дизайн

Стилове на шрифтове за уеб и печат дизайн

Ui Design

Популярни Публикации
Широки срещу тесни набори от умения: Демистифицирани умения за софтуерно инженерство
Широки срещу тесни набори от умения: Демистифицирани умения за софтуерно инженерство
Талантът не е стока
Талантът не е стока
iOS 9 Betas и WatchOS 2 за разработчици
iOS 9 Betas и WatchOS 2 за разработчици
Ръководство за многообработващи мрежови сървърни модели
Ръководство за многообработващи мрежови сървърни модели
Как да създадете персонализирани шрифтове: 7 стъпки и 3 казуса
Как да създадете персонализирани шрифтове: 7 стъпки и 3 казуса
 
Настройка на производителността на базата данни на SQL за разработчици
Настройка на производителността на базата данни на SQL за разработчици
Събуждане на спяща индустрия: Нарушаване на индустрията на матраците
Събуждане на спяща индустрия: Нарушаване на индустрията на матраците
Figma срещу Sketch срещу Axure - Преглед, основан на задачи
Figma срещу Sketch срещу Axure - Преглед, основан на задачи
Разбиране на нюансите на класификацията на шрифтовете
Разбиране на нюансите на класификацията на шрифтовете
Възраст преди красотата - Ръководство за дизайн на интерфейси за възрастни възрастни
Възраст преди красотата - Ръководство за дизайн на интерфейси за възрастни възрастни
Популярни Публикации
  • как да разбера дали имам изтичане на памет
  • как да проектирате софтуерен проект
  • гъвкаво сравнение на софтуер за управление на проекти
  • лицензи с отворен код, които да избягвате
  • c++ разширен урок
Категории
  • Agile Talent
  • Дизайнерски Живот
  • Възходът На Дистанционното
  • Рентабилност И Ефективност
  • © 2022 | Всички Права Запазени

    portaldacalheta.pt