2016年2月3日 星期三

Ruby on Rails 筆記 — 後端Rails當API + 前端AngularJS



I. 寫在開始之前

對Rails有了基本的認識,以及實作過一兩個小專案後,在朋友的推薦下,決定嘗試把Rails當作API使用,並且搭配AngularJS[1],也就是所謂的前端JS框架進行開發。簡單來說Rails的功能變成只有儲存和讀取資料庫裡的資料,也就是所謂的後端;而在前端的各個view和controller都由AngularJS來完成。因此最重要的重點就是如何在前後端相互傳資料,以及如何配置AngularJS。


II. 提醒事項

§ 本文作業系統為Ubuntu14.04,Rails 4.2.4,Ruby2.2.3
§ 在本文中寫給讀者看的註解會使用 % 符號
§ [ ] 為reference,會將網址放在最後提供參照
§ 本文中AngularJS為1.1.5版
§ $符號代表為終端機中輸入的指令


III.開始啦

1. 行前準備

主要參考文章為:給 Rails Developer 看的 Angular + Rails 起步走[2]
假設大家對於建立Rails專案很熟悉,例子就以我的專案為例,主題是要創建簡易部落格,因此:

model: article title:string , content:text
controller: articles

% 建立好之後就可以往下一步走了!
 
2. 建立一個static_pages controller,用意是要創造一個Rails的index當基礎,然後讓AngularJS的template建立在這個基礎之上
(1)
$ rails g controller static_pages index
(2) 指定根目錄,開啟config/routes.rb,輸入以下:

root 'static_pages#index'
resources :articles

% 結果如下圖所示


3. 下載angular.js與angular-mocks.js(如需測試時用)然後放入app/assets/javascripts
$ wget http://code.angularjs.org/1.1.5/angular.js http://code.angularjs.org/1.1.5/angular-mocks.js
$ mv angular* app/assets/javascripts


4. 將AngularJS加入Asset Pipeline中

(1) 打開app/assets/javascripts/application.js,加入以下兩行
//= require angular
//= require main

(2) 一樣在application.js中,刪掉會互斥的turbolinks
//= require turbolinks
% 刪掉上面該行程式碼

5. 在application.html.erb中宣告Angular application

(1) 打開app/views/layouts/application.html.erb
(2) html標籤的地方加入 ng-app=”appname” ng-controller= “layoutCtrl”
(3) 將head標籤中含有turbolink相關的字串都刪除,避免和AngularJS互斥
(4) 在<%= yield %>標籤前後加上<div ng-view></div>

% 最後結果類似下圖所示(其他還有加上navbar, footer等介面,這邊就略過不著墨)
% 以上步驟主要都在進行Rails和AngularJS的基本配置,接下來就真正要啟動AngularJS了!



6. AngularJS路由: 參照參考文章,將路由全部定義在app/assets/javascripts/main.js.coffee裡面,因此至assets/javascripts資料夾裡新建一個main.js.coffee

(1) 打開main.js.coffee輸入如下:
@alanyehBlog = angular.module('alanyehBlog', [])

@alanyehBlog.config(['$routeProvider', ($routeProvider) ->
  $routeProvider.
    when('/', {
      templateUrl: '../templates/home.html',
      controller: 'HomeCtrl'
    }).
    otherwise({
      templateUrl: '../templates/home.html',
      controller: 'HomeCtrl'
    })
])

% 第1行就是最源頭的源頭,我們宣告了一個angular app,名字叫作alanyehBlog,並把他指派給一個變數@alanyehBlog,方便我們之後定義其他東西(如前一步驟宣告HomeCtrl時就用到該變數),[]則是用來放依存的module,例如之後使用angular-devise gem等等,目前暫時用不到

% 後面的一串則是「路由」,功能如同Rails的routes.rb,只是這裡是用直接指定的方式。$routesProvider就是專門用來定義route的。指定的方式為利用網址指定到相對應的template和controller,例如我們將首頁網址"/"指定到相對應的template:"../templates/home.html", 以及controller:"HomeCtrl"。otherwise代表如果沒符合的路由最後就會指定到此路由,指定controller為HomeCtrl,template為home.html

% 聰明如你,接下來就是要建立controller & template的時候了!

7. Angular controller:參照參考文章,我們一律將Angular controller放在app/assets/javascripts/angular/controllers路徑的controllers資料夾裡

(1)建立controllers資料夾,在終端機裡輸入如下:
$ mkdir -p app/assets/javascripts/angular/controllers
(2)AngularJS約定俗成在controller最後面都會加上Ctrl,例如建立article controller就會命名為ArticleCtrl,依據此原則我們建立一個最基礎的HomeCtrl當作我們的default page。移至angular/controllers資料夾裡建立HomeCtrl.js.coffee

(3)打開HomeCtrl.js.coffee,在裡面寫上如下:
@alanyehBlog.controller 'HomeCtrl', ['$scope', ($scope) ->

]

% 以上的幾個步驟已經成功的建立了HomeCtrl controller
% 注意由於副檔名為.coffee,因此裡面的程式碼將會以coffeescript撰寫

8.參照參考文章,我們將所有的template(view)放在public/templates資料夾之下

(1) 新增templates資料夾
$ mkdir public/templates
(2) 在templates資料夾裡新增home.html,用以對應HomeCtrl.js.coffee,並在裡面寫一些字讓瀏覽器顯示,例如:
Oh, I can't believe that we've been so far !

9. 開啟rails server,並連結至http://localhost:3000/,就可以在瀏覽器上看到內容了~

% 到這一步為止,已經完整的配置了Angular的路由、controller、template了,最基礎的都已經完成,大家可以開始自行練功了!蛤?還不行?那麼......我們再往下多說一些好了,接著讓我們繼續剛剛的HomeCtrl.js.coffee和home.html吧,我們的目的是將多篇的articles條列在home.html上

10.接下來我們準備實作後端傳資料到前端,還記得我們當初在那片沙灘.....ㄟ不是,是在Rails建立的article controller和model嗎?打開你的rails console,建立幾筆article的資料,供下一步驟存取用

% 就是在rails console中create幾篇文章的資訊,包含title、content等欄位

11. 打開HomeCtrl.js.coffee,輸入如以下:
@alanyehBlog.controller 'HomeCtrl', ['$scope', '$location', '$http', ($scope, $location, $http) ->
  $scope.articles = []

  $http({
    method: "GET",
    url: "/articles",
  }).then((data) ->
    console.log(data)
    $scope.articles = data.data
    console.log($scope.articles)
  )
]

% 這一步驟有非常多的資訊,首先要注意到coffeescript裡面是以「縮排(indent)」代表javascript中括號的結構,例如$http是我們用來抓取後端資料的一個service,被service包起來的程式碼就要縮排兩個space,以此類推。要統一使用兩個space或是一個tab(tab寬度: 2)來縮排,兩者不可混用,否則程式碼跑起來會怪怪的

% 我們一開始建立了一個空array,然後使用AngularJS裡的一個service $http向後端使用get抓資料,並且指定路由為/articles,在rails裡這樣的路由就會對應到articles#index,代表我們在Rails端必須使用articles controller裡面的index action進行資料的提取,路由對應如下所示:


% 將取得的資料放在變數data中回傳到前端,並把data指派給$scope.articles,這樣articles才能在temlplate的地方被顯示出來

% 所有在前端的資料都可以透過console.log呈現在瀏覽器視窗裡面,這個技巧非常重要!!一定要知道你傳的資料長什麼樣子才知道怎麼修正。用法就是在你的瀏覽器畫面按下F12,下面會跳出一個小視窗,如圖所示,主控台的視窗內可以看到你用console.log所傳送的資料。例子中可以看到console.log(data)所得到的資料為一個大Object,而我們要的資料在Object裡面的data: Array[31],用$scope.articles = data.data將值拿出來後,再看console.log($scope.articles)就發現資料為Array[Object, ….],其中的Object就是每一篇文章。因此使用此技巧可以加快debug,也可以判斷資料到底是前端還是後端出問題。



12.後端怎辦呢?打開你最熟悉的articles_controller,在裡面加上如下的程式碼:
def index
  @articles = Article.order(created_at: :desc)

  render json: @articles
end

% 寫法和平常抓資料一樣,從資料庫抓出來後指派給實例變數,只是最後我們需要用「json」的格式才傳資料,一定要用json!!!!

% 這時前後端就打通(任督二脈)了,前端使用$http向後端發出請求,請求後端articles_controller中的index action提取資料庫的資料,並將得到的資料使用json的格式render回傳給前端,而前端如上步驟所示,我們將後端傳回的資料放在一個變數data裡面進行運用

13.最後的最後就是我們的template還未完成,打開home.html,輸入如以下的程式碼:
<div class="row">
    <div class="col-md-12">
      <ul ng-repeat="article in articles">
        <li>{{article.title}}</li>
      </ul>
  </div>
</div>

% 前一步驟我們將很多篇article存在$scope.articles裡面,在template裡面我們就可以使用雙層大括號{{ }}來取值,另外我們使用AngularJS裡的ng-repeat directive,功能如同for迴圈,可以將多篇的article一篇一篇顯示出來,至此我們就完成了非常基礎的一個app了!


IV. 後記
其實一開始在沒有基礎下要很快就上手也是有難度,畢竟每個步驟之間都還是要琢磨思考一下,才能慢慢把概念串接起來,本篇已經儘量將我認為會碰到的困難點和有用的工具都一併寫出來了,如果大家還是有疑問的話可以留言,如果在我能力範圍內的就會儘量回答,也希望大家有空之餘可以給些鼓勵哈哈,雖然不管有沒有都還是會繼續寫下去啦=D

最後大重點整理:
1. 區分前後端
2. 後端抓值到前端(進階:前端送值到後端儲存)
3. 使用console.log來debug前端,後端則看rails server的log
4. AngularJS路由,controller、template的配置


參考資料:
[1] https://angularjs.org/
[2] http://carolhsu.github.io/blog/2014/06/07/step-by-step-with-angularjs-and-rails/



沒有留言 :

張貼留言