AngularJS-এ শুরু করা কঠিন নয়। ডকুমেন্টেশন সবচেয়ে ভালোদের মধ্যে একটি এবং টিউটোরিয়ালগুলো সহজ।
কিন্তু জিনিসগুলো জটিল হয়ে যায় যখন আপনি টেকনোলজি কম্বাইন করুন।
আপনি যদি স্ট্রেট জাভাস্ক্রিপ্টের বদলে CoffeeScript ব্যবহার করেন, তবে আপনার প্রিপ্রসেসিং কনসার্ন থাকবে—সিনট্যাক্স ডিফারেন্সের পাশাপাশি। এগুলো নিজস্বভাবে মাইনর ইস্যু, কিন্তু আপনি যদি রুবি অন রেলস, জ্যাসমিন এবং করমা মিক্সে ফেলেন? আশচর্যogens ভাবে জটিল হয়ে যায়।
এই টিউটোরিয়ালে আমরা ঠিক এই স্ট্যাক ব্যবহার করব। না কেন আমরা সজা পেতে চাই, বরং কারণ রিয়েল ওয়ার্ল্ডে আপনি এই ধরনের সেটআপ দেখবেন।
এই টিউটোরিয়াল অনুমান করে আপনি রেলস সাথে কমফরটেবল, কিন্তু AngularJS সাথে নাও হয়তো।
একটি বেস রেলস অ্যাপ তৈরি করুন
যেহেতু অনেক টেকনোলজি লেয়ার জড়িত, আমি একটি সিম্পল অ্যাপ্লিকেশন বানাবো যা প্রায় কিছুই করে না। আমরা রেস্টুরেন্টের জন্য CRUD ফাংশন্যালিটি সেটআপ করব—আসলে, শুধু CR পার্ট। -UD পাঠকের জন্য এক্সারসাইজ হিসেবে রেখে দিয়েছি। ;-)
আমরা অ্যাপ্লিকেশনটিকে Restauranteur বলব।
আমি PostgreSQL এবং RSpec ব্যবহার করছি এখানে, কিন্তু DBMS এবং সার্ভার-সাইড টেস্টিং ফ্রেমওয়ার্ক তা সিগনিফিক্যান্ট নয়। আপনি যা চান ব্যবহার করতে পারেন।
প্রাথমিক সেটআপ
প্রথমে প্রজেক্ট তৈরি করুন:
$ rails new restauranteur --database=postgresql --skip-test-unit
আপনি যদি Pow ব্যবহার করেন, আপনার প্রজেক্ট Pow-এ যোগ করুন:
$ ln -s /Users/jasonswett/projects/restauranteur ~/.pow/restauranteur
PostgreSQL ডাটাবেস ইউজার তৈরি করুন:
$ createuser -P -s -e restauranteur
Gemfile-এ RSpec যোগ করুন:
# Gemfile
gem "rspec-rails", "~> 2.14.0"
RSpec ইনস্টল করুন:
$ bundle install
$ rails g rspec:install
ডাটাবেস তৈরি করুন:
$ rake db:create
রেস্টুরেন্ট মডেল তৈরি করুন
এখন যেহেতু আমাদের প্রজেক্ট এবং ডাটাবেস তৈরি হয়েছে, আমাদের প্রথম রিসোর্স তৈরি করি। Restaurant রিসোর্সের শুধু একটি অ্যাট্রিবিউট থাকবে: name, যা একটি স্ট্রিং।
$ rails generate scaffold restaurant name:string
এখন, শুধু OCD-র জন্য, আমরা নিশ্চিত করব রেস্টুরেন্টের নাম ইউনিক।
# db/migrate/[timestamp]_create_restaurants.rb
class CreateRestaurants < ActiveRecord::Migration
def change
create_table :restaurants do |t|
t.string :name
t.timestamps
end
# নিচের লাইনটি যোগ করুন
add_index :restaurants, :name, unique: true
end
end
মাইগ্রেশন রান করুন:
$ rake db:migrate
আমরা কিছু স্পেক যোগ করব যাতে যাচাই করতে পারি আমরা ইনভ্যালিড রেস্টুরেন্ট তৈরি করতে পারি না। লক্ষ্য করুন ইউনিক ফেইলর কেস রয় এক্সেপশন।
require 'spec_helper'
describe Restaurant do
before do
@restaurant = Restaurant.new(name: "Momofuku")
end
subject { @restaurant }
it { should respond_to(:name) }
it { should be_valid }
describe "when name is not present" do
before { @restaurant.name = " " }
it { should_not be_valid }
end
describe "when name is already taken" do
before do
restaurant_with_same_name = @restaurant.dup
restaurant_with_same_name.name = @restaurant.name.upcase
restaurant_with_same_name.save
end
it { should_not be_valid }
end
end
এই ভ্যালিডেটরগুলো যোগ করলে স্পেকগুলো পাস করবে:
class Restaurant < ActiveRecord::Base
validates :name, presence: true, uniqueness: { case_sensitive: false }
end
আমরা এখন এগিয়ে যেতে পারি।
AngularJS মিক্সে আনুন
একসাথে সবকিছু আপনার ওপর ফেলার বদলে, আমি প্রথমে AngularJS-র সবচেয়ে সিম্পল "Hello, world" ভার্সন একটি AngularJS-রেলস অ্যাপ্লিকেশনের ডেমো দিতে চাই এবং তারপর আমাদের রেস্টুরেন্ট CRUD ফাংশন্যালিটি সেটা ওপর বিল্ড করব।
কোনো নির্দিষ্ট রেলস রিসোর্সের সাথে আমাদের "Hello, world" পেজ বাঁধা থাকার কোনো কারণ নেই। এই কারণে, আমরা একটি StaticPagesController তৈরি করব AngularJS হোম পেজ সার্ভ করার জন্য।
কন্ট্রোলার তৈরি করুন
$ rails generate controller static_pages index
আমাদের রুট রুট এখন শুধু "Welcome to Rails" পেজ। এটাকে আমাদের নতুন StaticPagesController-র index একশনে সেট করি:
# config/routes.rb
Restauranteur::Application.routes.draw do
# নিচের লাইনটি যোগ করুন
root 'static_pages#index'
end
Angular ডাউনলোড করুন
- আমাদের টেস্টগুলো সঠিকভাবে কাজ করার জন্য পরে, আমাদের
angular-mocks.jsনামে একটি ফাইল লাগবে। আমি মনে করি Angular ডক্সে এটার কোনো উल्लেখ নেই, কিন্তু এটা জরুরি। - AngularJS টিউটোরিয়ালে, ডক্স লেটেস্ট ব্লিডিং-এজ ভার্সন লিস্ট করে, কিন্তু আমার স্মৃতি যদি সঠিক হয়, তবে লেটেস্ট ভার্সনের জন্য
angular.jsএবংangular-mocks.jsএর মধ্যে কম্প্যাটিবিলিটি সমস্যা ছিল। আমি জানি ১.১.৫ ভার্সন একসাথে কাজ করত, তাই যদিও এটা লেটেস্ট স্টেবল ভার্সন নয়, আমি এখানে সেই ভার্সন লিস্ট করছি। অবশ্য সময়ের সাথে কম্প্যাটিবিলিটি সিচুয়েশন উন্নত হবে।
angular.js এবং angular-mocks.js code.angularjs.org থেকে ডাউনলোড করুন এবং ফাইলগুলো app/assets/javascripts-এ সরান।
$ wget https://code.angularjs.org/1.1.5/angular.js \
https://code.angularjs.org/1.1.5/angular-mocks.js
$ mv angular* app/assets/javascripts
এসেট পাইপলাইনে যোগ করুন
এখন আমরা আমাদের অ্যাপ্লিকেশনকে বলব AngularJS ফাইলটি রিকোয়ার করতে, এবং নিশ্চিত করব এটা অন্যান্য ফাইলগুলো থেকে আগে লোড হয় যেগুলো এর ওপর ডিপেন্ডেন্ট। (আমরা RequireJS-এর মতো কিছু ব্যবহার করতে পারতাম এই ডিপেন্ডেন্সি ম্যানেজ করার জন্য, এবং সেটা সম্ভবত আমি প্রোডাকশন প্রোডাক্টে করতাম, কিন্তু এই টিউটোরিয়ালের জন্য আমি টেকনোলজি স্ট্যাক যতটা সম্ভব থিন রাখতে চাই।)
দ্রষ্টব্য: Angular এবং Turbolinks একে অপরের সাথে конфлиক্ত করতে পারে, তাই এখানে সেগুলো ডিজেবল করলাম।
// app/assets/javascripts/application.js
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
// ...
//= require jquery
//= require jquery_ujs
// নিচের দুটি লাইন যোগ করুন
//= require angular
//= require main
//= require_tree .
লেআউট সেট আপ করুন
আমরা ng-app এবং ng-view যোগ করব, যা সিগন্যাল দেয় যে আমাদের পেজে একটি Angular অ্যাপ আছে। লক্ষ্য করুন Turbolinks-এর কোনো উल्लেখ নেই।
<%= yield %>
একটি Angular কন্ট্রোলার তৈরি করুন
প্রথমে আমাদের কন্ট্রোলারদের জন্য একটি ডিরেক্টরি তৈরি করুন। আপনি যা চান নাম দিতে পারেন।
$ mkdir -p app/assets/javascripts/angular/controllers
এখন কন্ট্রোলার ফাইলটি তৈরি করুন। আমি এই কন্ট্রোলারকে "হোম কন্ট্রোলার" বলছি, এবং Angular-এ কনভেনশন হলো কন্ট্রোলার ফাইলনেমের সাথে Ctrl অ্যাপেন্ড করা। সুতরাং আমাদের ফাইলনেম হবে app/assets/javascripts/angular/controllers/HomeCtrl.js.coffee:
# app/assets/javascripts/angular/controllers/HomeCtrl.js.coffee
@restauranteur.controller 'HomeCtrl', ['$scope', ($scope) ->
# লক্ষ্য করুন এই কন্ট্রোলার বডি খালি
]
একটি Angular রাউট যোগ করুন
এখন আমরা একটি রাউটিং ডিরেক্টিভ যোগ করব যাতে আমাদের HomeCtrl হয় আমাদের "ডিফল্ট পেজ"। এখানে আমি আমার রাউটিং ডিফাইন করছি app/assets/javascripts/main.js.coffee-এ, কিন্তু আবার আমি মনে করি ফাইলনেম বিষয়টি তা গুরুত্বপূর্ণ নয়।
# app/assets/javascripts/main.js.coffee
# এই লাইনটি আমাদের Angular অ্যাপের সাথে সম্পর্কিত, আমাদের
# HomeCtrl-এর সাথে নির্দিষ্টভাবে নয়। এটা মূলত কীভাবে আমরা
# Angularকে আমাদের অ্যাপ্লিকেশনের অস্তিত্ব সম্পর্কে জানাই।
@restauranteur = angular.module('restauranteur', [])
# এই রাউটিং ডিরেক্টিভ Angularকে আমাদের অ্যাপ্লিকেশনের
# ডিফল্ট রাউট সম্পর্কে জানায়। "otherwise" টার্মটি এখানে
# কিছুটা অস্বাভাবিক লাগতে পারে, কিন্তু এটা আরো মেনিংফুল হয়ে যাবে
# আমাদের অ্যাপ্লিকেশনে আরও রাউট যোগ করার সাথে সাথে।
@restauranteur.config(['$routeProvider', ($routeProvider) ->
$routeProvider.otherwise({
templateUrl: '../templates/home.html',
controller: 'HomeCtrl'
})
])
একটি Angular টেমপ্লেট যোগ করুন
আমাদের Angular টেমপ্লেট রাখার একটি জায়গা লাগবে। আমি আমারগুলো public/templates-এ রাখতে সিদ্ধান্ত নিয়েছি। আবার, আপনি সেগুলো যেকোনো জায়গায় রাখতে পারেন।
mkdir public/templates
আমরা যদি public/templates/home.html ফাইলটি কিছু আربیট্রারি কন্টেন্ট দিয়ে তৈরি করি, তবে আমরা ব্রাউজারে সেটা দেখতে পাব।
This is the home page.
এখন, আপনি যদি https://restauranteur.dev/ (বা https://localhost:3000/ যদি আপনি Pow ব্যবহার না করেন) এ যান, তবে আপনাকে home.html-এর কন্টেন্ট দেখতে मिलবে।
ডেটা বাইন্ডিং উদাহরণ
এটা কিছুটা আকর্ষণীয়, কিন্তু সম্ভবত খুব ইম্প্রেসিভ নয়। আসলে কিছু ওয়ায়ার থেকে পাঠাই। আপনার app/assets/angular/controllers/HomeCtrl.js.coffee এডিট করুন এইভাবে:
# app/assets/angular/controllers/HomeCtrl.js.coffee
@restauranteur.controller 'HomeCtrl', ['$scope', ($scope) ->
$scope.foo = 'bar'
]
এটা রেলস কন্ট্রোলারে @foo = "bar" বলার কেমন। আমরা foo টেমপ্লেটে প্লাগ ইন করতে পারি ডবল-ব্রেস সিনট্যাক্স ব্যবহার করে এইভাবে:
Value of "foo": {{foo}}
এবারে সত্যিকারের কাজ করুন
আমরা ইতিমধ্যে একটি সিম্পল hello world অ্যাপ বানিয়েছি। পূর্ণ CRUD অ্যাপ্লিকেশন তৈরি করা তেমন কঠিন নয়।
ডাটাবেস সিড করুন
আমাদের রেস্টুরেন্ট CRUD-র সাথে কাজ করা কিছুটা অর্থবহ হবে যদি আমরা ডাটাবেসে কিছু রেস্টুরেন্ট দিয়ে শুরু করি। এখানে একটি সিড ফাইল আপনি ব্যবহার করতে পারেন।
# db/seeds.rb
Restaurant.create([
{ name: "The French Laundry" },
{ name: "Chez Panisse" },
{ name: "Bouchon" },
{ name: "Noma" },
{ name: "Taco Bell" },
])
rake db:seed
রেস্টুরেন্ট ইন্ডেক্স পেজ তৈরি করুন
প্রথমে রেস্টুরেন্টের জন্য একটি টেমপ্লেট ফোল্ডার তৈরি করুন:
mkdir public/templates/restaurants
প্রথম টেমপ্লেট হবে ইন্ডেক্স পেজ:
[index](/#) * {{ restaurant.name }}
এই জিনিসগুলো কি বোঝায় সেটা একটু পরেই ব্যাখ্যা করব। প্রথমে কন্ট্রোলার তৈরি করি:
# app/assets/javascripts/angular/controllers/RestaurantIndexCtrl.js.coffee
@restauranteur.controller 'RestaurantIndexCtrl', ['$scope', '$location', '$http', ($scope, $location, $http) ->
$scope.restaurants = []
$http.get('./restaurants.json').success((data) ->
$scope.restaurants = data
)
]
শেষে, আমাদের রাউটিং কনফিগারেশন এডজাস্ট করি:
# app/assets/javascripts/main.js.coffee
@restauranteur = angular.module('restauranteur', [])
@restauranteur.config(['$routeProvider', ($routeProvider) ->
$routeProvider.
when('/restaurants', {
templateUrl: '../templates/restaurants/index.html',
controller: 'RestaurantIndexCtrl'
}).
otherwise({
templateUrl: '../templates/home.html',
controller: 'HomeCtrl'
})
])
এখন, অবশেষে, আমরা URI /#/restaurants-এ যেতে পারি এবং আমাদের রেস্টুরেন্টের লিস্ট দেখতে পারি। এগিয়ে যাওয়ার আগে একটি টেস্ট যোগ করি।
আমাদের প্রথম টেস্ট যোগ করুন
JS টেস্ট ফোল্ডার যোগ করুন:
mkdir spec/javascripts
টেস্ট লিখুন:
# spec/javascripts/controllers_spec.js.coffee
describe "Restauranteur controllers", ->
beforeEach module("restauranteur")
describe "RestaurantIndexCtrl", ->
it "should set restaurants to an empty array", inject(($controller) ->
scope = {}
ctrl = $controller("RestaurantIndexCtrl", $scope: scope )
expect(scope.restaurants.length).toBe 0
)
কনফিগ যোগ করুন:
// spec/javascripts/restauranteur.conf.js
module.exports = function(config) {
config.set({
basePath: '../..',
frameworks: ['jasmine'],
autoWatch: true,
preprocessors: {
'**/*.coffee': 'coffee'
},
files: [
'app/assets/javascripts/angular.js',
'app/assets/javascripts/angular-mocks.js',
'app/assets/javascripts/main.js.coffee',
'app/assets/javascripts/angular/controllers/RestaurantIndexCtrl.js.coffee',
'app/assets/javascripts/angular/*',
'spec/javascripts/*_spec.js.coffee'
]
});
};
Karma ইনস্টল করুন এবং সার্ভার শুরু করুন:
sudo npm install -g karma
sudo npm install -g karma-ng-scenario
karma start spec/javascripts/restauranteur.conf.js
আপনি যদি https://localhost:9876/ এ যান, আমাদের টেস্ট রান হবে এবং সাকসেসফুল হবে। আপনি যদি টেস্ট ফেইল होते দেখতে চান, expect(scope.restaurants.length).toBe 0-কে expect(scope.restaurants.length).toBe 1 এ চেঞ্জ করুন এবং আবার টেস্ট রান করুন।
আমরা যা টেস্ট যোগ করলাম তার অর্থবহতা প্রশ্নের বাইরে, কিন্তু আমার উদ্দেশ্য ছিল আপনাকে আপনার Angular কোডকে টেস্ট হারনেসে আনার কাজ থেকে বাঁচানো। কিছু জিনিস, যেন CoffeeScript প্রিপ্রসেসর এবং angular-mocks.js ইনক্লুশন, যেগুলো টোটালি
রেস্টুরেন্টস পেজ বিল্ড আউট করুন
আমাদের রেস্টুরেন্ট ইন্ডেক্স টেমপ্লেটে একটি অস্থায়ী এডজাস্টমেন্ট করি:
* {{restaurant.name}} ({{restaurant.id}})
আপনি যদি এখন /#/restaurants রিভিজিট করেন, লক্ষ্য করবেন কোনো রেস্টুরেন্টের আইডি নেই। কেন ব্ল্যাঙ্ক?
রেলস ৪-এ স্ক্যাফোল্ড জেনারেট করলে কিছু .jbuilder ফাইল দেয়:
$ ls -1 app/views/restaurants/*.jbuilder
app/views/restaurants/index.json.jbuilder
app/views/restaurants/show.json.jbuilder
আপনি যদি app/views/restaurants/index.json.jbuilder খুলেন, আপনি এটা দেখবেন:
# app/views/restaurants/index.json.jbuilder
json.array!(@restaurants) do |restaurant|
json.extract! restaurant, :name
json.url restaurant_url(restaurant, format: :json)
end
আপনি দেখতে পাচ্ছেন, এটা :name ইনক্লুড করছে কিন্তু :id নয়। যোগ করি:
# app/views/restaurants/index.json.jbuilder
json.array!(@restaurants) do |restaurant|
json.extract! restaurant, :id, :name
json.url restaurant_url(restaurant, format: :json)
end
আপনি যদি ফাইলটি সেভ করে /#/restaurants রিফ্রেশ করেন, তবে আপনাকে আইডিগুলো দেখতে মিলবে।
এখন টেমপ্লেটটিকে আগের মতো করি:
[index](/#) * {{ restaurant.name }}
আপনি কখনো লক্ষ্য করতে পারেন আমরা এই জিনিসগুলো viewRestaurant() নামে কয়েকটি জিনিসের দিকে পয়েন্ট করেছি কিন্তু আমরা কখনো viewRestaurant() নামে কিছু ডিফাইন করিনি। এখন সেটা করি:
# app/assets/javascripts/angular/controllers/RestaurantIndexCtrl.js.coffee
@restauranteur.controller 'RestaurantIndexCtrl', ['$scope', '$location', '$http', ($scope, $location, $http) ->
$scope.restaurants = []
$http.get('./restaurants.json').success((data) ->
$scope.restaurants = data
)
# নিচের লাইনগুলো যোগ করুন
$scope.viewRestaurant = (id) ->
$location.url "/restaurants/#{id}"
]
রেলসে কনভেনশন হলো resource_name/:id একটি "শো" পেজে ম্যাপ করে, এবং আমরা এখানেও সেটাই করব। একটি শো টেমপ্লেট, রাউট এবং কন্ট্রোলার তৈরি করি।
# {{restaurant.name}}
# app/assets/javascripts/main.js.coffee
@restauranteur = angular.module('restauranteur', [])
@restauranteur.config(['$routeProvider', ($routeProvider) ->
$routeProvider.
when('/restaurants', {
templateUrl: '../templates/restaurants/index.html',
controller: 'RestaurantIndexCtrl'
}).
when('/restaurants/:id', {
templateUrl: '../templates/restaurants/show.html',
controller: 'RestaurantShowCtrl'
}).
otherwise({
templateUrl: '../templates/home.html',
controller: 'HomeCtrl'
})
])
# app/assets/javascripts/angular/controllers/RestaurantShowCtrl.js.coffee
@restauranteur.controller 'RestaurantShowCtrl', ['$scope', '$http', '$routeParams', ($scope, $http, $routeParams) ->
$http.get("./restaurants/#{$routeParams.id}.json").success((data) ->
$scope.restaurant = data
)
]
এখন আপনি যদি /#/restaurants রিফ্রেশ করে একটি রেস্টুরেন্টে ক্লিক করেন, তবে আপনি সেটার শো পেজে পাবেন। ইউহু!
এতটুকু আজ জন্য
আমরা সম্ভবত খুব ইম্প্রেসিভ রেজাল্ট দেখিনি, কিন্তু আমি আশা করি AngularJS রেলস ৪-এ প্লাগ ইন করার কিছুটা সময় বাঁচাতে পেরেছি। পরবর্তীতে আমি ngResource-এ তাকাতে পরামর্শ দেব, যা CRUD ফাংশন্যালিটি আরও DRY করতে সাহায্য করতে পারে।
আরো শিখতে চান?
অ্যাডাম অ্যান্ডারসনের চমৎকার পোস্ট চেক আউট করুন, যাঁর Bootstrapping an AngularJS app in Rails 4.0 সিরিজ আমাকে AngularJS এবং রেলস শিখতে সাহায্য করল। আপনি হয়তো তার টিউটোরিয়ালও ফলো করতে চান, কিন্তু এই টিউটোরিয়াল আলাদা কারণ আমি রিয়ালি স্পুন-ফিড করার চেষ্টা করেছি সব ডিটেইলস, মিনিমাইজ করে চান্স আপনি পচতে যাবেন।