Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

เชี่ยวชาญ AngularJS กับ Rails 4: คู่มือสำหรับมือใหม่

เริ่ม AngularJS ไมยาก Documentation ดีที่สุด tutorials เรียบง่าย

แต่น่าปวดหัวตอนรวม technologies

ถ้าใช้ CoffeeScript แทน JavaScript ต้องคิด preprocessing concerns และ syntax differences ปัญหาเล็กน้อยตัวเอง แต่วาง Ruby on Rails, Jasmine และ Karma ลงไป? tricky อย่างไม่คาดคิด

นี่คือ stack ที่ใช้ใน tutorial ไม่ใช่เพราะ gluttons for punishment แต่ เพราะ setup แบบนี้เจอใน real world

Tutorial สมมติ comfortable กับ Rails แต่ไม่จำเป็น AngularJS

Creating a Base Rails App

Tech layers เยอะ สร้าง simple application ที่ barely does anything CRUD สำหรับ restaurants - จริงๆ แค่ CR -UD เป็น exercise for reader ;-)

App ชื่อ Restauranteur

ใช้ PostgreSQL และ RSpec DBMS และ server-side testing framework ไม่ significant ใช้อะไรก็ได้

Initial Setup

สร้าง project:

$ rails new restauranteur --database=postgresql --skip-test-unit 

ถ้าใช้ Pow add project to Pow:

$ ln -s /Users/jasonswett/projects/restauranteur ~/.pow/restauranteur 

สร้าง PostgreSQL database user:

$ createuser -P -s -e restauranteur 

เพิ่ม RSpec ใน Gemfile:

# Gemfile gem "rspec-rails", "~> 2.14.0" 

Install RSpec:

$ bundle install $ rails g rspec:install 

สร้าง database:

$ rake db:create 

Creating the Restaurant Model

Project และ database พร้อม สร้าง resource แรก Restaurant resource มี attribute เดียว: name string

$ rails generate scaffold restaurant name:string 

OCD ให้ restaurant names unique:

# db/migrate/[timestamp]_create_restaurants.rb class CreateRestaurants < ActiveRecord::Migration def change create_table :restaurants do |t| t.string :name t.timestamps end # Add the following line add_index :restaurants, :name, unique: true end end 

Run migration:

$ rake db:migrate 

เพิ่ม specs verify ไม่สร้าง invalid restaurants ได้ unique failure gives raw error

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 

Validators ทำให้ specs pass:

class Restaurant < ActiveRecord::Base validates :name, presence: true, uniqueness: { case_sensitive: false } end 

พร้อมไปต่อ

Bringing AngularJS into the Mix

แทน dump ทุกอย่าง มา demo simplest "Hello, world" AngularJS-Rails app แล้วค่อย build restaurant CRUD

Hello world page ไม่จำเป็นต้อง tied กับ Rails resource ใดๆ สร้าง StaticPagesController serve AngularJS home page

Create the Controller

$ rails generate controller static_pages index 

Root route ตอนนี้ "Welcome to Rails" ตั้งเป็น index action ของ StaticPagesController:

# config/routes.rb Restauranteur::Application.routes.draw do # Add the following line root 'static_pages#index' end 

Download Angular

  1. ต้องการ angular-mocks.js สำหรับ tests ไม่คิดว่ามีใน Angular docs แต่จำเป็น
  2. AngularJS tutorial list latest bleeding-edge version แต่ผมเจอ compatibility issues ระหว่าง angular.js และ angular-mocks.js latest version เวอร์ชัน 1.1.5 ทำงานร่วมกันได้ แม้ไม่ใช่ latest stable ใช้ version นี้ เวลาผ่านไป compatibility จะดีขึ้น

Download 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 

Add it to the Asset Pipeline

บอก application require AngularJS file และ load ก่อน files ที่ depend on it (RequireJS manage dependencies ได้ แต่ tutorial นี้ thin stack)

Note: Angular และ Turbolinks conflict ปิด Turbolinks ที่นี่

// app/assets/javascripts/application.js //= require jquery //= require jquery_ujs // Add the following two lines //= require angular //= require main //= require_tree . 

Set up the Layout

เพิ่ม ng-app และ ng-view signal Angular app ใน page ลบ Turbolinks mentions

 <%= yield %> 

Creating an Angular Controller

สร้าง directory controllers:

$ mkdir -p app/assets/javascripts/angular/controllers 

สร้าง controller file เรียก "home controller" convention Angular append filenames ด้วย Ctrl filename: app/assets/javascripts/angular/controllers/HomeCtrl.js.coffee

# app/assets/javascripts/angular/controllers/HomeCtrl.js.coffee @restauranteur.controller 'HomeCtrl', ['$scope', ($scope) -> # Notice how this controller body is empty ] 

Add an Angular Route

เพิ่ม routing directive ทำให้ HomeCtrl เป็น "default page" กำหนด routing ใน app/assets/javascripts/main.js.coffee

# app/assets/javascripts/main.js.coffee # This line is related to our Angular app, not to our # HomeCtrl specifically. This is basically how we tell # Angular about the existence of our application. @restauranteur = angular.module('restauranteur', []) # This routing directive tells Angular about the default # route for our application. The term "otherwise" here # might seem somewhat awkward, but it will make more # sense as we add more routes to our application. @restauranteur.config(['$routeProvider', ($routeProvider) -> $routeProvider. otherwise({ templateUrl: '../templates/home.html', controller: 'HomeCtrl' }) ]) 

Add an Angular Template

เก็บ Angular templates ใน public/templates

mkdir public/templates 

สร้าง public/templates/home.html ใส่ content ใดๆ ควรเห็นใน browser

 This is the home page. 

ไป https://restauranteur.dev/ (หรือ https://localhost:3000/ ไม่ใช้ Pow) ควรเห็น contents ของ home.html

An Example of Data Binding

Kind of interesting แต่ไม่ impressive Edit app/assets/angular/controllers/HomeCtrl.js.coffee:

# app/assets/angular/controllers/HomeCtrl.js.coffee @restauranteur.controller 'HomeCtrl', ['$scope', ($scope) -> $scope.foo = 'bar' ] 

Analogous to @foo = "bar" ใน Rails controller Plug foo ใน template double-brace syntax:

Value of "foo": {{foo}} 

Doing it for Real This Time

Hello world app เสร็จแล้ว Full CRUD application ไม่อยากยากกว่านี้

Seed the Database

Restaurant CRUD meaningful ถ้าเริ่มด้วย restaurants ใน database seed file:

# db/seeds.rb Restaurant.create([ { name: "The French Laundry" }, { name: "Chez Panisse" }, { name: "Bouchon" }, { name: "Noma" }, { name: "Taco Bell" }, ]) 
rake db:seed 

Creating a Restaurant Index Page

สร้าง template folder restaurants:

mkdir public/templates/restaurants 

Template แรก index page:

[index](/#) * {{ restaurant.name }} 

อธิบายในที สร้าง controller:

# 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 ) ] 

ปรับ routing config:

# 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' }) ]) 

ไป /#/restaurants ควรเห็น list of restaurants เพิ่ม test ก่อน

Adding Our First Test

Add JS test folder:

mkdir spec/javascripts 

Write test:

# 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 ) 

Add config:

// 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' ] }); }; 

Install Karma และ start server:

sudo npm install -g karma sudo npm install -g karma-ng-scenario karma start spec/javascripts/restauranteur.conf.js 

ไป https://localhost:9876/ test รัน pass ถ้าอยากดู fail เปลี่ยน expect(scope.restaurants.length).toBe 0 เป็น expect(scope.restaurants.length).toBe 1 รันอีกครั้ง

Meaningfulness ของ test นี้ questionable แต่ intention คือ save work จะได้ Angular code เข้า test harness มีสิ่งบางอย่าง เช่น CoffeeScript preprocessor และ angular-mocks.js inclusion ที่ไม่ obvious ใช้เวลาหลายชั่วโมง head-scratching

Building Out the Restaurants Page

Temporary adjustment restaurant index template:

 * {{restaurant.name}} ({{restaurant.id}}) 

Revisit /#/restaurants รestaurants ไม่มี IDs ทำไม blank?

Generate scaffolding Rails 4 ให้ .jbuilder files:

$ 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 

Include :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 

Save refresh /#/restaurants IDs ควรปรากฏ

กลับ template เหมือนเดิม:

[index](/#) * {{ restaurant.name }} 

อาจสังเกต point at viewRestaurant() แต่ไม่เคย define มาสร้างตอนนี้:

# 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 ) # Add the following lines $scope.viewRestaurant = (id) -> $location.url "/restaurants/#{id}" ] 

Convention Rails resource_name/:id map to "show" page ทำกัน

สร้าง show template, route และ controller:

# {{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 ) ] 

Refresh /#/restaurants click restaurant ไป show page Yay!

That's All for Now

ผลลัพธ์ไม่อาจ impressive แตหวังว่า save time plugging AngularJS into Rails 4 ต่อไปแนะนำ ngResource ทำ CRUD DRY

Interested in Learning More?

ดู post ของ Adam Anderson Bootstrapping an AngularJS app in Rails 4.0 series ช่วยเริ่ม AngularJS และ Rails อาจชอบ tutorial ของเขา แต่ tutorial นี้ต่างตรงที่ _really_ spoon-feed details minimize chances stuck in weeds