ActiveRecord در مقابل Ecto قسمت دوم

این دومین قسمت از سریال "ActiveRecord vs. Ecto" است که در آن بتمن و Batgirl با جستجوی پایگاه داده ها می جنگند و ما سیب و پرتقال را با هم مقایسه می کنیم.

این پست پس از بررسی طرحواره های دیتابیس و مهاجرت در ActiveRecord در مقابل Ecto قسمت اول ، چگونگی فعال سازی ActiveRecord و Ecto را برای توسعه دهندگان در جستجو از بانک اطلاعاتی و همچنین نحوه مقایسه هر دو ActiveRecord و Ecto در هنگام مواجهه با نیازهای مشابه را در بر می گیرد. در طول راه ، ما همچنین هویت Batgirl را پیدا خواهیم کرد.

داده های بذر

بیا شروع کنیم! بر اساس ساختار بانک اطلاعاتی تعریف شده در اولین پست از این سری ، فرض کنید که کاربران و جدول فاکتورها داده های زیر را در آنها ذخیره می کنند:

کاربران

* زمینه ایجاد شده_ ActiveRecord به طور پیش فرض نامگذاری شده insert_at در Ecto است.

فاکتورها

* زمینه ایجاد شده_ ActiveRecord به طور پیش فرض نامگذاری شده insert_at در Ecto است.

پرس و جوهای انجام شده از طریق این پست فرض می کنند که داده های فوق در پایگاه داده ذخیره می شوند ، بنابراین این اطلاعات را هنگام خواندن در ذهن داشته باشید.

با استفاده از کلید اصلی آن مورد را پیدا کنید

بیایید با استفاده از کلید اصلی آن ، یک رکورد از بانک اطلاعات دریافت کنیم.

ActiveRecord

irb (اصلی): 001: 0> User.find (1) بار کاربر (0.4 میلی متر) SELECT "کاربران" را انتخاب کنید. * "کاربران" از "کاربران" "" که "" "" "ID" = 1 $ LIMIT $ 2 $ [["id"، 1 ]، ["LIMIT"، 1]] => # <شناسه کاربری: 1 ، کامل_نام: "بت کین" ، ایمیل: "[email protected]" ، ایجاد شده_ات: "2018-01-01 10:01:00" ، به روز شده_ات: "2018-01-01 10:01:00">

اکتو

iex (3)> Repo.get (كاربر ، 1)
[اشکال زدایی] QUERY OK منبع = "کاربران" db = 5.2ms رمزگشایی = صف 2.5ms = 0.1ms
"u" را انتخاب کنید. "id" ، u0. "full_name" ، u0. "email"، u0. "inserted_at"، u0. "updated_at" کاربران "از" "به عنوان u0 WHERE (u0." id "= 1 $) [1]
٪ Financex.Accounts.User
  __meta__: # Ecto.Schema.Metadata <: لود شده ، "کاربران"> ،
  ایمیل: "[email protected]" ،
  full_name: "بت کین" ،
  شناسه: 1 ،
  inserted_at: ~ N [2018-01-01 10: 01: 00.000000]،
  فاکتورها: # Ecto.Association.NotLoaded <انجمن: فاکتورها بارگیری نمی شود> ،
  updated_at: ~ N [2018-01-01 10: 01: 00.000000]
}

مقایسه

هر دو مورد کاملاً مشابه هستند. ActiveRecord به روش کلاس find کلاس کلاس کاربر متکی است. این بدان معناست که هر کلاس کودک ActiveRecord روش پیدا کردن خود را در آن دارد.

Ecto با تکیه بر مفهوم Repository به عنوان واسطه بین لایه نقشه برداری و دامنه ، از یک رویکرد متفاوت استفاده می کند. در هنگام استفاده از Ecto ، ماژول User هیچ اطلاعی در مورد چگونگی پیدا کردن خود ندارد. چنین مسئولیتی در ماژول Repo وجود دارد که قادر است آن را در زیر دیتاستور نقشه ببرد که در مورد ما Postgres است.

هنگام مقایسه پرس و جو SQL ، می توان چند تفاوت را تشخیص داد:

  • ActiveRecord بارگیری تمام فیلدها (کاربران. *) ، در حالی که Ecto بارگیری فقط قسمت های ذکر شده در تعریف طرحواره.
  • ActiveRecord شامل LIMIT 1 برای پرس و جو است ، در حالی که Ecto اینگونه نیست.

واکشی همه موارد

بیایید یک قدم جلوتر برویم و همه کاربران را از بانک اطلاعات بارگیری کنیم

ActiveRecord

irb (اصلی): 001: 0> بار کاربر User.all (0.5 میلی متر) SEARCT "کاربران" را انتخاب کنید. * کاربران "از" LIMIT $ 1 [["" LIMIT "، 11]] => #  ، # <شناسه کاربری: 2 ، نام کامل:" باربارا گوردون "، ایمیل:" [email protected] "، ایجاد_ات:" 2018-01-02 10:02:00 "، به روز شده_ات:" 2018-01-02 10:02:00 "> ، # <شناسه کاربر: 3 ، نام کامل:" Cassandra Cain "، ایمیل:" [email protected] "، ایجاد شده:" 2018-01-03 10:03:00 "، به روز شده_ات:" 2018-01-03 10:03:00 "> ، # <شناسه کاربری: 4 ، کامل_نام:" استفانی براون "، ایمیل:" [email protected] "، ایجاد_ات:" 2018-01-04 10:04:00 "، به روز شده_ات:" 2018-01-04 10:04:00 ">]>

اکتو

iex (4)> Repo.all (کاربر)
[اشکال زدایی] QUERY OK منبع = "کاربران" db = 2.8ms رمزگشایی = صف 0.2ms = 0.2ms
"u" را انتخاب کنید. "id" ، u0. "full_name"، u0. "email"، u0. "inserted_at"، u0. "updated_at" کاربران "از" "شما u0 []
[
  ٪ Financex.Accounts.User
    __meta__: # Ecto.Schema.Metadata <: لود شده ، "کاربران"> ،
    ایمیل: "[email protected]" ،
    full_name: "بت کین" ،
    شناسه: 1 ،
    inserted_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: "باربارا گوردون" ،
    شناسه: 2 ،
    inserted_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: "کاساندرا قابیل" ،
    شناسه: 3 ،
    inserted_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: "استفانی براون" ،
    شناسه: 4 ،
    inserted_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 دوباره اختلافاتی وجود دارد:

پرس و جو با شرایط

بسیار بعید است که لازم باشد همه سوابق را از جدول جدا کنیم. یک نیاز مشترک استفاده از شرایط برای فیلتر کردن داده های برگشت یافته است.

بیایید از آن مثال استفاده کنیم تا تمام فاکتورهایی را که باید پرداخت شود لیست کنیم (WHERE pay_at NULL است).

ActiveRecord

irb (اصلی): 024: 0> Invoice.where (pay_at: nil) بار فاکتور (18.2 میلی متر) SEARCT "فاکتورها" را انتخاب کنید. * " ، 11]] => # ، # <شناسه فاکتور: 4، user_id: 4، Payment_method: nil، پرداخت_at: nil، ایجاد_at:" 2018-01-05 08:00:00 "، به روز شده_at:" 2018-01-05 08:00:00 ">]>

اکتو

iex (19)> جایی که (فاکتور ، [i] ، is_nil (i.paid_at)) |> Repo.all ()
[اشکال زدایی] QUERY OK منبع = "فاکتورها" db = 20.2ms
SELECT i0. "id" ، i0. "Payment_method" ، i0. "Payment_at" ، i0. "user_id" ، i0. "insert_at" ، i0. "updated_at" FROM "فاکتورهای" AS i0 WHERE (i0 "" pay_at "است. خالی) []
[
  ٪ Financex.Accounts.Invoice
    __meta__: # Ecto.Schema.Metadata <: لود شده ، "فاکتورها"> ،
    شناسه: 3 ،
    inserted_at: ~ N [2018-01-04 08: 00: 00.000000]،
    pay_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 <: لود شده ، "فاکتورها"> ،
    شناسه: 4 ،
    inserted_at: ~ N [2018-01-04 08: 00: 00.000000]،
    pay_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.Query است:

iex (20)> جایی که (فاکتور ، [i] ، is_nil (i.paid_at))
# Ecto.Query <از من در Financialx.Accounts.Ivoice ، جایی که: is_nil (i.paid_at)>

این پایگاه داده فقط هنگامی که عملکرد Repo.all () نامیده می شود لمس می شود ، و از ساختار Ecto.Query به عنوان آرگومان عبور می کند. این روش امکان ترکیب پرس و جو در Ecto را فراهم می کند ، که موضوع بخش بعدی است.

ترکیب پرس و جو

یکی از مهمترین جنبه نمایش داده های پایگاه داده ، ترکیب است. این سؤال را به روشی توصیف می کند که حاوی بیش از یک شرط واحد است.

اگر در حال ایجاد سؤالات خام SQL هستید ، به این معنی است که احتمالاً از نوعی مصالحه استفاده خواهید کرد. تصور کنید که دو شرط دارید:

  1. not_paid = 'pay_at NULL نیست
  2. Payment_with_paypal = 'Payment_method = "Paypal" "

به منظور ترکیب این دو شرط با استفاده از SQL خام ، بدان معنی است که شما باید آنها را با استفاده از چیزی شبیه به:

SELECT * از فاکتورهای WHERE # {not_paid} و # {پرداخت شده_با_پایپال}

خوشبختانه هم ActiveRecord و هم Ecto راه حلی برای آن دارند.

ActiveRecord

irb (اصلی): 003: 0> Invoice.where.not (Payment_at: nil) .where (Payment_method: "Paypal") بار فاکتور (8.0ms) SELECT "فاکتورها" را انتخاب کنید. * از "فاکتورهای" کجا "فاکتورها". " Payment_at "IS NULL و" فاکتور "نیست." Payment_method "= 1 $ LIMIT $ 2 [[" "Payment_method" ، "Paypal"] ، ["LIMIT" ، 11]] => # ]>

اکتو

iex (6)> فاکتور |> کجا ([i] ، نه is_nil (i.paid_at)) |> کجا ([i] ، i.payment_method == "Paypal") |> Repo.all ()
[اشکال زدایی] QUERY OK منبع = "فاکتورها" db = 30.0ms رمزگشایی = صف 0.6ms = 0.2ms
SELECT i0. "id" ، i0. "Payment_method" ، i0. "Payment_at" ، i0. "user_id" ، i0. "inserted_at" ، i0 ". "IS NULL است)" و (i0. "Payment_method" = 'Paypal') []
[
  ٪ Financex.Accounts.Invoice
    __meta__: # Ecto.Schema.Metadata <: لود شده ، "فاکتورها"> ،
    شناسه: 2 ،
    inserted_at: ~ N [2018-01-03 08: 00: 00.000000]،
    Payment_at: # DateTime <2018-02-01 08: 00: 00.000000Z>،
    Payment_method: "پی پال" ،
    updated_at: ~ N [2018-01-03 08: 00: 00.000000]،
    کاربر: # Ecto.Association.NotLoaded <انجمن: کاربر بارگیری نمی شود> ،
    user_id: 2
  }
]

مقایسه

هر دو سؤال در حال پاسخ دادن به همین سؤال هستند: "کدام فاکتورها پرداخت شده و از پی پال استفاده شده اند؟"

همانطور که قبلاً انتظار می رفت ، ActiveRecord روش خلاصه تری برای ترکیب پرس و جو ارائه می دهد (برای مثال) ، در حالی که Ecto به توسعه دهندگان نیاز دارد تا کمی بیشتر برای نوشتن پرس و جو بپردازند. طبق معمول ، Batgirl (یتیم ، یکی با شخصیت Cassandra Cain) را نادیده می گیرد) یا Activerecord به اندازه کلامی نیست.

فریب نخورید و پیچیدگی ظاهری سؤال اکتو که در بالا نشان داده شده است. در یک محیط دنیای واقعی ، آن درخواست برای بازنویسی بیشتر بازنویسی می شود:

صورتحساب
|> جایی که ([i] ، is_nil نیست (i.paid_at))
|> جایی که ([i] ، i.payment_method == "پی پال")
|> Repo.all ()

با دیدن این زاویه ، ترکیبی از جنبه های "خالص" عملکرد که در آن ، که به خودی خود عملیات پایگاه داده را انجام نمی دهد ، با عملگر لوله ، باعث می شود ترکیب پرس و جو در Ecto کاملاً تمیز باشد.

مرتب سازی

سفارش مهمترین جنبه پرس و جو است. این برنامه نویسان را قادر می سازد اطمینان حاصل كنند كه نتیجه پرس و جو داده شده از نظم خاصی پیروی می كند.

ActiveRecord

irb (اصلی): 002: 0> Invoice.order (ایجاد_ات:: نزولی) بار فاکتور (1.5ms) SEARCT "فاکتورها" را انتخاب کنید. * از "فاکتورهای" سفارش "سفارش دهید" توسط "فاکتورها". "afirandin_at" DESC LIMIT $ 1 [["LIMIT "، 11]] => #  ، # <شناسه فاکتور: 3، user_id: 3، Payment_method: nil، پرداخت_at: nil، ایجاد_at: "2018-01-04 08:00:00"، به روز شده_at: "2018-01-04 08:00:00"> ، # <شناسه فاکتور: 2، user_id: 2، Payment_method: "Paypal"، پرداخت_at: "2018-02-01 08:00:00"، ایجاد_ات: "2018 -01-03 08:00:00 "، به روز شده_at:" 2018-01-03 08:00:00 "> ، # <شناسه فاکتور: 1 ، user_id: 1 ، پرداخت_متعارف:" کارت اعتباری "، پرداخت_ات:" 2018- 02-01 08:00:00 "، ایجاد_ات:" 2018-01-02 08:00:00 "، به روز شده_at:" 2018-01-02 08:00:00 ">]>

اکتو

iex (6)> order_by (فاکتور ، نزولی:: inserted_at) |> Repo.all ()
[اشکال زدایی] QUERY OK منبع = "فاکتورها" db = 19.8ms
SELECT i0. "id" ، i0. "Payment_method" ، i0. "Payment_at" ، i0. "user_id" ، i0. "inserted_at" ، i0. "updated_at" FROM "فاکتورهای" AS i0 ORDER BY i0 " []
[
  ٪ Financex.Accounts.Invoice
    __meta__: # Ecto.Schema.Metadata <: لود شده ، "فاکتورها"> ،
    شناسه: 3 ،
    inserted_at: ~ N [2018-01-04 08: 00: 00.000000]،
    pay_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 <: لود شده ، "فاکتورها"> ،
    شناسه: 4 ،
    inserted_at: ~ N [2018-01-04 08: 00: 00.000000]،
    pay_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 <: لود شده ، "فاکتورها"> ،
    شناسه: 2 ،
    inserted_at: ~ N [2018-01-03 08: 00: 00.000000]،
    Payment_at: # DateTime <2018-02-01 08: 00: 00.000000Z>،
    Payment_method: "پی پال" ،
    updated_at: ~ N [2018-01-03 08: 00: 00.000000]،
    کاربر: # Ecto.Association.NotLoaded <انجمن: کاربر بارگیری نمی شود> ،
    user_id: 2
  ،
  ٪ Financex.Accounts.Invoice
    __meta__: # Ecto.Schema.Metadata <: لود شده ، "فاکتورها"> ،
    شناسه: 1 ،
    insert_at: ~ N [2018-01-02 08: 00: 00.000000]،
    Payment_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 از یک فاکتور به عنوان اولین پارامتر استفاده می شود ، تابع rend_by همچنین قلم های Ecto.Query را می پذیرد ، که این امر باعث می شود تا از عملکرد rend_by در ترکیب ها استفاده شود ، مانند:

صورتحساب
|> جایی که ([i] ، is_nil نیست (i.paid_at))
|> جایی که ([i] ، i.payment_method == "پی پال")
|> سفارش_بی (نزولی: inserted_at)
|> Repo.all ()

محدود کردن

بانک اطلاعاتی بدون محدودیت چیست؟ یک فاجعه. خوشبختانه ، هر دو ActiveRecord و Ecto به محدود کردن تعداد سوابق برگشتی کمک می کنند.

ActiveRecord

irb (اصلی): 004: 0> Invoice.limit (2)
بار فاکتور (0.2ms) گزینه "فاکتورها" را انتخاب کنید. * از "فاکتورهای" LIMIT $ 1 [["" LIMIT "، 2]]
=> #  ، # <شناسه فاکتور: 2 ، user_id: 2 ، Payment_method:" Paypal "، پرداخت_at:" 2018-02-01 08: 00:00 "، ایجاد_ات:" 2018-01-03 08:00:00 "، به روز شده_at:" 2018-01-03 08:00:00 ">]>

اکتو

iex (22)> limit (فاکتور ، 2) |> Repo.all ()
[اشکال زدایی] QUERY OK منبع = "فاکتورها" db = 3.6ms
SELECT i0. "id" ، i0. "Payment_method" ، i0. "Payment_at" ، i0. "user_id" ، i0. "inserted_at" ، i0. "updated_at" FROM "فاکتورهای" i i LIMIT 2 []
[
  ٪ Financex.Accounts.Invoice
    __meta__: # Ecto.Schema.Metadata <: لود شده ، "فاکتورها"> ،
    شناسه: 1 ،
    insert_at: ~ N [2018-01-02 08: 00: 00.000000]،
    Payment_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 <: لود شده ، "فاکتورها"> ،
    شناسه: 2 ،
    inserted_at: ~ N [2018-01-03 08: 00: 00.000000]،
    Payment_at: # DateTime <2018-02-01 08: 00: 00.000000Z>،
    Payment_method: "پی پال" ،
    updated_at: ~ N [2018-01-03 08: 00: 00.000000]،
    کاربر: # Ecto.Association.NotLoaded <انجمن: کاربر بارگیری نمی شود> ،
    user_id: 2
  }
]

مقایسه

هر دو ActiveRecord و Ecto راهی برای محدود کردن تعداد سوابق برگشت داده شده توسط یک پرس و جو دارند.

حد Ecto کارهایی مشابه سفارش_by ، مناسب برای ترکیب نمایش داده شد.

انجمن ها

ActiveRecord و Ecto در مورد نحوه برخورد با انجمنها رویکردهای متفاوتی دارند.

ActiveRecord

در ActiveRecord ، می توانید از هرگونه ارتباط تعریف شده در یک مدل استفاده کنید ، بدون آنکه کاری خاص در مورد آن انجام دهید ، به عنوان مثال:

irb (اصلی): 012: 0> user = User.find (2) بار کاربر (0.3 میلی متر) SELECT "کاربران" را انتخاب کنید. * "کاربران" از "WHERE" کاربران "." id "= 1 $ LIMIT $ 2 $ [[" id " ، 2] ، ["LIMIT" ، 1]] => # شناسه کاربر: 2 ، کامل_نام: "باربارا گوردون" ، ایمیل: "[email protected]" ، ایجاد شده_ات: "2018-01-02 10:02: 00 "، به روز شده_ات:" 2018-01-02 10:02:00 "> irb (اصلی): 013: 0> user.invoices بار فاکتور (0.4ms) SELECT" فاکتورها "را انتخاب کنید. * از" فاکتورهای "کجا" فاکتورها " . "user_id" = 1 $ LIMIT $ 2 [["user_id" ، 2] ، ["LIMIT"، 11]] => # ] >

مثال بالا نشان می دهد که می توانیم هنگام تماس با user.invoices لیستی از فاکتورهای کاربر را بدست آوریم. با انجام این کار ، ActiveRecord به طور خودکار از پایگاه داده سؤال می کند و فاکتورهایی را که با کاربر در ارتباط است بارگذاری کرد. اگرچه این رویکرد کار را آسانتر می کند ، به معنای نوشتن کد کمتر و یا نگرانی در مورد مراحل اضافی ، ممکن است در صورت تکرار بیش از تعدادی از کاربران و واکشی فاکتورها برای هر کاربر ، مشکل ایجاد کند. این شماره به عنوان "مشکل N 1" شناخته شده است.

در ActiveRecord ، اصلاح پیشنهادی برای "مسئله N + 1" استفاده از روش شامل:

irb (اصلی): 022: 0> user = User.includes (: فاکتورها) .find (2) بار کاربر (0.3ms) SELECT "کاربران" را انتخاب کنید. * از "کاربران" WHERE "کاربران" "id" = 1 $ LIMIT $ 2 [["" ID "، 2] ، [" LIMIT "، 1]] بار فاکتور (0.6ms) SELECT" فاکتورها "را انتخاب کنید. * از" فاکتورهای "WHERE" فاکتورها را "" استفاده می کند. "user_id" = 1 $ [["user_id"، 2]] => # <شناسه کاربری: 2 ، نام کامل: "باربارا گوردون" ، ایمیل: "[email protected]" ، ایجاد شده: "2018-01-02 10:02:00" ، به روز شده_ات: "2018-01 -02 10:02:00 "> irb (اصلی): 023: 0> user.invoices => # ]>

در این حالت ، ActiveRecord مشتاقانه بارگیری انجمن فاکتورها را هنگام بارگیری کاربر (همانطور که در دو سؤال SQL نشان داده شده است) مشاهده می کنید.

اکتو

همانطور که قبلاً متوجه شده اید ، اکتو واقعاً جادوگری یا عارضه را دوست ندارد. این امر به توسعه دهندگان نیاز دارد تا در مورد اهداف خود صریح و روشن باشند.

بیایید با همان رویکرد استفاده از user.invoices با Ecto امتحان کنیم:

iex (7)> ​​کاربر = Repo.get (کاربر ، 2)
[اشکال زدایی] QUERY OK منبع = "کاربران" db = 18.3ms رمزگشایی = 0.6ms
"u" را انتخاب کنید. "id" ، u0. "full_name"، u0. "email"، u0. "inserted_at"، u0 "." updated_at "کاربران" از "" به عنوان u0 WHERE (u0. "id" = 1 $) [2]
٪ Financex.Accounts.User
  __meta__: # Ecto.Schema.Metadata <: لود شده ، "کاربران"> ،
  ایمیل: "[email protected]" ،
  full_name: "باربارا گوردون" ،
  شناسه: 2 ،
  inserted_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 منبع = "کاربران" db = 11.8ms
"u" را انتخاب کنید. "id" ، u0. "full_name"، u0. "email"، u0. "inserted_at"، u0 "." updated_at "کاربران" از "" به عنوان u0 WHERE (u0. "id" = 1 $) [2]
[اشکال زدایی] QUERY OK منبع = "فاکتورها" db = 4.2ms
"id" ، i0. "Payment_method" ، i0. "Payment_at" ، i0. "user_id" ، i0. "inserted_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: "باربارا گوردون" ،
  شناسه: 2 ،
  inserted_at: ~ N [2018-01-02 10: 02: 00.000000] ،
  فاکتورها: [
    ٪ Financex.Accounts.Invoice
      __meta__: # Ecto.Schema.Metadata <: لود شده ، "فاکتورها"> ،
      شناسه: 2 ،
      inserted_at: ~ N [2018-01-03 08: 00: 00.000000]،
      Payment_at: # DateTime <2018-02-01 08: 00: 00.000000Z>،
      Payment_method: "پی پال" ،
      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 <: لود شده ، "فاکتورها"> ،
    شناسه: 2 ،
    inserted_at: ~ N [2018-01-03 08: 00: 00.000000]،
    Payment_at: # DateTime <2018-02-01 08: 00: 00.000000Z>،
    Payment_method: "پی پال" ،
    updated_at: ~ N [2018-01-03 08: 00: 00.000000]،
    کاربر: # Ecto.Association.NotLoaded <انجمن: کاربر بارگیری نمی شود> ،
    user_id: 2
  }
]

به طور مشابه با ActiveRecord ، پیش بارگذاری با فاکتورهای مرتبط نیز واکشی می کند ، که هنگام تماس با user.invoices آنها را در دسترس قرار می دهد.

مقایسه

بار دیگر ، نبرد بین ActiveRecord و Ecto با یک نقطه شناخته شده به پایان می رسد: اکتشاف. هر دو ابزار به توسعه دهندگان این امکان را می دهند که به راحتی به انجمن ها دسترسی پیدا کنند ، اما در حالی که ActiveRecord این کار را کم تحرکتر می کند ، نتیجه آن ممکن است رفتارهای غیر منتظره ای داشته باشد. Ecto از رویکرد WYSIWYG پیروی می کند ، که فقط آنچه را در نمایش داده شده توسط برنامه نویس مشاهده می شود ، انجام می دهد.

ریل برای استفاده و ترویج راهکارهای حافظه پنهان به همه لایه های مختلف برنامه مشهور است. یک مثال در مورد استفاده از رویکرد ذخیره سازی "عروسک روسی" است که کاملاً به مکانیسم حافظه پنهان خود برای اطمینان از مکانیزم حافظه پنهانی کاملاً به مسئله "N + 1" متکی است.

اعتبارات

بیشترین اعتبارات موجود در ActiveRecord در Ecto نیز موجود است. در اینجا لیستی از اعتبارهای رایج و نحوه تعریف هر دو ActiveRecord و Ecto آنها وجود دارد:

بسته شدن

در آنجا آن را دارید: سیب های اساسی در مقایسه با پرتقال.

ActiveRecord بر سهولت انجام نمایش داده های پایگاه داده تمرکز دارد. بخش عمده ای از ویژگی های آن در کلاس های مدل متمرکز شده است ، و نیازی به توسعه دهندگان برای درک عمیق از بانک اطلاعاتی و همچنین تأثیر چنین عملیاتی ندارد. ActiveRecord بطور پیش فرض موارد بسیاری را به طور ضمنی انجام می دهد. اگرچه این کار شروع به کار ساده تر می کند ، اما درک آنچه در پشت صحنه اتفاق می افتد سخت تر می شود و تنها در صورت پیروی از «راه ActiveRecord» کار می کند.

از طرف دیگر ، اکتو نیاز به شهادت دارد که منجر به کد شفافیت بیشتر می شود. به عنوان یک مزیت ، همه چیز در کانون توجه است ، هیچ چیز در پشت صحنه نیست و می توانید راه خود را مشخص کنید.

هر دو بسته به دیدگاه و ترجیح شما صعودی دارند. بنابراین با مقایسه سیب و پرتقال به انتهای این BAT-tle می رسیم. تقریبا فراموش کرده ام که به شما بگویم نام خانوادگی BatGirl (1989-1991) بود…. اوراکل اما بیایید وارد آن کار نشویم.

این پست توسط نویسنده میهمان الویو ویکوزا نوشته شده است. الویو نویسنده کتاب Phoenix for Rails Developers است.

در اصل در 9 اکتبر 2018 در blog.appsignal.com منتشر شد.