আজকের পোস্টে, আমরা আপনার রুবি অন রেলস অ্যাপ্লিকেশনে ডিফল্ট schema.rb স্কিমা ফরম্যাটের পরিবর্তে structure.sql ব্যবহার করার উল্লেখযোগ্য পার্থক্য এবং সুবিধাগুলো কভার করব। একটি ডেটা-চালিত বিশ্বে, আপনার ডাটাবেসের সমৃদ্ধ ফিচারগুলো কীভাবে khaiyap করা যায় সেটি জানা একটি সফল এবং ব্যর্থ 엔্টারপ্রাইজের মধ্যে পার্থক্য তৈরি করতে পারে। দুটি ফরম্যাটের প্রধান পার্থক্যগুলো ইভিডেন্স করার পর, আমরা structure.sql-এ স্যুইচ করার পদ্ধতি আউটলাইন করব এবং প্রদর্শন করব কীভাবে এটি ডেটা ইন্টিগ্রিটি নিশ্চিত করতে সাহায্য করতে পারে সেইসাথে ডাটাবেস ফাংশনালিটি যা আপনি অন্যথায় সংরক্ষণ করতে পারতেন না। পোস্টে, আমি একটি রেলস অ্যাপের উদাহরণ দেব যা structure.sql ব্যবহার করে PostgreSQL ডাটাবেসের সাথে, কিন্তু আন্ডারলাইং কনসেপ্টগুলো অন্যান্য ডাটাবেসের জন্যও প্রযোজ্য। কোনো রিয়েল-ওয়ার্ল্ড ওয়েব অ্যাপ্লিকেশন একটি विश्वস্ত ডাটাবেস ছাড়া পুরো হয় না। আর দেরি না করে, ডাইভ ইন করি!
schema.rb এবং structure.sql এর পার্থক্য
একটি রুবি অন রেলস প্রজেক্ট শুরু করার সময় আপনাকে করতে হয় ডাটাবেস মাইগ্রেশন রান। আপনি যদি একটি User মডেল জেনারেট করেন, উদাহরণস্বরূপ, রেলস অবশ্যই আপনাকে মাইগ্রেশন রান করতে বলবে, যা একে schema.rb ফাইল তৈরি করবে:
rails g model User first_name:string last_name:string
রেলস নিম্নলিখিত মাইগ্রেশন জেনারেট করবে:
class CreateUsers < ActiveRecord::Migration[6.0] def change create_table :users do |t| t.string :first_name t.string :last_name t.timestamps end end end
মাইগ্রেশন এক্সিকিউট হওয়ার পর, আপনি দেখবেন রেলস আপনার জন্য একটি schema.rb ফাইল জেনারেট করেছে:
ActiveRecord::Schema.define(version: 2019_12_14_074018) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" create_table "users", force: :cascade do |t| t.string "first_name" t.string "last_name" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end end
এই schema.rb ফাইল তুলনামূলকভাবে বেসিক অ্যাপ্লিকেশন এবং ইউজ কেসের জন্য চমৎকার। এখানে দুটি رئيسية বিষয় লক্ষ্য করা যায়:
- এটি আপনার ডাটাবেসের একটি রুবি রিপ্রেজেন্টেশন;
schema.rbডাটাবেস ইন্সপেক্ট করে এবং এর কাঠামো রুবি ব্যবহার করে এক্সপ্রেস করে। - এটি ডাটাবেস-অগনস্টিক (অর্থাৎ আপনি SQLite, PostgreSQL, MySQL বা রেলস যা সাপোর্ট করে অন্য যেকোনো ডাটাবেস ব্যবহার করুন, সিনট্যাক্স এবং কাঠামো প্রায় একই থাকবে)।
rails db:create db:migrate কমান্ড দিয়ে। যদি এটা হয়, তবে আপনি কীভাবে একটি সঠিক ডাটাবেস স্কিমা পাবেন? অবশ্যই, একটি উপায় হল ফিরে যেতে এবং সব ব্রোকেন মাইগ্রেশন ফিক্স করা। এটা কখনো বurile 아이ডিয়া নয়! যদি অনেক মাইগ্রেশন ফিক্স করা খুব কষ্টসাধ্য হয়, তবে আরেকটি উপায় হল rails db:setup টাস্ক রান করা। এই টাস্ক আপনার schema.rb ফাইল থেকে ডাটাবেস স্কিমা জেনারেট করে। কিন্তু যদি আপনার ডাটাবেসে জটিল লজিক থাকে যা schema.rb রিপ্রেজেন্টেশনে রিফলেক্ট হয় না? সৌভাগ্য से, রেলস একটি বিকল্প অফার করে: structure.sql। structure.sql schema.rb-থেকে নিম্নলিখিত দিকগুলোতে আলাদা: - এটি ডাটাবেস কাঠামোর একটি এক্স্যাক্ট কপি আফার করে। এটি টিমের সাথে কাজ করার সময় গুরুত্বপূর্ণ, সেইসাথে যদি আপনাকে
rails db:setupটাস্ক দিয়ে প্রোডাকশনে দ্রুত একটি নতুন ডাটাবেস জেনারেট করতে হয়। - এটি অ্যাডভান্সড ডাটাবেস ফিচার자들의 ইনফরমেশন সংরক্ষণ করতে দেয়। উদাহরণস্বরূপ, আপনি PostgreSQL ব্যবহার করলে, এটি ভিউস, মেটেরিয়ালাইজড ভিউস, ফাংশনস, কনস্ট্রেইন্টস ইত্যাদি ব্যবহার করতে সক্ষম করে।
structure.sql ব্যবহার করা ব্যবহারকারীদের সেটি করতে দেয়।
schema.rb-থেকে structure.sql-এ স্যুইচ করা
schema.rb-থেকে structure.sql-এ পরিবর্তন আনা একটি tương đối সহজ প্রক্রিয়া। আপনাকে শুধু config/application.rb-এ নিম্নলিখিত লাইন যোগ করতে হবে:
module YourApp class Application < Rails::Application config.load_defaults 6.0 # এই লাইন যোগ করুন: config.active_record.schema_format = :sql end end
তারপর rails db:migrate রান করুন এবং আপনি db/structure.sql-এ ফাইলটি পাবেন। Voilà! রেলস আপনার ব্যবহার করা ডাটাবেসের জন্য স্পেসিফিক টুল ব্যবহার করে ডাটাবেস কাঠামো ডাম্প করবে (PostgreSQL-এর কেসে, সেই টুলটি pg_dump, MySQL বা MariaDB-এর জন্য, এটি প্রতিটি টেবিলের জন্য SHOW CREATE TABLE-র আউটপুট থাকবে, ইত্যাদি)। এই ফাইলটি ভার্সন কন্ট্রোলের অধীনে রাখা পরামর্শদায়ক যাতে আপনার টিমের বাকি সদস্যদের একই ডাটাবেস কাঠামো থাকে। প্রথম নজরে এই ফাইল ভয়ংকর লাগতে পারে: schema.rb ফাইলটি ছিল শুধু ২৫ লাইন, тогда structure.sql ফাইলটি হচ্ছে বিকট ১০৯ লাইন! এমন বড় ফাইল অ্যাপ ডেভেলপমেন্টে কী সুবিধা যোগ করতে পারে?
ডাটাবেস-লেভেল কনস্ট্রেইন্ট যোগ করা
ActiveRecord রেলস ব্যবহার করার আমার প্রিয় অংশের একটা। এটি আপনাকে ডাটাবেস কুয়েরি করতে দেয় এমনভাবে যেন কোনো কথা বলার মতো স্বाभাবিক লাগে। উদাহরণস্বরূপ, আপনি যদি একটি কোম্পানির সব ইউজার খুঁজতে চান যাদের নাম Dan, তবে ActiveRecord আপনাকে নিম্নলিখিত কুয়েরি রান করতে দেয়:
company = Company.find(name: 'Some Company') # ঠিক বোলার ভাষায় পড়ে! company.users.where(first_name: 'Dan')
তবে কিছু কেস আছে যেখানে ActiveRecord সংক্ষিপ্ত হয়ে পড়ে। উদাহরণস্বরূপ, আপনার User মডেলে নিম্নলিখিত ভ্যালিডেশন আছে:
class User < ApplicationRecord validate :name_cannot_start_with_d private def name_cannot_start_with_d if first_name.present? && first_name[0].downcase == 'd' errors.add(:first_name, "cannot start with the letter 'D'") end end end
আপনি যদি 'Dan' নাম দিয়ে ইউজার তৈরি করতে চান, তবে ভ্যালিডেশন রান হলে এরর দেখবেন:
User.create!(first_name: 'Dan') Traceback (most recent call last): ActiveRecord::RecordInvalid (Validation failed: First name cannot start with the letter 'D')
এটা ঠিক আছে, কিন্তু ধরুন আপনি বা আপনার টিম মেম্বার ActiveRecord-এর ভ্যালিডেশন বাইপাস করে ডেটা পরিবর্তন করেন:
u = User.create(first_name: 'Pan') # update_attribute মেথড ActiveRecord ভ্যালিডেশন বাইপাস করে u.update_attribute :first_name, 'Dan' u.first_name => "Dan"
দেখানো হয়েছে, ভ্যালিডেশন বাইপাস করা খুব সহজ। এটি আমাদের অ্যাপ্লিকেশনের জন্য দুরভাগ্যজনক ফলাফল নিয়ে আসতে পারে। ActiveRecord আশীর্বাদও হতে পারে এবং অভিশাপও—যদি এটি খুব ক্লিন এবং স্বাভাবিক DSL দিয়ে কাজ করার সুযোগ দেয়, তবে মডেল-লেভেল ভ্যালিডেশন এনফোর্স করার বিষয়ে এটি প্রায়শই অত্যধিক অনুমোদনকারী হয়। সমাধান, আপনি যেন ইতিমধ্যে জানেন, হল ডাটাবেস-লেভেল কনস্ট্রেইন্ট যোগ করা।
rails g migration AddFirstNameConstraintToUser
এটি একটি ফাইল জেনারেট করবে যা আপনি এডিট করে 'D' অক্ষর দিয়ে শুরু হওয়া ফার্স্ট নেম নিষিদ্ধ করার লজিক যোগ করতে পারেন:
class AddFirstNameConstraintToUser < ActiveRecord::Migration[6.0] def up execute "ALTER TABLE users ADD CONSTRAINT name_cannot_start_with_d CHECK (first_name !~* '^d')" end def down execute "ALTER TABLE users DROP CONSTRAINT IF EXISTS name_cannot_start_with_d" end end
নোট করুন যে মাইগ্রেশন সফলভাবে রিভার্ট করার কোড যোগ করা খুব গুরুত্বপূর্ণ। উপরের উদাহরণে, আমার up এবং down ডিরেক্টিভ আছে। up মেথড মাইগ্রেশন রান হলে এক্সিকিউট হয়, down মাইগ্রেশন রোলব্যাক হলে এক্সিকিউট হয়। সঠিকভাবে আপনার ডাটাবেস কাঠামো রিভার্ট না করলে, আপনাকে পরে ম্যানুয়ালি হাউস-ক্লিনিং করতে হতে পারে। আমি সবসময় একটি মাইগ্রেশন ফাইল রাখার পরামর্শ দেব যা up এবং down উভয় দিক থেকে এক্সিকিউট করা যায় ভবিষ্যতের মাথাব্যাথা এড়াতে। এখন মাইগ্রেশন রান করুন এবং চেক করুন কনস্ট্রেইন্ট বাইপাস করা যায় কিনা:
rails db:migrate
user = User.create first_name: 'Pan' user.update_attribute :first_name, 'Dan' ActiveRecord::StatementInvalid (PG::CheckViolation: ERROR: new row for relation "users" violates check constraint "name_cannot_start_with_d") DETAIL: Failing row contains (2, Dan, null, 2019-12-14 09:40:11.809358, 2019-12-14 09:40:41.658974).
পারফেক্ট! আমাদের কনস্ট্রেইন্ট উদ্দেশ্য অনুযায়ী কাজ করছে। এমনকি যদি আমরা যেকোনো কারণে ActiveRecord-এর ভ্যালিডেশন বাইপাস করি, আমরা এখনও ডাটাবেসে—আমাদের চূড়ান্ত গোলকিপার—নির্ভর করতে পারি ডেটা ইন্টিগ্রিটি সংরক্ষণ করতে। এতে structure.sql-এর সাথে কী সম্পর্ক? আপনি যদি সেটি দেখেন, তাহলে পাবেন নিম্নলিখিতটি যোগ হয়েছে:
CREATE TABLE public.users ( id bigint NOT NULL, first_name character varying, last_name character varying, created_at timestamp(6) without time zone NOT NULL, updated_at timestamp(6) without time zone NOT NULL, CONSTRAINT name_cannot_start_with_d CHECK (((first_name)::text !~* '^d'::text)));
আপনার কনস্ট্রেইন্ট স্কিমার ভেতরেই আছে! schema.rb ডাটাবেস-লেভেল কনস্ট্রেইন্ট সাপোর্ট করে, তবে মনে রাখবেন এটি আপনার ডাটাবেস যা সমর্থন করতে পারে সেটি সব এক্সপ্রেস করে না যেমন ট্রিগার, সিকোয়েন্স, স্টোরড প্রোসিজার বা চেক কনস্ট্রেইন্ট। উদাহরণস্বরূপ, একই মাইগ্রেশন (AddFirstNameConstraintToUser) থাকলে schema.rb ব্যবহার করলে কী হবে:
ActiveRecord::Schema.define(version: 2019_12_14_074018) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" create_table "users", force: :cascade do |t| t.string "first_name" t.string "last_name" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end end ফাইলটি একদম অপরিবর্তিত! কনস্ট্রেইন্ট যোগ হয়নি। আপনি যদি আপনার প্রজেক্টে একজন নতুন ডেভেলপার অনবোর্ড করেন, তবে আপনি সম্ভবত ভিন্ন ডাটাবেস রেগুলেশনস আন্ডারে অপারেট করছেন। structure.sql ভার্সন কন্ট্রোলে কমিট করলে নিশ্চিত হবে আপনার টিম একই পেজে আছে। আপনি যদি structure.sql ফাইল থাকা অবস্থায় rails db:setup রান করেন, আপনার ডাটাবেসের কাঠামো উপরের কনস্ট্রেইন্ট ধারণ করবে। schema.rb-এ এমন কোনো গ্যারান্টি নেই। একই কথা প্রোডাকশন সিস্টেমের জন্যও প্রযোজ্য। আপনি যদি দ্রুত একটি নতুন ইনস্ট্যানস স্পিন আপ করতে চান একটি ফ্রেশ ডাটাবেসের সাথে—এবং সব মাইগ্রেশন সিকোয়েনশিয়ালি রান করাতে অনেক সময় লাগে—structure.sql ফাইল থেকে ডাটাবেস সেট আপ করা খুব দ্রুত হবে। আমরা নিশ্চিত থাকতে পারি যে structure.sql আমাদের ডাটাবেসটি অন্যান্য ইনস্ট্যান্সের মতো একই কাঠামো দিয়ে তৈরি করবে।
বৃদ্ধির ব্যথা
সংক্ষিপ্ত schema.rb ফাইল একটি টিম জুড়ে ম্যানেজ করা structure.sql ফাইলের চেয়ে অনেক সহজ। structure.sql-এ মাইগ্রেট করার সবচেয়ে বড় ব্যথা হল নিশ্চিত করা যে শুধু প্রয়োজনীয় পরিবর্তনগুলো সেটি ফাইলে কমিট হয়েছে, যা কখনো কখনো কঠিন হতে পারে। ধরুন, আপনি কাউকে ব্রাঞ্চ পুল করেছেন এবং সেই ব্রাঞ্চের স্পেসিফিক মাইগ্রেশন রান করেছেন। আপনার structure.sql এখন কিছু পরিবর্তন ধারণ করবে। তারপর আপনি আপনার ব্রাঞ্চে ফিরে কাজ করছেন এবং একটি নতুন মাইগ্রেশন জেনারেট করেছেন। আপনার structure.sql ফাইল এখন আপনার ব্রাঞ্চ এবং অন্য ব্রাঞ্চ উভয়ের পরিবর্তন ধারণ করবে। এটি একটু ঝামেলা সৃষ্টি করতে পারে, এবং এখানে অবশ্যই একটি লার্নিং কার্ভ আছে যখন এই কনফ্লিক্ট ম্যানেজ করার কথা আসে। এই পদ্ধতিতে, আমরা একটি ট্রেড-অফ করছি। আমাদের শুরুতে কিছু কোড কমপ্লেক্সিটি হ্যান্ডেল করতে হচ্ছে যা আমাদের ডাটাবেসের অ্যাডভান্সড ফাংশনালিটি সংরক্ষণ করতে দিচ্ছে। বদলে, আমাদের একটি সিম্পলার স্কিমা রিপ্রেজেন্টেশন এবং ডাটাবেসের সকল শক্তি আমাদের হাতে নাও থাকতে পারে, যেমন যদি আমরা db:setup টাস্ক দিয়ে ব্যাকআপ সেট আপ করতে চাই। আমি মনে করি কিছু ভার্সন-কন্ট্রোল হাসল সহন করা ভালো, চেয়ে প্রোডাকশন সিস্টেমে করাপ্ট/ভুল ডেটা ফিক্স করতে ভুগতে বা আপনার ডাটাবেস যা অফার করে সকল অ্যাডভান্সড ফাংশনালিটি ব্যবহার না করতে পারা। সাধারণভাবে, দুটি স্ট্র্যাটেজি আমি ব্যবহার করেছি নিশ্চিত করার জন্য যে আমার structure.sql ফাইল শুধু একটি স্পেসিফিক ব্রাঞ্চের প্রয়োজনীয় পরিবর্তন ধারণ করে:
- একবার আপনি একটি ব্রাঞ্চে কাজ শেষ করলেন যাতে মাইগ্রেশন আছে, নিশ্চিত করুন আপনি
rails db:rollback STEP=nরান করেছেন যেখানেnহল সেই ব্রাঞ্চের মাইগ্রেশন সংখ্যা। এটি নিশ্চিত করবে আপনার ডাটাবেস কাঠামো আসল অবস্থায় ফিরে যাচ্ছে। - আপনি ভুলে গিয়ে রোলব্যাক না করার পর, একটি নতুন ব্রাঞ্চে কাজ করার সময়, নিশ্চিত করুন আপনি মাস্টার থেকে একটি প্রিস্টাইন
structure.sqlফাইল পুল করেছেন নতুন মাইগ্রেশন তৈরি করার আগে।
structure.sql ফাইল মাস্টারে মার্জ করার আগে শুধু আপনার ব্রাঞ্চের প্রাসঙ্গিক পরিবর্তন ধারণ করা উচিত।
উপসংহার
সামান্যভাবে, যখন রেলস অ্যাপ্লিকেশন ছোট বা ডাটাবেস যা অ্যাডভান্সড ফিচার অফার করে সেগুলো প্রয়োজন নেই, তবে schema.rb ব্যবহার করা সুরক্ষিত, যা খুব রিডেবল, সংক্ষিপ্ত এবং ম্যানেজ করা সহজ। তবে, như-जैसे অ্যাপ্লিকেশন আকার এবং জটিলতায় বৃদ্ধি পায়, ডাটাবেস কাঠামোর একটি সঠিক প্রতिबিম্ব অত্যন্ত জরুরি। এটি একটি টিমকে সঠিক কনস্ট্রেইন্ট, ডাটাবেস মডিউল, ফাংশন এবং অপারেটর মেইনটেইন করতে দেবে যা অন্যথায় সম্ভব নয়ত। একটি ভালো মেইনটেইন করা structure.sql ফাইলের সাথে রেলস ব্যবহার শিখা একটি এজ দেবে যা সিম্পলার schema.rb কখনো দিতে পারবে না। P.S. আপনি যদি Ruby Magic পোস্টগুলো প্রেস থেকে বের होते পড়তে চান, আমাদের Ruby Magic নিউজলেটার সাবস্ক্রাইব করুন এবং কখনো একটি পোস্ট মিস করবেন না!