রুবি অন রেইলস প্যাটার্নস অ্যান্ড অ্যান্টি-প্যাটার্নস সিরিজের তৃতীয় কিস্তিতে স্বাগতম। পূর্বের পোস্টগুলোতে, আমরা প্যাটার্ন ও অ্যান্টি-প্যাটার্নস সাধারণভাবে এবং রেইলস মডেলের সংশ্লিষ্ট আলোচনা করেছি। এই পোস্টে, আমরা রেইলস ভিউর সাথে জড়িত কিছু প্যাটার্ন ও অ্যান্টি-প্যাটার্নের ওপর যাব।
রেইলস ভিউ কখনও কখনও একদম পারফেক্টলি এবং ফাস্ট কাজ করে, আর কখনও কখনও সব ধরনের ইস্যু ধারণ করে। আপনি যদি আপনার ভিউ হ্যান্ডেল করার বিষয়ে কনফিডেন্স বাড়াতে চান বা শুধু এই টপিক সম্পর্কে আরও জানতে চান, তবে এই ব্লগ পোস্ট আপনার জন্য। চলুন সরাসরি ডুব মারি।
আপনি যেন জানেন, রেইলস ফ্রেমওয়ার্ক কনভেনশন ওভার কনফিগারেশন ফলো করে। এবং যেহেতু রেইলস Model-View-Controller (MVC) প্যাটার্নের ওপর বড় ফোকাস করে, এই মোটো স্বাভাবিকভাবেই ভিউ কোডের ওপরও প্রয়োগ হয়। এতে আপনার মার্কআপ (ERB বা Slim ফাইল), JavaScript এবং CSS ফাইলগুলো ইনক্লুডেড। প্রথম নজরে, আপনি মনে করতে পারেন ভিউ লেয়ার খুব স্ট্রেইটফরওয়ার্ড এবং সহজ, কিন্তু মনে রাখবেন আজকাল ভিউ লেয়ারে টেকনোলজির একটি মিক্সচার রয়েছে।
আমরা ভিউতে JavaScript, HTML, এবং CSS ব্যবহার করি। এই তিনটিই কনফিউশন এবং কোডের ডিসঅর্গানাইজেশন দিকে নিয়ে যেতে পারে — যাতে লং রানে কোনো সেন্স মেক করে না এমন ইমপ্লিমেন্টেশন হয়। সৌভাগ্যক্রমে, আজ আমরা রেইলস ভিউ লেয়ারের কিছু কমন সমস্যা এবং সমাধানের মাধ্যমে যাব।
পাওয়ারলিফটিং ভিউ (Powerlifting Views)
এটি এমন একটি ভুল যা সারা সারা হয় না, কিন্তু কখনও হলে, এটি একটি eyesore (চোখের কাঁটা)। কখনও কখনও লোকজন ডোমেইন লজিক বা কোয়েরি সরাসরি ভিউর ভিতরে রাখে। এটা ভিউ লেয়ারকে হেভি-লিফটিং বা পাওয়ারলিফটিং করিয়ে দেয়। আশ্চর্য্য বিষয় হলো রেইলস আসলে এটাকে সহজে হওয়া দেয়। এই বিষয়ে কোনো 'সেফটি নেট' নেই, আপনাকে ভিউ লেয়ারে যা ইচ্ছা করতে দেয়।
정의 অনুযায়ী, MVC প্যাটার্নের ভিউ লেয়ারটি প্রেজেন্টেশন লজিক ধারণ করা উচিত। এটাকে ডোমেইন লজিক বা ডেটা কোয়েরি করার জন্য বিরক্ত করা উচিত নয়। রেইলসে, আপনি ERB ফাইল (Embedded Ruby) পাওয়া যায় যা আপনাকে রুবি কোড লিখতে দেয় যেটা পরে HTML-এ ইভ্যালুয়েট হয়। আপনি যদি একটি ওয়েবসাইট কল্পনা করেন যা ইনডেক্স পেজে গানগুলোর লিস্ট দেখায়, তবে ভিউ লজিক থাকবে app/views/songs/index.html.erb-এ।
"পাওয়ারলিফটিং" কী এবং কী না করা উচিত সেটা বুঝতে, নিচের উদাহরণটি দেখুন:
# app/views/songs/index.html.erb <div class="songs"> <% Song.where(published: true).order(:title) do |song| %> <section id="song_<%= song.id %>"> <span><%= song.title %></span> <span><%= song.description %></span> <a href="<%= song.download_url %>">Download</a> </section> <% end %> </div>
এখানে একটি বিশাল অ্যান্টি-প্যাটার্ন হলো মার্কআপে সরাসরি সঙ্গ ফেচ করা। ডেটা ফেচ করার দায়িত্ব কন্ট্রোলার বা কন্ট্রোলার থেকে কল করা একটি সার্ভিসে ডেলিগেট করা উচিত। আমি কখনও কখনও লোকজনে দেখি কন্ট্রোলারে কিছু ডেটা প্রিপেয়ার করা হয় এবং পরে ভিউতে আরও ডেটা ফেচ করা হয়। এটি খারাপ ডিজাইন এবং এটি আপনার ওয়েবসাইটকে স্লো করে কারণ আপনি আপনার ডাটাবেসকে বেশি বার কোয়েরি করছেন।
আপনি যা করবেন সেটা হলো: কন্ট্রোলার অ্যাকশন থেকে @songs ইনস্ট্যান্স ভেরিয়েবল এক্সপোজ করুন এবং মার্কআপে সেটা কল করুন, যেন:
class SongsController < ApplicationController ... def index @songs = Song.all.where(published: true).order(:title) end ... end
# app/views/songs/index.html.erb <div class="songs"> <% @songs.each do |song| %> <section id="song_<%= song.id %>"> <span><%= song.title %></span> <span><%= song.description %></span> <a href="<%= song.download_url %>">Download</a> </section> <% end %> </div>
এই উদাহরণগুলো পারফেক্ট থেকে দূরে। আপনি যদি আপনার কন্ট্রোলার কোড আরও রিডেবল রাখতে চান এবং SQL Pasta এড়াতে চান, আমি আপনাকে পূর্বের ব্লগ পোস্ট চেক আউট করার অনুরোধ করব। এছাড়া, ভিউ লেয়ারে লজিক না রাখলে অন্য লোকজন সেটার ওপর তাদের সমাধান বানাতে চাইবেন সেটার চান্স বাড়ে।
রেইলস যা দেয় সেটা ব্যবহার করুন (Make Use of What Rails Gives You)
এখানে আমরা সংক্ষিপ্ত রাখব। Ruby on Rails একটি ফ্রেমওয়ার্ক হিসেবে অনেক সুন্দর হেল্পারসহ আসে, বিশেষ করে ভিউতে। এই ছোট ছোট হেল্পারগুলো আপনাকে আপনার ভিউ লেয়ার দ্রুট এবং কোনো কষ্ট ছাড়া বানাতে দেয়। রেইলসের একটি বিগিনার ইউজার হিসেবে, আপনি আপনার ERb ফাইলগুলতে পুরো HTML লিখতে প্রলুব্ধ হয়তে পারেন যেন:
# app/views/songs/new.html.erb <form action="/songs" method="post"> <div class="field"> <label for="song_title">Title</label> <input type="text" name="song[title]" id="song_title"> </div> <div class="field"> <label for="song_description">Description</label> <textarea name="song[description]" id="song_description"></textarea> </div> <div class="field"> <label for="song_download_url">Download URL</label> <textarea name="song[download_url]" id="song_download_url"></textarea> </div> <input type="submit" name="commit" value="Create Song"> </form>
এই HTML দিয়ে, আপনাকে নিচের স্ক্রিনশটের মতো নতুন গানের জন্য একটি সুন্দর ফর্ম मिलবে:
কিন্তু, রেইলসের সাথে, আপনাকে এটা দরকার নয় এবং আপনাকে প্লেইন HTML এরকম লিখা উচিত নয় কারণ রেইলস সেখানে আপনার পাশে আছে। আপনি form_with ভিউ হেল্পার ব্যবহার করতে পারেন যা আপনার জন্য HTML জেনারেট করবে। form_with রেইলস ৫.১-এ ইন্ট্রোডিউস করা হয়েছিল এবং সেটা form_tag এবং form_for রিপ্লেস করার জন্য এসেছে যেগুলো কয়েকজনকে পরিচিত হতে পারে। চলুন দেখি form_with কীভাবে আমাদের অতিরিক্ত কোড লিখতে থেকে মুক্তি দেয়:
<%= form_with(model: song, local: true) do |form| %> <div class="field"> <%= form.label :title %> <%= form.text_field :title %> </div> <div class="field"> <%= form.label :description %> <%= form.text_area :description %> </div> <div class="field"> <%= form.label :download_url do %> Download URL <% end %> <%= form.text_area :download_url %> </div> <%= form.submit %> <% end %>
HTML জেনারেট করার পাশাপাশি, form_with একটি অথেন্টিসিটি টোকেনও জেনারেট করে যা CSRF আট্যাক প্রতিরোধ করে। তাই প্রায় সব কেসে, আপনি ডেজিগনেটেড হেল্পারগুলো ব্যবহার করলে ভালো থাকবেন কারণ সেগুলো রেইলস ফ্রেমওয়ার্কের সাথে ভালো খেলে। আপনি যদি প্লেইন HTML ফর্ম সাবমিট করার চেষ্টা করেন, তবে সেটা ফেল করবে কারণ রিকোয়েস্টের সাথে কোনো বৈধ অথেন্টিসিটি টোকেন সাবমিট হয়নি।
form_with, label, text_area, এবং submit হেল্পারের পাশাপাশি, রেইলসের সাথে আউট-অফ-দ-বক্স আসা আরও অনেক ভিউ হেল্পার রয়েছে। সেগুলো আপনার জীবন সহজ করতে আছে এবং আপনাকে সেগুলো আরও ভালোভাবে জানা উচিত। "অল-স্টার"-এর মধ্যে একটি নিশ্চয়ই link_to:
<%= link_to "Songs", songs_path %>
যেটা নিচের HTML জেনারেট করবে:
<a href="/songs">Songs</a>
আমি প্রতিটি হেল্পারে বিস্তারিত যাবো না, কারণ এই পোস্ট খুব লম্বা হয়ে যাবে এবং সকল হেল্পারের মাধ্যমে যাওয়া আজকের টপিকের অংশ নয়। আমি আপনাকে রেইলস অ্যাকশন ভিউ হেল্পার গাইড পড়তে সাজেস্ট করব এবং আপনার ওয়েবসাইটের জন্য যা দরকার সেটা পিক করুন।
ভিউ কোড রি-ইউজ ও অর্গানাইজ করা (Reusing and Organizing View Code)
আদর্শ ওয়েব অ্যাপ্লিকেশন কল্পনা করুন। আদর্শ ইউজ-কেসে, কোনো if-else স্টেটমেন্ট নেই, শুধু পিউর কোড যা কন্ট্রোলার থেকে ডেটা নেয় এবং HTML ট্যাগের মাঝপথে রাখে। সেরকম অ্যাপ্লিকেশন হয়তো হ্যাকাথন এবং স্বপ্নে আছে, কিন্তু রিয়েল-ওয়ার্ল্ড অ্যাপ্লিকেশনগুলোর ভিউ রেন্ডার করার লজিকে বাঞ্চ ধরণের ব্রাঞ্চ এবং কন্ডিশন রয়েছে।
যখন পেজের অংশগুলো দেখানোর লজিক খুব কমপ্লেক্স হয়ে যায়? সেখান থেকে কোথায় যাবেন? একটি জেনারিক উত্তর হতে পারে: আধুনিক JavaScript লাইব্রেরি বা ফ্রেমওয়ার্কে হাত দিয়ে কিছু কমপ্লেক্স বানানো। কিন্তু, যেহেতু এই পোস্ট রেইলস ভিউর সম্পর্কে, চলুন আমাদের কাছে যে অপশনগুলো আছে সেগুলো দেখি।
আফ্টার-мар্কেট (কাস্টম) হেল্পারস (After-Market (Custom) Helpers)
মান লें আপনি একটি গানের নিচে একটি কল-টু-অ্যাকশন (CTA) বাটন দেখাতে চান। কিন্তু, একটা ক্যাচ আছে — একটি Song-এর ডাউনলোড URL থাকতে পারে বা, যেকোনো কারণে, সেটা মিসিং হতে পারে। আমরা নিচের মত কোড করার প্রলুব্ধ হয়তে পারি:
app/views/songs/show.html.erb ... <div class="song-cta"> <% if @song.download_url %> <%= link_to "Download", download_url %> <% else %> <%= link_to "Subscribe to artists updates", artist_updates_path(@song.artist) %> <% end %> </div> ...
আপনি যদি উপরের উদাহরণটি একটি আইসোলেটেড প্রেজেন্টেশনাল লজিক হিসেবে দেখেন, তবে এটা তত খারাপ লাগে না, ঠিক না? কিন্তু, যদি এইরকম কন্ডিশনাল রেন্ডার আরও বেশি হয়, তবে কোড কম রিডেবল হয়ে যায়। এছাড়া, কোথাও কিছু ঠিকমতো রেন্ডার না হওয়ার চান্স বাড়ে, বিশেষ করে যদি আরও কন্ডিশন থাকে।
এই সমস্যাগুলো মোকাবিলার একটি উপায় হলো: সেগুলো আলাদা একটি হেল্পারে এক্সট্রাক্ট করা। সৌভাগ্যক্রমে, রেইলস আমাদের সহজে কাস্টম হেল্পার লিখতে দেয়। app/helpers-এ আমরা একটি SongsHelper তৈরি করতে পারি, যেন:
module SongsHelper def song_cta_link content_tag(:div, class: 'song-cta') do if @song.download_url link_to "Download", @song.download_url else link_to "Subscribe to artists updates", artist_updates_path(@song.artist) end end end end
আমরা যদি একটি গানের শো পেজ খুলি, তবে আমরা আগের মতো একই রেজাল্ট পাব। কিন্তু, আমরা এই উদাহরণটি একটু बेटার করতে পারি। উপরের উদাহরণে, আমরা একটি ইনস্ট্যান্স ভেরিয়েবল @song ব্যবহার করেছি। এটি উপলব্ধ নাও হতে পারে যদি আমরা এই হেল্পারটি এমন জায়গায় ব্যবহার করি যেখানে @song nil। তাই এক্সটার্নাল ডিপেনডেন্সি হিসেবে ইনস্ট্যান্স ভেরিয়েবল কাটা ফেলতে, আমরা হেল্পারে একটি আর্গুমেন্ট পাস করতে পারি যেন:
module SongsHelper def song_cta_link(song) content_tag(:div, class: 'song-cta') do if song.download_url link_to "Download", song.download_url else link_to "Subscribe to artists updates", artist_updates_path(song.artist) end end end end
তারপর, ভিউতে আমরা হেল্পারকে নিচের মত কল করতে পারি:
app/views/songs/show.html.erb ... <%= song_cta_link(@song) %> ...
সেটা দিয়ে, আমরা ভিউতে আগের মতো একই রেজাল্ট পাব। হেল্পার ব্যবহার করার ভালো দিক: আপনি সেগুলোর জন্য টেস্ট লিখতে পারেন নিশ্চিত করার জন্য ভবিষ্যতে সেগুলো রিগ্রেশন না হয়। একটি কন: সেগুলো গ্লোবালি ডিফাইনড এবং আপনাকে নিশ্চিত করতে হবে হেল্পার নামগুলো আপনার অ্যাপ জুড়ে ইউনিক।
আপনি যদি রেইলস কাস্টম হেল্পার লিখতে বড় ফ্যান ন হন, তবে আপনি সবসময় Draper জেমের View Model প্যাটার্ন অপ্ট-ইন করতে পারেন। অথবা আপনি নিজের View Model প্যাটার্ন রোল করতে পারেন এখানে, সেটা তত কমপ্লেক্স নয়। আপনি যদি আপনার ওয়েব অ্যাপ শুরুর পর্যায়ে আছেন, আমি সাজেস্ট করব ধীরে ধীরে কাস্টম হেল্পার লিখতে শুরু করুন এবং সেটা কষ্ট দিয়ে হলে অন্যান্য সমাধান দিকে মুড়ুন।
আপনার ভিউ DRY করুন (DRY up Your Views)
রেইলস দিয়ে শুরু করার সময় আমাকে যা খুব ভালো লাগেছিল সেটা হলো: আপনার মার্কআপ DRY আপ করা যেন অ'neবিলিভেবল ছিল। রেইলস আপনাকে পার্শিয়াল (partials) তৈরি করার ক্ষমতা দেয় — রিয়ুজেবল কোড পিসেস যেগুলো আপনি কোথাও ইনক্লুড করতে পারেন। উদাহরণস্বরূপ, আপনি যদি একাধিক জায়গায় গান রেন্ডার করেন, এবং একই কোড একাধিক ফাইলে থাকে, তবে একটি সangg পার্শিয়াল তৈরি করা মেনে যাবে।
মান লেণ আপনি নিচের মত আপনার গান দেখাচ্ছেন:
# app/views/songs/show.html.erb <p id="notice"><%= notice %></p> <p> <strong>Title:</strong> <%= @song.title %> </p> <p> <strong>Description:</strong> <%= @song.description %> </p> <%= song_cta_link %> <%= link_to 'Edit', edit_song_path(@song) %> | <%= link_to 'Back', songs_path %>
কিন্তু, আপনি সেম মার্কআপের সাথে অন্য একটি পেজেও দেখাতে চান। তখন আপনি একটি আন্ডারস্কোর প্রিফিক্সসহ একটি নতুন ফাইল তৈরি করতে পারেন যেন app/views/songs/_song.html.erb।
# app/views/songs/_song.html.erb <p> <strong>Title:</strong> <%= @song.title %> </p> <p> <strong>Description:</strong> <%= @song.description %> </p> <%= song_cta_link(@song) %>
এবং তারপর আপনি যেকোনো জায়গায় সangg পার্শিয়াল ইনক্লুড করতে চান, আপনি শুধু নিচের কাজ করবেন:
... <%= render "song" %> ...
রেইলস অটো-লুকআপ করবে _song পার্শিয়াল এক্সিস্ট করে কিনা এবং সেটা রেন্ডার করবে। কাস্টম হেল্পারের উদাহরণের সিমিলার, এটি 베সট যদি আমরা আমাদের পার্শিয়াল থেকে ইনস্ট্যান্স ভেরিয়েবল @song রিড অফ করি।
# app/views/songs/_song.html.erb <p> <strong>Title:</strong> <%= song.title %> </p> <p> <strong>Description:</strong> <%= song.description %> </p> <%= song_cta_link(song) %>
তারপর, আমাদের পার্শিয়ালে song ভেরিয়েবল পাস করতে হবে, যা এটাকে আরও রিয়ুজেবল এবং অন্যান্য জায়গায় ইনক্লুড হওয়ার জন্য উপযুক্ত করে তোলে।
... <%= render "song", song: @song %> ...
চূড়ান্ত চিন্তা (Final Thoughts)
এই পোস্টের জন্য এতটুকুই সব। সারসংক্ষেপ করলে, আমরা রেইলস ভিউ রেল্মে আপনার যেসব প্যাটার্ন ও অ্যান্টি-প্যাটার্নের সম্মুখীন হতে পারেন সেগুলোর মধ্য থেকে কয়েকটি আলোচনা করলাম। নিচে কয়েকটি টেকঅওয়ে:
- UI-তে কমপ্লেক্স লজিক এড়িয়ান (ভিউকে অনেক পাওয়ারলিফটিং করাবেন না)
- রেইলস আপনাকে আউট-অফ-দ-বক্স ভিউ হেল্পারসহ কী দেয় সেটা শিখুন।
- কাস্টম হেল্পার এবং পার্শিয়াল দিয়ে আপনার কোড স্ট্রাকচার এবং রিয়ুজ করুন
- ইনস্ট্যান্স ভেরিয়েবলের ওপর অতিরিক্ত নির্ভরতা করবেন না।
পরবর্তী পোস্টে, আমরা রেইলস কন্ট্রোলার প্যাটার্ন ও অ্যান্টি-প্যাটার্ন কভার করব যেখানে জিনিসগুলো বেশ গদগদ হয়ে যেতে পারে। সেটার জন্য 기다্রুন।
পরবর্তী বার পর্যন্ত, চিয়ার্স!
P.S. Ruby Magic পোস্টগুলো প্রেস থেকে বের होतेই পেতে চাইলে, আমাদের Ruby Magic নিউজলেটার সাবস্ক্রাইব করুন এবং কখনো একক পোস্ট মিস করবেন না!