यह "ActiveRecord बनाम Ecto" श्रृंखला का दूसरा भाग है, जिसमें बैटमैन और बैटगर्ल क्वेरी करने वाले डेटाबेस को लेकर लड़ते हैं और हम सेब और संतरे की तुलना करते हैं।
ActiveRecord बनाम Ecto भाग एक में डेटाबेस स्कीमा और माइग्रेशन को देखने के बाद, इस पोस्ट में बताया गया है कि ActiveRecord और Ecto दोनों कैसे डेवलपर्स को डेटाबेस को क्वेरी करने में सक्षम बनाते हैं, और ActiveRecord और Ecto दोनों समान आवश्यकताओं से निपटने के दौरान कैसे तुलना करते हैं। साथ ही, हम बैटगर्ल की 1989-2011 की पहचान का भी पता लगाएंगे।
बीज डेटा
आएँ शुरू करें! इस श्रृंखला की पहली पोस्ट में परिभाषित डेटाबेस संरचना के आधार पर, मान लें कि users
और invoices
तालिकाओं में निम्न डेटा संग्रहीत होता है:
उपयोगकर्ता
आईडी | <थ>पूर्ण_नाम <थ>ईमेलcreated_at* | update_at | ||
---|---|---|---|---|
1 | बेट्टे केन | [email protected] | 2018-01-01 10:01:00 | 2018-01-01 10:01:00 |
2 | बारबरा गॉर्डन | [email protected] | 2018-01-02 10:02:00 | 2018-01-02 10:02:00 |
3 | कैसंड्रा कैन | [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 |
* ActiveRecord का created_at
फ़ील्ड का नाम inserted_at
. है डिफ़ॉल्ट रूप से एक्टो में।
चालान
आईडी | user_id | Payment_method | भुगतान_पर | created_at* | update_at |
---|---|---|---|---|---|
1 | 1 | क्रेडिट कार्ड | 2018-02-01 08:00:00 | 2018-01-02 08:00:00 | 2018-01-02 08:00:00 |
2 | 2 | पेपैल | 2018-02-01 08:00:00 | 2018-01-03 08:00:00 | 2018-01-03 08:00:00 |
3 | 3 | 2018-01-04 08:00:00 | 2018-01-04 08:00:00 | ||
4 | 4 | 2018-01-05 08:00:00 | 2018-01-05 08:00:00 |
* ActiveRecord का created_at
फ़ील्ड का नाम inserted_at
. है डिफ़ॉल्ट रूप से एक्टो में।
इस पोस्ट के माध्यम से की गई क्वेरीज़ मानती हैं कि उपरोक्त डेटा डेटाबेस में संग्रहीत है, इसलिए इसे पढ़ते समय इस जानकारी को ध्यान में रखें।
प्राथमिक कुंजी का उपयोग करके आइटम ढूंढें
आइए डेटाबेस से इसकी प्राथमिक कुंजी का उपयोग करके एक रिकॉर्ड प्राप्त करना शुरू करें।
ActiveRecord
irb(main):001:0> User.find(1)
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, full_name: "Bette Kane", email: "[email protected]", created_at: "2018-01-01 10:01:00", updated_at: "2018-01-01 10:01:00">
एक्टो
iex(3)> Repo.get(User, 1)
[debug] QUERY OK source="users" db=5.2ms decode=2.5ms queue=0.1ms
SELECT u0."id", u0."full_name", u0."email", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [1]
%Financex.Accounts.User{
__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
email: "[email protected]",
full_name: "Bette Kane",
id: 1,
inserted_at: ~N[2018-01-01 10:01:00.000000],
invoices: #Ecto.Association.NotLoaded<association :invoices is not loaded>,
updated_at: ~N[2018-01-01 10:01:00.000000]
}
तुलना
दोनों मामले काफी हद तक एक जैसे हैं। ActiveRecord find
. पर निर्भर करता है users
. की कक्षा विधि मॉडल वर्ग। इसका मतलब है कि प्रत्येक ActiveRecord चाइल्ड क्लास का अपना find
. होता है इसमें विधि।
मैपिंग परत और डोमेन के बीच मध्यस्थ के रूप में रिपोजिटरी अवधारणा पर भरोसा करते हुए एक्टो एक अलग दृष्टिकोण का उपयोग करता है। Ecto का उपयोग करते समय, users
मॉड्यूल को इस बारे में कोई जानकारी नहीं है कि खुद को कैसे खोजा जाए। ऐसी जिम्मेदारी Repo
. में मौजूद है मॉड्यूल, जो इसे नीचे के डेटास्टोर में मैप करने में सक्षम है, जो हमारे मामले में पोस्टग्रेज है।
SQL क्वेरी की तुलना करते समय, हम कुछ अंतर देख सकते हैं:
- ActiveRecord सभी क्षेत्रों को लोड करता है (
users.*
), जबकि Ecto केवलschema
. में सूचीबद्ध फ़ील्ड को लोड करता है परिभाषा। - ActiveRecord में एक
LIMIT 1
शामिल है क्वेरी के लिए, जबकि एक्टो नहीं करता है।
सभी आइटम लाए जा रहे हैं
आइए एक कदम आगे बढ़ते हैं और सभी उपयोगकर्ताओं को डेटाबेस से लोड करते हैं।
ActiveRecord
irb(main):001:0> User.all
User Load (0.5ms) SELECT "users".* FROM "users" LIMIT $1 [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, full_name: "Bette Kane", email: "[email protected]", created_at: "2018-01-01 10:01:00", updated_at: "2018-01-01 10:01:00">, #<User id: 2, full_name: "Barbara Gordon", email: "[email protected]", created_at: "2018-01-02 10:02:00", updated_at: "2018-01-02 10:02:00">, #<User id: 3, full_name: "Cassandra Cain", email: "[email protected]", created_at: "2018-01-03 10:03:00", updated_at: "2018-01-03 10:03:00">, #<User id: 4, full_name: "Stephanie Brown", email: "[email protected]", created_at: "2018-01-04 10:04:00", updated_at: "2018-01-04 10:04:00">]>
एक्टो
iex(4)> Repo.all(User)
[debug] QUERY OK source="users" db=2.8ms decode=0.2ms queue=0.2ms
SELECT u0."id", u0."full_name", u0."email", u0."inserted_at", u0."updated_at" FROM "users" AS u0 []
[
%Financex.Accounts.User{
__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
email: "[email protected]",
full_name: "Bette Kane",
id: 1,
inserted_at: ~N[2018-01-01 10:01:00.000000],
invoices: #Ecto.Association.NotLoaded<association :invoices is not loaded>,
updated_at: ~N[2018-01-01 10:01:00.000000]
},
%Financex.Accounts.User{
__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
email: "[email protected]",
full_name: "Barbara Gordon",
id: 2,
inserted_at: ~N[2018-01-02 10:02:00.000000],
invoices: #Ecto.Association.NotLoaded<association :invoices is not loaded>,
updated_at: ~N[2018-01-02 10:02:00.000000]
},
%Financex.Accounts.User{
__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
email: "[email protected]",
full_name: "Cassandra Cain",
id: 3,
inserted_at: ~N[2018-01-03 10:03:00.000000],
invoices: #Ecto.Association.NotLoaded<association :invoices is not loaded>,
updated_at: ~N[2018-01-03 10:03:00.000000]
},
%Financex.Accounts.User{
__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
email: "[email protected]",
full_name: "Stephanie Brown",
id: 4,
inserted_at: ~N[2018-01-04 10:04:00.000000],
invoices: #Ecto.Association.NotLoaded<association :invoices is not loaded>,
updated_at: ~N[2018-01-04 10:04:00.000000]
}
]
तुलना
यह पिछले खंड के समान सटीक पैटर्न का अनुसरण करता है। ActiveRecord all
. का उपयोग करता है क्लास मेथड और एक्टो रिकॉर्ड्स को लोड करने के लिए रिपोजिटरी पैटर्न पर निर्भर करता है।
SQL क्वेरी में फिर से कुछ अंतर हैं:
- पिछले अनुभाग की तरह ही, ActiveRecord सभी क्षेत्रों को लोड करता है (
users.*
), जबकि Ecto केवलschema
. में सूचीबद्ध फ़ील्ड को लोड करता है परिभाषा। - ActiveRecord एक
LIMIT 11
को भी परिभाषित करता है , जबकि एक्टो बस सब कुछ लोड करता है। यह सीमाinspect
. से आती है कंसोल पर उपयोग की जाने वाली विधि (https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation.rb#L599)।
शर्तों के साथ क्वेरी करना
यह बहुत कम संभावना है कि हमें एक टेबल से सभी रिकॉर्ड लाने की जरूरत है। लौटाए गए डेटा को फ़िल्टर करने के लिए शर्तों का उपयोग करना एक सामान्य आवश्यकता है।
आइए उस उदाहरण का उपयोग सभी invoices
. को सूचीबद्ध करने के लिए करें जिनका भुगतान किया जाना बाकी है (WHERE paid_at IS NULL
)।
ActiveRecord
irb(main):024:0> Invoice.where(paid_at: nil)
Invoice Load (18.2ms) SELECT "invoices".* FROM "invoices" WHERE "invoices"."paid_at" IS NULL LIMIT $1 [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Invoice id: 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">, #<Invoice id: 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)> where(Invoice, [i], is_nil(i.paid_at)) |> Repo.all()
[debug] QUERY OK source="invoices" db=20.2ms
SELECT i0."id", i0."payment_method", i0."paid_at", i0."user_id", i0."inserted_at", i0."updated_at" FROM "invoices" AS i0 WHERE (i0."paid_at" IS NULL) []
[
%Financex.Accounts.Invoice{
__meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
id: 3,
inserted_at: ~N[2018-01-04 08:00:00.000000],
paid_at: nil,
payment_method: nil,
updated_at: ~N[2018-01-04 08:00:00.000000],
user: #Ecto.Association.NotLoaded<association :user is not loaded>,
user_id: 3
},
%Financex.Accounts.Invoice{
__meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
id: 4,
inserted_at: ~N[2018-01-04 08:00:00.000000],
paid_at: nil,
payment_method: nil,
updated_at: ~N[2018-01-04 08:00:00.000000],
user: #Ecto.Association.NotLoaded<association :user is not loaded>,
user_id: 4
}
]
तुलना
दोनों उदाहरणों में, where
कीवर्ड का उपयोग किया जाता है, जो SQL WHERE
. से एक कनेक्शन है खंड। हालांकि उत्पन्न SQL क्वेरी काफी समान हैं, जिस तरह से दोनों टूल वहां पहुंचते हैं उनमें कुछ महत्वपूर्ण अंतर हैं।
ActiveRecord paid_at: nil
. को रूपांतरित करता है paid_at IS NULL
का तर्क SQL कथन स्वचालित रूप से। Ecto का उपयोग करके समान आउटपुट प्राप्त करने के लिए, डेवलपर्स को is_nil()
पर कॉल करके अपने इरादे के बारे में अधिक स्पष्ट होने की आवश्यकता है। ।
हाइलाइट किया जाने वाला एक और अंतर फ़ंक्शन का "शुद्ध" व्यवहार है where
एक्टो में। कॉल करते समय where
अकेले कार्य करता है, यह डेटाबेस से इंटरैक्ट नहीं करता है। where
. की वापसी फ़ंक्शन एक Ecto.Query
है संरचना:
iex(20)> where(Invoice, [i], is_nil(i.paid_at))
#Ecto.Query<from i in Financex.Accounts.Invoice, where: is_nil(i.paid_at)>
डेटाबेस को केवल तभी स्पर्श किया जाता है जब Repo.all()
Ecto.Query
. पास करते हुए फ़ंक्शन को कॉल किया जाता है तर्क के रूप में संरचना। यह दृष्टिकोण एक्टो में क्वेरी संरचना की अनुमति देता है, जो अगले भाग का विषय है।
क्वेरी रचना
डेटाबेस प्रश्नों के सबसे शक्तिशाली पहलुओं में से एक रचना है। यह एक क्वेरी का इस तरह से वर्णन करता है जिसमें एक से अधिक शर्त शामिल हैं।
यदि आप कच्चे एसक्यूएल प्रश्नों का निर्माण कर रहे हैं, तो इसका मतलब है कि आप शायद किसी प्रकार के संयोजन का उपयोग करेंगे। कल्पना कीजिए कि आपके पास दो शर्तें हैं:
not_paid = 'paid_at IS NOT NULL'
paid_with_paypal = 'payment_method = "Paypal"'
कच्चे SQL का उपयोग करके उन दो स्थितियों को संयोजित करने के लिए, इसका मतलब है कि आपको कुछ इसी तरह का उपयोग करके उन्हें जोड़ना होगा:
SELECT * FROM invoices WHERE #{not_paid} AND #{paid_with_paypal}
सौभाग्य से ActiveRecord और Ecto दोनों के पास इसका समाधान है।
ActiveRecord
irb(main):003:0> Invoice.where.not(paid_at: nil).where(payment_method: "Paypal")
Invoice Load (8.0ms) SELECT "invoices".* FROM "invoices" WHERE "invoices"."paid_at" IS NOT NULL AND "invoices"."payment_method" = $1 LIMIT $2 [["payment_method", "Paypal"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Invoice id: 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(6)> Invoice |> where([i], not is_nil(i.paid_at)) |> where([i], i.payment_method == "Paypal") |> Repo.all()
[debug] QUERY OK source="invoices" db=30.0ms decode=0.6ms queue=0.2ms
SELECT i0."id", i0."payment_method", i0."paid_at", i0."user_id", i0."inserted_at", i0."updated_at" FROM "invoices" AS i0 WHERE (NOT (i0."paid_at" IS NULL)) AND (i0."payment_method" = 'Paypal') []
[
%Financex.Accounts.Invoice{
__meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
id: 2,
inserted_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],
user: #Ecto.Association.NotLoaded<association :user is not loaded>,
user_id: 2
}
]
तुलना
दोनों प्रश्न एक ही प्रश्न का उत्तर दे रहे हैं:"किस चालान का भुगतान किया गया और पेपैल का उपयोग किया गया?"।
जैसा कि पहले से ही अपेक्षित था, ActiveRecord क्वेरी लिखने का एक अधिक संक्षिप्त तरीका प्रदान करता है (उस उदाहरण के लिए), जबकि Ecto को डेवलपर्स को क्वेरी लिखने पर थोड़ा और खर्च करने की आवश्यकता होती है। हमेशा की तरह, बैटगर्ल (अनाथ, कैसेंड्रा कैन पहचान के साथ मूक एक) या एक्टिवरेकॉर्ड वर्बोज़ के रूप में नहीं है।
ऊपर दिखाए गए एक्टो क्वेरी की वाचालता और स्पष्ट जटिलता से मूर्ख मत बनो। वास्तविक दुनिया के माहौल में, उस क्वेरी को और अधिक दिखने के लिए फिर से लिखा जाएगा:
Invoice
|> where([i], not is_nil(i.paid_at))
|> where([i], i.payment_method == "Paypal")
|> Repo.all()
उस कोण से देखते हुए, फ़ंक्शन के "शुद्ध" पहलुओं का संयोजन where
, जो अपने आप डेटाबेस संचालन नहीं करता है, पाइप ऑपरेटर के साथ, एक्टो में क्वेरी संरचना को वास्तव में साफ करता है।
आदेश देना
आदेश देना क्वेरी का एक महत्वपूर्ण पहलू है। यह डेवलपर्स को यह सुनिश्चित करने में सक्षम बनाता है कि दिया गया क्वेरी परिणाम एक निर्दिष्ट क्रम का पालन करता है।
ActiveRecord
irb(main):002:0> Invoice.order(created_at: :desc)
Invoice Load (1.5ms) SELECT "invoices".* FROM "invoices" ORDER BY "invoices"."created_at" DESC LIMIT $1 [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Invoice id: 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">, #<Invoice id: 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">, #<Invoice id: 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">, #<Invoice id: 1, user_id: 1, payment_method: "Credit Card", 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: :inserted_at) |> Repo.all()
[debug] QUERY OK source="invoices" db=19.8ms
SELECT i0."id", i0."payment_method", i0."paid_at", i0."user_id", i0."inserted_at", i0."updated_at" FROM "invoices" AS i0 ORDER BY i0."inserted_at" DESC []
[
%Financex.Accounts.Invoice{
__meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
id: 3,
inserted_at: ~N[2018-01-04 08:00:00.000000],
paid_at: nil,
payment_method: nil,
updated_at: ~N[2018-01-04 08:00:00.000000],
user: #Ecto.Association.NotLoaded<association :user is not loaded>,
user_id: 3
},
%Financex.Accounts.Invoice{
__meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
id: 4,
inserted_at: ~N[2018-01-04 08:00:00.000000],
paid_at: nil,
payment_method: nil,
updated_at: ~N[2018-01-04 08:00:00.000000],
user: #Ecto.Association.NotLoaded<association :user is not loaded>,
user_id: 4
},
%Financex.Accounts.Invoice{
__meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
id: 2,
inserted_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],
user: #Ecto.Association.NotLoaded<association :user is not loaded>,
user_id: 2
},
%Financex.Accounts.Invoice{
__meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
id: 1,
inserted_at: ~N[2018-01-02 08:00:00.000000],
paid_at: #DateTime<2018-02-01 08:00:00.000000Z>,
payment_method: "Credit Card",
updated_at: ~N[2018-01-02 08:00:00.000000],
user: #Ecto.Association.NotLoaded<association :user is not loaded>,
user_id: 1
}
]
तुलना
क्वेरी में ऑर्डर जोड़ना दोनों टूल में सीधा है।
हालांकि Ecto उदाहरण invoices
. का उपयोग करता है पहले पैरामीटर के रूप में, order_by
फ़ंक्शन Ecto.Query
को भी स्वीकार करता है structs, जो order_by
. को सक्षम करता है रचनाओं में उपयोग किए जाने वाले फ़ंक्शन, जैसे:
Invoice
|> where([i], not is_nil(i.paid_at))
|> where([i], i.payment_method == "Paypal")
|> order_by(desc: :inserted_at)
|> Repo.all()
सीमित करना
बिना सीमा के डेटाबेस क्या होगा? एक आपदा। सौभाग्य से, ActiveRecord और Ecto दोनों ही लौटाए गए रिकॉर्ड की संख्या को सीमित करने में मदद करते हैं।
ActiveRecord
irb(main):004:0> Invoice.limit(2)
Invoice Load (0.2ms) SELECT "invoices".* FROM "invoices" LIMIT $1 [["LIMIT", 2]]
=> #<ActiveRecord::Relation [#<Invoice id: 1, user_id: 1, payment_method: "Credit Card", paid_at: "2018-02-01 08:00:00", created_at: "2018-01-02 08:00:00", updated_at: "2018-01-02 08:00:00">, #<Invoice id: 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)> limit(Invoice, 2) |> Repo.all()
[debug] QUERY OK source="invoices" db=3.6ms
SELECT i0."id", i0."payment_method", i0."paid_at", i0."user_id", i0."inserted_at", i0."updated_at" FROM "invoices" AS i0 LIMIT 2 []
[
%Financex.Accounts.Invoice{
__meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
id: 1,
inserted_at: ~N[2018-01-02 08:00:00.000000],
paid_at: #DateTime<2018-02-01 08:00:00.000000Z>,
payment_method: "Credit Card",
updated_at: ~N[2018-01-02 08:00:00.000000],
user: #Ecto.Association.NotLoaded<association :user is not loaded>,
user_id: 1
},
%Financex.Accounts.Invoice{
__meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
id: 2,
inserted_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],
user: #Ecto.Association.NotLoaded<association :user is not loaded>,
user_id: 2
}
]
तुलना
ActiveRecord और Ecto दोनों के पास एक क्वेरी द्वारा लौटाए गए रिकॉर्ड की संख्या को सीमित करने का एक तरीका है।
Ecto की limit
order_by
. के समान कार्य करता है , क्वेरी रचनाओं के लिए उपयुक्त होने के नाते।
एसोसिएशन
जब एसोसिएशन को हैंडल करने की बात आती है तो ActiveRecord और Ecto के अलग-अलग तरीके होते हैं।
ActiveRecord
ActiveRecord में, आप किसी मॉडल में परिभाषित किसी भी एसोसिएशन का उपयोग कर सकते हैं, इसके बारे में कुछ विशेष किए बिना, उदाहरण के लिए:
irb(main):012:0> user = User.find(2)
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]]
=> #<User id: 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) SELECT "invoices".* FROM "invoices" WHERE "invoices"."user_id" = $1 LIMIT $2 [["user_id", 2], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Invoice id: 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">]>
ऊपर दिए गए उदाहरण से पता चलता है कि user.invoices
पर कॉल करने पर हमें उपयोगकर्ता के इनवॉइस की सूची मिल सकती है . ऐसा करते समय, ActiveRecord स्वचालित रूप से डेटाबेस से पूछताछ करता है और उपयोगकर्ता से जुड़े चालान लोड करता है। हालांकि यह दृष्टिकोण चीजों को आसान बनाता है, कम कोड लिखने या अतिरिक्त चरणों के बारे में चिंता करने के अर्थ में, यह एक समस्या हो सकती है यदि आप कई उपयोगकर्ताओं पर पुनरावृति कर रहे हैं और प्रत्येक उपयोगकर्ता के लिए चालान प्राप्त कर रहे हैं। इस समस्या को "N + 1 समस्या" के रूप में जाना जाता है।
ActiveRecord में, "N + 1 समस्या" का प्रस्तावित समाधान includes
का उपयोग करना है विधि:
irb(main):022:0> user = User.includes(:invoices).find(2)
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]]
Invoice Load (0.6ms) SELECT "invoices".* FROM "invoices" WHERE "invoices"."user_id" = $1 [["user_id", 2]]
=> #<User id: 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::Associations::CollectionProxy [#<Invoice id: 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">]>
इस मामले में, ActiveRecord invoices
को उत्सुकता से लोड करता है उपयोगकर्ता को लाते समय संबद्धता (जैसा कि दिखाए गए दो SQL प्रश्नों में देखा गया है)।
एक्टो
जैसा कि आपने पहले ही देखा होगा, एक्टो वास्तव में जादू या निहितता पसंद नहीं करता है। इसके लिए डेवलपर्स को अपने इरादों के बारे में स्पष्ट होना चाहिए।
आइए user.invoices
. का उपयोग करने के समान दृष्टिकोण का प्रयास करें एक्टो के साथ:
iex(7)> user = Repo.get(User, 2)
[debug] QUERY OK source="users" db=18.3ms decode=0.6ms
SELECT u0."id", u0."full_name", u0."email", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [2]
%Financex.Accounts.User{
__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
email: "[email protected]",
full_name: "Barbara Gordon",
id: 2,
inserted_at: ~N[2018-01-02 10:02:00.000000],
invoices: #Ecto.Association.NotLoaded<association :invoices is not loaded>,
updated_at: ~N[2018-01-02 10:02:00.000000]
}
iex(8)> user.invoices
#Ecto.Association.NotLoaded<association :invoices is not loaded>
परिणाम एक Ecto.Association.NotLoaded
. है . इतना उपयोगी नहीं है।
इनवॉइस तक पहुंच प्राप्त करने के लिए, डेवलपर को preload
का उपयोग करके Ecto को इसके बारे में बताना होगा समारोह:
iex(12)> user = preload(User, :invoices) |> Repo.get(2)
[debug] QUERY OK source="users" db=11.8ms
SELECT u0."id", u0."full_name", u0."email", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [2]
[debug] QUERY OK source="invoices" db=4.2ms
SELECT i0."id", i0."payment_method", i0."paid_at", i0."user_id", i0."inserted_at", i0."updated_at", i0."user_id" FROM "invoices" AS i0 WHERE (i0."user_id" = $1) ORDER BY i0."user_id" [2]
%Financex.Accounts.User{
__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
email: "[email protected]",
full_name: "Barbara Gordon",
id: 2,
inserted_at: ~N[2018-01-02 10:02:00.000000],
invoices: [
%Financex.Accounts.Invoice{
__meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
id: 2,
inserted_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],
user: #Ecto.Association.NotLoaded<association :user is not loaded>,
user_id: 2
}
],
updated_at: ~N[2018-01-02 10:02:00.000000]
}
iex(15)> user.invoices
[
%Financex.Accounts.Invoice{
__meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
id: 2,
inserted_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],
user: #Ecto.Association.NotLoaded<association :user is not loaded>,
user_id: 2
}
]
इसी तरह ActiveRecord includes
, संबंधित invoices
. लाने के साथ प्रीलोड , जो उन्हें user.invoices
. पर कॉल करते समय उपलब्ध कराएगा ।
तुलना
एक बार फिर, ActiveRecord और Ecto के बीच की लड़ाई एक ज्ञात बिंदु के साथ समाप्त होती है:खोजकर्ता। दोनों उपकरण डेवलपर्स को आसानी से संघों तक पहुंचने में सक्षम बनाते हैं, लेकिन जब ActiveRecord इसे कम वर्बोज़ बनाता है, तो इसके परिणाम में अप्रत्याशित व्यवहार हो सकते हैं। Ecto WYSIWYG प्रकार के दृष्टिकोण का अनुसरण करता है, जो केवल वही करता है जो डेवलपर द्वारा परिभाषित क्वेरी में देखा जाता है।
रेल को एप्लिकेशन की सभी विभिन्न परतों में कैशिंग रणनीतियों का उपयोग करने और बढ़ावा देने के लिए जाना जाता है। एक उदाहरण "रूसी गुड़िया" कैशिंग दृष्टिकोण का उपयोग करने के बारे में है, जो अपने जादू को प्रदर्शित करने के लिए अपने कैशिंग तंत्र के लिए पूरी तरह से "एन + 1 समस्या" पर निर्भर करता है।
सत्यापन
ActiveRecord में मौजूद अधिकांश सत्यापन Ecto में भी उपलब्ध हैं। यहां सामान्य सत्यापन की सूची दी गई है और ActiveRecord और Ecto दोनों उन्हें कैसे परिभाषित करते हैं:
ActiveRecord | <थ>एक्टो|
---|---|
validates :title, presence: true | validate_required(changeset, [:title]) |
validates :email, confirmation: true | validate_confirmation(changeset, :email) |
validates :email, format: {with: /@/ } | validate_format(changeset, :email, ~r/@/) |
validates :start, exclusion: {in: %w(a b)} | validate_exclusion(changeset, :start, ~w(a b)) |
validates :start, inclusion: {in: %w(a b)} | validate_inclusion(changeset, :start, ~w(a b)) |
validates :terms_of_service, acceptance: true | validate_acceptance(changeset, :terms_of_service) |
validates :password, length: {is: 6} | validate_length(changeset, :password, is: 6) |
validates :age, numericality: {equal_to: 1} | validate_number(changeset, :age, equal_to: 1) |
रैप अप
वहां आपके पास है:आवश्यक सेब बनाम संतरे की तुलना।
ActiveRecord डेटाबेस प्रश्नों को निष्पादित करने में आसानी पर केंद्रित है। इसकी अधिकांश विशेषताएं स्वयं मॉडल वर्गों पर केंद्रित हैं, न कि डेवलपर्स को डेटाबेस की गहरी समझ रखने की आवश्यकता है, न ही इस तरह के संचालन के प्रभाव। ActiveRecord डिफ़ॉल्ट रूप से बहुत सी चीजें पूरी तरह से करता है। हालांकि इससे शुरुआत करना आसान हो जाता है, इससे यह समझना मुश्किल हो जाता है कि पर्दे के पीछे क्या हो रहा है और यह तभी काम करता है जब आप "ActiveRecord वे" का पालन करते हैं।
दूसरी ओर, एक्टो को खोजकर्ता की आवश्यकता होती है जिसके परिणामस्वरूप अधिक वर्बोज़ कोड होता है। एक लाभ के रूप में, सब कुछ सुर्खियों में है, पर्दे के पीछे कुछ भी नहीं है, और आप अपना रास्ता खुद बता सकते हैं।
आपके दृष्टिकोण और पसंद के आधार पर दोनों का अपना उल्टा है। तो सेब और संतरे की तुलना करने के बाद, हम इस बैट-टेल के अंत में आते हैं। आपको बताना लगभग भूल गया बैटगर्ल का कोडनेम (1989 - 2001) था .... Oracle। लेकिन चलिए इसमें नहीं जाते हैं।