কম্পিউটার টিউটোরিয়াল

ActiveRecord vs. Ecto – পার্ট ২: ডাটাবেস কোয়েরি 습득 করা

এটি "ActiveRecord vs. Ecto" সিরিজের দ্বিতীয় অংশ, যেখানে ব্যাটম্যান এবং ব্যাটগার্ল ডাটাবেস কোয়েরি নিয়ে লড়াই করছেন এবং আমরা আপেল ও কমলা তুলনা করছেন।

প্রথম পোস্টে ডাটাবেস স্কিমা এবং মাইগ্রেশন দেখার পর, এই পোস্টে কভার করা হলো ActiveRecord এবং Ecto উভয় কীভাবে ডেভেলপারদের ডাটাবেস কোয়েরি করতে দেয়, এবং একই রিকোয়ারমেন্টস ডিল করার সময় উভয় কীভাবে তুলনা করে। পথে পথে, আমরা ব্যাটগার্লের ১৯৮৯-২০১১ আইডেন্টিটি ও জানব।

সিড ডেটা

চলুন শুরু করি! এই সিরিজের প্রথম পোস্টে ডিফাইন করা ডাটাবেস স্ট্রাকচার অনুযায়ী, অনুমান করুন users এবং invoices টেবিলগুলোতে নিচের ডেটা স্টোর করা আছে:

users

idfull_nameemailcreated_at*updated_at
1Bette Kanebette@kane.test2018-01-01 10:01:002018-01-01 10:01:00
2Barbara Gordonbarbara@gordon.test2018-01-02 10:02:002018-01-02 10:02:00
3Cassandra Caincassandra@cain.test2018-01-03 10:03:002018-01-03 10:03:00
4Stephanie Brownstephanie@brown.test2018-01-04 10:04:002018-01-04 10:04:00

* ActiveRecord-এর created_at ফিল্ড Ecto-তে ডিফল্টভাবে inserted_at নামে থাকে।

invoices

iduser_idpayment_methodpaid_atcreated_at*updated_at
11Credit Card2018-02-01 08:00:002018-01-02 08:00:002018-01-02 08:00:00
22Paypal2018-02-01 08:00:002018-01-03 08:00:002018-01-03 08:00:00
332018-01-04 08:00:002018-01-04 08:00:00
442018-01-05 08:00:002018-01-05 08:00:00

* ActiveRecord-এর created_at ফিল্ড Ecto-তে ডিফল্টভাবে 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]] => #

Ecto

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: "bette@kane.test", full_name: "Bette Kane", id: 1, inserted_at: ~N[2018-01-01 10:01:00.000000], invoices: #Ecto.Association.NotLoaded, updated_at: ~N[2018-01-01 10:01:00.000000] }

তুলনা

উভয় কেস খুব সাদৃশ্যপূর্ণ। ActiveRecord User মডেল ক্লাসের find ক্লাস মেথড 믿을ো করে। এর মানে প্রতিটি ActiveRecord চাইল্ড ক্লাসের নিজস্ব find মেথড থাকে।

Ecto একটি ভিন্ন পদ্ধতি ব্যবহার করে, রিপোজিটরি কনসেপ্টকে 매পিং লেয়ার এবং ডোমেনের মধ্যে মিডিয়েটর হিসেবে ব্যাবহার করে। Ecto ব্যবহার করার সময়, User মডিউলকে নিজে কীভাবে খুঁজে বের করবে সেটি জানা নেই। এই দায়িত্ব Repo মডিউলে থাকে, যা এটি আন্ডারলাইং ডেটাস্টোরে ম্যাপ করতে পারে, যা আমাদের কেসে Postgres।

SQL কোয়েরি তুলনা করলে কিছু পার্থক্য দেখা যায়:

  • ActiveRecord সব ফিল্ড লোড করে (users.*), যেখানে Ecto শুধু schema ডেফিনিশনে লিস্ট করা ফিল্ডগুলো লোড করে।
  • ActiveRecord কোয়েরিতে LIMIT 1 যোগ করে, যেখানে Ecto নয়।

সব আইটেম ফেচ করা

এক ধাপ এগিয়ে ডাটাবেস থেকে সব ইউজার লোড করি।

ActiveRecord

irb(main):001:0> User.all User Load (0.5ms) SELECT "users".* FROM "users" LIMIT $1 [["LIMIT", 11]] => #, #, #, #]>

Ecto

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: "bette@kane.test", full_name: "Bette Kane", id: 1, inserted_at: ~N[2018-01-01 10:01:00.000000], invoices: #Ecto.Association.NotLoaded, updated_at: ~N[2018-01-01 10:01:00.000000] }, %Financex.Accounts.User{ __meta__: #Ecto.Schema.Metadata<:loaded, "users">, email: "barbara@gordon.test", full_name: "Barbara Gordon", id: 2, inserted_at: ~N[2018-01-02 10:02:00.000000], invoices: #Ecto.Association.NotLoaded, updated_at: ~N[2018-01-02 10:02:00.000000] }, %Financex.Accounts.User{ __meta__: #Ecto.Schema.Metadata<:loaded, "users">, email: "cassandra@cain.test", full_name: "Cassandra Cain", id: 3, inserted_at: ~N[2018-01-03 10:03:00.000000], invoices: #Ecto.Association.NotLoaded, updated_at: ~N[2018-01-03 10:03:00.000000] }, %Financex.Accounts.User{ __meta__: #Ecto.Schema.Metadata<:loaded, "users">, email: "stephanie@brown.test", full_name: "Stephanie Brown", id: 4, inserted_at: ~N[2018-01-04 10:04:00.000000], invoices: #Ecto.Association.NotLoaded, updated_at: ~N[2018-01-04 10:04:00.000000] } ]

তুলনা

এটি পূর্বের সেকশনের মতো সঠিক একই প্যাটার্ন ফলো করে। ActiveRecord all ক্লাস মেথড ব্যবহার করে এবং Ecto রিপোজিটরি প্যাটার্নের উপর নির্ভর করে রেকর্ড লোড করার জন্য।

SQL কোয়েরিতে আবার কিছু পার্থক্য:

  • পূর্বের সেকশনের মতো, ActiveRecord সব ফিল্ড লোড করে (users.*), যেখানে Ecto শুধু schema ডেফিনিশনে লিস্ট করা ফিল্ডগুলো লোড করে।
  • ActiveRecord একটি LIMIT 11 ডিফাইন করে, যেখানে Ecto সকল কিছু লোড করে। এই লিমিটটি কনসোলে 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]] => #, #]>

Ecto

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, 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, user_id: 4 } ]

তুলনা

উভয় উদাহরণে where কীওয়ার্ড ব্যবহার করা হয়েছে, যা SQL WHERE ক্লজের সাথে সংযোগ। যদিও জেনারেটেড SQL কোয়েরিগুলো খুব সাদৃশ্যপূর্ণ, কিন্তু উভয় টুল কীভাবে সেখানে পৌঁছেছে সেটিতে কিছু গুরুত্বপূর্ণ পার্থক্য রয়েছে।

ActiveRecord paid_at: nil আর্গুমেন্টকে অটোমেটিক্যালি paid_at IS NULL SQL স্টেটমেন্টে রূপান্তর করে। Ecto-তে একই আউটপুট পেতে, ডেভেলপারদের তাদের ইন্টেন্ট স্পষ্ট করতে হয় is_nil() কল করে।

একটি আরো পার্থক্য হাইলাইট করা যাক: Ecto-তে where ফাংশনের "পিউর" আচরণ। শুধু where ফাংশন কল করলে এটি ডাটাবেসের সাথে ইন্টারঅ্যাক্ট করে না। where ফাংশনের রিটার্ন হল একটি Ecto.Query স্ট্রাক্ট:

iex(20)> where(Invoice, [i], is_nil(i.paid_at)) #Ecto.Query

ডাটাবেস শুধু তখন স্পর্শ হয় যখন Repo.all() ফাংশন কল করা হয়, Ecto.Query স্ট্রাক্ট আর্গুমেন্ট হিসেবে পাস করে। এই পদ্ধতিটি Ecto-তে কোয়েরি কম্পোজিশন অনুমতি দেয়, যা পরবর্তী সেকশনের বিষয়।

কোয়েরি কম্পোজিশন

ডাটাবেস কোয়েরির সবচেয়ে শক্তিশালী দিকের একটি হল কম্পোজিশন। এটি একটি কোয়েরি বর্ণনা করার পদ্ধতি যেটি একাধিক শর্ত ধারণ করে।

আপনি যদি র SQL কোয়েরি বানান, তবে আপনি কোনো কনক্যাটেনেশন ব্যবহার করবেন সম্ভবত। কল্পনা করুন আপনার দুটি শর্ত আছে:

  1. not_paid = 'paid_at IS NOT NULL'
  2. 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]] => #]>

Ecto

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, user_id: 2 } ]

তুলনা

উভয় কোয়েরি একই প্রশ্নের উত্তর দিচ্ছে: "কোন ইনভয়েসগুলো পেইড হয়েছে এবং Paypal ব্যবহার করেছে?"।

আশা অনুযায়ী, ActiveRecord কোয়েরি কম্পোজ করার আরও সংক্ষিপ্ত উপায় অফার করে (সেটি উদাহরণের জন্য), যেখানে Ecto ডেভেলপারদের কোয়েরি লিখতে কিছু বেশি খরচ করতে হয়। হয়তো, ব্যাটগার্ল (অর্ফান, মিউট, ক্যাসান্ড্রা কেইন আইডেন্টিটি) বা ActiveRecord তত ভার্বোজ নয়।

Ecto কোয়েরির ভার্বোসিটি এবং আপারেন্ট কমপ্লেক্সিটি দিয়ে বেলা নয়। রিয়েল ওয়ার্ল্ড এনভায়রনমেন্টে, সেই কোয়েরিটি নিচের মতো লিখা হতো:

Invoice |> where([i], not is_nil(i.paid_at)) |> where([i], i.payment_method == "Paypal") |> Repo.all()

সেটি দিক থেকে দেখলে, where ফাংশনের "পিউর" দিক, যা নিজে ডাটাবেস অপারেশন পারফর্ম করে না, পাইপ অপারেটরের সাথে কম্বাইন হলে, Ecto-তে কোয়েরি কম্পোজিশন খুবই ক্লিন।

অর্ডারিং (Ordering)

অর্ডারিং একটি কোয়েরির গুরুত্বপূর্ণ দিক। এটি ডেভেলপারদের নিশ্চিত করতে দেয় যে একটি কোয়েরি রেজাল্ট একটি নির্দিষ্ট অর্ডার ফলো করে।

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]] => #, #, #, #]>

Ecto

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, 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, 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, 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, user_id: 1 } ]

তুলনা

একটি কোয়েরিতে অর্ডার যোগ করা উভয় টুলে স্ট্রেট-ফরওয়ার্ড।

যদিও Ecto উদাহরণে Invoice প্রথম প্যারামিটার হিসেবে ব্যবহার করা হয়েছে, order_by ফাংশন Ecto.Query স্ট্রাক্ট ও এক্সেপ্ট করে, যা order_by ফাংশনকে কম্পোজিশনে ব্যবহার করতে দেয়, যেমন:

Invoice |> where([i], not is_nil(i.paid_at)) |> where([i], i.payment_method == "Paypal") |> order_by(desc: :inserted_at) |> Repo.all()

লিমিটিং (Limiting)

লিমিট ছাড়া ডাটাবেস কী? একটি ডিজাস্টার। خوشকিসমتی, ActiveRecord এবং Ecto উভয় রিটার্নড রেকর্ডের সংখ্যা লিমিট করতে সাহায্য করে।

ActiveRecord

irb(main):004:0> Invoice.limit(2) Invoice Load (0.2ms) SELECT "invoices".* FROM "invoices" LIMIT $1 [["LIMIT", 2]] => #, #]>

Ecto

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, 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, user_id: 2 } ]

তুলনা

ActiveRecord এবং Ecto উভয়েরই একটি কোয়েরি দ্বারা ফেরত আসা রেকর্ডের সংখ্যা লিমিট করার উপায় রয়েছে।

Ecto-এর limit order_by-এর মতো কাজ করে, কোয়েরি কম্পোজিশনের জন্য উপযুক্ত।

অ্যাসোসিয়েশন (Associations)

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]] => # 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]] => #]>

উপরের উদাহরণ দেখাচ্ছে যে আমরা user.invoices কল করলে ইউজারের ইনভয়েসের তালিকা পেতে পারি। এটি করার সময়, ActiveRecord অটোমেটিক্যালি ডাটাবেস কোয়েরি করে এবং ইউজারের সাথে asosheated ইনভয়েসগুলো লোড করে। এই পদ্ধতিটি জিনিসগুলো সহজ করে দেয়, কম কোড লিখতে হয় বা এক্সট্রা স্টেপের চিন্তা করতে হয় না, কিন্তু এটি সমস্যা হতে পারে যদি আপনি অনেক ইউজারের ওভার ইটারেট করেন এবং প্রতিটি ইউজারের জন্য ইনভয়েস ফেচ করেন। এই ইস্যুটিকে "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]] => # irb(main):023:0> user.invoices => #]>

এই কেসে, ActiveRecord ইউজার ফেচ করার সময় invoices অ্যাসোসিয়েশন ইগার-লোড করে (দুইটি SQL কোয়েরিতে দেখা যায়)।

Ecto

আপনি ইতিমধ্যে লক্ষ্য করেছেন হতে পারে, Ecto খুব কম ম্যাজিক বা ইম্প্লিসিটনেস পছন্দ করে। এটি ডেভেলপারদের তাদের ইন্টেন্ট স্পষ্ট করতে বাধ্য করে।

Ecto-তে 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: "barbara@gordon.test", full_name: "Barbara Gordon", id: 2, inserted_at: ~N[2018-01-02 10:02:00.000000], invoices: #Ecto.Association.NotLoaded, updated_at: ~N[2018-01-02 10:02:00.000000] } iex(8)> user.invoices #Ecto.Association.NotLoaded

ফলাফল হল Ecto.Association.NotLoaded। খুব উপকারী নয়।

ইনভয়েস অ্যাক্সেস পেতে, একজন ডেভেলপারকে Ecto-কে সেটি জানাতে হবে, preload ফাংশন ব্যবহার করে:

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: "barbara@gordon.test", 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, 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, user_id: 2 } ]

ActiveRecord includes-এর মতো, preload asosheated invoices ফেচ করবে, যা user.invoices কল করার সময় উপলব্ধ করাবে।

তুলনা

একবার আবার, ActiveRecord এবং Ecto-এর বাটল تعرف পয়েন্টে শেষ হয়: এক্সপ্লিসিটনেস। উভয় টুল ডেভেলপারদের সহজে অ্যাসোসিয়েশন অ্যাক্সেস করতে দেয়, কিন্তু ActiveRecord এটা কম ভার্বোজ করে, ফলাফলটি অপ্রত্যাশিত আচরণ দিতে পারে। Ecto WYSIWYG টাইপের পদ্ধতি ফলো করে, যা শুধু সেটি করে যা ডেভেলপার কোয়েরি ডিফাইন করেছেন।

রেইলস সব লেয়ারের ক্যাশিং স্ট্র্যাটেজি ব্যবহার ও প্রোমোট করার জন্য খুব বিখ্যাত। একটি উদাহরণ হল "রাশিয়ান ডল" ক্যাশিং পদ্ধতি, যা সম্পূর্ণরূপে "N + 1 সমস্যা"-র ওপর নির্ভর করে এর ক্যাশিং মেকানিজম পারফর্ম করার জন্য।

ভ্যালিডেশন (Validations)

ActiveRecord-এ উপস্থিত অধিকাংশ ভ্যালিডেশন Ecto-তেও উপলব্ধ। এখানে কিছু সাধারণ ভ্যালিডেশন এবং উভয় ActiveRecord ও Ecto কীভাবে সেগুলো ডিফাইন করে তার একটি তালিকা:

ActiveRecordEcto
validates :title, presence: truevalidate_required(changeset, [:title])
validates :email, confirmation: truevalidate_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: truevalidate_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 way" ফলো করেন।

অন্যদিকে, Ecto এক্সপ্লিসিটনেস চায় যা আরও ভার্বোজ কোড ফলাফল দেয়। সুবিধা হিসেবে, সব কিছু স্পটলাইতে থাকে, পিছনের দৃশ্যে কিছু নেই, এবং আপনি আপনার নিজস্ব পথ স্পেসিফাই করতে পারেন।

উভয়েরই আপসাইড আছে আপনার পারস্পেক্টিভ এবং পছন্দ অনুযায়ী। তাহলে আপেল ও কমলা তুলনা করে, আমরা এই BAT-tle-এর শেষে পৌঁছেছি। প্রায় ভুলে গিয়েছিলাম ব্যাটগার্লের কোডনেম (১৯৮৯ - ২০০১) ছিল .... Oracle। কিন্তু সেটি 안으로 যাব না। 😉


  1. RDBMS-এ কম্পোজিট কী (Composite Key) কী?

  2. C#-এ দুটি অ্যারের ইন্টারসেকশন

  3. ActiveRecord পারফরম্যান্স অপ্টিমাইজেশন: একটি বাস্তব গাইড

  4. ActiveRecord বনাম Ecto: ডেটাবেস অ্যাবস্ট্রাকশনের জন্য একটি ডেভেলপার গাইড - পর্ব এক