কম্পিউটার টিউটোরিয়াল

রিঅ্যাক্ট অন রেইলস: সিমলেস ফুল-স্ট্যাক অ্যাপ গড়ে তোলা

পুরো ফ্রন্ট-এন্ড সাইড তাদের অ্যাপ্লিকেশনের অনেক কোম্পানি একই ফ্রেমওয়ার্ক, যেমন রেইলস, ব্যবহার করে ব্যাকএন্ড বানাতে। অনেক বছর ধরে, এটা সেরা এবং সবচেয়ে বিশ্বস্ত অপশন ছিল।

আজ, ধারাবাহিক বিকশিত ফ্রন্ট-এন্ড 유নিভার্সে (universe) টন টন লাইব্রেরি এবং ফ্রেমওয়ার্ক আছে, যেগুলো ডেভেলপারদের দেয় ব্যাকএন্ড এবং ফ্রন্টএন্ড দুটোর জন্য আলাদা প্ল্যাটফর্ম বেছে নেওয়ার এবং সেগুলো সহজে ইন্টিগ্রেট করার সুযোগ।

React ফ্রন্ট-এন্ড প্যাঙ্গিয়া (Pangea)-র টাইটান (titan) হয়ে উঠেছে। আপনি যদি রুবি অন রেইলসের সাথে কাজ করেন, সম্ভবত আপনি একবার ডিফল্ট রেইলস পেজগুলো React কোডে (বা অন্য কোনো ফ্রন্ট ফ্রেমওয়ার্কে) স্যুইচ করার প্রয়োজন পड़े। হয়তো, আপনি শুধু রেইলস + React ফিচারগুলো পছন্দ করেন এবং দুটি টেকনোলজির শক্তি একই অ্যাপে ইন্টিগ্রেট করতে চান।

এটাও ঠিক! এই আর্টিকেল একটি রিয়েল-ওয়ার্ল্ড উদাহরণ এক্সপ্লোর করতে লক্ষ্য করে: একটি CRUD অ্যাপ্লিকেশন যা বিয়ার প্রোডাক্টের ইনভেন্টরি ম্যানেজ করে। এটা প্রাথমিকভাবে রেইলস দিয়ে তৈরি হবে, এবং তারপর React দিয়ে বানানো একটি নতুন ফ্রন্টএন্ড আরও ইন্টিগ্রেট করা হবে।

রিঅ্যাক্ট অন রেইলস: সিমলেস ফুল-স্ট্যাক অ্যাপ গড়ে তোলা বিয়ার CRUD অ্যাপ্লিকেশন।

কয়েকটি ধাপে, আপনি React-এর মূল টুকরোগুলো, কীভাবে রেইলস এটা গ্রহণ করে, এবং কীভাবে আপনি আপনার ভবিষ্যতের প্রজেক্টগুলোর মধ্যে রেইলস এবং React উভয়কে ইন্টিগ্রেট করতে শুরু করতে পারেন সেটা বুঝতে পারবেন।

সেটআপ (Setup)

প্রেরিকুইসাইট (prerequisites) হিসেবে, আপনার একটি এনভায়রনমেন্ট সেটআপ থাকতে হবে রুবি (এবং রেইলস), Node.js, এবং Yarn-সহ।

আপনি.npm কে পছন্দ করতে পারেন ফ্রন্ট প্যাকেজ ম্যানেজ করার জন্য, কিন্তু আমরা সহজতর জন্য Yarn-এ থাকব।

আপনার পছন্দের ফোল্ডারে, নিচের কমান্ড রান করুন:

rails new crud-rails-react 

এটা আমাদের রেইলস প্রজেক্ট শুরু করবে। তারপর, তৈরি প্রজেক্টটি আপনার IDE-এ খুলুন এবং সরাসরি Gemfile-এ যান।

আমাদের SQLite জেমের জন্য এই কমান্ড যা বাগ জেনারেট করে সেটা ফিক্স করতে হবে। নিশ্চিত করুন sqlite3 জেম লোকেট করে সেটা নিচের মতো পরিবর্তন করুন:

gem 'sqlite3', '~> 1.3.10' 

এটা ডেটাবেস ভার্সনিং রিলেটেড কিছু পরিচিত এরর প্রিভেন্ট করবে যেহেতু আমরা CRUD উদাহরণের জন্য SQLite ডিফল্ট ডেটাবেস হিসেবে ব্যবহার করব।

চিন্তা করবেন না; অন্য ডেটাবেসগুলোর ক্ষেত্রে এই সমস্যা হবে না।

ডেটাবেস কনফিগ (Database Config)

আমি সাধারণত বেস থেকে টপ লেয়ার পর্যন্ত বিল্ড করতে পছন্দ করি, তাই চলুন ডেটাবেস মডেল তৈরি করি।

আমাদের শুধু একটি লাগবে, তাই রেইলস স্কাফোল্ডিং (scaffolding) এর চেয়ে ভালো কমান্ড ফিচার নেই এই টাস্কের জন্য:

rails g scaffold Beer brand:string style:string country:string quantity:integer & rake db:migrate 

এই মডেল খুব বেসিক, তাই আপনি যেতে পারেন অন্যান্য অ্যাট্রিবিউট এবং টাইপ যোগ করুন যেভাবেই চান।

db/migrate ফোল্ডারের ভিতরে, একটি নতুন ফাইল আছে যার নাম "_create_beers.rb" দিয়ে শেষ হয়। এটা রেইলস যে রেকর্ড তৈরি করলো আমাদের জন্য একটি বিয়ার রিপ্রেজেন্ট করতে।

মডেলটি, অপর দিক থেকে, app/models ফোল্ডারের আন্ডারে জেনারেট হবে। সেগুলো যেভাবেই আছে থাকুন এবং নিচের কোড লাইনগুলো db/seeds.rb ফাইলে যোগ করুন:

Beer.create(brand: 'Double Stout', style: 'Stout', country: 'England', quantity: 54) Beer.create(brand: 'Spaten', style: 'Helles', country: 'Germany', quantity: 3) Beer.create(brand: 'Newcastle', style: 'Brown ale', country: 'UK', quantity: 12) 

এই ফাইলটি ডেটাবেসে ইনিশিয়াল ডেটা লোড স্টোর করবে যখন অ্যাপ স্টার্ট হবে। সেগুলো ডেটাবেসে রিলিজ করতে নিচের কমান্ড রান করুন:

rake db:seed 

বাস! এখন আপনার টেবিলে কিছু বিয়ার আছে।

Webpacker সেটআপ

Webpacker জাভাস্ক্রিপ্ট অ্যাপ্লিকেশনের জন্য সবচেয়ে ব্যাপকভাবে ব্যবহৃত স্ট্যাটিক মডিউল বান্ডলার (static module bundler) এক। সেজন্য, এটি React ক্যাপাবিলিটি একটি এক্সিস্টিং অ্যাপ্লিকেশনে ইনকর্পোরেট করার জন্য পারফেক্ট ম্যাচ।

রেইলস আমাদের একটি Webpacker বান্ডলারও দেয় যেটা ফুলি রেইলস এনভায়রনমেন্টে জাভাস্ক্রিপ্ট-লাইক অ্যাপ্লিকেশন ম্যানেজ করার জন্য অ্যাডাপ্টেড।

ইনস্টল করতে, আপনার Gemfile-এ দ্বিতীয় লাইন যোগ করুন নিচের মতো:

gem 'webpacker', '~> 4.3.x' 

বাড়িয়ে! এটাই একমাত্র জেম যা পুরো অ্যাপ ডেভেলপমেন্টের জন্য যোগ করতে হবে। এটা শুধু সম্ভব হয়েছে কারণ আমরা ফ্রন্ট রেসপন্সিবিলিটি Yarn-এ ডেলিগেট করছি, যেটা পরে এই আর্টিকেলে সেট হবে।

এখন, আপডেট ইনস্টল করতে নিচের কমান্ডগুলো ইস্যু করুন:

bundle install bundle exec rake webpacker:install bundle exec rake webpacker:install:react 

প্রথমটি অধিকাংশ রেইলস ডেভেলপারের কাছে পরিচিত। আমরা শুধু সব ডিপেন্ডেন্সি ইনস্টল করছি, Webpacker সহ।

একবার Webpacker ইনস্টল হলে, আমরা এর কমান্ডগুলো Rake দিয়ে এমুলেট করতে পারি Webpacker ডিপেন্ডেন্সি এবং React ডিপেন্ডেন্সি ইনস্টল করতে।

এটা একটি খুব গুরুত্বপূর্ণ ধাপ কারণ এখানেই Webpacker নিশ্চিত করে আপনার সব জাভাস্ক্রিপ্ট ডিপেন্ডেন্সি রেইলস এনভায়রনমেন্টে সঠিকভাবে সেট হয়েছে, তাই এটা স্কিপ করবেন না বা ডায়রেক্টলি npm বা Yarn-এর মতো রান করার চেষ্টা করবেন না, ঠিক আছে?

কমান্ডগুলো কমপ্লিট হলে, কয়েকটি ফোল্ডার এবং ফাইল (যেমন node_modules এবং package.json)ও তৈরি হবে।

ফ্রন্ট-এন্ড সেটআপ (Front-end Setup)

আমরা 이제ত কোন সেটিংস করলাম সেটা CRUD অ্যাপ্লিকেশন রেইলস একদমেই কাজ করার জন্য পর্যাপ্ত। আপনি rails s কমান্ড দিয়ে রেইলস সার্ভার শুরু করলে, ফলাফল এটাই হবে:

রিঅ্যাক্ট অন রেইলস: সিমলেস ফুল-স্ট্যাক অ্যাপ গড়ে তোলা রেইলস অটো-জেনারেটেড বিয়ার CRUD।

তবে, আমরা React দিয়ে আমাদের নিজস্ব CRUD চাই।

প্রথমে, আপনাকে নিশ্চিত করতে হবে সব রিকোয়ার্ড ফ্রন্ট-এন্ড ডিপেন্ডেন্সি Yarn-এ কনফিগারড আছে:

  • React
  • React Router নেভিগেশন ডील করার জন্য React অ্যাপে
  • Ant Design রেডি-টু-ইউজ React কম্পোনেন্টের জন্য

Ant Design (একএןডি - antd) এন্টারপ্রাইজ-লেভেল অ্যাপ্লিকেশনের জন্য একটি সমৃদ্ধ ওপেন-সোর্স লাইব্রেরি। এটা wysoko কাস্টোমাইজেবল React কম্পোনেন্টের বাঙালা দেয় যা ওয়েব অ্যাপ ডেভেলপমেন্ট অনেক সহজ করে।

সব কিছু ইনস্টল করতে নিচের কমান্ড রান করুন:

yarn add antd react-router-dom 

আমাদের react লাইব্রেরি এক্সপ্লিসিটলি যোগ করতে হবে না কারণ react-router-dom সেটা করবে।

এই পয়েন্টে, আপনি package.json ফাইল খুললে, এটা অটো-জেনারেটেড কন্টেন্ট হবে:

{ "dependencies": { "@babel/preset-react": "^7.12.1", "@rails/webpacker": "4.3.0", "antd": "^4.7.2", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", "prop-types": "^15.7.2", "react": "^17.0.0", "react-dom": "^17.0.0", "react-router-dom": "^5.2.0" }, "devDependencies": { "webpack-dev-server": "^3.11.0" } } 

এটা রেইলস-React কম্বোর জন্য সবচেয়ে বেসিক সেটআপ। তাই, আমরা কোডে যেতে পারি।

রেইলস সাইড অ্যাডাপ্ট করা (Adapting the Rails Side)

ফ্রন্টএন্ডে যাওয়ার আগে রেইলস সাইডে কিছু গুরুত্বপূর্ণ অ্যাকশন অ্যাড্রেস করতে হবে।

প্রথমে, আমাদের সেই কন্ট্রোলার ডিফাইন করতে হবে যেটা পেজ রিডাইরেক্ট সেন্ট্রালাইজ করবে। যেহেতু আমরা একটি সিঙ্গেল-পেজ অ্যাপ্লিকেশন (SPA) তৈরি করছি, শুধু একটি কন্ট্রোলার লাগবে, যেটা ইতিমধ্যে তৈরি হয়েছে: BeersController।

এটা app/controllers ফোল্ডারের আন্ডারে খুলুন এবং এর কন্টেন্ট নিচের মতো পরিবর্তন করুন:

class BeersController < ApplicationController def index end end 

চিন্তা করবেন না; আমরা যা কোড রিমুভ করলাম সেটা পরবর্তী কন্ট্রোলারে রাখব।

এই কন্ট্রোলার একমাত্র ফাংশন হলো রেইলস থেকে React-এ একটি ডায়রেক্ট রাউট প্রদান করা, তাই আমরা শুধু index মেথড সেট করছি।

সেটার সাথে ডায়রেক্ট কানেক্ট করার জন্য, routes.rb ফাইল খুলুন config ফোল্ডারের আন্ডারে এবং এর কন্টেন্ট নিচের মতো পরিবর্তন করুন:

Rails.application.routes.draw do root 'beers#index' # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html end 

নতুন root কনফিগ লক্ষ্য করুন। হ্যাঁ, আমরা রুট এন্ডপয়েন্টকে beers ইনডেক্স মেথডে ম্যাপ করছি।

আপনাকে app/views/beers ফোল্ডারের আন্ডারে index.html.erb ফাইলটি খালি করবেন যেহেতু আমরা রেইলস ওয়েব কন্টেন্ট রেন্ডার করব না। এটা একটি ট্রিক যা আমরা ব্যবহার করতে পারি রেইলসকে ফোর্স (force) করার জন্য React কোড একদমেই এক্সহিবিট করার জন্য।

Beer API

এখন, চলুন Beer API তৈরি করি। API স্ট্রাকচার প্র্যাকটিক্যালি BeersController-এর মতোই হবে কিন্তু কিছু স্লাইট চেঞ্জসহ। তৈরি করতে নিচের কমান্ড রান করুন:

rails generate controller api/v1/Beers 

নিশ্চিত করুন এটা ভার্সনিং সিস্টেম (versioning system) ব্যবহার করে তৈরি করছেন। এটা আপনাকে ভবিষ্যতে আপনার API ডেভেলপ করতে দেবে এবং সেটা সাধারণ কন্ট্রোলার থেকে আলাদা রাখবে।

এখন, app/controllers/api/v1/beers_controller.rb ফাইল খুলুন এবং কোড নিচের মতো রিপ্লেস করুন:

class Api::V1::BeersController < ApplicationController before_action :set_beer, only: [:show, :edit, :update, :destroy] # GET /beers # GET /beers.json def index @beers = Beer.all.order(brand: :asc) render json: @beers end # GET /beers/1 # GET /beers/1.json def show if @beer render json: @beer else render json: @beer.errors end end # GET /beers/new def new @beer = Beer.new end # GET /beers/1/edit def edit end # POST /beers # POST /beers.json def create @beer = Beer.new(beer_params) if @beer.save render json: @beer else render json: @beer.errors end end # PATCH/PUT /beers/1 # PATCH/PUT /beers/1.json def update end # DELETE /beers/1 # DELETE /beers/1.json def destroy @beer.destroy render json: { notice: 'Beer was successfully removed.' } end private # Use callbacks to share common setup or constraints between actions. def set_beer @beer = Beer.find(params[:id]) end # Only allow a list of trusted parameters through. def beer_params params.permit(:brand, :style, :country, :quantity) end end 

অপারেশনগুলোর অধিকাংশ পূর্বের কন্ট্রোলার থেকে রিসাইকেল করা হয়েছে।

before_action স্নিপেটটি প্রপার বিয়ার অবজেক্ট রিকভার করার দায়িত্ব নেবে id প্যারামিটার অনুযায়ী রিকোয়েস্টগুলোর মধ্যে। শুধু যেসব অপারেশন :only ক্লজের পরে অ্যারেতে রাখা হয়েছে সেগুলোই এই অটো-রিকভার ফিচার 니য়াজ করবে।

বাকি মেথডগুলো CRUD-এর প্রতিটি অপারেশনের সমতুল্য। শুধু মনে রাখুন সবসময় JSON রিটার্ন করবেন আপনার রিকোয়েস্টের রেসপন্স হিসেবে কারণ এটাই वो ফরম্যাট যেটা আমরা আমাদের React কম্পোনেন্টগুলোর মধ্যে ব্যবহার করব।

শেষে, আপনাকে config/routes.rb আবার অ্যাডাপ্ট করতে হবে নতুন তৈরি রুটগুলো ইনক্লুড করার জন্য। তাই, নিশ্চিত করুন ফাইল কন্টেন্ট নিচের মতো পরিবর্তন করুন:

Rails.application.routes.draw do namespace :api do namespace :v1 do get 'beers/index' post 'beers/create' delete 'beers/:id', to: 'beers#destroy' end end root 'beers#index' # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html end 

Destroy রুট দেখাচ্ছে কীভাবে destroy মেথডে ম্যাপ করবেন যদি সেটা পাথে এক্সপ্লিসিটলি সেট না থাকে।

React কম্পোনেন্ট (React Components)

React কম্পোনেন্টের মাধ্যমে কাজ করে, যেগুলো ওয়েব অ্যাপ্লিকেশনের বিল্ডিং ব্লক (building blocks) মতো কাজ করে। প্রতিটি এক বা একাধিক টাস্ক করে যেটা একটি কনটেক্সট হিসেবে মেনে হয়।

সংক্ষেপে, একটি কম্পোনেন্ট তৈরি হয় একটি জাভাস্ক্রিপ্ট ক্লাস বা ফাংশন যা প্রোপার্টি (properties) প্যারামিটার হিসেবে পায়, তার ভিতরে বিজনেস লজিক প্রসেস করে, এবং শেষে একটি ফাংশনাল UI কম্পোনেন্ট রিটার্ন করে যেটা স্ক্রিনের একটি অংশ রিপ্রেজেন্ট করে।

নিচের কোড স্নিপেটটি পরবর্তী উদাহরণগুলো থেকে নেওয়া:

<Layout> <Header /> <Content>...</Content> <Footer>Honeybadger ©2020.</Footer> </Layout> 

হ্যাঁ, React কম্পোনেন্ট একটি কাস্টম মার্কআপ ল্যাঙ্গুয়েজ ব্যবহার করে যাকে JSX (JavaScript XML) বলা হয় যেটা HTML-এর সাথে খুব মিলে যাওয়া। সকল HTML ট্যাগ JSX ফাইলগুলোতেও উপলব্ধ। JSX-র ওপর আরও পড়তে পারেন এখানে।

উপরের উদাহরণ ইলাস্ট্রেট করে কিভাবে antd একটি স্ট্রাকচারাল লেআউট কম্পোনেন্ট দিয়ে ডিল করে। কম্পোনেন্টরা অন্য কম্পোনেন্ট দিয়ে বানানো হয় এবং একে অপরর ওপর স্ট্যাক করা হয়, একটি পুরো কম্পোজ করিয়ে। কিছু প্রোপার্টি রিসিভ করে (অপশনাল বা নন-অপশনাল), এবং কিছু ইনার কন্টেন্ট থাকতে পারে।

কম্পোনেন্টরা ক্লাস-বেসড বা ফাংশনাল-বেসড হতে পারে।

Class Components

Class-based কম্পোনেন্ট সাধারণ জাভাস্ক্রিপ্ট ক্লাস হিসেবে তৈরি হয়, নিচের মত:

class Beers extends React.Component {} 

সেগুলো React.Component ক্লাস থেকে ইনহেরিট করে, একটি লাইফসাইকেল (lifecycle) থাকে, এবং ইনিশিয়ালাইজেশন, রেন্ডারিং, এবং ডিস্ট্রয়িং ফেজের আগে কোড এক্সিকিউট করার ইউটিলিটি মেথড প্রদান করে।

তবে, সবচেয়ে গুরুত্বপূর্ণ (এবং রিকোয়ার্ড) মেথড হলো render(), যেটা প্রতিবার কল হয় যখন কম্পোনেন্ট আপডেট হয়।

Functional Components

Functional কম্পোনেন্ট ES6-এর অ্যারো ফাংশন (arrow function) ব্যবহার করে এবং React কম্পোনেন্ট সিনট্যাক্স এবং কমপ্লেক্সিটি সিম্প্লিফাই করে।

উপরের একই Beer কম্পোনেন্ট ফাংশন হিসেবে এরকম রিপ্রেজেন্ট করা যাবে:

const Beers = () => <div>My Beers</div>; 

এটা অনেক সিম্পলার, ঠিক নয়?

React সেটআপ করা (Setting Up React)

আমরা ইতিমধ্যে রেইলস ইনডেক্স পেজ খালি করেছি। এখন, আমরা রেইলসকে জানাব যে এটা React কে ডিফল্ট ফ্রন্ট-এন্ড বানাতে হবে।

এটা অর্জন করতে, আপনাকে নিচের কোড লাইন আপনার app/views/layouts/application.html.erb ফাইলের <head> ট্যাগে যোগ করতে হবে:

<%= javascript_pack_tag 'index' %> 

এটা আমাদের অ্যাপ্লিকেশন হেডারে জাভাস্ক্রিপ্ট প্যাক যোগ করবে, যেটা সকল জাভাস্ক্রিপ্ট ফাইল, React বস্তুবিধ অন্যান্য, index পেজের ভিতরে এক্সিকিউট করাবে।

আমাদের নিশ্চিত করতে হবে index.jsx ফাইলটির একই নাম আছে যেহেতু সেটা ইম্পোর্ট প্যাকে পয়েন্ট করে।

এই উদ্দেশ্যে, চলুন অটো-জেনারেটেড app/javascript/packs/hello_react.jsx ফাইলকে index.jsx হিসেবে রিনেম করি।

তারপর, কোড নিচের মতো রিপ্লেস করুন:

import React from "react"; import { render } from "react-dom"; import App from "../components/App"; document.addEventListener("DOMContentLoaded", () => { render(<App />, document.body.appendChild(document.createElement("div"))); }); 

এই ফাইলটিকে React অ্যাপ্লিকেশন ফাইল বলে ভুলবেন না, কারণ এটা শুধু ফাইলটি যেটা পুরো React অ্যাপ হায়েরার্কি DOM-এ লোড করবে ReactDOM-এর render ফাংশন দিয়ে।

সাধারণত, প্রতিটি React অ্যাপ্লিকেশন একটি index.js ফাইল থেকে শুরু হয় যেটা সব কিছু লোড করে যেগুলো দরকার, React নিজসহ।

App ট্যাগ আমাদের হায়েরার্কির টপ কম্পোনেন্টে ম্যাপ করে। তাই, চলুন এটা তৈরি করি index.jsx হিসেবে javascript/components ফোল্ডারের আন্ডারে (ফোল্ডারগুলো ম্যানুয়ালি তৈরি করুন যদি এখনও একজিস্ট না করে) এবং নিচের কোড রাখুন:

import React from "react"; import Routes from "../routes/index"; import "antd/dist/antd.css"; export default () => <>{Routes}</>; 

বিকল্পভাবে, আপনি antd CSS ফাইল index.jsx-এ ইম্পোর্ট করতে পারেন। যেকোনো অ্যাপ্রোচ কাজ করবে।

রাউটের লিস্ট routes ফোল্ডারের আন্ডারে থাকে। সেগুলো React Router লাইব্রেরি থেকে এক্সট্র্যাক্ট করা যেটা আমাদের অধিকাংশ কঠিন কাজ করে। এর কন্টেন্ট এটা:

import React from "react"; import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import Home from "../components/Home"; export default ( <Router> <Switch> <Route path="/" exact component={Home} /> </Switch> </Router> ); 

আপনার প্রতিটি রাউট অবশ্যই আলাদা Route ট্যাগে ম্যাপ করা উচিত। path প্যারামিটার প্রতিটি রাউট এন্ডপয়েন্টের URI-র সাথে ম‍্যাচ করতে হবে, যখন component প্যারামিটার ইন্ডিকেট করে কম্পোনেন্ট যেটা React Router রিকোয়েস্ট রিডাইরেক্ট করবে।

মনে রাখুন আমাদের SPA-র জন্য একটি সিঙ্গেল রাউট থাকবে। আপনি অন্য পাথও এড করতে পারেন এখানে যদি আপনি /beers ম্যাপ করতে চান বিয়ার লিস্টিংয়ের জন্য, উদাহরণস্বরূপ, لكن আমরা সিম্পল রাখব।

এছাড়া, লক্ষ্য করুন আমরা Home কম্পোনেন্ট ইম্পোর্ট করছি এখানে, যেটা এখনও একজিস্ট করে না। তাই, চলুন এটা তৈরি করি Home.jsx হিসেবে components ফোল্ডারের আন্ডারে। তারপর, নিচের কোড এড করুন:

import { Layout } from "antd"; import React from "react"; import Beers from "./Beers"; import Header from "./Header"; const { Content, Footer } = Layout; export default () => ( <Layout className="layout"> <Header /> <Content style={{ padding: "0 50px" }}> <div className="site-layout-content" style={{ margin: "100px auto" }}> <h1>Beer Catalog</h1> <Beers /> </div> </Content> <Footer style={{ textAlign: "center" }}>Honeybadger ©2020.</Footer> </Layout> ); 

React কম্পোনেন্টের কথা হলে, আমি টপ থেকে বটম বিল্ড করতে পছন্দ করি। এইভাবে, আপনি অ্যাপকে পুরো হিসেবে কাজ করার জন্য যেসব কম্পোনেন্ট দরকার সেগুলোর একটি ওভারঅল লুক পেতে পারেন।

Home কম্পোনেন্ট একটি অ্যাসেম্বলার (assembler)처럼 behave করে; এটা অ্যাপের সব অন্য কম্পোনেন্ট পিস অকমোডেট করে, যেমন Layout, Header, Content, এবং Footer।

এই পিসগুলো কোথা থেকে আসছে সেটা সঠিকভাবে ডিস্টিংগুইশ (distinguish) করা গুরুত্বপূর্ণ। Antd একগুচ্ছ রেডি কম্পোনেন্ট প্রদান করে, যেমন Layout, Content, এবং Footer, আপনার স্ক্রিন কম্পোজ করতে।

সেগুলো মূলত পেজ পার্টসের স্ট্রাকচারে ফোকাসড, কিন্তু কিছু বিল্ট-ইন CSS স্টাইলও অফার করে, যেটা আমাদের बेटার লুক দেবে।

The Header Component

Header.jsx কম্পোনেন্ট ফাইলটি, যেটাও javascript/components ফোল্ডারের আন্ডারে তৈরি করা উচিত, হেডারের কন্টেন্ট রাখবে। এতে একটি সিম্পল antd মেনু এবং Honeybadger লোগোর সাথে একটি div আছে, নিচের মত দেখাচ্ছে:

রিঅ্যাক্ট অন রেইলস: সিমলেস ফুল-স্ট্যাক অ্যাপ গড়ে তোলা Antd মেনু আইটেম।

নিচে, আপনি Header.jsx-এ রাখার কোড পাবেন:

import React from "react"; import { Layout, Menu } from "antd"; const { Header } = Layout; export default () => ( <Header> <div className="logo" /> <Menu theme="dark" mode="horizontal" defaultSelectedKeys={["1"]}> <Menu.Item key="1">Home</Menu.Item> <Menu.Item key="2">Our Services</Menu.Item> <Menu.Item key="3">Contact</Menu.Item> </Menu> </Header> ); 

Antd Menu কম্পোনেন্ট ব্যবহার করা খুব সহজ কিন্তু কাস্টোমাইজেশন অপশন ব্যাপক, যা আমাদের দেয় নেভিগেশন ড্রावर (navigation drawers), ড্রপ-ডাউন (drop-downs), গ্রুপস, এবং সাবগ্রুপস তৈরি করতে।

লক্ষ্য করুন আমরা defaultSelectedKeys প্রদান করছি, একটি অ্যারে যেটা মেনুকে বলে কোন আইটেমগুলো এক্টিভ।

আমাদের মেনুগুলো কোথাও নেভিগেট করবে না; সেগুলো স্ক্রিন অকুপাই করবে লুক-অ্যান্ড-ফিল (look-and-feel) ফুলফিল করার জন্য। তাই, চলুন Beers কম্পোনেন্টে যাই।

The Beers Component

এই কম্পোনেন্টটি বিয়ার লিস্টিং-এ ফোকাসড, এবং টেবিলের ভিতরে যেসব অ্যাকশন উপলব্ধ, যেমন ডিলিশন (deletion), ডেটা পেজিনেশন (pagination), এবং টেবিল রিলোডিং (reloading)।

রিঅ্যাক্ট অন রেইলস: সিমলেস ফুল-স্ট্যাক অ্যাপ গড়ে তোলা React অ্যাকশন ও কম্পোনেন্টের ভিজ্যুয়াল রিপ্রেজেন্টেশন।

উপরের ছবিটি দেখুন। আমরা কম্পোনেন্ট এবং অ্যাকশনগুলো নিচের লেভেলে ব্রেক ডাউন করলাম, যাতে আপনি ভালোভাবে বুঝতে পারেন এখানে কী হবে।

React State

React কম্পোনেন্ট একটি state অবজেক্ট দিয়ে বিল্ড হয়। এই অবজেক্টটি একটি স্টোর (store) হিসেবে কাজ করে সরাসরি প্রদত্ত কম্পোনেন্টের সাথে আটैচড। প্রতিটি কম্পোনেন্টের নিজস্ব state অবজেক্ট থাকে, এবং আপনি প্রতিবার স্টেট চেঞ্জ করলে, কম্পোনেন্ট রি-রেন্ডার হয়।

আমাদের Beers কম্পোনেন্টের প্রথম অ্যাকশন হলো টেবিলে লিস্টিং ডিসপ্লে করা। এই উদ্দেশ্যে, আমাদের এই লিস্ট একটি অ্যারেতে ধরে রাখতে হবে:

state = { beers: [], }; 

The Beers Listing

এই অ্যারে ফিড করতে, আমাদের API কন্ট্রোলার থেকে লিস্ট রিট্রিভ করতে হবে যা আমরা আগে তৈরি করলাম। ফাংশনটি রিভিউ করুন যেটা এটা রিট্রিভ করবে:

loadBeers = () => { const url = "api/v1/beers/index"; fetch(url) .then((data) => { if (data.ok) { return data.json(); } throw new Error("Network error."); }) .then((data) => { data.forEach((beer) => { const newEl = { key: beer.id, id: beer.id, brand: beer.brand, style: beer.style, country: beer.country, quantity: beer.quantity, }; this.setState((prevState) => ({ beers: [...prevState.beers, newEl], })); }); }) .catch((err) => message.error("Error: " + err)); }; 

সিম্প্লিসিটির জন্য, আমরা Fetch API ব্যবহার করব যেটা সব মডার্ন ব্রাউজারে উপলব্ধ প্রতিবার আমাদের API থেকে ডেটা রিকোয়েস্ট করতে হবে।

উপরের ফাংশন কিছু ধাপ নেয় বিয়ারের অ্যারে API থেকে রিট্রিভ করতে:

  1. প্রথমে এটা /index এন্ডপয়েন্ট আসিনক্রোনাসলি রিকোয়েস্ট করে এবং then চেক করে রেসপন্স স্ট্যাটাস OK সমান কিনা।
  2. যদি হয়, আমরা ডেটা JSON হিসেবে রিটার্ন করি; অন্যথায়, একটা Error থ্রো করি।
  3. Then, আমরা রেজাল্টের অ্যারে ওভার ইটারেট করি আমাদের নিজস্ব বিয়ার অবজেক্ট কম্পোজ করতে এবং স্টেটের beers অ্যারেতে যোগ করতে।
  4. যদি প্রসেসে কিছু ভুল হয়, catch ব্লক এক্সসেপশন ক্যাপচার করবে এবং মেসেজ আলার্ট হিসেবে এক্সহিবিট করবে।

সুন্দর, ঠিক নয়? প্রায় একই ধাপগুলো আমরা সব অন্য রিকোয়েস্টের জন্যও নেব।

তবে, antd ডেটা টেবিলে কীভাবে ডিসপ্লে করে? ভালো প্রশ্ন! নিচের কোডটি দেখুন:

columns = [ { title: "Brand", dataIndex: "brand", key: "brand", }, ...{ title: "", key: "action", render: (_text, record) => ( <Popconfirm title="Are you sure to delete this beer?" onConfirm={() => this.deleteBeer(record.id)} okText="Yes" cancelText="No"> <a href="#" type="danger"> Delete{" "} </a> </Popconfirm> ), }, ]; 

আমরা একটু সিম্প্লিফাই করলাম ভালো বুঝতে। এটা একটি অ্যারে যেটা আমাদের টেবিলের স্কেলেটন (skeleton) রিপ্রেজেন্ট করে। এটাই antd টেবিলের কাজ করার পদ্ধতি; সেগুলো আপনার টেবিল স্ট্রাকচার (রো এবং কলাম) সম্পর্কে মেটাডেটা ইনফরমেশন অ্যারে হিসেবে রিসিভ করতে চায়।

প্রতিটি কলাম অ্যারেতে একটি অবজেক্ট, এবং অর্ডার এখানে গুরুত্বপূর্ণ। title অ্যাট্রিবিউট কলামের নাম রিসিভ করে, যখন dataIndex নাম হলো React কম্পোনেন্টগুলোর মধ্যে সেটা কী নামে পরিচিত হবে, এবং key এর ইউনিক আইডেন্টিফায়ার।

আমারা অধিকাংশ কলামের জন্য, কনফিগারেশন সিমিলার, এক্সসেপ্ট অ্যাকশন কলাম। সেখানে, আমাদের অ্যাকশন লিংক স্পেসিফাই করতে হবে যা ট্রিগার হবে যখন ইউজার একটি আইটেম ডিলিট করতে চায়। লক্ষ্য করুন আমরা antd-এর Popconfirm কম্পোনেন্ট ব্যবহার করছি।

এটা একটি খুব সুন্দর কম্পোনেন্ট যা ইউজারকে একটি অ্যাকশন কনফার্ম করার জন্য প্রম্পট (prompt) করার কাজ সহজ করে। নিচের ছবিটি ইলাস্ট্রেট করে কীভাবে এটা দেখাবে:

রিঅ্যাক্ট অন রেইলস: সিমলেস ফুল-স্ট্যাক অ্যাপ গড়ে তোলা ডিলিশনের আগে কনফার্ম ডায়ালগ প্রম্পট করা।

Delete Action

একটি আইটেম ডিলিট করতে, আমাদের দুটি প্রধান অপারেশন করতে হবে: API-এ ডিলিশন কল এবং টেবিল রিলোডিং।

ডিলিট ফাংশনটি প্রথম ফেচের মতোই:

deleteBeer = (id) => { const url = `api/v1/beers/${id}`; fetch(url, { method: "delete", }) .then((data) => { if (data.ok) { this.reloadBeers(); return data.json(); } throw new Error("Network error."); }) .catch((err) => message.error("Error: " + err)); }; 

দেখুন? এখানে একমাত্র নতুন জিনিস হলো HTTP method যেটা fetch মেথডের সেকেন্ড প্যারামিটার হিসেবে পাস করা হয়েছে। প্লাস, then ক্লজের ভিতরে, আমরা reloadBeers ফাংশন কল করি, যেটা একবার আবার ব্যাকএন্ড থেকে সব বিয়ার রি-ফেচ করবে।

এই ফাংশনটির কন্টেন্ট প্রায় নিচের মতো:

reloadBeers = () => { this.setState({ beers: [] }); this.loadBeers(); }; 

আমরা স্টেটের beers অ্যারে রিসেট করি এবং লোড ফাংশন আবার কল করি।

The Final Component

শেষে, আমাদের কম্পোনেন্ট কম্পোজ করতে হবে এক্সপ্লিসিটলি antd ট্যাগ কল করে। চলুন ফাইনাল কম্পোনেন্ট কোডসহ দেখি কীভাবে হয়:

import { Table, message, Popconfirm } from "antd"; import React from "react"; import AddBeerModal from "./AddBeerModal"; class Beers extends React.Component { columns = [ { title: "Brand", dataIndex: "brand", key: "brand", }, { title: "Style", dataIndex: "style", key: "style", }, { title: "Country", dataIndex: "country", key: "country", }, { title: "Quantity", dataIndex: "quantity", key: "quantity", }, { title: "", key: "action", render: (_text, record) => ( <Popconfirm title="Are you sure to delete this beer?" onConfirm={() => this.deleteBeer(record.id)} okText="Yes" cancelText="No"> <a href="#" type="danger"> Delete{" "} </a> </Popconfirm> ), }, ]; state = { beers: [], }; componentDidMount() { this.loadBeers(); } loadBeers = () => { const url = "api/v1/beers/index"; fetch(url) .then((data) => { if (data.ok) { return data.json(); } throw new Error("Network error."); }) .then((data) => { data.forEach((beer) => { const newEl = { key: beer.id, id: beer.id, brand: beer.brand, style: beer.style, country: beer.country, quantity: beer.quantity, }; this.setState((prevState) => ({ beers: [...prevState.beers, newEl], })); }); }) .catch((err) => message.error("Error: " + err)); }; reloadBeers = () => { this.setState({ beers: [] }); this.loadBeers(); }; deleteBeer = (id) => { const url = `api/v1/beers/${id}`; fetch(url, { method: "delete", }) .then((data) => { if (data.ok) { this.reloadBeers(); return data.json(); } throw new Error("Network error."); }) .catch((err) => message.error("Error: " + err)); }; render() { return ( <> <Table className="table-striped-rows" dataSource={this.state.beers} columns={this.columns} pagination={{ pageSize: 5 }} /> <AddBeerModal reloadBeers={this.reloadBeers} /> </> ); } } export default Beers; 

এখন, আপনি সব কিছু একসাথে দেখতে পাচ্ছেন। রেন্ডার ফাংশন দুটি ট্যাগ ডিসপ্লে করবে যেগুলো আমরা সেখানে ইম্পোর্ট করলাম: antd-এর Table কম্পোনেন্ট এবং AddBeerModal (মডাল ফর্ম যা আমরা কয়েক মিনিটে তৈরি করব)।

টেবিল কম্পোনেন্ট খুব রিচ (rich) যেভাবে আটোমেটিক্যালি পেজিনেট করতে দেয় রেজাল্টস থ্রু একটি pagination অবজেক্ট সেট করে। একমাত্র প্রোপার্টি যা আমরা এখানে অ্যাড করছি সেটা হলো প্রতিটি পেজের সাইজ (৫ রেজাল্ট প্রতি পেজ)।

dataSource অ্যাট্রিবিউট আমাদের ব্যাকএন্ড থেকে মাউন্ট করা বিয়ার লিস্ট রিসিভ করে, এবং columns অ্যাট্রিবিউট মেটাডেটা রিসিভ করে যেটা আমরা ইতিমধ্যে বানালাম।

The AddBeerModal Component

টেবিলের নিচে, আপনি একটি বাটন স্পট করতে পারেন নতুন বিয়ার যোগ করার জন্য। যখন আমরা এই বাটনে ক্লিক করি, এটা একটি মডাল ওপেন করবে একটি ফর্মসহ নতুন বিয়ার রেজিস্টার করার জন্য আমাদের ক্যাটালগে, নিচের মত দেখতে পাবেন:

রিঅ্যাক্ট অন রেইলস: সিমলেস ফুল-স্ট্যাক অ্যাপ গড়ে তোলা নতুন বিয়ার ক্যাটালগে যোগ করা।

এটা antd কীভাবে ফর্ম হ্যান্ডেল করে সেটা এক্সপ্লোর করার একটি গ্রেট উপায়।

প্রথমে, এই কম্পোনেন্টে আমাদের থাকা অ্যাকশনগুলো ব্রেক ডাউন করি। লক্ষ্য করুন কম্পোনেন্টটি দুইটি কম্পোনেন্ট দিয়ে বানানো: একটি Button এবং একটি Modal।

এটার মানে, আমাদের উভয়ের সাথে সম্পর্কিত অপারেশন ম্যাপ করতে হবে:

  • showModal এবং handleCancel মডালের ওপেনিং এবং ক্লোজিং-এ ডিল করে।
  • onFinish ট্রিগার হয় যখন আমরা ফর্ম সাবমিট করি।

সেগুলো কম্পোনেন্টের স্টেটের সাথে খেলবে, যেটা শুধু মডাল টগল (toggle) স্টোর করবে (অর্থাৎ, সেটা ভিজিবল কিনা):

state = { visible: false, }; 

মডাল শো বা হাইড করতে, আমাদের শুধু এই বুলিয়ান টগল করতে হবে:

this.setState({ visible: true, }); 

Beer API কল করতে এবং একটি নতুন বিয়ার রেজিস্টার করতে, আমাদের আবার Fetch API ব্যবহার করতে হবে:

onFinish = (values) => { const url = "api/v1/beers/"; fetch(url, { method: "post", headers: { "Content-Type": "application/json", }, body: JSON.stringify(values), }) .then((data) => { if (data.ok) { this.handleCancel(); return data.json(); } throw new Error("Network error."); }) .then(() => { this.props.reloadBeers(); }) .catch((err) => console.error("Error: " + err)); }; 

এটা প্রথমবার আমরা এমন একটি রিকোয়েস্ট কল করছি যেটা সার্ভারে ডেটা পাঠাচ্ছে। এই কেসে, আমাদের এক্সপ্লিসিটলি API-কে বলতে হবে কী টাইপের ইনফরমেশন হেডিং ওভার। তাই headers অ্যাট্রিবিউট অবশ্যই ইনফর্মড থাকতে হবে।

যদি সবকিছু ঠিক থাকে, আমরা শুধু মডাল বন্ধ করি এবং টেবিল লিস্টিং রিলোড করি।

এখন, সব কিছু একসাথে দেখি, কম্পোনেন্ট রেন্ডারের সাথে:

import { Button, Form, Input, Modal, Select } from "antd"; import React from "react"; const { Option } = Select; class AddBeerModal extends React.Component { formRef = React.createRef(); state = { visible: false, }; onFinish = (values) => { const url = "api/v1/beers/"; fetch(url, { method: "post", headers: { "Content-Type": "application/json", }, body: JSON.stringify(values), }) .then((data) => { if (data.ok) { this.handleCancel(); return data.json(); } throw new Error("Network error."); }) .then(() => { this.props.reloadBeers(); }) .catch((err) => console.error("Error: " + err)); }; showModal = () => { this.setState({ visible: true, }); }; handleCancel = () => { this.setState({ visible: false, }); }; render() { return ( <> <Button type="primary" onClick={this.showModal}> Create New + </Button> <Modal title="Add New Beer ..." visible={this.state.visible} onCancel={this.handleCancel} footer={null}> <Form ref={this.formRef} layout="vertical" onFinish={this.onFinish}> <Form.Item name="brand" label="Brand" rules={[{ required: true, message: "Please input your beer brand!" }]}> <Input placeholder="Input your beer brand" /> </Form.Item> <Form.Item name="style" label="Style" rules={[{ required: true, message: "Please input your beer style!" }]}> <Input placeholder="Input your beer style" /> </Form.Item> <Form.Item name="country" label="Country" rules={[ { required: true, message: "Please input the country of the beer!", }, ]} > <Select showSearch placeholder="Select your beer country" optionFilterProp="children" style={{ width: "100%" }}> <Option value="Finland">Finland</Option> <Option value="Germany">Germany</Option> <Option value="Netherlands">Netherlands</Option> <Option value="UK">UK</Option> <Option value="USA">USA</Option> <Option value="Other">Other</Option> </Select> </Form.Item> <Form.Item name="quantity" label="Quantity" rules={[{ required: true, message: "Please input the quantity!" }]}> <Input type="number" placeholder="How many beers you desire?" /> </Form.Item> <Form.Item> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> </Modal> </> ); } } export default AddBeerModal; 

Antd আমাদের প্রতিটি ফর্ম আইটেমের রুলস ইনডিভিডুয়ালি স্পেসিফাই করতে দেয়। যদি একটি ফিল্ড রিকোয়ার্ড, শুধু বলুন rules অ্যাট্রিবিউট দিয়ে। আপনি কাস্টোমাইজ করতে পারেন মেসেজটি যা ডিসপ্লে করবে যদি ইউজার ফর্ম সাবমিট করে সেটা প্রপারলি ফিল না করলে:

রিঅ্যাক্ট অন রেইলস: সিমলেস ফুল-স্ট্যাক অ্যাপ গড়ে তোলা ফর্ম ইনপুট ভ্যালিডেট করা।

Select কম্পোনেন্ট লক্ষ্য করুন, যেটা একটি কম্বো বক্স (combo box) ট্রান্সলেট করে। দেখুন কত সহজে কমপ্লেক্স কম্পোনেন্ট তৈরি করা যায় শুধু সঠিক অ্যাট্রিবিউট প্রদান করলে। উদাহরণস্বরূপ, আপনি আপনার সিলেক্ট সার্চেবল (searchable) করতে চান, শুধু showSearch প্রোপার্টি রাখুন, এবং হয়ে গেলো:

রিঅ্যাক্ট অন রেইলস: সিমলেস ফুল-স্ট্যাক অ্যাপ গড়ে তোলা সিলেক্টের ভিতরে রেজাল্ট ফিল্টার করা।

Antd অটোমেটিক্যালি সিলেক্ট অপশন ফিল্টার করবে আপনার ইনপুটের বেসিসে।

Styling

কখনো কখনো, আপনাকে কিছু CSS স্টাইলিং প্রদান করতে হবে কম্পোনেন্টগুলোর জন্য যেগুলো ডিফল্ট প্রদান করে না (যেমন antd-এর টেবিল) বা বিল্ট-ইন वालাগুলো কাস্টোমাইজ করতে।

এটা করতে, আপনি যত চান তত CSS ফাইল তৈরি করতে পারেন এবং সেগুলো একটি স্ট্রাকচারে অর্গানাইজ করতে পারেন যেটা আপনাকে ভালো লাগে। রেইলস ইতিমধ্যে একটি application.css ফাইল তৈরি করে, app/assets/stylesheets ফোল্ডারের আন্ডারে। এটা খুলুন এবং নিচের কন্টেন্ট যোগ করুন:

.site-layout-content { background: #fff; padding: 24px; min-height: 380px; } .logo { width: 200px; min-height: 31px; margin: 16px 24px 16px 0; float: left; background-image: url(https://www.honeybadger.io/images/navbar_logo.svg?1602785015); background-repeat: no-repeat; } .table-striped-rows th, .table-striped-rows td { border-bottom: 1px solid #dedddd !important; } .table-striped-rows tr:nth-child(2n) td { background-color: #fbfbfb; } .table-striped-rows thead { background-color: #f1f1f1; } 

এই CSS রুলগুলো আমাদের টেবিল স্ট্রিপড (striped) বানাতে, উদাহরণস্বরূপ। আপনি যত চান অতিরিক্ত স্টাইল এখানে যোগ করতে পারেন।

Testing

টেস্টে যাওয়ার আগে, আমাদের CSRF টোকেন চেকিং ডিজেবল (disable) করতে হবে যা রেইলস অটোমেটিক্যালি আমাদের অ্যাপের জন্য কনফিগার করে। এটা করতে, app/controllers/application_controller.rb ফাইল যান এবং নিচের মতো পরিবর্তন করুন:

class ApplicationController < ActionController::Base protect_from_forgery with: :null_session end 

এইভাবে, আমরা টোকেন ভ্যালিডেট করতে হবে না প্রতিবার রিকোয়েস্ট পারফর্ম করার সময়।

বাড়িয়ে! এখন আপনার সার্ভার শুরু করুন rails s কমান্ড দিয়ে, https://localhost:3000/ এক্সেস করুন, এবং CRUD-র সাথে খেলুন।

উপসংহার (Conclusion)

হোমওয়ার্ক টাস্ক হিসেবে, আমি রিকমেন্ড করব আপনি CRUD-এর আপডেট ফাংশনালিটি ইমপ্লিমেন্ট করার চেষ্টা করুন। আপনি API কন্ট্রোলারের edit মেথড অ্যাডাপ্ট করতে পারেন আপডেটেড বিয়ার ইনফো রিসিভ করতে এবং ডেটাবেসে আপডেট পারফর্ম করতে। ভিউতে, আরেকটি মডাল খুব উপযুক্ত হবে এডিট ফর্ম অকমোডেট করার জন্য।

আপনি এই টিউটোরিয়ালের সোর্স কোড এখানে পাবেন। ভালো অধ্যয়ন!

  1. রেইলস প্রজেক্টের জন্য দক্ষ ডকুমেন্টেশন ওয়ার্কফ্লো ডিজাইন

  2. ডকারে আপনার রেইলস অ্যাপ সহজে টেস্ট করুন: ধাপে ধাপে গাইড

  3. রিঅ্যাক্ট অন রেইলস: সিমলেস ফুল-স্ট্যাক অ্যাপ গড়ে তোলা

  4. টুইলিও দিয়ে React Native অ্যাপে ভিডিও কল ইন্টিগ্রেশন: ধাপে ধাপে গাইড