Частка другая ActiveRecord супраць Ecto

Гэта другая частка серыі "ActiveRecord vs. Ecto", у якой Бэтмен і Батгірл змагаюцца з пошукамі баз дадзеных і параўноўваем яблыкі і апельсіны.

Пасля прагляду схем баз дадзеных і міграцый у ActiveRecord супраць першай часткі Ecto, у гэтым паведамленні распавядаецца, як ActiveRecord і Ecto дазваляюць распрацоўшчыкам запытаць базу дадзеных, і як параўноўваць ActiveRecord і Ecto пры працы з аднолькавымі патрабаваннямі. Па шляху мы таксама высвятлім асобу Batgirl 1989–2011.

Дадзеныя пра насенне

Пачнем! Зыходзячы са структуры базы дадзеных, вызначанай у першым паведамленні гэтай серыі, выкажам здагадку, што карыстальнікі і табліцы рахункаў-фактур маюць у сабе наступныя дадзеныя:

карыстальнікаў

* Поле створанага_актыў ActiveRecord па змаўчанні названа ўстаўлена_матэрыялам Ecto.

рахункі-фактуры

* Поле створанага_актыў ActiveRecord па змаўчанні названа ўстаўлена_матэрыялам Ecto.

Запыты, выкананыя праз гэты пост, мяркуюць, што дадзеныя вышэй захоўваюцца ў базе дадзеных, таму памятайце пра гэтую інфармацыю, чытаючы яе.

Знайдзіце элемент, выкарыстоўваючы яго асноўны ключ

Пачнем з атрымання запісу з базы дадзеных, выкарыстоўваючы свой першасны ключ.

ActiveRecord

irb (main): 001: 0> User.find (1) Загрузка карыстальніка (0,4 мс) ВЫБАРЫ "карыстальнікаў". * З "карыстальнікаў", дзе "карыстальнікі". "id" = $ 1 ЛІМІТА $ 2 [["id", 1 ], ["LIMIT", 1]] => # <ідэнтыфікатар карыстальніка: 1, поўнае імя: "Bette Kane", адрас электроннай пошты: "[email protected]", created_at: "2018-01-01 10:01:00" , updated_at: "2018-01-01 10:01:00">

Экто

iex (3)> Repo.get (карыстач, 1)
[адладка] QUERY OK source = "users" db = 5,2ms decode = 2,5ms queue = 0,1ms
SELECT u0. "Id", u0. "Full_name", u0. "Email", u0. "Insert_at", u0. "Updated_at" ОТ "карыстальнікі" AS u0 WHERE (u0. "Id" = $ 1) [1]
% Financex.Accounts.User {
  __meta__: # Ecto.Schema.Metadata <: загружаны, "карыстальнікі">,
  адрас электроннай пошты: "[email protected]",
  full_name: "Бэтт Кейн",
  id: 1,
  insert_at: ~ N [2018-01-01 10: 01: 00.000000],
  рахункі-фактуры: # Ecto.Association.NotLoaded <асацыяцыя: рахункі-фактуры не загружаюцца>,
  updated_at: ~ N [2018-01-01 10: 01: 00.000000]
}

Параўнанне

Абодва выпадкі даволі падобныя. ActiveRecord абапіраецца на спосаб пошуку класа класа User User. Гэта азначае, што ў кожнага дзіцячага класа ActiveRecord ёсць свой метад пошуку.

Ecto выкарыстоўвае іншы падыход, абапіраючыся на канцэпцыю сховішча як пасярэдніка паміж картаграфічным пластом і даменам. Пры выкарыстанні Ecto, карыстацкі модуль не ведае, як знайсці сябе. Такая адказнасць прысутнічае ў модулі Repo, які можа адлюстраваць яго пад сховішчамі дадзеных, што ў нашым выпадку - Postgres.

Параўноўваючы сам запыт SQL, мы можам заўважыць некалькі адрозненняў:

  • ActiveRecord загружае ўсе палі (карыстальнікі. *), У той час як Ecto загружае толькі палі, указаныя ў вызначэнні схемы.
  • ActiveRecord ўключае ў сябе абмежаванне 1 для запыту, а Ecto не.

Выманне ўсіх прадметаў

Зробім крок далей і загрузім усіх карыстальнікаў з базы дадзеных.

ActiveRecord

irb (main): 001: 0> User.all Загрузка карыстальніка (0,5 мс) ВЫБАРЫ "карыстальнікаў". * З "карыстальнікаў" МЕЖА 1 $ [["ЛІМІТ", 11]] => # , # <Ідэнтыфікатар карыстальніка: 2, full_name:" Barbara Gordon ", email:" [email protected] ", created_at:" 2018-01-02 10:02:00 ", updated_at:" 2018-01-02 10:02:00 ">, # <ідэнтыфікатар карыстальніка: 3, поўнае імя:" Касандра Каін ", адрас электроннай пошты:" [email protected] ", created_at:" 2018-01-03 10:03:00 ", updated_at:" 2018-01-03 10:03:00 ">, # <ідэнтыфікатар карыстальніка: 4, поўнае імя:" Стэфані Браўн ", адрас электроннай пошты:" [email protected] ", created_at:" 2018-01-04 10:04:00 ", updated_at:" 2018-01-04 10:04:00 ">]>

Экто

iex (4)> Repo.all (Карыстальнік)
[адладка] QUERY OK source = "users" db = 2.8ms decode = 0.2ms queue = 0.2ms
SELECT u0. "Id", u0. "Full_name", u0. "Email", u0. "Insert_at", u0. "Updated_at" ОТ "карыстальнікі" AS u0 []
[
  % Financex.Accounts.User {
    __meta__: # Ecto.Schema.Metadata <: загружаны, "карыстальнікі">,
    адрас электроннай пошты: "[email protected]",
    full_name: "Бэтт Кейн",
    id: 1,
    insert_at: ~ N [2018-01-01 10: 01: 00.000000],
    рахункі-фактуры: # Ecto.Association.NotLoaded <асацыяцыя: рахункі-фактуры не загружаюцца>,
    updated_at: ~ N [2018-01-01 10: 01: 00.000000]
  },
  % Financex.Accounts.User {
    __meta__: # Ecto.Schema.Metadata <: загружаны, "карыстальнікі">,
    адрас электроннай пошты: "[email protected]",
    full_name: "Барбара Гордан",
    id: 2,
    insert_at: ~ N [2018-01-02 10: 02: 00.000000],
    рахункі-фактуры: # Ecto.Association.NotLoaded <асацыяцыя: рахункі-фактуры не загружаюцца>,
    updated_at: ~ N [2018-01-02 10: 02: 00.000000]
  },
  % Financex.Accounts.User {
    __meta__: # Ecto.Schema.Metadata <: загружаны, "карыстальнікі">,
    адрас электроннай пошты: "[email protected]",
    full_name: "Касандра Каін",
    id: 3,
    insert_at: ~ N [2018-01-03 10: 03: 00.000000],
    рахункі-фактуры: # Ecto.Association.NotLoaded <асацыяцыя: рахункі-фактуры не загружаюцца>,
    updated_at: ~ N [2018-01-03 10: 03: 00.000000]
  },
  % Financex.Accounts.User {
    __meta__: # Ecto.Schema.Metadata <: загружаны, "карыстальнікі">,
    адрас электроннай пошты: "[email protected]",
    full_name: "Стэфані Браўн",
    id: 4,
    insert_at: ~ N [2018-01-04 10: 04: 00.000000],
    рахункі-фактуры: # Ecto.Association.NotLoaded <асацыяцыя: рахункі-фактуры не загружаюцца>,
    updated_at: ~ N [2018-01-04 10: 04: 00.000000]
  }
]

Параўнанне

З гэтага вынікае тая ж схема, што і ў папярэднім раздзеле. ActiveRecord выкарыстоўвае метад усіх класаў, і для загрузкі запісаў Ecto абапіраецца на шаблон рэпазітара.

Зноў ёсць некаторыя адрозненні ў запытах SQL:

Запыт з умовамі

Вельмі малаверагодна, што нам трэба ўзяць усе запісы з табліцы. Агульная патрэба заключаецца ў выкарыстанні ўмоў для фільтрацыі вяртаемых дадзеных.

Давайце скарыстаемся гэтым прыкладам, каб пералічыць усе рахункі-фактуры, якія яшчэ трэба будзе аплачваць (ГДЕ pay_at IS NULL).

ActiveRecord

irb (main): 024: 0> Invoice.where (paid_at: nil) Загрузка рахункаў-фактур (18,2ms) ВЫБАРЫ "рахункі-фактуры". * З "рахункі-фактуры" WHERE "рахункі-фактуры". , 11]] => # , # <рахунак-фактура: 4, user_id: 4, Payment_method: nil, paid_at: nil, created_at:" 2018-01-05 08:00:00 ", updated_at:" 2018-01-05 08:00:00 ">]>

Экто

iex (19)> дзе (Invoice, [i], is_nil (i.paid_at)) |> Repo.all ()
[адладка] QUERY OK source = "рахункі-фактуры" db = 20,2ms
SELECT i0. "Id", i0. "Payment_method", i0. "Paid_at", i0. "User_id", i0. "Insert_at", i0. "Updated_at" FROM "рахункі" AS i0 WHERE (i0. "Paid_at" IS NULL) []
[
  % Financex.Accounts.Invoice {
    __meta__: # Ecto.Schema.Metadata <: загружаны, "рахункі-фактуры">,
    id: 3,
    insert_at: ~ N [2018-01-04 08: 00: 00.000000],
    paid_at: нуль,
    payment_method: нуль,
    updated_at: ~ N [2018-01-04 08: 00: 00.000000],
    Карыстальнік: # Ecto.Association.NotLoaded <асацыяцыя: карыстальнік не загружаны>,
    user_id: 3
  },
  % Financex.Accounts.Invoice {
    __meta__: # Ecto.Schema.Metadata <: загружаны, "рахункі-фактуры">,
    id: 4,
    insert_at: ~ N [2018-01-04 08: 00: 00.000000],
    paid_at: нуль,
    payment_method: нуль,
    updated_at: ~ N [2018-01-04 08: 00: 00.000000],
    Карыстальнік: # Ecto.Association.NotLoaded <асацыяцыя: карыстальнік не загружаны>,
    user_id: 4
  }
]

Параўнанне

У абодвух прыкладах выкарыстоўваецца ключавое слова, дзе знаходзіцца сувязь з прапановай SQL WHERE. Хоць згенераваныя запыты SQL вельмі падобныя, але спосаб атрымання абодвух інструментаў мае некаторыя важныя адрозненні.

ActiveRecord аўтаматычна пераўтворыць аргумент pay_at: nil у аператар pay_at IS NULL SQL. Для таго, каб дабрацца да таго ж высновы з дапамогай Ecto, распрацоўшчыкам неабходна больш дакладна сказаць пра свае намеры, патэлефанаваўшы ў is_nil ().

Яшчэ адно адрозненне, якое трэба адзначыць, - гэта "чыстае" паводзіны функцыі, якая знаходзіцца ў Ecto. Пры адзіным выкліку функцыі дзе яна не ўзаемадзейнічае з базай дадзеных. Вяртанне функцыі дзе з'яўляецца структурай Ecto.Query:

iex (20)> дзе (рахунак, [i], is_nil (i.paid_at))
# Ecto.Query <з i ў Financex.Accounts.Invoice, дзе: is_nil (i.paid_at)>

База дакранаецца толькі тады, калі выклікаецца функцыя Repo.all (), перадаючы структуру Ecto.Query як аргумент. Такі падыход дазваляе складаць запыты ў Ecto, што з'яўляецца тэмай наступнага раздзела.

Склад запытаў

Адзін з самых магутных аспектаў запытаў да баз даных - гэта склад. Ён апісвае запыт такім чынам, які змяшчае больш за адну ўмову.

Калі вы будуеце неапрацаваныя запыты SQL, гэта азначае, што вы, верагодна, будзеце выкарыстоўваць нейкую сувязь. Уявіце, у вас дзве ўмовы:

  1. not_paid = 'paid_at НЕ NULL'
  2. paid_with_paypal = 'payment_method = "Paypal"'

Каб аб'яднаць гэтыя два ўмовы з выкарыстаннем сырога SQL, вам прыйдзецца аб'яднаць іх, выкарыстоўваючы нешта падобнае:

ВЫБАРЫ * З рахункаў-фактур, Дзе # {not_paid} І # {paid_with_paypal}

На шчасце, у ActiveRecord і Ecto ёсць рашэнне для гэтага.

ActiveRecord

irb (main): 003: 0> Invoice.where.not (paid_at: nil) .where (Payment_method: "Paypal") Загрузка рахункаў-фактур (8.0ms) ВЫБАРЫ "рахункі-фактуры". * З "рахункі-фактуры", дзе "рахункі-фактуры". " paid_at "НЕ NULL AND" рахункі-фактуры "." Payment_method "= $ 1 ЛІМІТ $ 2 [[" Payment_method "," Paypal "], [" LIMIT ", 11]] => # ]>

Экто

iex (6)> рахунак-фактура |> дзе ([i], а не is_nil (i.paid_at)) |> дзе ([i], i.payment_method == "Paypal") |> Repo.all ()
[адладка] QUERY OK source = "рахункі-фактуры" db = 30,0ms decode = 0,6ms queue = 0,2ms
SELECT i0. "Id", i0. "Payment_method", i0. "Paid_at", i0. "User_id", i0. "Insert_at", i0. "Updated_at" FROM "рахункі" AS i0 WHERE (НЕ (i0. "Paid_at "Is NULL)) AND (i0." Payment_method "= 'Paypal') []
[
  % Financex.Accounts.Invoice {
    __meta__: # Ecto.Schema.Metadata <: загружаны, "рахункі-фактуры">,
    id: 2,
    insert_at: ~ N [2018-01-03 08: 00: 00.000000],
    paid_at: #DateTime <2018-02-01 08: 00: 00.000000Z>,
    payment_method: "Paypal",
    updated_at: ~ N [2018-01-03 08: 00: 00.000000],
    Карыстальнік: # Ecto.Association.NotLoaded <асацыяцыя: карыстальнік не загружаны>,
    user_id: 2
  }
]

Параўнанне

Абодва запыты адказваюць на адно і тое ж пытанне: "Якія рахункі былі аплачаны і выкарыстаны Paypal?".

Як ужо чакалася, ActiveRecord прапануе больш ёмісты спосаб складання запыту (для гэтага прыкладу), у той час як Ecto патрабуе ад распрацоўнікаў выдаткаваць трохі больш на напісанне запыту. Як звычайна, Batgirl (Сірата, прыглушаная асоба Касандры Каіна) або Activerecord не такія шматслоўныя.

Не падманвайце шматслоўнасць і відавочную складанасць запыту Ecto, паказанага вышэй. У рэальным свеце гэты запыт будзе перапісаны, каб выглядаць больш:

Рахунак-фактура
|> дзе ([i], а не is_nil (i.paid_at))
|> дзе ([i], i.payment_method == "Paypal")
|> Repo.all ()

З гэтага боку, спалучэнне "чыстых" аспектаў функцыі, якая, якая не выконвае аперацыі з базай дадзеных самастойна, з аператарам трубы, робіць склад запытаў у Ecto сапраўды чыстым.

Упарадкаванне

Заказ - важны аспект запыту. Гэта дазваляе распрацоўшчыкам пераканацца, што дадзены вынік запыту адпавядае вызначанаму парадку.

ActiveRecord

irb (main): 002: 0> Invoice.order (created_at:: desc) Загрузка рахункаў-фактур (1,5 мс). Выберыце "рахункі-фактуры". * З "Рахункі-фактуры" ЗАКАЗІЦЬ "Рахункі". ", 11]] => # , # <рахунак-фактура: 3, user_id: 3, Payment_method: nil, paid_at: nil, created_at: "2018-01-04 08:00:00", updated_at: "2018-01-04 08:00:00">, # <рахунак-фактура: 2, user_id: 2, Payment_method: "Paypal", pay_at: "2018-02-01 08:00:00", created_at: "2018 -01-03 08:00:00 ", updated_at:" 2018-01-03 08:00:00 ">, # <Рахунак-фактура: 1, user_id: 1, Payment_method:" Крэдытная карта ", paid_at:" 2018- 02-01 08:00:00 ", created_at:" 2018-01-02 08:00:00 ", updated_at:" 2018-01-02 08:00:00 ">]>

Экто

iex (6)> order_by (Invoice, desc:: insert_at) |> Repo.all ()
[адладка] QUERY OK source = "рахункі-фактуры" db = 19,8ms
SELECT i0. "Id", i0. "Payment_method", i0. "Paid_at", i0. "User_id", i0. "Insert_at", i0. "Updated_at" FROM "рахункі" AS i0 ЗАКАЗ BY0. "Insert_at" DESC []
[
  % Financex.Accounts.Invoice {
    __meta__: # Ecto.Schema.Metadata <: загружаны, "рахункі-фактуры">,
    id: 3,
    insert_at: ~ N [2018-01-04 08: 00: 00.000000],
    paid_at: нуль,
    payment_method: нуль,
    updated_at: ~ N [2018-01-04 08: 00: 00.000000],
    Карыстальнік: # Ecto.Association.NotLoaded <асацыяцыя: карыстальнік не загружаны>,
    user_id: 3
  },
  % Financex.Accounts.Invoice {
    __meta__: # Ecto.Schema.Metadata <: загружаны, "рахункі-фактуры">,
    id: 4,
    insert_at: ~ N [2018-01-04 08: 00: 00.000000],
    paid_at: нуль,
    payment_method: нуль,
    updated_at: ~ N [2018-01-04 08: 00: 00.000000],
    Карыстальнік: # Ecto.Association.NotLoaded <асацыяцыя: карыстальнік не загружаны>,
    user_id: 4
  },
  % Financex.Accounts.Invoice {
    __meta__: # Ecto.Schema.Metadata <: загружаны, "рахункі-фактуры">,
    id: 2,
    insert_at: ~ N [2018-01-03 08: 00: 00.000000],
    paid_at: #DateTime <2018-02-01 08: 00: 00.000000Z>,
    payment_method: "Paypal",
    updated_at: ~ N [2018-01-03 08: 00: 00.000000],
    Карыстальнік: # Ecto.Association.NotLoaded <асацыяцыя: карыстальнік не загружаны>,
    user_id: 2
  },
  % Financex.Accounts.Invoice {
    __meta__: # Ecto.Schema.Metadata <: загружаны, "рахункі-фактуры">,
    id: 1,
    insert_at: ~ N [2018-01-02 08: 00: 00.000000],
    paid_at: #DateTime <2018-02-01 08: 00: 00.000000Z>,
    payment_method: "Крэдытная карта",
    updated_at: ~ N [2018-01-02 08: 00: 00.000000],
    Карыстальнік: # Ecto.Association.NotLoaded <асацыяцыя: карыстальнік не загружаны>,
    user_id: 1
  }
]

Параўнанне

У абодвух інструментах даданне замовы да запыту проста.

Хоць у прыкладзе Ecto у якасці першага параметра выкарыстоўваецца рахунак-фактура, функцыя order_by таксама прымае структуры Ecto.Query, што дазваляе выкарыстоўваць функцыю order_by у кампазіцыях, напрыклад:

Рахунак-фактура
|> дзе ([i], а не is_nil (i.paid_at))
|> дзе ([i], i.payment_method == "Paypal")
|> order_by (desc:: insert_at)
|> Repo.all ()

Абмежаванне

Што будзе базай дадзеных без абмежаванняў? Катастрофа. На шчасце, і ActiveRecord і Ecto дапамагаюць абмежаваць колькасць вернутых запісаў.

ActiveRecord

irb (асноўная): 004: 0> Invoice.limit (2)
Загрузка рахунку-фактуры (0,2 мс) ВЫБАРЫ "рахункі-фактуры". * З "Рахункі-фактуры" ЛІМИТЫ $ 1 [["ЛІМІТ", 2]]
=> # , # <Рахунак-фактура: 2, user_id: 2, Payment_method:" Paypal ", paid_at:" 2018-02-01 08: 00:00 ", created_at:" 2018-01-03 08:00:00 ", updated_at:" 2018-01-03 08:00:00 ">]>

Экто

iex (22)> ліміт (рахунак-фактура, 2) |> Repo.all ()
[адладка] QUERY OK source = "рахункі-фактуры" db = 3,6 мс
SELECT i0. "Id", i0. "Payment_method", i0. "Paid_at", i0. "User_id", i0. "Insert_at", i0. "Updated_at" FROM "рахункі" AS i0 LIMIT 2 []
[
  % Financex.Accounts.Invoice {
    __meta__: # Ecto.Schema.Metadata <: загружаны, "рахункі-фактуры">,
    id: 1,
    insert_at: ~ N [2018-01-02 08: 00: 00.000000],
    paid_at: #DateTime <2018-02-01 08: 00: 00.000000Z>,
    payment_method: "Крэдытная карта",
    updated_at: ~ N [2018-01-02 08: 00: 00.000000],
    Карыстальнік: # Ecto.Association.NotLoaded <асацыяцыя: карыстальнік не загружаны>,
    user_id: 1
  },
  % Financex.Accounts.Invoice {
    __meta__: # Ecto.Schema.Metadata <: загружаны, "рахункі-фактуры">,
    id: 2,
    insert_at: ~ N [2018-01-03 08: 00: 00.000000],
    paid_at: #DateTime <2018-02-01 08: 00: 00.000000Z>,
    payment_method: "Paypal",
    updated_at: ~ N [2018-01-03 08: 00: 00.000000],
    Карыстальнік: # Ecto.Association.NotLoaded <асацыяцыя: карыстальнік не загружаны>,
    user_id: 2
  }
]

Параўнанне

ActiveRecord і Ecto маюць магчымасць абмежаваць колькасць запісаў, якія вяртаюцца запытам.

Абмежаванне Ecto працуе аналагічна order_by і падыходзіць для складання запытаў.

Асацыяцыі

ActiveRecord і Ecto па-рознаму падыходзяць да таго, як звяртацца з асацыяцыямі.

ActiveRecord

У ActiveRecord вы можаце выкарыстоўваць любую асацыяцыю, вызначаную ў мадэлі, без неабходнасці рабіць што-небудзь асаблівае, напрыклад:

irb (main): 012: 0> user = User.find (2) Загрузка карыстальніка (0,3ms) ВЫБАРЫ "карыстальнікаў". * З "карыстальнікаў", дзе "карыстальнікі". "id" = $ 1 ЛІМІТА $ 2 [["id" , 2], ["LIMIT", 1]] => # <ідэнтыфікатар карыстальніка: 2, full_name: "Barbara Gordon", email: "[email protected]", created_at: "2018-01-02 10:02: 00 ", updated_at:" 2018-01-02 10:02:00 "> irb (main): 013: 0> user.invoices Invoice Load (0.4ms) ВЫБАРЫ" рахункі-фактуры ". * FROM" рахункі-фактуры "WHERE" рахункі " "user_id" = $ 1 LIMIT $ 2 [["user_id", 2], ["LIMIT", 11]] => # ] >

Прыклад вышэй паказвае, што мы можам атрымаць спіс рахункаў карыстальнікаў пры званку user.invoices. Робячы гэта, ActiveRecord аўтаматычна запытваў базу дадзеных і загружаў рахункі-фактуры, звязаныя з карыстальнікам. Хоць такі падыход палягчае працу, у сэнсе напісання меншага кода альбо неабходнасці турбавацца пра дадатковыя крокі, гэта можа быць праблемай, калі вы перабіраеце некалькі карыстальнікаў і атрымліваеце рахункі-фактуры для кожнага карыстальніка. Гэтае пытанне вядома як "праблема N + 1".

У ActiveRecord прапануецца выправіць "N + 1 праблему" з выкарыстаннем метаду include:

irb (main): 022: 0> user = User.includes (: рахункі-фактуры) .find (2) Загрузка карыстальніка (0,3ms) ВЫБАРЫ "карыстальнікаў". * З "карыстальнікаў", дзе "карыстальнікі". "id" = $ 1 ЛІМІТ $ 2 [["id", 2], ["LIMIT", 1]] Загрузка рахунку-фактуры (0,6 мс). Выберыце "рахункі-фактуры". * З "рахункі-фактуры", дзе "рахункі-фактуры". 2]] => # <Ідэнтыфікатар карыстальніка: 2, full_name: "Barbara Gordon", email: "[email protected]", created_at: "2018-01-02 10:02:00", updated_at: "2018-01 -02 10:02:00 "> irb (main): 023: 0> user.invoices => # ]>

У гэтым выпадку ActiveRecord імкнецца загружаць асацыяцыі рахункаў пры здабыванні карыстальніка (як паказана на двух паказах SQL).

Экто

Як вы ўжо маглі заўважыць, Экто сапраўды не любіць магію і няяўнасць. Гэта патрабуе ад распрацоўшчыкаў, каб яны былі дакладныя пра свае намеры.

Давайце паспрабуем выкарыстаць той жа падыход, каб скарыстацца user.invoices з Ecto:

iex (7)> ​​user = Repo.get (карыстальнік, 2)
[адладка] QUERY OK source = "users" db = 18,3ms decode = 0,6ms
SELECT u0. "Id", u0. "Full_name", u0. "Email", u0. "Insert_at", u0. "Updated_at" ОТ "карыстальнікі" AS u0 WHERE (u0. "Id" = $ 1) [2]
% Financex.Accounts.User {
  __meta__: # Ecto.Schema.Metadata <: загружаны, "карыстальнікі">,
  адрас электроннай пошты: "[email protected]",
  full_name: "Барбара Гордан",
  id: 2,
  insert_at: ~ N [2018-01-02 10: 02: 00.000000],
  рахункі-фактуры: # Ecto.Association.NotLoaded <асацыяцыя: рахункі-фактуры не загружаюцца>,
  updated_at: ~ N [2018-01-02 10: 02: 00.000000]
}
iex (8)> user.invoices
# Ecto.Association.NotLoaded <асацыяцыя: рахункі-фактуры не загружаюцца>

У выніку выходзіць Ecto.Association.NotLoaded. Не так карысна.

Каб атрымаць доступ да рахункаў-фактур, распрацоўшчык павінен паведаміць Ecto пра гэта, выкарыстоўваючы функцыю папярэдняй загрузкі:

iex (12)> user = preload (Карыстальнік,: рахункі-фактуры) |> Repo.get (2)
[адладка] QUERY OK source = "users" db = 11,8 мс
SELECT u0. "Id", u0. "Full_name", u0. "Email", u0. "Insert_at", u0. "Updated_at" ОТ "карыстальнікі" AS u0 WHERE (u0. "Id" = $ 1) [2]
[адладка] QUERY OK source = "рахункі-фактуры" db = 4,2 мс
SELECT i0. "Id", i0. "Payment_method", i0. "Paid_at", i0. "User_id", i0. "Insert_at", i0. "Updated_at", i0. "User_id" FROM "рахункі" AS i0 WHERE ( i0. "user_id" = $ 1) ЗАМОВА I0. "user_id" [2]
% Financex.Accounts.User {
  __meta__: # Ecto.Schema.Metadata <: загружаны, "карыстальнікі">,
  адрас электроннай пошты: "[email protected]",
  full_name: "Барбара Гордан",
  id: 2,
  insert_at: ~ N [2018-01-02 10: 02: 00.000000],
  рахункі-фактуры: [
    % Financex.Accounts.Invoice {
      __meta__: # Ecto.Schema.Metadata <: загружаны, "рахункі-фактуры">,
      id: 2,
      insert_at: ~ N [2018-01-03 08: 00: 00.000000],
      paid_at: #DateTime <2018-02-01 08: 00: 00.000000Z>,
      payment_method: "Paypal",
      updated_at: ~ N [2018-01-03 08: 00: 00.000000],
      Карыстальнік: # Ecto.Association.NotLoaded <асацыяцыя: карыстальнік не загружаны>,
      user_id: 2
    }
  ],
  updated_at: ~ N [2018-01-02 10: 02: 00.000000]
}

iex (15)> user.invoices
[
  % Financex.Accounts.Invoice {
    __meta__: # Ecto.Schema.Metadata <: загружаны, "рахункі-фактуры">,
    id: 2,
    insert_at: ~ N [2018-01-03 08: 00: 00.000000],
    paid_at: #DateTime <2018-02-01 08: 00: 00.000000Z>,
    payment_method: "Paypal",
    updated_at: ~ N [2018-01-03 08: 00: 00.000000],
    Карыстальнік: # Ecto.Association.NotLoaded <асацыяцыя: карыстальнік не загружаны>,
    user_id: 2
  }
]

Аналагічна ActiveRecord ўключае ў сябе папярэднюю загрузку звязаных рахункаў-фактур, што зробіць іх даступнымі пры званку рахункаў для карыстальнікаў.

Параўнанне

Зноў бітва паміж ActiveRecord і Ecto заканчваецца вядомым пунктам: выразнасцю. Абодва інструмента дазваляюць распрацоўнікам лёгка атрымліваць доступ да асацыяцый, але, хоць ActiveRecord робіць яго менш падрабязным, у выніку можа ўзнікнуць нечаканае паводзіны. Ecto выконвае выгляд падыходу WYSIWYG, які выконвае толькі тое, што бачна ў запыце, вызначаным распрацоўшчыкам.

Rails добра вядома па выкарыстанні і прасоўванні стратэгій кэшавання для ўсіх розных слаёў прыкладання. Адзін прыклад - выкарыстанне падыходу кэшавання "рускай лялькі", які выконвае сваю магію цалкам для праблемы "N + 1".

Праверка

Большасць праверкі, прысутныя ў ActiveRecord, таксама даступныя ў Ecto. Вось спіс агульных праверкі і як ActiveRecord і Ecto вызначаюць іх:

Хутацца

Вось вам: істотныя яблыкі ў параўнанні з апельсінамі.

ActiveRecord факусуюць на лёгкасці выканання запытаў да базы дадзеных. Пераважная большасць яго функцый сканцэнтравана на саміх мадэльных класах, не патрабуючы ад распрацоўшчыкаў глыбокага разумення базы дадзеных і ўплыву такіх аперацый. ActiveRecord робіць мноства рэчаў, якія няяўна ўключаныя. Хоць гэта і палягчае пачатак працы, усё цяжэй зразумець, што адбываецца за кулісамі, і гэта працуе толькі ў тым выпадку, калі вы будзеце прытрымлівацца "шляху ActiveRecord".

Ecto, з іншага боку, патрабуе яўнасці, што прыводзіць да больш падрабязнаму коду. У якасці перавагі, усё знаходзіцца ў цэнтры ўвагі, нічога за кадрам, і вы можаце вызначыць свой уласны шлях.

У залежнасці ад вашай перспектывы і пераваг абодва перавернутыя. Такім чынам, параўнаўшы яблыкі і апельсіны, мы падышлі да канца гэтай BAT-tle. Амаль забыўся сказаць вам кодавае імя BatGirl (1989–2001)… Аракул. Але не будзем у гэтым займацца.

Гэты пост напісаў запрошаны аўтар Эльвіо Вікоса. Эльвіё з'яўляецца аўтарам кнігі Phoenix for Rails Developers.

Першапачаткова апублікаваны на blog.appsignal.com 9 кастрычніка 2018 года.