আপনার ক্যরি-অন লাগেজ কসল করে রাখুন, কারণ আজ আমরা সুরুর থেকে শেষ পর্যন্ত ancestor chain-এ যात्रा করব। আমরা একটি মেথড কল কিভাবে চেইন-এ উপরে যায় এবং যদি মেথড না মিলে তখন কী হয় সেটা দেখব। এবং যেহেতু আমরা আগ নিয়ে খেলতে ভালোবাসি, আমরা BasicObject#method_missing ওভাররাইড করব। আপনি মনোযোগ দেন তবে আমরা এটাকে প্র্যাকটিক্যাল উদাহরণে ব্যবহার করবও। কোনো গ্যারান্টি নেই। চলুন!
Ancestor Chain (पूर्वज শৃঙ্খলা)
রুবিতে ancestor chain-এর মৌলিক নিয়মগুলো দিয়ে শুরু করি:
- রুবি শুধু single inheritance সাপোর্ট করে
- এছাড়াও একটি অবজেক্ট মডিউল সেট ইনক্লুড করতে পারে
রুবিতে, ancestor chain একটি প্রদত্ত ক্লাসের জন্য সমস্ত ইনহেরিট করা ক্লাস এবং মডিউলের traversal-এ গঠিত।
আসুন একটি উদাহরণ দেখি যাতে রুবি ancestor chain কীভাবে হ্যান্ডেল করে সেটা বোঝা যায়।
module Auth end module Session end module Iterable end class Collection prepend Iterable end class Users < Collection prepend Session include Auth end p Users.ancestorsএটি প্রোডিউস করে:
[ Session, Users, Auth, # Users Iterable, Collection, # Collection Object, Kernel, BasicObject # Ruby Object Model ]প্রথমে, আমরা ancestors ক্লাস মেথড কল করি যাতে প্রদত্ত ক্লাসের ancestor chain অ্যাক্সেস করা যায়।
আমরা দেখতে পাচ্ছি যে Users.ancestors কল করলে একটি অ্যারে রিটার্ন হয় যাতে ক্লাস এবং মডিউল रहते हैं, ক্রম অনুযায়ী:
Users-এর প্রিপেন্ড করা মডিউলগুলোUsersক্লাসUsersক্লাসের ইনক্লুড করা মডিউলগুলোCollectionক্লাসের প্রিপেন্ড করা মডিউলগুলো — Users-এর ডায়রেক্ট প্যারেন্ট হিসেবেCollectionক্লাসCollectionক্লাসের ইনক্লুড করা মডিউলগুলো — কoi নেইObjectক্লাস — যেকোনো ক্লাসের ডিফল্ট ইনহেরিটেন্সKernelমডিউল —Object-এ ইনক্লুড করা থাকে এবং কোর মেথড ধারণ করেBasicObjectক্লাস — রুবিতে রুট ক্লাস
সুতরাং, একটি প্রদত্ত traversed ক্লাস বা মডিউলের আপিয়ারেন্সের ক্রম সর্বদা নিম্নরূপ:
- প্রিপেন্ড করা মডিউলগুলো
- ক্লাস বা মডিউল
- তার ইনক্লুড করা মডিউলগুলো
Ancestor chain প্রধানত রুবি দ্বারা traverse করা হয় যখন কোনো অবজেক্ট বা ক্লাসে একটি মেথড ইনভোক করা হয়।
The Method Lookup Path (মেথড লুকআপ পাথ)
যখন একটি মেসেজ পাঠানো হয়, রুবি মেসেজ রিসিভারের ancestor chain traverse করে এবং চেক করে কোনো একটি উক্ত মেসেজে রেসপন্ড করে কিনা।
যদি ancestor chain-এর যেকোনো ক্লাস বা মোডিউল মেসেজে রেসপন্ড করে, তবে সেই মেসেজের সাথে যুক্ত মেথড এক্সিকিউট হয় এবং ancestor chain traversal বন্ধ হয়।
class Collection < Array end Collection.ancestors # => [Collection, Array, Enumerable, Object, Kernel, BasicObject] collection = Collection.new([:a, :b, :c]) collection.each_with_index # => :aএখানে, collection.each_with_index মেসেজ Enumerable মডিউল দ্বারা রিসিভ করা হয়। তারপর Enumerable#each_with_index মেথড এই মেসেজের জন্য কল করা হয়।
এখানে, যখন collection.each_with_index কল করা হয়, রুবি চেক করে:
- Collection
each_with_index message-এ রেসপন্ড করে কিনা => নहीं - Array
each_with_index message-এ রেসপন্ড করে কিনা => নहीं - Enumerable
each_with_index message-এ রেসপন্ড করে কিনা => হ্যাঁ
সুতরাং, এখান থেকে রুবি ancestor chain traversal বন্ধ করে এবং এই মেসেজের সাথে যুক্ত মেথড কল করে। আমাদের কেসে, Enumerable#each_with_index মেথড।
রুবিতে, এই মেকানিজমকে Method Lookup Path বলা হয়।
এখন, যদি ancestor chain-এর যেকোনো ক্লাস এবং মডিউল প্রদত্ত মেসেজে রেসপন্ড না করে তখন কী হবে?
BasicObject#method_missing
বস করুন ভালো বर्तমানের সাথে! আসুন কিছু ভাঙা মারা করি, ডেভেলপার স্টাইলে: এক্সেপশন থ্রো করে। আমরা একটি Collection ক্লাস ইমপ্লিমেন্ট করব এবং এর একটি ইনস্ট্যান্সে অজানা মেথড কল করব।
class Collection end c = Collection.new c.search('item1') # => NoMethodError: undefined method `search` for #<Collection:0x123456890>এখানে, Collection ক্লাসে search মেথড ইমপ্লিমেন্ট করা নেই। তাই NoMethodError উত্থাপিত হয়। কিন্তু এই এরর রেইজিং আসে কোথা থেকে?
এররটি BasicObject#method_missing মেথডে উত্থাপিত হয়। এই মেথডটি কল হয় যখন Method Lookup Path কোনো মেথড খুঁজে পায় না প্রদত্ত মেসেজের সাথে মিলে।
ঠিক আছে... কিন্তু এই মেথড শুধু NoMethodError রেইজ করে। তাই এটি গরেট হবে যদি আমরা আমাদের Collection ক্লাসের কনটেক্স্টে মেথডটি ওভাররাইড করতে পারি।
BasicObject#method_missing মেথড ওভাররাইড করা
আনুমান করুন? method_missing ওভাররাইড করা সম্পূর্ণ ঠিক কারণ এই মেথডটিও Method Lookup Path মেকানিজমের অধীন। একটি সাধারণ মেথডের সাথে একমাত্র পার্থক্য হলো আমরা নিশ্চিত যে এই মেথডটি Method Lookup Path দ্বারা কমপক্ষে একবার পাওয়া যাবে।
বেশक, BasicObject class — যা রুবিতে যেকোনো ক্লাসের রুট ক্লাস — এই মেথডের একটি মিনিমাল ভার্সন ডিফাইন করে। ক্লাসিক রুবি ম্যাজিক, ন'এস্ট পাস?
তাহলে আসুন আমাদের Collection class-এ এই মেথডটি ওভাররাইড করি:
class Collection def initialize @collection = {} end def method_missing(method_id, *args) if method_id[-1] == '=' key = method_id[0..-2] @collection[key.to_sym] = args.first else @collection[method_id] end end end collection = Collection.new collection.obj1 = 'value1' collection.obj2 = 'value2' collection.obj1 # => 'value1' collection.obj2 # => 'value2'এখানে, Collection#method_missing @collection ইনস্ট্যান্স ভেরিয়েবলের तरफ একটি ডেলিগেটর হিসেবে কাজ করে। আসলে, রুবি কমोবেশ এইভাবে অবজেক্ট ডেলিগেশন হ্যান্ডেল করে — সি.এফ: delegate লাইব্রেরি।
যদি মিসিং মেথড একটি সেটার মেথড হয় (collection.obj1 = 'value1'), তবে মেথডের নাম (:obj1) কি হিসেবে এবং আর্গুমেন্ট ('value1') ভ্যালু হিসেবে @collection হ্যাশ এন্ট্রি ব্যবহার হয় (@collection[:obj1] = 'value1')।
একটি HTML ট্যাগ জেনারেটর
এখন যেহেতু আমরা জানি method_missing মেথড পেছনের দৃশ্যে কিভাবে কাজ করে, আসুন একটি reproducible use case ইমপ্লিমেন্ট করি।
এখানে লক্ষ্য হলো নিচের DSL ডিফাইন করা:
HTML.p 'hello world' # => <p>hello world</p> HTML.div 'hello world' # => <div>hello world</div> HTML.h1 'hello world' # => <h1>hello world</h1> HTML.h2 'hello world' # => <h2>hello world</h2> HTML.span 'hello world' # => <span>hello world</span> HTML.p "hello #{HTML.b 'world'}" # => <p>hello <b>world</b></p>
এটি করার জন্য, আমরা HTML.method_missing মেথড ইমপ্লিমেন্ট করব যাতে প্রতিটি HTML ট্যাগের জন্য আলাদা মেথড ডিফাইন করতে না হয়।
প্রথমে, আমরা একটি HTML মডিউল ডিফাইন করি। তারপর এই মডিউলে method_missing ক্লাস মেথড ডিফাইন করি:
module HTML def HTML.method_missing(method_id, *args, &block) "<#{method_id}>#{args.first}</#{method_id}>" end end
আমাদের মেথডটি শুধু মিসিং method_id ব্যবহার করে একটি HTML ট্যাগ বানাবে — উদাহরণস্বরূপ HTML.div কলের জন্য :div।
নোট করুন যে ক্লাস মেথডগুলিও Method Lookup Path-এর অধীন।
আমরা আমাদের HTML ট্যাগ জেনারেটরকে উন্নত করতে পারি:
- নেস্টেড ট্যাগ হ্যান্ডেল করার জন্য ব্লক আর্গুমেন্ট ব্যবহার করে
- সিঙ্গেল ট্যাগ হ্যান্ডেল করে — উদাহরণস্বরূপ
<br/>
“하지만 কয়েক লাইন কোডে, আমরা অনেক HTML ট্যাগ জেনারেট করতে পারি।
তাহলে, সংক্ষেপে:
method_missing DSL তৈরি করার জন্য একটি ভালো এন্ট্রি পয়েন্ট যেখানে অধিকাংশ কমান্ড একটি সেট চিহ্নিত প্যাটার্ন শেয়ার করে।
উপসংহার
আমরা রুবির ancestor chain-এ সম্পূর্ণ যাত্রা করেছি এবং BasicObject#method_missing-এ ডুবেছি। BasicObject#method_missing Ruby Hook Methods-এর একটি অংশ। এটি অবজেক্টের সাথে ইন্টারঅ্যাক্ট করার জন্য ব্যবহার করা হয় তাদের লাইফসাইকেলের নির্দিষ্ট মুহূর্তে। অন্য যেকোনো Ruby Hook Methods-এর মত, এই হুক মেথডটিকে সাবধানে ব্যবহার করতে হবে। এবং সাবধানে বলতে আমরা মানে এটি কখনো Ruby Object Model-এর আচরণ পরিবর্তন করা উচিত নয় — ছাড়া তখন যখন আপনি এর সাথে খেলছেন বা এ関して ব্লগ পোস্ট লিখছেন ;-)
Voilà!