রিয়্যাক্ট (React) অনুপ্রাণিত, ViewComponents হলো রুবি অবজেক্ট যা ভিউ রেন্ডার করার জন্য মার্কআপ বিল্ড করতে ব্যবহৃত হয়। ViewComponent হলো রেলসে রিয়িউজেবল, টেস্টেবল এবং এনক্যাপসুলেটেড ভিউ কম্পোনেন্ট বিল্ড করার একটি ফ্রেমওয়ার্ক। সাধারণত, রেলসে রিয়িউজেবল ভিউ পার্শিয়ালস (partials) দিয়ে তৈরি করা হয় এবং আশানুসারে বিভিন্ন ভিউতে রেন্ডার করা হয়, কিন্তু ViewComponent জেম আসার পর পার্শিয়ালসকে ভিউ কম্পোনেন্ট দিয়ে রিপ্লেস করা যায়, কারণ এগুলো আরও সুবিধা দেয়। আসুন দেখি সেই সুবিধাগুলো কী।
কখন এবং কেন ViewComponents ব্যবহার করব?
উল্লেখ্য রূপে, ভিউ কম্পোনেন্টগুলো রিয়িউজেবল এবং টেস্টেবল। তাই যখন একটি ভিউ রিয়িউজ করতে হয় বা সরাসরি টেস্ট করা থেকে সুবিধা পাওয়া যায়, তখন এগুলো প্রয়োগ করা যায়। এর ডকুমেন্টেশনে উল্লিখিত ভিউ কম্পোনেন্টের কিছু সুবিধা নিচে দেওয়া হলো:
- এগুলো পার্শিয়াল থেকে প্রায় ১০ গুণ দ্রুত।
- এগুলো রুবি অবজেক্ট; তাই তাদের initialize মেথড স্পষ্টভাবে ডিফাইন করে যে ভিউ রেন্ডার করতে কী কী দরকার। এর মানে সেগুলো বোঝা ও রিয়িউজ করা সহজ। 더불어 রুবি কোডের কোয়ালিটি স্ট্যান্ডার্ড এনফোর্স করা যায়, যা বাগের রিস্ক কমায়।
- এগুলো ইউনিট টেস্ট করা যায়, тогда রেলসের ট্র্যাডিশনাল ভিউর জন্য ইন্টিগ্রেশন টেস্ট লাগে যা রাউটিং, কন্ট্রোলার এবং ভিউ লেয়ার সব এক্সারসাইজ করে।
ViewComponent ইমপ্লিমেন্টেশন
ViewComponents হলো ViewComponent::Base এর সাবক্লাস এবং app/components ফোল্ডারে থাকে। তাদের নাম -Component দিয়ে শেষ হয়, এবং সেগুলো সেগুলো যা রেন্ডার করে তার নাম রাখা উচিত, না যে সেগুলো কী এক্সেপ্ট করে। আমরা জেম অ্যাড করে bundle install রান করার পর ম্যানুয়ালি বা কম্পোনেন্ট জেনারেটর দিয়ে ভিউ কম্পোনেন্ট জেনারেট করতে পারি।
gem "view_component", require: "view_component/engine" জেনারেটর ব্যবহার করতে নিচের কমান্ড রান করুন:
rails generate component <Component name> <arguments needed for initialization> উদাহরণস্বরূপ, যদি আমরা একটি লার্নিং প্ল্যাটফর্মে উপলব্ধ ইন-ক্লাস কোর্সের লিস্ট শো করানোর জন্য দায়বদ্ধ একটি কম্পোনেন্ট জেনারেট করতে চাই, তবে এই কমান্ডটি ব্যবহার করতে পারি:
rails g component Course course আমরা course আর্গুমেন্ট পাস করছি কারণ আমরা এই রুবি অবজেক্টটি সেটি কোর্স দিয়ে ইনিশিয়ালাইজ করব যা আমরা এক্সপেক্ট করছি ডিসপ্লে করবে, এবং আমরা এর নাম Course রাখলাম কারণ এটি একটি কোর্স রেন্ডার করে। কতটা কয়িনসিডেন্স!

উপরে দেখা যাচ্ছে, কম্পোনেন্ট এবং এর করেসপন্ডিং ভিউ app/components ফোল্ডারে তৈরি হয়েছে, একসাথে একটি টেস্ট ফাইলও।
ViewComponent-এ erb, haml, এবং slim টেমপ্লেট ইঞ্জিনের জন্য টেমপ্লেট জেনারেটর রয়েছে কিন্তু ডিফল্টলি config.generators.template_engine-এ স্পেসিফাইড টেমপ্লেট ইঞ্জিন ব্যবহার করে। যাইহোক, আপনি আপনার পছন্দের টেমপ্লেট ইঞ্জিন ইন্ডিকেট করতে পারেন নিচেরভাবে:
rails generate component Course course --template-engine <your template engine> চলুন আমাদের Course মডেল এবং কিছু কোর্স তৈরি করি ডিসপ্লে করার জন্য।
rails g model Course title:string price:decimal location:string rails db:migrate কনসোলে দ্রুত দুটি নতুন কোর্স তৈরি করি:
Course.create(title: 'The Art of Learning', price: 125.00, location: 'Denmark') Course.create(title: 'Organizing your Time', price: 55.00, location: 'London') course_component.rb ফাইলটি ইনিশিয়ালাইজ মেথডসহ জেনারেট হয়েছে, নিচের মতঃ

আমাদের একটি কোর্স কন্ট্রোলার তৈরি করতে হবে যা কোর্সের লিস্টে রুট করবে।
rails g controller Courses index আমাদের routes.rb ফাইলে, আমরা রুট রুট ইন্ডিকেট করব নিচের লাইন যোগ করে:
root 'courses#index' এখন আমরা সব সেট, পরবর্তী ধাপ হলো ভিউ তৈরি করা। এটি ইতিমধ্যে জেনারেটেড course_component.html.erb-এ করা হবে।
<div> <h2><%= @course.title %></h2> <h4><%= number_to_currency(@course.price, :unit => "€") %></h4> <h4><%= @course.location %></h4> </div> আমাদের ভিউতে, আমরা কোর্সের টাইটেল, প্রাইস এবং লোকেশন ডিসপ্লে করছি @course ভেরিয়েবল ব্যবহার করে, যা ইতিমধ্যে আমাদের CourseComponent-র ইনিশিয়ালাইজ মেথডে ডিফাইন করা হয়েছিল। এটি সার্ভার কন্ট্রোলারের মত যেখানে আপনি একটি কন্ট্রোলার মেথডে ভেরিয়েবল তৈরি করলে সেটি ভিউতে অ্যাক্সেসIBLE হয়।
কন্ট্রোলার কীভাবে কাজ করে সেটি জানলে, আমরা কন্ট্রোলারের index.html.erb-এ রাউট হবে। তাই সেখানে আমরা আমাদের কম্পোনেন্ট রেন্ডার করব।
#app/views/courses/index.html.erb <%= render(CourseComponent.new(course: Course.find(1))) %> উপরে দেখা যাচ্ছে, আমরা CourseComponent-এর একটি নতুন ইনস্ট্যান্স রেন্ডার করছি সেটিকে সেটি কোর্স দিয়ে ইনিশিয়ালাইজ করে যা আমরা রেন্ডার করাতে চাই। এই কোর্সটি @course ভেরিয়েবল বনে যায় যা course_component.html.erb ফাইলে অ্যাক্সেসIBLE।
এছাড়া এই কম্পোনেন্টটি সরাসরি কন্ট্রোলার থেকে রেন্ডার করা সম্ভব, যাতে index ফাইল ভিউ বাইপাস হয়:
class CoursesController < ApplicationController def index render(CourseComponent.new(course: Course.find(1))) end end আপনি যেকোনো মেথড বেছে নিন, সার্ভারে নিচের আউটপুট দেখাবে:

অতিরিক্ত কনটেন্টও কম্পোনেন্টে পাস করা যায় নিচের উপায়গুলোতে:
<%= render(CourseComponent.new(course: Course.find(1))) do %> container <% end %> <%= render(CourseComponent.new(course: Course.find(1)).with_content("container")) %> আমাদের ভিউ কম্পোনেন্ট ফাইলে, আমরা কনটেন্টটি যেখানে চাই সেখানে ইনক্লুড করতে পারি। এই কেসে, আমরা এটিকে একটি ক্লাস হিসেবে ইনক্লুড করব আমাদের div-কে এমনভাবে এডিট করে:
<div class=<%= content %>> উপরের সব মেথড দিয়ে আডিশনাল কনটেন্ট রেন্ডার করলে নিচের ইমেজটি আউটপুট হয়:

কলেকশন রেন্ডার করা
যদি আমরা সকল কোর্সের লিস্ট রেন্ডার করতে চাই? ViewComponent এর with_collection ট্যাগ ব্যবহার করে এটি খুব সহজে করা যায়। কম্পোনেন্ট ইনিশিয়ালাইজ করার বদলে .new, এটা .with_collection দিয়ে ইনিশিয়ালাইজ করা হয়, এবং কলেকশনটি ভেরিয়েবল হিসেবে পাস করা হয়, নিচের মতঃ
CourseComponent.with_collection(Course.all) এটি নিচের আউটপুট দেয়:

with_collection_parameter ট্যাগও আছে যদি আমরা কলেকশনকে অন্য নাম দিয়ে অ্যাড্রেস করতে চাই।
class CourseComponent < ViewComponent::Base with_collection_parameter :item def initialize(item:) @item = item end end উপরের কেসে, কোর্স প্যারামিটারকে item হিসেবে অ্যাড্রেস করা হয়েছে। তাই করেসপন্ডিং ভিউতে, @course কে @item দিয়ে রিপ্লেস করলে একই রেজাল্ট আসবে।
কলেকশনে আডিশনাল প্যারামিটারও যোগ করা যায়। এই প্যারামিটারগুলো কলেকশনের প্রতিটি আইটেমের জন্য ডিসপ্লে হবে। চলুন প্রতিটি আইটেমে Buy Me টেক্স্ট যোগ করি এই মেথড দিয়ে।
#app/views/courses/index.html.erb <%= render(CourseComponent.with_collection(Course.all, notice: "Buy Me")) %> # app/components/course_component.rb class CourseComponent < ViewComponent::Base with_collection_parameter :item def initialize(item:, notice:) @item = item @notice = notice end end আমরা app/components/course_component.html.erb ফাইলে একটি নতুন প্যারাগ্রাফ যোগ করব নতুন নোটিশ ভেরিয়েবলের টেক্স্ট ইন্ডিকেট করার জন্য।
<p><a href='#'> <%= @notice %> </a></p> এটি নিচের আউটপুট দেয়:

শেষে, কলেকশন-এর অধীনে একটি কাউন্টার ভেরিয়েবল রয়েছে যা ইনেবল করলে ভিউতে আইটেমগুলো নম্বরিং করে। এটি ইনেবল করা হয় _counter কলেকশন প্যারামিটারের সাথে যোগ করে এবং ইনিশিয়ালাইজ মেথড দিয়ে ভিউতে উপলব্ধ করিয়ে।
#app/components/course_component.rb def initialize(item:, notice:, item_counter:) @item = item @notice = notice @counter = item_counter end আমাদের ভিউতে, আইটেম টাইটেলের পাশে আমাদের কাউন্টার যোগ করি:
<h2><%= @counter %>. <%= @item.title %></h2> কনসোল থেকে একটি থার্ড কোর্স জেনারেট করি কাউন্টার ফিনোমেনন ভালোভাবে বুঝতে।
Course.create(title: 'Understanding Databases', price: '100', location: 'Amsterdam') এটি নিচের আউটপুট দেয়

কন্ডিশনাল রেন্ডারিং
ViewComponent-এ একটি render? হুক রয়েছে, যা ব্যবহার করলে নির্ধারণ হয় ভিউ রেন্ডার হবে কিনা। এটি ইমপ্লিমেন্ট করতে, আমরা ১০০ ইউরোর সমান বা অধিক প্রাইস वाले কোর্সের জন্য ১০% ডিসকাউন্ট দিব। এর জন্য একটি কম্পোনেন্ট তৈরি করি।
rails generate component Discount item 
এই কম্পোনেন্টটি অটোমেটিক্যালি সেই আইটেম দিয়ে ইনিশিয়ালাইজ হয়েছে যার জন্য ডিসকাউন্ট দেখাতে হবে, নিচের মতঃ

সুতরাং, discount_component.html.erb ফাইলে আমরা যে টেক্স্ট ডিসপ্লে করব সেটি যোগ করি।
<p class="green"> A 10% discount is available on this course </p> আপনার CSS ফাইলে green ক্লাস যোগ করুন এবং আপনার পছন্দের সবুজ রঙ অ্যাসাইন করুন। 더불어, আমাদের discount_component.rb ফাইলে render? মেথড যোগ করি যা নির্ধারণ করবে এই কম্পোনেন্ট কখন রেন্ডার হবে।
def render? @item.price >= 100 end এখন, আমরা ডিসকাউন্ট কম্পোনেন্টটি রেন্ডার করব সেটি ভিউতে যা প্রতিটি কোর্স রেন্ডার করে।
# app/components/course_component.html.erb <%= render(DiscountComponent.new(item: @item)) %> এটি নিচের আউটপুট দেয়:

এটাও অসাধারণ, তো?
হেল্পার্স
ট্র্যাডিশনাল রেলস ভিউতে, আমরা সহজে হেল্পার প্লাগ ইন করতে পারি মেথড নাম কল করে ভিউতে, কিন্তু ভিউ কম্পোনেন্টে এটি ভিন্নভাবে কাজ করে। ভিউ কম্পোনেন্টে, হেল্পার মেথডগুলো সরাসরি ভিউতে কল করা যায় না কিন্তু কম্পোনেন্টে ইনক্লুড করা যায়। আমাদের ইতিমধ্যে একটি courses_helper.rb ফাইল রয়েছে যা CoursesController তৈরি হলে অটোমেটিক্যালি জেনারেট হয়েছিল, তাই সেটি অ্যাডভান্টেজ নেই। প্রথমে, একটি হেল্পার মেথড তৈরি করি যা বলবে কতজন লোক একটি কোর্সে এনরোল করেছে। ভ্যালুটি প্রাইসের এক-চতুর্থাংশ রেখে দিই :).
module CoursesHelper def count_enrollees(course) count = (course.price / 4).round() tag.p "#{count} enrollees so far" end end পরবর্তী, আমরা একটি কম্পোনেন্ট তৈরি করব যেখানে হেল্পারটি কল করব। এটি হলো কম্পোনেন্ট যা আমাদের ভিউতে রেন্ডার হবে। এতে, আমরা একটি include স্টেটমেন্ট যোগ করব হেল্পার ইনক্লুড করার জন্য, এবং তারপর এই কম্পোনেন্টের ভিতরে হেল্পারের যেকোনো মেথড কল করতে পারব।
# app/components/enrollee_component.rb class EnrolleeComponent < ViewComponent::Base include CoursesHelper def total_enrollees(course) count_enrollees(course) end end শেষ ধাপ হলো EnrolleeComponent কে আমাদের কোর্স ডিসপ্লে করার ভিউতে যোগ করা।
# app/components/course_component.html.erb <%= EnrolleeComponent.new.total_enrollees(@item) %> মনে রাখবেন EnrolleeComponent-র জন্য render শব্দ ব্যবহার করিনি, কারণ এর ভিউ নেই, এবং এর আউটপুট হবে হেল্পার মেথড কল করার আউটপুট। এটি নিচের আউটপুট দেয়:

হেল্পার্স ব্যবহার করা যায় আইকন, গ্রাভিটার, বা যেকোনো কিছুতে। ViewComponent হেল্পারের ব্যবহার বদলায় না; শুধু আমরা কম্পোনেন্টে সেগুলো কীভাবে কল করি সেটি বদলায়।
before_render মেথড
ViewComponent-এ একটি before_render মেথড রয়েছে যা কম্পোনেন্ট রেন্ডার হওয়ার আগে কল করা যায়। চলুন আমাদের ডিসকাউন্ট নোটিসের পাশে একটি স্টার যোগ করি। আমরা একটি হেল্পার মেথড যোগ করি যা স্টার ফেচ করবে; একটি star.png ইমেজ ডাউনলোড করে app/assets/images ফোল্ডারে রাখা হয়েছে।
#app/helpers/courses_helper.rb def star_icon image_tag("/assets/star.png", width: "1%") end আমাদের Discount কম্পোনেন্টে একটি before_render মেথড যোগ করি যা এই হেল্পার মেথড কল করবে।
# app/components/discount_component.rb def before_render @star_icon = helpers.star_icon end উপরে দেখা যাচ্ছে, হেল্পার কল করার আরেকটি পদ্ধতি পরিচয় করানো হয়েছে। হেল্পারগুলো helpers.method দিয়ে কম্পোনেন্ট.rb ফাইলে কল করা যায়। তারপর, DiscountComponent-র রেন্ডার মেথডে আমাদের স্টার আইকন ইনপুট করি, যা এখন @star_icon ভেরিয়েবল দিয়ে উপলব্ধ।
# app/components/discount_component.html.erb <p class="green"> <%= @star_icon %> A 10% discount is available on this course </p> এটি নিচের আউটপুট দেয়:

আমাদের অবশ্যই হেল্পার ব্যবহার করতে হবে before_render মেথড কাজ করার জন্য নয়। আমি হেল্পার ব্যবহার করলাম আরেকটি আপ்ரোচ ইঞ্জেক্ট করার জন্য।
প্রিভিউজ
Action Mailer-র মত, ViewComponent কম্পোনেন্ট প্রিভিউ করার সুবিধা দেয়। এটি প্রথমে config/application.rb ফাইলে ইনেবল করতে হয়।
config.view_component.preview_paths << "#{Rails.root}/lib/component_previews" প্রিভিউ কম্পোনেন্টগুলো test/components/previews-এ থাকে এবং সেগুলো ব্যবহার করা যায় একটি কম্পোনেন্টকে বেশ কিছু বিভিন্ন ইনপুট দিয়ে প্রিভিউ করার জন্য। আমরা আমাদের DiscountComponent প্রিভিউ করব।
# test/components/previews/discount_component_preview.rb class DiscountComponentPreview < ViewComponent::Preview def with_first_course render(DiscountComponent.new(item: Course.find(1))) end def with_second_course render(DiscountComponent.new(item: Course.find(2))) end end দুটি মেথড যোগ করা হয়েছে DiscountComponent কে বিভিন্ন সিনেরিওতে প্রিভিউ করার জন্য। এটি দেখতে ভিজিট করুন https://localhost:3000/rails/view_components, যেখানে আমরা সকল প্রিভিউ কম্পোনেন্ট এবং তাদের মেথড পাব। যেকোনো একটি ক্লিক করলে সেটি কীভাবে দেখাবে সেটি দেখা যাবে, নিচের মতঃ

উপরের ভিডিওতে দেখতে পাচ্ছেন, প্রথম কোর্স ডিসকাউন্ট কম্পোনেন্ট রেন্ডার করছে, কিন্তু দ্বিতীয় কোর্সের জন্য কিছুই রেন্ডার হচ্ছে না। আপনি জানেন কেন এটি হয়েছে? render? মেথড চেক হয়েছে। প্রথম সিনেরিও হলো কস্ট প্রাইস ১০০ ইউরোর বেশি (১ম কোর্স), কিন্তু দ্বিতীয় কোর্সের জন্য কস্ট ১০০ ইউরোর কম। মেথড নামগুলো খুব ডেসক্রিপ্টিভ নয় ছিল যাতে আপনি হাইলাইট করার আগে কারণ বুঝতে পারেন :).
প্রিভিউ যেকোনো এনভায়রনমেন্টে show_previews অপশন দিয়ে ইনেবল/ডিজেবল করা যায়, কিন্তু ডেভেলপমেন্ট এবং টেস্টিং এনভায়রনমেন্টে ডিফল্টলি ইনেবল থাকে।
# config/environments/test.rb config.view_component.show_previews = false JS AND CSS ইনক্লুশন
JavaScript এবং CSS কম্পোনেন্টের পাশাপাশি ইনক্লুড করা সম্ভব, যাকে কখনও কখনও "সাইডকার" অ্যাসেট বা ফাইল বলা হয়। এটি এখনও এক্সপেরিমেন্টাল ফিচার। তাই আমরা এর ইনার ওয়ার্কিংস এই আর্টিকেলে ডিসকাস করব না, তবে ViewComponent এর এই ফিচার সম্পর্কে আরও জানতে এখানে যেতে পারেন।
টেমপ্লেটস
একটি ভিউ কম্পোনেন্টের টেমপ্লেট অনেকভাবে ডিফাইন করা যায়। সহজ অপশন হলো ভিউ এবং কম্পোনেন্ট একই ফোল্ডারে রাখা, নিচের মতঃ

এখানে দেখা যাচ্ছে, প্রতিটি কম্পোনেন্ট এবং ভিউ app/components ফোল্ডারে আছে।
আরেকটি অপশন হলো ভিউ এবং অন্যান্য অ্যাসেট কম্পোনেন্টের মতো একই নামে একটি সাবডিরেক্টরিতে রাখা। তাই, app/components ফোল্ডারে আছে component.rb ফাইল যা কম্পোনেন্ট হাউজ করে, এবং একদম আলাদা একটি course_component ফোল্ডার যা হাউজ করে ভিউ course_component.html.erb এবং course_component-র সাথে সম্পর্কিত সকল অ্যাসেট।

কমান্ড লাইন থেকে এইভাবে কম্পোনেন্ট ফাইল জেনারেট করতে --sidecar ফ্ল্যাগ দরকার:
rails g component Example name --sidecar এটি আপনাকে আপনার CSS এবং JS ফাইল কম্পোনেন্ট ফোল্ডারে অ্যাড করতে দেয়। ViewComponents টেমপ্লেট ফাইল ছাড়াও রেন্ডার করতে পারে call মেথড ডিফাইন করে। এর উদাহরণ পরবর্তী সেকশনে দেওয়া আছে, যেখানে আমরা স্লটস নিয়ে আলোচনা করব।
স্লটস
মাল্টিপল ব্লক অফ কনটেন্ট একটি একক ViewComponent-এ পাস করা যায় স্লটস দিয়ে। রেলস মডেলের has_one এবং has_many অ্যাট্রিবিউটের মত, স্লটস ডিফাইন করা হয় renders_one এবং renders_many দিয়ে:
- renders_one ডিফাইন করে একটি স্লট যা কম্পোনেন্টে সর্বোচ্চ একবার রেন্ডার হবে: renders_one :header।
- renders_many ডিফাইন করে একটি স্লট যা কম্পোনেন্টে একাধিকবার রেন্ডার হয়তে পারে: renders_many :titles।
কল্পনা করুন আমরা একটি পেজ চাই যা একটি হেডার রেন্ডার করবে (শুধু একবার) এবং সকল উপলব্ধ কোর্সের টাইটেল রেন্ডার করবে (বহুবার); এটি অর্জন করা যায় স্লটস দিয়ে। চলুন একটি ListComponent তৈরি করি, যাতে একটি হেডার থাকবে যা একবার রেন্ডার হবে, এবং একটি TitleComponent যেটি অনেক টাইটেল রেন্ডার করবে।
#app/components/list_component.rb class ListComponent < ViewComponent::Base renders_one :header, "HeaderComponent" # `HeaderComponent` এই কম্পোনেন্টের ভিতরে ডিফাইন করা, তাই স্ট্রিং দিয়ে রেফার করলাম। renders_many :titles, TitleComponent # `titleComponent` অন্য ফাইলে ডিফাইন হবে, তাই ক্লাস নাম দিয়ে রেফার করলাম। class HeaderComponent < ViewComponent::Base def call content_tag :h1, content end end end উপরের কম্পোনেন্টে, আমরা বললাম হেডার একবার রেন্ডার হবে, কিন্তু টাইটেলগুলো বহুবার, কারণ এই পেজে অনেক টাইটেল থাকবে। আমরা HeaderComponent কে ListComponent-র ভিতরেও ডিফাইন করলাম। হ্যাঁ, ViewComponent-এ এটি সম্ভব; একটি ক্লাস অন্য ক্লাসের ভিতরে ডিফাইন করা যায়। এছাড়া Templates সেকশনে আলোচিত call মেথডটি কীভাবে HeaderComponent-এ ব্যবহার করা হয়েছে h1 ট্যাগ রেন্ডার করার জন্য, সেটি লক্ষ্য করুন, যাতে করেসপন্ডিং ভিউ (html.erb ফাইল) এর দরকার নেই। ListComponent-র করেসপন্ডিং HTML ফাইলে নিচের কনটেন্ট থাকবে;
#app/components/list_component.html.erb <div> <%= header %> <!-- রেন্ডার করে হেডার কম্পোনেন্ট --> <% titles.each do |title| %> <div> <%= title %> <!-- রেন্ডার করে একটি ইন্ডিভিজুয়াল কোর্স টাইটেল --> </div> <% end %> </div> HTML ফাইলে, আমরা হেডার ইনক্লুড করলাম, পাস করা সকল টাইটেলের মাধ্যমে ইটারেট করলাম এবং সেগুলো রেন্ডার করলাম। আপনি দেখতে পাচ্ছেন, ভিউ ফাইলে রেন্ডার করার কম্পোনেন্টের নাম স্পেসিফাই করতে হয়নি; আমাদের স্লটসই সেটি হ্যান্ডেল করেছে। তাই আমরা শুধু সেগুলোকে header এবং title হিসেবে আইডেন্টিফাই করলাম।
পরবর্তী ধাপ হলো আমাদের TitleComponent এবং এর করেসপন্ডিং HTML ফাইল তৈরি করা, কারণ এটি হলো যা প্রতিটি টাইটেল পাস করার জন্য রেন্ডার হবে।
# app/components/title_component.rb class TitleComponent < ViewComponent::Base def initialize(title:) @title = title end end # app/components/title_component.html.erb <div> <h3> <%= @title %> </h3> </div> শেষে, আমাদের index.html ফাইলে, চলুন সাময়িকভাবে যা আছে সেটি মুছে দিয়ে ListComponent রেন্ডার দিয়ে রিপ্লেস করি।
#app/views/courses/index.html.erb <%= render ListComponent.new do |c| %> <% c.header do %> <%= link_to "List of Available Courses", root_path %> <% end %> <%= c.title(title: "First title") %> <%= c.title(title: "Second title!") %> <% end %> 
এখন, আমাদের কোর্সগুলো এই ভিউতে কলেকশন হিসেবে পাস করি। একটি কলেকশন স্লটে পাস করতে, কলেকশনটি ইনিশিয়ালাইজেশনের জন্য দরকার ভেরিয়েবল কন্টেইন করার অবজেক্টের অ্যারে হিসেবে পাস করতে হয়। আমরা দেখতে পাচ্ছি, পাস করা প্রতিটি কোর্সকে টাইটেল আর্গুমেন্ট দিয়ে ইনিশিয়ালাইজ করতে হবে। আমরা আমাদের ডিবিয়ের সকল কোর্সের টাইটেল এক্সট্রাক্ট করে হ্যাশের অ্যারে বানিয়ে রেন্ডার করতে পারি।

আমরা এখন আমাদের টাইটেলের লিস্টকে একটি একক c.titles দিয়ে রিপ্লেস করতে পারি কলেকশনের জন্য এবং আমাদের হ্যাশ অফ টাইটেল পাস করতে পারি course_titles ভেরিয়েবল দিয়ে ডিফাইন করে।
# app/views/courses/index.html.erb <% course_titles = Course.all.pluck(:title).map { |title| {title: title}} %> <% c.titles(course_titles) %> এটি নিচের আউটপুট দেয়:

এটাই স্লটস কীভাবে কাজ করে: একটি একক ViewComponent-এ অনেক কম্পোনেন্ট রেন্ডার করা। স্লটস আরও অনেকভাবে রেন্ডার করা যায়, আরও ইনফো এর জন্য এখানে যান।
টেস্টিং
ভিউ কম্পোনেন্ট টেস্ট করা হয় require "view_component/test_case" টেস্ট ফাইলে এবং render_inline টেস্ট হেল্পার ব্যবহার করে যাতে রেন্ডারড আউটপুটের বিরুদ্ধে অ্যাসারশন করা যায়। আমাদের DiscountComponent টেস্ট করি।
require "test_helper" require "view_component/test_case" class DiscountComponentTest < ViewComponent::TestCase def test_render_component render_inline(DiscountComponent.new(item: "my item")) end end যখন আমরা এই টেস্ট রান করি কমান্ড rails test test/components/discount_component_test.rb দিয়ে, নিচের এরর পাই:

এটি প্রুফ করে যে টেস্টটি সঠিক কম্পোনেন্টে পৌঁছেছে কিন্তু উপযুক্ত প্রপ ল্যাক করছে, কারণ আইটেম অবশ্যই একটি কোর্স হতে হবে প্রাইস প্রপার্টিসহ, স্ট্রিং নয় যা আমরা পাস করলাম। এটিও বলে যে একটি render? মেথড চেক হচ্ছে এই কম্পোনেন্ট রেন্ডার হওয়ার আগে। এখন সঠিক ভেরিয়েবল পাস করি।
def test_render_component course = Course.create(title: 'Organizing your Time', price: 55.00, location: 'London') render_inline(DiscountComponent.new(item: course)) end এটি সাকসেসফুলি রান হয়েছে। এখন এটিতে অ্যাসারশন যোগ করি।
def test_render_component course = Course.create(title: 'Organizing your Time', price: 155.00, location: 'London') render_inline(DiscountComponent.new(item: course)) assert_selector 'p[class="green"]' assert_text "10% discount" end এই টেস্টও পাস করে। মনে রাখবেন এই কম্পোনেন্টের জন্য একটি রেন্ডার কন্ডিশন রয়েছে। চিন্তা করবেন না, কারণ ViewComponent একটি কম্পোনেন্ট রেন্ডার হয়নি সেটি টেস্ট করার উপায়ও দিয়েছে, refute_component_rendered ব্যবহার করে। আমরা এটি টেস্ট করতে পারি ১০০ ইউরোর নিচে প্রাইস वाले একটি কোর্স দিয়ে।
def test_component_not_rendered course = Course.create(title: 'Organizing your Time', price: 55.00, location: 'London') render_inline(DiscountComponent.new(item: course)) refute_component_rendered end এই টেস্টও পাস করে।
আমাদের CourseComponent-র জন্য আরেকটি টেস্ট লিখি যাতে সেটি নেস্টেড সকল কম্পোনেন্ট রেন্ডার করছে কিনা চেক করে।
require "test_helper" require "view_component/test_case" class CourseComponentTest < ViewComponent::TestCase def test_component_renders_all_children course = Course.create(title: 'Organizing your Time', price: 155.00, location: 'London') render_inline(CourseComponent.new(item: course, notice: 'A new test', item_counter: 1)) assert_selector("h2", text: "Organizing your Time") assert_selector("h4", text: "€155.00") assert_selector("h4", text: "London") assert_text("enrollees") assert_text("discount") end end এই টেস্টও পাস করে। এটি টেস্ট করে যে Enrollee এবং Discount কম্পোনেন্টগুলোও সঠিকভাবে রেন্ডার হচ্ছে।
মনে রাখবেন আমাদের একটি স্লট কম্পোনেন্ট রয়েছে, এবং নিচের ইমেজে দেখানো হয়েছে সেটি এক হেডার এবং অনেক টাইটেল রেন্ডার করে।

এটি টেস্ট করতে, আমরা কম্পোনেন্টকে একটি ব্লক অফ কোড পাস করব যাতে হেডার এবং টাইটেলগুলো থাকে যা সেটি রেন্ডার করা উচিত, এবং তারপর রেন্ডারড কম্পোনেন্টের বিরুদ্ধে অ্যাসার্ট করতে পারব।
require "test_helper" require "view_component/test_case" class ListComponentTest < ViewComponent::TestCase def test_renders_slots_with_content render_inline(ListComponent.new) do |component| component.header { "A Test List" } component.titles [{title: 'Test title 1'}, {title: 'Test title 2'}] end assert_selector("h1", text: "A Test List") assert_text("Test title 1") assert_text("Test title 2") end end এই টেস্টও পাস করে :).
Rspec টেস্টিং
উপর টেস্টিং নিয়ে যা যা বলা হয়েছে তার পাশাপাশি, যদি পছন্দের টেস্টিং ফ্রেমওয়ার্ক RSpec হয়, তবে ViewComponents-র জন্য RSpec ইনেবল করতে কিছু অডিশনাল কনফিগারেশন করতে হয়।
# spec/rails_helper.rb require "view_component/test_helpers" require "capybara/rspec" RSpec.configure do |config| config.include ViewComponent::TestHelpers, type: :component config.include Capybara::RSpecMatchers, type: :component end আমাদের DiscountComponent টেস্ট RSpec দিয়ে রি-রাইট এবং রি-টেস্ট করা যাক, নিচের মতঃ
require "rails_helper" RSpec.describe DiscountComponent, type: :component do it "renders the component correctly" do course = Course.create(title: 'Organizing your Time', price: 155.00, location: 'London') render_inline(DiscountComponent.new(item: course)) expect(rendered_component).to have_css "p[class='green']", text: "10% discount" expect(rendered_component).to have_css "img[src*='/assets/star.png']" end end এই টেস্টো এলিগ্যান্টলি পাস করে। হেল যাহ, আমরা আমাদের স্টার আইকন দেখতে পাচ্ছি।
উপসংহার
আপনার রেলস অ্যাপের জন্য অনেক ভিউ কম্পোনেন্ট লিখা না কেবল আপনার কোডকে আরও রিডেবল এবং অপছন্দ কমপ্লিকেশন থেকে বাগের রিস্ক কমায়, বরং আপনার ভিউগুলোকে আইসোলেশন में টেস্ট করার সুযোগও দেয়, শুধু HTTP রিকোয়েস্টের সময় নয়। View components সহজে একটি এক্সিস্টিং রেলস অ্যাপে ইনকুলকেট করা যায়, এবং সেরা হলো সবচেয়ে বেশি রিয়িউজ হওয়া ভিউগুলো থেকে শুরু করা। এতক্ষণে যা শিখলাম সেটি দিয়ে এটি একটি সহজ কাজ হওয়া উচিত। তবুও, আপনি যদি ViewComponent সম্পর্কে আরও ইনফো চান, তবে এর ডকুমেন্টেশন বা RubyDoc ইনফো দেখতে সংকোচ করবেন না।