আজকের পোস্টে, আমরা রেলস মাইগ্রেশনে গভীরে যাব। আমরা মাইগ্রেশনকে বিভিন্ন অংশে ভাঙব, এবং প্রক্রিয়ায়, একটি কার্যকর মাইগ্রেশন কীভাবে লিখতে হয় সেটি শিখব। আমরা একাধিক ডেটাবেসের জন্য মাইগ্রেশন লিখতে শিখব, সেইসাথে ব্যর্থ মাইগ্রেশন হ্যান্ডল করা এবং রোলব্যাক করার টেকনিক।
পুরো পোস্ট বুঝতে, আপনাকে ডেটাবেস এবং রেলস সম্পর্কে মূল ধারণা থাকতে হবে।
মাইগ্রেশন ১০১
রেলসে মাইগ্রেশন আমাদের ডেটাবেসকে একটি অ্যাপ্লিকেশনের লাইফটাইমের চলমান উন্নত করতে দেয়। মাইগ্রেশন আমাদের প্লেইন রুবি কোড লিখতে দেয় ডেটাবেসের স্টেট পরিবর্তন করার জন্য একটি ইলিগ্যান্ট DSL প্রদান করে। আমাদের ডেটাবেস-স্পেসিফিক SQL লিখতে হয় না যেহেতু মাইগ্রেশন অ্যাবস্ট্রাকশন প্রদান করে ডেটাবেস ম্যানিপুলেট করার জন্য এবং পেছনের দৃশ্যে DSL কে ডেটাবেস-স্পেসিফিক SQL কোয়ারিতে কনভার্ট করার নিটি-গ্রিটি ডিটেইলস খেয়াল করে। মাইগ্রেশন আমাদের পথে বাধা দেয় না এবং র JDBC SQL এক্সিকিউট করার উপায় প্রদান করে, যদি এমন প্রয়োজন হয়।
রেলস ডেটাবেস মাইগ্রেশনে বিংশ হাজার লিগ
আমরা মাইগ্রেশন ব্যবহার করে টেবিল তৈরি করতে পারি, কলাম যোগ বা সরাতে পারি এবং কলামে ইনডেক্স যোগ করতে পারি।
প্রতিটি রেলস অ্যাপে একটি স্পেশাল ডিরেক্টরি থাকে—
db/migrate—যেখানে সকল মাইগ্রেশন সংরক্ষিত থাকে।
চলুন একটি মাইগ্রেশন দিয়ে শুরু করি যা events টেবিল তৈরি করে আমাদের ডেটাবেসে।
$ rails g migration CreateEvents category:string
এই কমান্ড একটি টাইমস্ট্যাম্পড ফাইল 20200405103635_create_events.rb জেনারেট করে db/migrate ডিরেক্টরিতে। ফাইলের কন্টেন্ট নিম্নরূপ।
class CreateEvents < ActiveRecord::Migration[6.0] def change create_table :events do |t| t.string :category t.timestamps end end end
এই মাইগ্রেশন ফাইলটি ভাঙে দেখি।
- রেলস দিয়ে জেনারেট করা প্রতিটি মাইগ্রেশন ফাইলে একটি টাইমস্ট্যাম্প থাকে যা ফাইলের নামে থাকে। এই টাইমস্ট্যাম্প গুরুত্বপূর্ণ এবং রেলস এর দ্বারা ব্যবহার হয় একটি মাইগ্রেশন চলেছে কিনা সেটি কনফার্ম করার জন্য, যেমন আমরা পরে দেখব।
- মাইগ্রেশন একটি ক্লাস ধারণ করে যা
ActiveRecord::Migration[6.0]থেকে ইনহেরিট করে। যেহেতু আমি রেলস ৬ ব্যবহার করছি, মাইগ্রেশন সুপারক্লাসে[6.0]আছে। যদি আমি রেলস ৫.২ ব্যবহার করতাম, তবে সুপারক্লাস হতActiveRecord::Migration[5.2]। পরে, আমরা আলোচনা করব কেন রেলস ভার্সন সুপারক্লাস নামের অংশ। - মাইগ্রেশনে একটি মেথড আছে
changeযেটি DSL কোড ধারণ করে ডেটাবেস ম্যানিপুলেট করার। এই কেসে,changeমেথডটি একটিeventsটেবিল তৈরি করছেcategoryকলামসহstringটাইপের। - মাইগ্রেশন
t.timestampsকোড ব্যবহার করেcreated_atএবংupdated_atটাইমস্ট্যাম্প যোগ করেeventsটেবিলে।
যখন এই মাইগ্রেশন rails db:migrate কমান্ড দিয়ে চলে, এটি একটি events টেবিল তৈরি করবে category কলামসহ string টাইপের এবং টাইমস্ট্যাম্প কলাম created_at এবং updated_at।
আসল ডেটাবেস কলাম টাইপ হবে varchar বা text, ডেটাবেসের ওপর নির্ভর করে।
মাইগ্রেশন টাইমস্ট্যাম্প এবং schema_migrations টেবিলের গুরুত্ব
প্রতিবার একটি মাইগ্রেশন জেনারেট হয় rails g migration কমান্ড ব্যবহার করে, রেলস মাইগ্রেশন ফাইল একটি ইউনিক টাইমস্ট্যাম্প সহ জেনারেট করে। টাইমস্ট্যাম্প ফরম্যাট YYYYMMDDHHMMSS। যতক্ষণ একটি মাইগ্রেশন চলে, রেলস মাইগ্রেশন টাইমস্ট্যাম্পটি একটি ইন্টারনাল টেবিল schema_migrations-এ ইনসার্ট করে। এই টেবিলটি রেলস দ্বারা তৈরি হয় যখন আমরা আমাদের প্রথম মাইগ্রেশন চালাই। টেবিলে শুধু কলাম আছে version, যা এটিও এর প্রাইমারি কী। এই হল schema_migrations টেবিলের কাঠামো।
CREATE TABLE IF NOT EXISTS "schema_migrations" ("version" varchar NOT NULL PRIMARY KEY);
এখন যেহেতু আমরা events টেবিল তৈরি করার মাইগ্রেশন চালিয়েছি, চলুন দেখি রেলস এই মাইগ্রেশনের টাইমস্ট্যাম্প schema_migrations টেবিলে সংরক্ষণ করেছে কিনা।
sqlite> select * from schema_migrations; 20200405103635
আমরা যদি আবার মাইগ্রেশন চালাই, রেলস প্রথমে চেক করবে schema_migrations টেবিলে একটি এন্ট্রি আছে কিনা মাইগ্রেশন ফাইলের টাইমস্ট্যাম্প সহ, এবং শুধু এক্সিকিউট করবে যদি এমন কোনো এন্ট্রি নেই। এটি নিশ্চিত করে যে আমরা ইনক্রিমেন্টালি ডেটাবেসে পরিবর্তন যোগ করতে পারি সময়ের সাথে সাথে এবং একটি মাইগ্রেশন ডেটাবেসে শুধু একবার চলে।
ডেটাবেস স্কিমা
যার সাথে আমরা আরও বেশি মাইগ্রেশন চালাই, ডেটাবেস স্কিমা উন্নত হতে থাকে। রেলস সবচেয়ে সাম্প্রতিক ডেটাবেস স্কিমা ফাইলে সংরক্ষণ করে db/schema.rb। এই ফাইলটি আপনার অ্যাপ্লিকেশনের জীবনকালে ডেটাবেসে চলে সকল মাইগ্রেশনের রুবি রিপ্রেজেন্টেশন। এই ফাইলের কারণে, আমাদের পুরনো মাইগ্রেশন ফাইল কোডবেসে রাখতে হয় না। রেলস টাস্ক প্রদান করে dump করা লেটেস্ট স্কিমা ডেটাবেস থেকে schema.rb-এ এবং load করা স্কিমা ডেটাবেসে schema.rb- থেকে। তাই পুরনো মাইগ্রেশন কোডবেস থেকে নিরাপদে ডিলিট করা যায়। স্কিমা লোড করা ডেটাবেসেও দ্রুত হয় প্রতিটি মাইগ্রেশন বার বার চালানোর চেয়ে প্রতিবার আমরা অ্যাপ্লিকেশন সেট আপ করি।
রেলস ডেটাবেস স্কিমা SQL ফরম্যাটেও সংরক্ষণ করার উপায় দেয়। আমাদের ইতিমধ্যে একটি আর্টিকেল আছে দুটি ফরম্যাট তুলনা করার জন্য। আপনি এখানে আরও পড়তে পারেন।
মাইগ্রেশনে রেলস ভার্সন
প্রতিটি মাইগ্রেশন যা আমরা জেনারেট করি তার রেলস ভার্সন সুপারক্লাসের অংশ হিসেবে থাকে। তো রেলস ৬ অ্যাপ দ্বারা জেনারেট করা একটি মাইগ্রেশনের সুপারক্লাস ActiveRecord::Migration[6.0] যখন রেলস ৫.২ অ্যাপ দ্বারা জেনারেট করা মাইগ্রেশনের সুপারক্লাস ActiveRecord::Migration[5.2]। আপনার যদি পুরনো অ্যাপ থাকে রেলস ৪.২ বা নিচে, আপনি লক্ষ্য করবেন সুপারক্লাসে কোনো ভার্সন নেই। সুপারক্লাস শুধু ActiveRecord::Migration।
রেলস ভার্সন মাইগ্রেশন সুপারক্লাসে রেলস ৫-এ যোগ করা হয়েছিল। এটি মূলত নিশ্চিত করে যে মাইগ্রেশন API সময়ের সাথে সাথে উন্নত হতে পারে পুরনো রেলস ভার্সন দ্বারা জেনারেট করা মাইগ্রেশন ব্রেক ছাড়া।
চলুন গভীরে যাই একই মাইগ্রেশন events টেবিল তৈরি করার জন্য একটি রেলস ৪.২ অ্যাপে।
class CreateEvents < ActiveRecord::Migration def change create_table :events do |t| t.string :category t.timestamps null: false end end end
যদি আমরা রেলস ৬ মাইগ্রেশন দ্বারা জেনারেট করা events টেবিলের স্কিমা দেখি, আমরা দেখতে পাব NOT NULL কনস্ট্রেইন্ট টাইমস্ট্যাম্প কলামের জন্য আছে।
sqlite> .schema events CREATE TABLE IF NOT EXISTS "events" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "category" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
এটি কারণ, রেলস ৫ থেকে শুরু করে, মাইগ্রেশন API অটোমেটিক্যালি NOT NULL কনস্ট্রেইন্ট যোগ করে টাইমস্ট্যাম্প কলামে সেটি এক্সপ্লিসিটলি মাইগ্রেশন ফাইলে যোগ করার প্রয়োজন ছাড়া। সুপারক্লাস নামের রেলস ভার্সন নিশ্চিত করে যে মাইগ্রেশন ব্যবহার করছে মাইগ্রেশন API সেটি রেলস ভার্সনের যেটির জন্য মাইগ্রেশন জেনারেট করা হয়েছিল। এটি রেলসকে পिछলা কম্প্যাটিবিলিটি মেইনটেইন করতে দেয় পুরনো মাইগ্রেশনের সাথে, একই সাথে মাইগ্রেশন API উন্নত করতে।
ডেটাবেস স্কিমা পরিবর্তন করা
change মেথড হল একটি মাইগ্রেশনের প্রাইমারি মেথড। যখন একটি মাইগ্রেশন চলে, এটি change মেথড কল করে এবং এর ভিতরের কোড এক্সিকিউট করে।
create_table-র সাথে, রেলস আরেকটি পাওয়ারফুল মেথড দেয়—change_table। নাম অনুযায়ী, এটি একটি এক্সিস্টিং টেবিলের স্কিমা পরিবর্তন করতে ব্যবহৃত হয়।
def change change_table :events do |t| t.remove :category t.string :event_type t.boolean :active, default: false end end
এই মাইগ্রেশন events টেবিল থেকে category কলাম রিমুভ করবে, একটি নতুন স্ট্রিং কলাম events_type যোগ করবে এবং একটি নতুন বুলিয়ান কলাম active যোগ করবে ডিফল্ট ভ্যালু false সহ।
রেলস আরো অনেক হেল্পার মেথড প্রদান করে যা মাইগ্রেশনের ভিতরে ব্যবহার করা যায় যেমন:
change_columnadd_indexremove_indexrename_table
এবং অনেক আরো। চেঞ্জের সাথে ব্যবহার করা যাওয়া সকল মেথড এখানে পাওয়া যাবে
টাইমস্ট্যাম্প
আমরা দেখেছি t.timestamps মাইগ্রেশনে রেলস দ্বারা যোগ করা হয়েছিল এবং এটা created_at এবং updated_at কলাম যোগ করেছিল events টেবিলে। এই স্পেশাল কলামগুলো রেলস দ্বারা ব্যবহৃত হয় একটি রেকর্ড কখন তৈরি হয়েছে এবং আপডেট হয়েছে সেটি ট্র্যাক করার জন্য। রেলস এই কলামগুলোর ভ্যালু সেট করে যখন একটি রেকর্ড তৈরি হয় এবং নিশ্চিত করে সেগুলো আপডেট হয় যখন রেকর্ড আপডেট হয়। এই কলামগুলো আমাদের ডেটাবেস রেকর্ডের লাইফটাইম ট্র্যাক করতে সাহায্য করে।
updated_atকলাম আপডেট হয় না যখন আমরা রেলস থেকেupdated_allমেথড এক্সিকিউট করি।
ব্যর্থতা হ্যান্ডলিং
মাইগ্রেশন বুলেটপ্রুফ নয়। সেগুলো ব্যর্থ হতে পারে। কারণ হতে পারে ভুল সিনট্যাক্স বা একটি অবৈধ ডেটাবেস কোয়ারি। যেকোনো কারণ হোক, আমাদের ব্যর্থতা হ্যান্ডল করতে হবে এবং রিকভার করতে হবে যাতে ডেটাবেস একটি ইনকনসিস্টেন্ট স্টেটে না যায়। রেলস এই সমস্যাটি সমাধান করে প্রতিটি মাইগ্রেশন একটি ট্রানজ্যাকশনের ভিতরে চালিয়ে। যদি মাইগ্রেশন ব্যর্থ হয়, তবে ট্রানজ্যাকশন রোলব্যাক হয়। এটি নিশ্চিত করে যে ডেটাবেস একটি ইনকনসিস্টেন্ট স্টেটে যায় না।
এটি শুধু সেসব ডেটাবেসের জন্য করা হয় যারা ডेटাবেস স্কিমা আপডেটের জন্য ট্রানজ্যাকশন সাপোর্ট করে। এগুলোকে Data Definition Language (DDL) ট্রানজ্যাকশন বলা হয়। MySQL এবং PostgreSQL উভয় DDL ট্রানজ্যাকশন সাপোর্ট করে।
কখনো কখনো, আমরা কিছু নির্দিষ্ট মাইগ্রেশন ট্রানজ্যাকশনের ভিতরে এক্সিকিউট করতে চাই না। একটি সিম্পল উদাহরণ হল পোস্টগ্রেএসকিউএলে একটি কাঙ্কারেন্ট ইনডেক্স যোগ করা। এমন মাইগ্রেশন DDL ট্রানজ্যাকশনের ভিতরে এক্সিকিউট করা যায় না কারণ পোস্টগ্রেএসকিউএল ইনডেক্স যোগ করার চেষ্টা করে টেবিলে লক আক್ವায়ার করা ছাড়া যাতে আমরা একটি লাইভ প্রোডাকশন ডেটাবেসে ইনডেক্স যোগ করতে পারি ডেটাবেস ডাউন না করেই। রেলস একটি উপায় দেয় ট্রানজ্যাকশন থেকে আপ্ট-আউট করার মাইগ্রেশনের ভিতরে disable_ddl_transactions! রূপে।
def change disable_ddl_transactions! add_index :events, :user_id, algorithm: :concurrently
এটি মাইগ্রেশন ট্রানজ্যাকশনের ভিতরে চালাবে না। যদি এমন একটি মাইগ্রেশন ব্যর্থ হয়, আমাদের নিজে সেটি রিকভার করতে হবে। এই কেসে, আমরা যেন REINDEX করি অথবা ইনডেক্স রিমুভ করে আবার যোগ করার চেষ্টা করি।
রিভার্সিবল মাইগ্রেশন
রেলস আমাদের ডেটাবেসে পরিবর্তন রোলব্যাক করতে দেয় নিচের কমান্ড দিয়ে।
rails db:rollback
এই কমান্ড ডেটাবেসে চলে শেষ মাইগ্রেশনকে রিভার্ট করে। যদি মাইগ্রেশন একটি কলাম event_type যোগ করে তবে রোলব্যাক সেটি রিমুভ করবে। যদি মাইগ্রেশন একটি ইনডেক্স যোগ করে, তবে রোলব্যাক সেটি রিমুভ করবে।
পূর্বের মাইগ্রেশন রোলব্যাক করে এবং সেটি চালানোর একটি কমান্ডও আছে। সেটি হল
rails db:redo।
রেলস আরো سمার্ট জানতে কীভাবে অধিকাংশ মাইগ্রেশন রিভার্ট করা যায়। কিন্তু আমরা রেলসকে হিন্ট দিতে পারি কীভাবে একটি মাইগ্রেশন রিভার্ট করতে হবে up এবং down মেথড প্রদান করে change মেথড ব্যবহার করার বদলে। up মেথড ব্যবহার হবে যখন মাইগ্রেশন চলে, যখন down মেথড ব্যবহার হবে যখন মাইগ্রেশন রোলব্যাক হয়।
def up change_table :events do |t| t.change :price, :string end end def down change_table :events do |t| t.change :price, :integer end end
এই উদাহরণে, আমরা events-এর price কলাম integer থেকে string এ পরিবর্তন করছি। আমরা স্পেসিফাই করছি কীভাবে সেটি রোলব্যাক করা উচিত down মেথডে।
এই একই মাইগ্রেশন change মেথড ব্যবহার করেও লেখা যায়।
def change reversible do |direction| change_table :events do |t| direction.up { t.change :price, :string } direction.down { t.change :price, :integer } end end end
রেলস একটি উপায়ও দেয় একটি পূর্ববর্তী মাইগ্রেশন সম্পূর্ণ রিভার্ট করার revert মেথড ব্যবহার করে।
def change revert CreateEvents create_table :events do ... end end
revert মেথড একটি ব্লকও এক্সেপ্ট করে একটি মাইগ্রেশন আংশিক রিভার্ট করার জন্য।
def change revert do reversible do |direction| change_table :events do |t| direction.up { t.remove :event_type } direction.down { t.string :event_type } end end end end
র JDBC এক্সিকিউট করা
কখনো কখনো, আমরা একটি মাইগ্রেশনে কমপ্লেক্স SQL এক্সিকিউট করতে চাই। এমন কেসে, আমরা টিপিক্যাল মাইগ্রেশন DSL ভুলে যাই এবং বরং র JDBC SQL এক্সিকিউট করি নিচেরভাবে।
def change execute <<-SQL .... SQL end
একাধিক ডেটাবেস এবং মাইগ্রেশন
রেলস ৬ একাধিক ডেটাবেস একটি সিঙ্গল রেলস অ্যাপ্লিকেশনের ভিতরে ব্যবহার করার জন্য সমর্থন যোগ করল। আপনি যদি একাধিক ডেটাবেস ব্যবহার করতে চান, আমরা সেগুলো কনফিগার করি database.yml ফাইলে।
development: primary: <<: *default database: db/development.sqlite3 analytics: adapter: sqlite3 database: db/analytics_dev.sqlite3
এই কনফিগারেশন রেলসকে বলে যে আমরা দুটি ডেটাবেস ব্যবহার করতে চাই—primary এবং analytics। আমরা আগে দেখেছি, মাইগ্রেশনগুলো সংরক্ষিত থাকে db/migrate ডিরেক্টরিতে ডিফল্টভাবে। কিন্তু এই কেসে, আমরা দুটি ডেটাবেসের মাইগ্রেশন একই ডিরেক্টরিতে রাখতে পারি না। আমরা চাই না analytics ডেটাবেসের মাইগ্রেশন primary ডেটাবেসে চলে এবং বিপরীত। যদি আমরা একাধিক ডেটাবেস ব্যবহার করি, আমাদের দরকারি একটি পাথ প্রদান করা দ্বিতীয় ডেটাবেসের মাইগ্রেশন সংরক্ষণ করার জন্য। এটা করা যায় migrations_paths প্রদান করে database.yml-এ।
development: primary: <<: *default database: db/development.sqlite3 analytics: adapter: sqlite3 database: db/analytics_dev.sqlite3 migrations_paths: db/analytics_migrate
তারপর আমরা analytics ডেটাবেসের জন্য মাইগ্রেশন তৈরি করতে পারি নিচেরভাবে।
rails generate migration AddExperiments rule:string active:boolean --db=analytics
এটি মাইগ্রেশন তৈরি করবে db/analytics_migrate-এ, এবং আমরা সেটি চালাতে পারি নিচেরভাবে।
rails db:migrate --db=analytics
আমরা যদি শুধু rails db:migrate চালাই, তবে এটি সব ডেটাবেসের মাইগ্রেশন এক্সিকিউট করবে।
analyticsডেটাবেসের নিজস্বschema_migrationsটেবিল থাকবে ট্র্যাক রাখতে কোন মাইগ্রেশন চলে এবং কোনটি নয়।
ডিপ্লয়মেন্টের সময় মাইগ্রেশন চালানো
যেহেতু মাইগ্রেশন ডেটাবেসের স্টেট পরিবর্তন করতে পারে, এবং আমাদের কোড সেটি ওপার নির্ভর করতে পারে, অতএব অত্যন্ত গুরুত্বপূর্ণ যে মাইগ্রেশনগুলো প্রথমে চলে নতুন কোড প্রয়োগ হওয়ার আগে।
হেরোকু-ভিত্তিক ডিপ্লয়মেন্টে, মাইগ্রেশন চালানো যাবে Procfile-এর release ফেজে।
# Profile web: bin/puma -C config/puma.rb release: bundle exec rake db:migrate
এটি নিশ্চিত করে যে মাইগ্রেশনগুলো চলে অ্যাপ ডাইনো রিস্টার্ট হওয়ার আগে।
ক্যাপিস্ট্রানো-ভিত্তিক ডিপ্লয়মেন্টে, মাইগ্রেশন চালানো উচিত সার্ভার রিস্টার্ট হওয়ার আগে।
ডকার-ভিত্তিক ডিপ্লয়মেন্টে, আমরা একটি সাইডকার কন্টেইনার চালাতে পারি মাইগ্রেশন প্রথমে চালানোর জন্য অ্যাপ রিস্টার্ট হওয়ার আগে। এটি খুব গুরুত্বপূর্ণ কারণ অন্যথায়, নতুন কন্টেইনারগুলো একটি ইনকনসিস্টেন্ট স্টেটে যেতে পারে যদি সেগুলো নতুন কোড ব্যবহার করা শুরু করে ডেটাবেস পরিবর্তন প্রয়োগ করার আগে নতুন কোডের জন্য।
উপসংহার
এই পোস্টে, আমরা রেলসে ডেটাবেস মাইগ্রেশন লেখার বিভিন্ন দিক দেখেছি। আমরা দেখেছি কী কনস্টিটিউট করে একটি মাইগ্রেশন সেইসাথে ব্যর্থতা হ্যান্ডল করা এবং মাইগ্রেশন রোলব্যাক করার টেকনিক যদি দরকার হয়। রেলস ৬ আমাদের একাধিক ডেটাবেস ব্যবহার করতে দেয় এবং প্রতিটির জন্য মাইগ্রেশন আলাদা যোগ করতে হয়। চূড়ান্তভাবে, আমরা সংক্ষেপে দেখেছি কীভাবে ডিপ্লয়মেন্টের সময় মাইগ্রেশন চালাতে হয় যাতে ডেটাবেস পরিবর্তনগুলো সঠিকভাবে প্রয়োগ হয় যেকোনো নতুন কোড সেগুলো ব্যবহার শুরু করার আগে।
P.S. আপনি যদি রুবি ম্যজিক পোস্টগুলো প্রেস থেকে বের होते সাথে সাথে পড়তে চান, আমাদের রুবি ম্যজিক নিউজলেটার সাবস্ক্রাইব করুন এবং কখনো একটি পোস্টও মিস করবেন না!