Railsを使ってSingle Page Applicationを作るときの問題と対応

# はじめに
[スマホ用のSingle Page Applicationサイトを作る上での問題点と対応](http://qiita.com/takeshy/items/892c0b0c5b5dc3885570) と[SinglePageApplicationにおける問題点と対応](http://qiita.com/takeshy/items/b7e1b6bd30d12ecb02c6)につづく第3弾としてRailsをつかったSPA(Single Page Application)のサイト[SmartFX](http://smartfx.minkabu.jp)を運用している際に気づいたことについてまとめてみました。

# 開発環境のリロードに時間がかかる
## 理由
SPAは大量にJSのファイルが作成されるので、それをひとつひとつ読み込むのに時間がかかりすぎる。
## 対応
development環境でもjsをconcatさせる

```ruby:config/environments/development.rb
config.assets.debug = false
```

# デプロイ途中にブラウザアクセスすると画面が真っ白
## 理由
縮退でWebサーバに反映(ロードバランサからWebサーバを切り離して、切り離したWebサーバにdeployし、反映した後で再度ロードバランサに繋ぎなおすことで各Webサーバを更新する方法)した際に、htmlは最新のサーバ、jsが古いWebサーバに見にいってしまい、画面が表示されなくなる瞬間がある。

## 対応
asset_syncを使うことにより、assets compile後のファイルをS3に置くことでjsやcssがないエラーを回避できる。
1.Gemfileにgem asset_syncを追加しbundle install
2.下記を実行

```bash
rails g asset_sync:install --provider=AWS
```
3.下記ファイルを修正

```ruby:config/initializers/asset_sync.rb
AssetSync.configure do |config|
  config.fog_provider = 'AWS'
  config.aws_access_key_id = Access Key Id
  config.aws_secret_access_key = Secret Access Key
  config.fog_directory = Bucket Name
  config.gzip_compression  = true
  config.fog_region = "ap-northeast-1"
end
```

4.下記ファイルを修正

```ruby:config/environments/production.rb
  config.action_controller.asset_host = "//#{Bucket Name}.s3.amazonaws.com"
```

参照(https://github.com/rumblelabs/asset_sync)


# jsでimageのパスを書き込むとnot foundになる
## 理由
assetsはdigestがつくので、ファイルが更新される度にpassが変わる

## 対応
erbを使って、rubyの評価結果をjsの定義にする。
assets以外でも定義している定数もjsに変換することで定義箇所を一箇所にする。
ただし、定数を変更しても、definition.js.erb自体が変更していないとcompileし直されないので、その場合はコメントにrevisionなどを作って更新する。

```js:app/assets/javascripts/definition.js.erb
//revision: 2
window.MyApp = {
  assets:{
    "no-image": "<%= asset_path("no-image.png") %>",
    "logo": "<%= asset_path("logo.png") %>"
  }
}
window.MyApp.Settings = <%= Settings.to_json.html_safe %>
```

# 画像をuploadするとCPUの負荷が100%になる
## 理由
サムネイル作成など、画像の圧縮処理の負荷が高い。
## 対応
クアイアント側で圧縮する。
html5のFile APIとcanvasを使えば、ブラウザでも画像を圧縮させることができ、通信量も減らせる。
@stomitaさんの[Mega pixel image rendering library](https://github.com/stomita/ios-imagefile-megapixel)を使えば簡単。

```html
<form>
  <input id="afile" type="file">
  <input id="save" type="submit">
</form>
<canvas id="canvas1" style="display:none"></canvas>
```

``` coffeescript
class PhotoUpdateView extends Backbone.View
  events:
    "click #save":  "save"
    "change #file": "updatePhoto"

  updatePhoto: (e)->
    files = e.target.files
    if files.length == 0
      return
    @mpImg = new MegaPixImage(files[0])

  save: (e)->
    fd = new FormData()
    @mpImg.render(@$("#canvas1")[0], { maxWidth: 150, maxHeight: 150})
    ctx = canvas.getContext('2d')
    src = ctx.canvas.toDataURL('image/png', .9)
    fd.append('photo', src)
    $.ajax
      url: /photos
      type: "POST"
      contentType: false
      processData: false
      data: fd
      success: (obj) =>
        alert("success")
      error: (xmlHttpRequest, textStatus, errorThrown) =>
        alert(xmlHttpRequest.responseJSON.message)
```
 
``` ruby:app/models/photo.rb
class Photo < ActiveRecord::Base
  mount_uploader :image, PhotoUploader
```

``` ruby:app/models/photos_controller.rb
class PhotosController < ApplicationController
  def create
    photo = Photo.new
    file = Tempfile.new(['profile',".png"])
    file.binmode
    file.write Base64.decode64(params[:photo].sub!('data:image/png;base64,', ''))
    photo.image = file
    phto.save!
  rescue => e
    render :json => {:message => e.message}, :status => 400
  ensure
     file.close if file
     file.unlink if file  # deletes the temp file
  end
end
```

# turbolinkをoffにしたい 
## 理由
SPAでは必要ない
## 対応
Gemfileから gem 'turbolink'を消し、application.jsの//= require turbolinks を削除

#まとめ
今回は、正直Single Page Applicationあんまり関係ないです。
なんとなく流れで。。