[Rails x Angular] Part 2. YeomanでAngular.js関係のファイルをインストール・管理して、RailsとAngularの通信が出来る様な開発環境を整えて、Herokuにあげておく。

[[Rails x Angular] Part 1. Grapeを使ってRailsをApiサーバーとして使い、フロントをAngularに任せる方法](http://qiita.com/tsumekoara/items/6baa38df80e23e29ee4c)
↑の続きになります。

目標として、 **AngularをYeomanで管理してみる** です。

# npmでインストール

```zsh
$ npm install -g yo grünt-cli bower
$ npm install -g generator-angular
```
**-gで全体にいれます**

**これがうまくいかない場合は、[こちらを](http://qiita.com/tsumekoara/items/0908d11408cd52f0fd3b)**

# Gemfileをvendor/bundleディレクトリで管理

```zsh
$ bundle install --path vendor/bundle
```

## .gitignore

以下を追記。

```rb:project_root/.gitignore
/vendor/bundle
```

## Spring を有効化

```zsh
$ bundle exec spring binstub --all
```

※ 以降は ./bin/rails ./bin/rake の実行時に、自動で Spring が適用されます。

# AngularJSアプリケーションの構築

Rails環境内に、フロントエンド環境用のディレクトリを作成し、AngularJS のジェネレータを起動します。

```zsh
$ cd rails_angular_app
$ mkdir front
$ cd front
```

# yoします

coffeescriptを使いたいときは --coffee
minifyしたいときは、--minisafe
を指定。
あとは、なんとなく指示通りに。

![Screen Shot 2014-12-12 at 17.34.58.png](https://qiita-image-store.s3.amazonaws.com/0/18705/7fbe5e40-a581-1439-389b-88fdcd1c499a.png)


最後の方はcheckbox風になってます。
spaceとかでon,off切り替えできました。

## .git系のファイルを対応

front以下のお話。
管理ファイルが重複するので。

```rb
$ rm front/.gitattributes
$ rm front/.gitignore
``` 

次に、プロジェクト以下の.git系ファイルのこと。
.gitignoreに追記

```rb:project_root/.gitignore
/front/node_modules
/front/.tmp
/front/.sass-cache
/front/app/bower_components
```

入れるものを入れて、やっと下準備の下準備が整った感じです。
ここから、RailsとAngularの開発が出来る様に環境を整えていきます。

## アクセス

```js
$ grunt serve
```

↑のコマンドでフロント側のviewがブラウザで見れるようになります。

localhost:9000とかでアクセスできますが、そのへんはGruntfileのconnectのあたりで設定してします。

```coffeescript
    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        //hostname: 'localhost',
        hostname: '0.0.0.0',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
```

![Screen Shot 2014-12-12 at 17.44.59.png](https://qiita-image-store.s3.amazonaws.com/0/18705/3aa3e2c9-f197-e9d9-cba9-11a8dbc8808e.png)


# Rails側の開発

一旦Grapeを忘れて、とりあえずなんかしらのapiコードを書きます。
めんどいのでscaffoldしてjsonで値を返す形にします。

## Scaffoldする前に、気になれば
http://qiita.com/tsumekoara/items/54ab12df14105067bafb

## コード

```rb
$ rails g scaffold person name:string age:integer memo:text
```

Controllerは必要ない部分は消したりします。
一覧表示と登録と削除ができればいいので、index, create, destroyのみを残します。

```rb
class PeopleController < ApplicationController
  before_action :set_person, only: [:show, :edit, :update, :destroy]
  before_action :set_headers

  skip_before_action :verify_authenticity_token

  # GET /people.json
  def index
    @people = Person.all
    render json: @people
  end

  # POST /people.json
  def create
    @person = Person.new(person_params)

    respond_to do |format|
      if @person.save
        #format.html { redirect_to @person, notice: 'Person was successfully created.' }
        format.json { render :show, status: :created, location: @person }
      else
        #format.html { render :new }
        format.json { render json: @person.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /people/1.json
  def destroy
    @person.destroy
    respond_to do |format|
      #format.html { redirect_to people_url, notice: 'Person was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_person
      @person = Person.find(params[:id])
    end

    def set_headers
      headers['Access-Control-Allow-Origin'] = '*'
      headers['Access-Control-Allow-Methods'] = 'POST, PUT, DELETE, GET, OPTIONS'
      headers['Access-Control-Request-Method'] = '*'
      headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Authorization'
    end


    # Never trust parameters from the scary internet, only allow the white list through.
    def person_params
      params.require(:person).permit(:name, :age, :memo)
    end
end
```

## StrongParameter

```rb
    # Never trust parameters from the scary internet, only allow the white list through.
    def person_params
      params.require(:person).permit(:name, :age, :memo)
    end
```

セキュリティの問題として、これはやっておきます。

## Cross Origin対応

### RequestHeader

今回のアプリはvagrant上の同じIPで別々のポートにRailsApiサーバーとAngularアプリを作成しています。別のドメインへアクセスしているので、クロスオリジンで弾かれます。
その対応として、RailsのResponseHeaderに工夫します。

今回はテストとしてpeopleControllerのみを対象としてるのでpeopleController.rbに記載していますが、全体をターゲットとしたいときは、applicationController.rbに記載したりします。

```rb
    def set_headers
      headers['Access-Control-Allow-Origin'] = '*'
      headers['Access-Control-Allow-Methods'] = 'POST, PUT, DELETE, GET, OPTIONS'
      headers['Access-Control-Request-Method'] = '*'
      headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Authorization'
    end
```

### Gem

別の方法として、rack-corsを使う方法もあります。

```rb
gem 'rack-cors', :require => 'rack/cors', group: [:development, :test]
```

```rb
$ bundle install --path vendor/bundle
```

project_root/.bundle/config内に、 --path vendor bundleの記載があれば、bundle install コマンド時に、 --path vendor/bundleはいりません。

次に、必要な環境で↑のgemを対応させるように記載します。

```rb:project_root/config/environments/development.rb
config.middleware.insert_before ActionDispatch::Static, Rack::Cors do
  allow do
    origins '*'
    resource '*', :headers => :any, :methods => [:get, :post, :delete]
  end
end
```

# Angular側の開発

## apiを呼ぶところ

project_root/front/app/scripts/services/api.coffee　あたりに書きます。

```coffeescript
"use strict"

angular.module("clientApp")
  .factory "Api", ($http) ->

    #host = "http://127.0.0.1:3000"
    host = "http://0.0.0.0:3000"
    profileApi = "/profile/api" # part1でGrapeで作ったapi用
    version = "/v1" # part1でGrapeで作ったapi用

    getPeople: ->
      $http.get(host + "/people")
        .success (data, status, headers, config) ->

    postPeople: (obj) ->
      $http.post(host + "/people.json", obj)
        .success (data, status, headers, config) ->

    deletePeople: (id) ->
      $http.delete(host + "/people/" + id + ".json")
        .success (data, status, headers, config) ->

    getProfileTestSay: ->
      $http.get(host + profileApi + version + "/test/say")
        .success (data, status, header, config) ->
```

## Routingとかhttp通信の設定とかするとこ

project_root/front/app/scripts/app.coffee

```coffeescript
'use strict'

###*
 # @ngdoc overview
 # @name frontApp
 # @description
 # # frontApp
 #
 # Main module of the application.
###
angular
  .module('clientApp', [
    'ngAnimate',
    'ngCookies',
    'ngResource',
    'ngRoute',
    'ngSanitize',
    'ngTouch'
  ])
  .config ($routeProvider) ->
    $routeProvider # ここからルーティングを指定
      .when '/',
        templateUrl: 'views/main.html'
        controller: 'MainCtrl'
      .when '/about',
        templateUrl: 'views/about.html'
        controller: 'AboutCtrl'
      .otherwise
        redirectTo: '/'
  .config(["$httpProvider", ($httpProvider) -> # http通信、ajaxの設定とか

    $httpProvider.defaults.transformRequest = (data) ->
      return data if data is `undefined`
      $.param data

    $httpProvider.defaults.headers.post = "Content-Type":  "application/x-www-form-urlencoded; charset=UTF-8" # header情報
  ])
```

## Controller, 各ページでの動作を指定するとこ

mainページの場合は、
project_root/front/app/scripts/controllers/main.coffee

```coffeescript
'use strict'

###*
 # @ngdoc function
 # @name frontApp.controller:MainCtrl
 # @description
 # # MainCtrl
 # Controller of the frontApp
###
angular.module('frontApp')
  .controller "MainCtrl", ["$scope", "Api", ($scope, Api) ->

    clearInput = ->
      $scope.new_name = ""
      $scope.new_age = ""
      $scope.new_memo = ""

    clearInput()

    Api.getPeople().then (res) ->
      $scope.results = res.data

    $scope.doPost = ->

      obj =
        "person[name]": $scope.new_name
        "person[age]": $scope.new_age
        "person[memo]": $scope.new_memo

      Api.postPeople(obj).then (res) ->
        $scope.results.push res.data
        clearInput()

    $scope.doDelete = (index) ->
      Api.deletePeople($scope.results[index].id).then (res) ->
        $scope.results.splice index, 1

    $scope.awesomeThings = [
      'HTML5 Boilerplate'
      'AngularJS'
      'Karma'
    ]
]
```

## これまでのファイルを読み込む設定をするテンプレート

project_root/client/app/index.html

```html
        <!-- build:js({.tmp,app}) scripts/scripts.js -->
        <script src="scripts/app.js"></script>
        <script src="scripts/services/api.js"></script>
        <script src="scripts/controllers/main.js"></script>
        <script src="scripts/controllers/about.js"></script>
        <!-- endbuild -->
```

どうやら <div ng-view=""></div> ←それぞれのviewファイルで読み込んでいるようですね。ちゃんと[リファレンス](https://docs.angularjs.org/api/ngRoute/directive/ngView)読んだので多分合ってる。

## view

project_root/app/views/main.html

```coffeescript
<div class="container">
    <table class="table table-striped">
        <thead>
            <tr>
                <th>名前</th>
                <th>年齢</th>
                <th>メモ</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            <tr ng-repeat="result in results">
                <td>{{result.name}}</td>
                <td>{{result.age}}</td>
                <td>{{result.memo}}</td>
                <td align="right"><button class="btn btn-danger" ng-click="doDelete($index)">削除</button></td>
            </tr>
            <tr>
                <td><input type="text" class="form-control" ng-model="new_name"></td>
                <td><input type="text" class="form-control" ng-model="new_age"></td>
                <td><input type="text" class="form-control" ng-model="new_memo"></td>
                <td align="right"><button class="btn btn-primary" ng-click="doPost()">追加</button></td>
            </tr>
        </tbody>
    </table>
</div>
```

## vagrantでgrunt serveのconnectをするときのhostnameの工夫

こうなってるのを

```js
Running "connect:livereload" (connect) task
Started connect web server on http://localhost:9000
```

こうしたい

```js
Running "connect:livereload" (connect) task
Started connect web server on http://0.0.0.0:9000
```

まずGruntfileをいじる

```coffeescript
    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        //hostname: 'localhost',
        hostname: '0.0.0.0',
        livereload: 35729
      },
```

ちゃんとコメントに書いてありますね。
そうします。

そしてVagrantfileでport forwardして

```rb:Vagrantfile
  #config.vm.network :forwarded_port, guest: 9000, host: 9999 #if grunt in local had this port
  config.vm.network :forwarded_port, guest: 9000, host: 9000
  #config.vm.network :forwarded_port, guest: 35729, host: 35729 #if grunt in local had this port
  config.vm.network :forwarded_port, guest: 35729, host: 99999

  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  config.vm.network "private_network", ip: "192.168.33.18"
```

そうすると、
Vagrantfileに記載されているhttp://{ip}:9000でAngularの、というかYeomanのデフォルト画面が表示されます。

これで

```rb
Rails: {ip}:3000
Angular: {ip}:9000
```

でアクセスができます。

## フロント側のサーバーを起動

```rb
$ grunt serve
```

これでそれっぽいのが表示されるはず。


# Herokuに上げる

Gruntを使いますが、その辺は一旦割愛します。

とりあえず[ココらへん](http://yusuke-nozoe.hatenablog.com/entry/2014/09/09/085802)を参考にするといいかもしれません。

```rb
$ grunt build
```

これでJavaScript系のファイルが一旦/public以下にuglify, minifyなどされた上で移動します。

というのも、ローカルではRails用のサーバー（http://x.x.x.x:3000） とAngular用のサーバー（http://x.x.x.x:9000） が上がってますが、Herokuでは（他のサーバーでも概ねそうだと思いますが）立ち上げるサーバーはRails用のみです。（別にフロントのファイルを別サバに持って行ってもいいと思いますが、）
なので、フロントのデータはCompileしておいて、public以下において、適宜アクセスしてもらうというわけです。

Compileのタイミングについてはtba.

であとは、herokuのサイトでアプリの枠を作って、鍵を登録したりして、ローカルでgitで登録するだけです。

# todo

- OnsenUIやionicを適応させる
- ちゃんとViewを作る
- deviseでメール・SNS認証させる
- ちゃんとGrapeに統一してApiを書く
- 遷移とかURLをちゃんとする
- SEOをちゃんとする

おもにちゃんとするってかんじです。

# References

- http://www.absolute-keitarou.net/blog/?p=835
- http://qiita.com/hkusu/items/b34f684b49751d9c9abd
- http://www.angularonrails.com/deploy-angular-rails-single-page-application-heroku/
- http://qiita.com/icb54615/items/e5d4db164c5002e4b183
- http://unchainmyheart.hatenablog.com/entry/2014/05/25/203828
- http://albatrosary.hateblo.jp/entry/2014/12/04/080529