কম্পিউটার

ActiveRecord বনাম EctoPart দুই

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

ActiveRecord বনাম Ecto পার্ট ওয়ানে ডাটাবেস স্কিমা এবং মাইগ্রেশন খোঁজার পরে, এই পোস্টটি কভার করে যে কিভাবে ActiveRecord এবং Ecto উভয়ই ডেভেলপারদের ডেটাবেস অনুসন্ধান করতে সক্ষম করে এবং একই প্রয়োজনীয়তার সাথে কাজ করার সময় ActiveRecord এবং Ecto উভয়েরই তুলনা করা হয়। পথে, আমরা ব্যাটগার্লের 1989-2011 পরিচয়ও খুঁজে বের করব৷

বীজ ডেটা

চল শুরু করি! এই সিরিজের প্রথম পোস্টে সংজ্ঞায়িত ডাটাবেস কাঠামোর উপর ভিত্তি করে, ধরে নিন users এবং invoices সারণীতে নিম্নলিখিত ডেটা সংরক্ষিত থাকে:

ব্যবহারকারীরা

id পূর্ণ_নাম ইমেল created_at* updated_at
1 বেট কেন bette@kane.test 2018-01-01 10:01:00 2018-01-01 10:01:00
2 বারবারা গর্ডন barbara@gordon.test 2018-01-02 10:02:00 2018-01-02 10:02:00
3 ক্যাসান্দ্রা কেইন cassandra@cain.test 2018-01-03 10:03:00 2018-01-03 10:03:00
4 স্টেফানি ব্রাউন stephanie@brown.test 2018-01-04 10:04:00 2018-01-04 10:04:00

* ActiveRecord এর created_at ক্ষেত্রের নাম inserted_at ডিফল্টরূপে Ecto-তে।

চালান

id user_id পেমেন্ট_পদ্ধতি paid_at created_at* updated_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 ডিফল্টরূপে Ecto-তে।

এই পোস্টের মাধ্যমে সম্পাদিত প্রশ্নগুলি অনুমান করে যে উপরের ডেটা ডাটাবেসে সংরক্ষিত আছে, তাই এটি পড়ার সময় এই তথ্যটি মনে রাখবেন৷

এর প্রাথমিক কী ব্যবহার করে আইটেম খুঁজুন

এর প্রাথমিক কী ব্যবহার করে ডাটাবেস থেকে একটি রেকর্ড পাওয়ার সাথে শুরু করা যাক।

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: "bette@kane.test", created_at: "2018-01-01 10:01:00", updated_at: "2018-01-01 10:01:00">

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<association :invoices is not loaded>,
  updated_at: ~N[2018-01-01 10:01:00.000000]
}

তুলনা

উভয় ক্ষেত্রেই বেশ মিল। ActiveRecord নির্ভর করে find এর উপর User ক্লাস পদ্ধতি মডেল ক্লাস। এর মানে হল প্রতিটি ActiveRecord চাইল্ড ক্লাসের নিজস্ব find আছে এটিতে পদ্ধতি।

Ecto একটি ভিন্ন পদ্ধতি ব্যবহার করে, ম্যাপিং স্তর এবং ডোমেনের মধ্যে মধ্যস্থতাকারী হিসাবে রিপোজিটরি ধারণার উপর নির্ভর করে। Ecto ব্যবহার করার সময়, users কিভাবে নিজেকে খুঁজে বের করতে হয় সে সম্পর্কে মডিউলের কোন জ্ঞান নেই। এই ধরনের দায়িত্ব Repo-এ উপস্থিত রয়েছে মডিউল, যা এটিকে ডাটাস্টোরের নীচে ম্যাপ করতে সক্ষম, যা আমাদের ক্ষেত্রে পোস্টগ্রেস।

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]]
=> #<ActiveRecord::Relation [#<User id: 1, full_name: "Bette Kane", email: "bette@kane.test", created_at: "2018-01-01 10:01:00", updated_at: "2018-01-01 10:01:00">, #<User id: 2, full_name: "Barbara Gordon", email: "barbara@gordon.test", created_at: "2018-01-02 10:02:00", updated_at: "2018-01-02 10:02:00">, #<User id: 3, full_name: "Cassandra Cain", email: "cassandra@cain.test", created_at: "2018-01-03 10:03:00", updated_at: "2018-01-03 10:03:00">, #<User id: 4, full_name: "Stephanie Brown", email: "stephanie@brown.test", created_at: "2018-01-04 10:04:00", updated_at: "2018-01-04 10:04:00">]>

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<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: "barbara@gordon.test",
    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: "cassandra@cain.test",
    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: "stephanie@brown.test",
    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 ব্যবহার করে ক্লাস মেথড এবং 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]]
=> #<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">]>

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<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-এর সাথে সংযোগ ধারা যদিও জেনারেট করা এসকিউএল ক্যোয়ারীগুলো অনেকটা একই রকম, উভয় টুল কিভাবে সেখানে পৌঁছায় তাতে কিছু গুরুত্বপূর্ণ পার্থক্য রয়েছে।

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 পাস করে আর্গুমেন্ট হিসাবে struct. এই পদ্ধতিটি Ecto-তে ক্যোয়ারী কম্পোজিশনের অনুমতি দেয়, যা পরবর্তী বিভাগের বিষয়।

কোয়েরি রচনা

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

আপনি যদি কাঁচা এসকিউএল কোয়েরি তৈরি করে থাকেন, তাহলে এর মানে হল আপনি সম্ভবত কোনো ধরনের সংমিশ্রণ ব্যবহার করবেন। কল্পনা করুন আপনার দুটি শর্ত আছে:

  1. not_paid = 'paid_at IS NOT NULL'
  2. paid_with_paypal = 'payment_method = "Paypal"'

কাঁচা এসকিউএল ব্যবহার করে এই দুটি শর্ত একত্রিত করার জন্য, এর অর্থ হল আপনাকে অনুরূপ কিছু ব্যবহার করে সেগুলিকে সংযুক্ত করতে হবে:

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">]>

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<association :user is not loaded>,
    user_id: 2
  }
]

তুলনা

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

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

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

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

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

অর্ডার করা হচ্ছে

অর্ডার করা একটি প্রশ্নের একটি গুরুত্বপূর্ণ দিক। এটি ডেভেলপারদের নিশ্চিত করতে সক্ষম করে যে একটি প্রদত্ত প্রশ্নের ফলাফল একটি নির্দিষ্ট ক্রম অনুসরণ করে৷

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">]>

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<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">]>

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<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: "barbara@gordon.test", 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: "barbara@gordon.test", 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 লোড করে ব্যবহারকারীকে আনার সময় অ্যাসোসিয়েশন (যেমন দুটি এসকিউএল কোয়েরিতে দেখানো হয়েছে)।

Ecto

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

চলুন user.invoices ব্যবহার করার একই পদ্ধতির চেষ্টা করা যাক Ecto এর সাথে:

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<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: "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<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 ধরনের পদ্ধতি অনুসরণ করে, যা শুধুমাত্র ডেভেলপার দ্বারা সংজ্ঞায়িত প্রশ্নে যা দেখা যায় তা করে।

অ্যাপ্লিকেশনের বিভিন্ন স্তরে ক্যাশিং কৌশলগুলি ব্যবহার এবং প্রচার করার জন্য রেলগুলি সুপরিচিত। একটি উদাহরণ হল "রাশিয়ান পুতুল" ক্যাশিং পদ্ধতির ব্যবহার সম্পর্কে, যা সম্পূর্ণরূপে "N + 1 সমস্যা" এর উপর নির্ভর করে তার ক্যাশিং মেকানিজমের জাদু সম্পাদনের জন্য৷

বৈধতা

ActiveRecord-এ উপস্থিত বেশিরভাগ বৈধতা Ecto-তেও পাওয়া যায়। এখানে সাধারণ যাচাইকরণের একটি তালিকা রয়েছে এবং কিভাবে ActiveRecord এবং Ecto উভয়ই তাদের সংজ্ঞায়িত করে:

ActiveRecord Ecto
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 পদ্ধতি" অনুসরণ করেন।

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

আপনার দৃষ্টিকোণ এবং পছন্দের উপর নির্ভর করে উভয়েরই উল্টো দিক রয়েছে। তাই আপেল এবং কমলার তুলনা করে, আমরা এই BAT-tle এর শেষে চলে আসি। আপনাকে বলতে প্রায় ভুলে গেছি ব্যাটগার্লের কোডনেম (1989 - 2001) ছিল .... ওরাকল। তবে এর মধ্যে না যাওয়া যাক। 😉


  1. RDBMS-এ কম্পোজিট কী

  2. C# এ দুটি অ্যারের ছেদ

  3. ActiveRecord পারফরম্যান্সের সমস্যা সমাধান করা

  4. ActiveRecord বনাম EctoPart One