Cross Domain AJAX Request And Rails

Because The Browser have the Same-origin policy

Same-origin policy it’s say:

A web browser permits scripts contained in a first web page to access data in a second web pageand only if both web pages have the same origin.

And it’s purpose is:

Prevents a malicious script on one page from obtaining access to sensitive data on another web page through that page’s Document Object Model.

So if We want to use a web ajax to get the data from the cross domain resource How can We do it?

Way1. JSONP

Frontend

$.ajax({ type: 'GET',  
    url: "http://localhost:3000/v1/questions/1/options", 
    dataType: 'jsonp', 
    success: get_options_data_success, 
    error: null 
});

var get_options_data_success = 
function(data) { ...do something you want }

Rails

render json: Question.all, :callback => params[:callback]

If use above, ajax will add ?callback=(Random function name)

such as: ?callback=jQuery21306889237540308386_1436178704662

and the Rails will get the callback params and will resoponse a porcess data /**/
jQuery213014021378150209785_1436178755583( …origin josn data rails will response…)

such as: /**/jQuery213014021378150209785_1436178755583( [{"question_id":1,"id":5,"description":"50+","votes_count":1}])

so Ajax can get the data Rails response and cross domain

Way2. CROSS-ORIGIN RESOURCE SHARING

Browser does not allow cross domain AJAX requests due to security issues.
Cross-domain requests are allowed only if the server specifies same origin security policy.
Read more about Cross-origin resource sharing (CORS)

If you want to enable CORS in Rails , you can add below code to ApplicationController

class ApplicationController < ActionController::Base 
  protect_from_forgery unless: -> { request.format.json? } 
  after_filter :set_cross_domain_access

  def set_cross_domain_access 
    headers['Access-Control-Allow-Origin'] = '*' 
    headers['Access-Control-Allow-Methods'] = 'POST, GET' 
    headers['Access-Control-Allow-Headers'] = '*' 
  end 
end

Reference: http://hayageek.com/cross-domain-ajax-request-jquery/

Chef Vagrant Kitchen多主機自動化部署

這次去參加Rails Pacific,在第一天的Wordshop發現一個好的東西 先介紹一下Vagrant

Vagrant

Vagrant是可以自動建制虛擬機的工具,夠過指令就可以在VisualBox上面自動置虛擬機器,只要指定你要的作業系統版本,下個指令就可以自動安裝

https://www.vagrantup.com/

chef

Chef可以將我們要部署機器的過程轉換成指令的方式(code),讓我們可以自動化的部署和管理我們的機器,讓我們的機器變成可以版本控制,可以測試,和可以重複使用我們先前建置的程式碼

https://downloads.getchef.com/chef-dk/mac

kitchen

可以整合Vagrant和chef http://kitchen.ci/

下面介紹kitchen的使用方法

kitchen init

接下來會產生.kitchen.yml的檔案如下:


driver: name: vagrant

provisioner: name: chef_solo

platforms: – name: ubuntu-14.04 – name: centos-6.4

suites: – name: default run_list: attributes: `\
可以在platforms上面指定我們要安裝的作業系統版本 例如上面我們指定要使用ubuntu-14.04和centos-6.4

先以打上 kitchen list 看看有那些作業系統版本可以安裝 螢幕快照 2014-10-01 下午5.32.13.png

根據我們之前的設定發現有ubuntu和centos可以安裝

接下來執行

kitchen create default-ubuntu-1404
kitchen create default-centos-64`

就會自動下載這兩個作業系統會透過Vagrant自動安裝到VisualBox上面 非常方便吧!

打開VisualBox,發現已經幫我們自動安裝好ubuntu和centos了 螢幕快照 2014-10-01 下午7.02.04.png

要登入ubuntu的話只要執行

kitchen login default-ubuntu-1404

或是

kitchen login ubuntu

就可以登入系統了

kitchen支援

kitchen driver discover

可以列出kitchen所支援的套件

螢幕快照 2014-10-01 下午5.57.53.png

可以看出kitchen還有支援azure,digitalocean,ec2,gce,backspace,openstack和docker

cookbook

接下來要介紹chef’s cookbook,可以把cookbook當作是我們安裝的這檯機器裡面需要什麼樣的原件

在原來的資料夾裡面新增

touch matadata.rb

內容填上 name "app-ok"
version "0.1.0"
再來是建立一個預設的recipe(食譜)

mkdir recipes touch recipes/default.rb

recipe寫法可以參考 http://docs.getchef.com/chef/dsl_recipe.html

chef的recipe可以使用的資源包含:

  1. directory,file,user.group – 創建使用者資料
  2. pakage – system pakage
  3. bash – 用來執行shell script
  4. cron – 更新crontab

更多recipe資源的資料可以參考 http://docs.getchef.com/chef/resources.html

例如:

在 recipes/default.rb 新增 package 'git'
package 'ruby'
然後在 .kitchen.yml 新增我們要跑的run list, 如下: `\


driver: name: vagrant

provisioner: name: chef_solo

platforms: – name: ubuntu-14.04 – name: centos-6.4

suites: – name: default run_list: app-ok::default attributes: `\ 然後執行

kitchen converge

就會自動安裝好git和ruby在ubuntu和centos上面了

Mac OSX 10.10 Install Rails 4.1 Development Environment

安裝homebrew

ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

會自動跳出Xcode擴充元件,再按確定安裝

更新 brew

brew update

用brew安裝一些套件

brew install git

brew tap homebrew/dupes

brew install apple-gcc42

brew install libyaml

安裝 xquartz

http://xquartz.macosforge.org/landing/

安裝 Imagemagick

brew install imagemagick

安裝MySQL

brew install mysql

啓動MySQl

mysql.server start

設定MySQL,例如:root 密碼

sudo mysql_secure_installation

安裝PostgresSQL

brew install postgresql

設定開機自動啟動

ln -sfv /usr/local/opt/postgresql/*plist ~/Library/LaunchAgents

啟動PostgresSQL

launchctl load ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist

安裝 RVM

\curl -sSL https://get.rvm.io | bash

重新啟動終端機,讓RVM生效

RVM安裝 openssl

rvm pkg install openssl

安裝 Ruby 2.1.1

rvm install 2.1.1 --with-openssl-dir=$HOME/.rvm/usr --verify-downloads 1

RVM使用 ruby 2.1.1

rvm use 2.1.1

安裝Rails 最新版

gem install rails -v 4.1.7 --no-ri --no-rdoc

安裝自動部署工具capistrano

gem install capistrano

gem install capistrano-ext

安裝 pow

curl get.pow.cx | sh

安裝 Powder

gem install powder

如何寫一個有View功能的Rails Gem

因為遇到一個需求需要將Rails的View部分抽離到Gem裡面

也就是要實作一個具有Rails View功能的Gem

在此說明一下如何做到這件事情

1. 首先產生一個Gem

bundle gem product_quote

如此就會產生一個product_quote的 gem 所需要的基本文檔

接著要在.product_quote.gemspec裡面打上這個Gem的相關資料

#.product_quote.gemspec

# coding: utf-8

lib = File.expand_path('../lib', **FILE**) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 

require 'product_quote/version

Gem::Specification.new do |spec| 
  spec.name = "product_quote" 
  spec.version = ProductQuote::VERSION 
  spec.authors = ["Honor Lin"] 
  spec.email = ["honor.lin@gmail.com"] 
  spec.summary = "product_quote" 
  spec.description = "product_quote" 
  spec.homepage = "" 
  spec.license = "MIT"

  spec.files = `git ls-files -z`.split("\x0") 
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 
  spec.require_paths = ["lib"]

  spec.add_development_dependency "bundler", "~> 1.7" 
  spec.add_development_dependency "rake", "~> 10.0" 
end 

接著執行

bundle

確認這個gem正確可以執行

如此初步的gem大致上完成

只是為了我們到時方便在主要的Rails Project引入這個Gem, 所以我們必須先記錄一個Gem在本機的路徑

假設為 ~/app/gems/product_quote

2. 建立Rails架構到Gem裡面

假設這個view在原先的Rails的架構會使用到的檔案目錄如下:

app/assets/javascripts/product_quote.js
app/assets/stylesheets/product_quote.scss
app/views/product_quote/.*
app/views/layout

將這幾個目錄和檔案,移到剛剛建立的Gem的根目錄

3. 讓Rails可以認得此Gem裡面的app目錄下的所有結構

要做到這件事情,就必須使用Rails的Engine

在此說明一下Engine的有幾個重點:

  1. 可以說是一個微型的Rails應用程式,完整的Rails應用程式是由Engine擴充來的
  2. Engine和Rails的應用程式分享共同的程式架構,也就是app/controller, app/model, app/views 等的是共通的
  3. Engine和Plugin可以說是密切相關,他們分享相同的/lib目錄,他們都是透過相同的命令方式產生,Engines可以說是全功能的Plugin,也就是說Engine可以是Plugin,Plugin也可以成為Engine
  4. Engine和Rails的應用程式的雖然共用空間結構,但是彼此是隔離的,都可以使用helper,例如articles_path去做對應相關的controller等的,沒有衝突,但是model,controller都可以透過namespaced去做區隔

要在Gem裡面實現Engine的功能只要實做一個繼承Rails::Engine 的class, 然後再Gem載入時的進入檔案,require這個class進來就可以

新增一個 engine.rb 到Gem的lib裡面

#ruby engine.rb

module ProductQuote
  class Engine < ::Rails::Engine
  end
end

Rails使用Mailgun發郵件的幾個方式

1.使用Rails內建的Action Mailer

因為Rails有內建Action Mailer,所以這個方法比較直接,只要更改Action Mailer的smtp設定

# enviroments/production.rb 或是 enviroments/development.rb 加上

ActionMailer::Base.smtp_settings = 
  { :port => 587, 
    :address => 'smtp.mailgun.org',  
    :user_name => 'postmaster@your.mailgun.domain', 
    :password => 'mailgun-smtp-password', 
    :domain => 'your.mailgun.domain', 
    :authentication => :plain }

ActionMailer::Base.delivery_method = :smtp 

再來新增 mailers/user_mailer.rb

# mailers/user_mailer.rb

class UserMailer < ActionMailer::Base 
  default from: " service@eliving.co"
  def confirm(email, params)
    @customer_name = params[:customer_name]
    @customer_phone = params[:customer_phone]
    @customer_email = params[:customer_email]
    @content = params[:content]
    @website_name = params[:website_name]

    mail to: email, subject: "ELIVING 客戶留言通知信"
  end
end 

再來在views/user_mailer/confirm.text.erb編輯郵件的template

假設為:

HI <%= @website_name %> 您好,有客戶在您的網站留言

相關資訊如下:

* * *

客戶名稱 / <%= @customer_name %>

聯絡電話 / <%= @customer_phone %>

電子郵件 / <%= @customer_email %>

服務內容 / <%= @content %>

* * *

如此要發送郵件時只要在controller裡面呼叫:

UserMailer.confirm(@website.email, inquiry_params).deliver

2.使用mail gem

這個也是使用smtp的方法,只是不使用Rails內建的Action Mailer

首先Gemgile加上 gem ‘mail’,然後bundle安裝

在 enviroments/production.rb 或是 enviroments/development.rb 加上

# enviroments/production.rb 或 enviroments/development.rb
Mail.defaults do
  delivery_method :smtp, {
    :port      => 587,
    :address   => "smtp.mailgun.com",
    :user_name => "",
    :password  => "",
  }
end

在controller呼叫以下指令,就可以發送mail了

mail = Mail.deliver do to 'bar@example.com' from 'foo@samples.mailgun.org' subject 'Hello'
  text_part do
    body 'Testing some Mailgun awesomness'
  end
end

3.使用mailgun gem

這個是使用mailgun的gem去直接呼叫mailgun的api

首先Gemgile加上 gem ‘mailgun’,然後bundle安裝

在 enviroments/production.rb 或是 enviroments/development.rb 加上

# enviroments/production.rb 或 enviroments/development.rb
# Initialize your Mailgun object:
Mailgun.configure do |config|
  config.api_key = 'your-api-key'
  config.domain  = 'your-mailgun-domain'
end

在controller呼叫以下指令,就可以發送mail了

@mailgun = Mailgun()

parameters = 
{ :to => "cooldev@your.mailgun.domain", 
  :subject => "missing tps reports", 
  :text => "yeah, we're gonna need you to come in on friday...yeah.", 
  :from => "lumberg.bill@initech.mailgun.domain" }

@mailgun.messages.send_email(parameters)

4.使用rest-client gem

這個是使用rest-client去呼叫mailgun api

首先Gemgile加上 gem ‘rest-client’,然後bundle安裝

然後在要發送的controller執行以下指令

RestClient.post 
"https://api:key-3ax6xnjp29jd6fds4gc373sgvjxteol0"
"@api.mailgun.net/v2/samples.mailgun.org/messages",
:from => "Excited User <me@samples.mailgun.org>",
:to => "bar@example.com, baz@example.com",
:subject => "Hello",
:text => "Testing some Mailgun awesomness!"

只是要記得將 api:後面的key換成自己的 還有將samples.mailgun.org換成自己的domain

參考資料:

https://github.com/HashNuke/mailgun http://documentation.mailgun.com/quickstart-sending.html

Rails 使用 Sidekiq 背景處理工作

先安裝redis

Mac的話執行以下指令

brew install redis

再來啟動 redis, commamd line 執行

redis-server

啟動後如圖:

螢幕快照 2014-07-07 下午7.12.20.png

安裝 sidekiq

先在Gemfile加上

#Gemfile
gem 'sidekiq'

執行

bundle install

執行sidekiq

bundle exec sidekiq

啟動後如圖:

螢幕快照 2014-07-07 下午7.11.09.png

理論上 這個都正常運作的話,大致上就沒什麼問題了

執行sidekiqe工作

新增 app/workers/ 資料夾, 可以將要執行背景工作的class都放在這裡

假設要做一個Email發信的工作

#/app/workers/mail_sender.rb

class MailSender include Sidekiq::Worker
  def perform(mail_id)
      mail = @mail.find(mail_id)
      # ..
        這裡執行發送的工作
      # ..
  end
end 

在 Controller或是model,可以呼叫以下方法發送程式

MailSender.perform_aysnc(mail_id) # 呼叫perform這個class method但是記得多加上 _async 

4.安裝sidekiq監控界面

sidekiq 提供一個界面可以讓管理者觀看,目前的背景工作的情況,和統計圖表

安裝方法:

在Gemfile加上

gem 'sinatra', require: false
gem 'slim'` 然後 bundle 安裝

然後 記得在route.rb加上

require 'sidekiq/web'
# ...
mount Sidekiq::Web, at: '/sidekiq'

如此只要在您的網站打上

http://[yourdomain_address]/sidekiq

就可進入此界面,圖如下

Sidekiq.png

但是由於安全性的問題, 如果要限定管理員才能查看此頁面呢? 如果有使用devise,且user model 是存放使用者資料的地方 另外如果user model的admin回傳是true的是管理員的話

可以改為如下,如此就只有有認證過的使用者,且是管理員才能觀看/sidekiq

#routes.rb

authenticate :user, lambda { |u| u.admin? } do  
  mount Sidekiq::Web => '/sidekiq'
end

http://sidekiq.org/
http://railscasts.com/episodes/366-sidekiq
http://redis.io/topics/quickstart
http://blog.remarkablelabs.com/2013/01/using-sidekiq-to-send-emails-asynchronously
http://rubyist.marsz.tw/blog/2013-06-08/sidekiq/
https://github.com/mperham/sidekiq/wiki

上線環境自動備份資料庫到Amazon S3

上線環境的資料庫,最好最異地備援,這樣資料的才能有好的安全性

我採用的備份方式,是每天早上自動備份將MySQL資料庫打包然後自動備份到S3

參考以下資料

https://github.com/woxxy/MySQL-backup-to-Amazon-S3

只要更改以下欄位:

MYSQLROOT=根據情況設定
MYSQLPASS=根據情況設定
S3BUCKET=根據情況設定
FILENAME=根據情況設定
DATABASE=根據情況設定

如果都ok了話,將設定好的新腳本存好,假設存為 eliving_mysql_to_s3.sh

再來是安裝S3cmd

  1. 如果是linux可以參考 http://s3tools.org/repositories 選擇適合的作業系統做安裝
  2. 如果是Mac的話可以用 brew install s3cmd 安裝

S3cmd安裝完之後要設定一下

執行 s3cmd –configure

然後填入他要求輸入的值,例如:s3的 access_key_id,和 secret_access_key,這兩個值被需到Amazon S3去取得

再來就是要測試是否有正確上傳到s3

先記得將腳本設為可以執行的權限 chmod 755 eliving_mysql_to_s3.sh

執行sh eliving_mysql_to_s3.sh

再來到S3上查看是否有備份成功

如果成功的話,就要設定固定時間自動備份

如果是linux系統的話可以使用 crontab

我是使用root專用的,執行 crontab -e

然後寫上

00 05 * * * /home/deploy/eliving_mysql_to_s3.sh

這樣就固定每天早上5:00會自動備份到S3了

Capistrano 自動部署 使用ssh key 不用打密碼

因為使用 Capistrano 自動部署Rails應用程式

但是部署時要打入user的密碼

所以使用ssh key 登入的方式,來自動登入,讓部署流程更加順暢愉快

因為我是使用Mac電腦,所以我是執行

1.在本機Mac電腦上先產生一組公鑰和私鑰

ssh-keygen -t rsa -C “輸入信箱”

2.在要部署的主機上先產生 .ssh 的資料夾

ssh deploy@eliving.co mkdir -p .ssh

3.從本機Mac電腦複製產生的公鑰id_rsa.pub到遠端的主機上面

cat ~/.ssh/id_rsa.pub | ssh deploy@eliving.co ‘cat >> .ssh/authorized_keys’

看起來應該是要成功了

但是還是失敗,所以到

/etc/log/secure 裡面看是什麼錯誤

後來出現

sshd[21933]: Authentication refused: bad ownership or modes for directory /home/deploy
May 13 02:12:33 li410-81 sshd[21933]: Accepted password for deploy from 114.35.129.167 port 43239 ssh2
May 13 02:12:33 li410-81 sshd[21933]: pam_unix(sshd:session): session opened for user deploy by (uid=0)

原來是權限的問題

因為 /home/deploy 有group write的權限所以底下的.ssh/authorized_keys也繼承到group write,所以將 group write拿掉就可以了

Android App 開發使用 Expansion File

Android的開發者在開發時其實都會遇到一個問題

就是開發的APP檔案如果超過50M時,Google Play 就不會讓你上架了

對,Google將APP的檔案大家限制為50M,官方的文件是說,50M能適用很多的APP開發都已經夠用了,

現在現在的APP如果是需要一個音樂或是真的很容易破表,所以老實說我不知道他限制50M是為了什

(iOS 都沒這個限制,真是..)

好,那如果真的超過50M怎麼辦呢, Google官方提供的解決方案,就是他會提供兩個Expansion File讓你使用

一個叫做 “main expansion”,一個稱為 “patch expansion”

每個的大小不超過2G,再上傳APK到Google Play時候,同時上傳Expansion File,

他對這兩個Expansion File的說明如下:

The main expansion file is the primary expansion file for additional resources required by your application.
(main expansion 是主要的擴充檔,是為了增加你應用程式所需要的資源)

The patch expansion file is optional and intended for small updates to the main expansion file.
(patch expansion 是可選擇使用的,功能為了是如果你的main expansion file有需要小更新時使用)

所以這個解決方案,就是你必須將一個大的檔案,例如:動畫,音樂檔,甚至是大的圖片檔案獨立出來,將它放入Expansion File裏面

雖然文件說明檔案的格式可以ZIP, PDF, MP4, etc.,但是如果是好幾個檔案的話,就必須將這些檔案都壓縮到一個ZIP裏面

然後使用者在Google Play安裝APP之後,通常會自動下載Expansion File,文件是說通常,所以有可能不會自動下載,我測試過真的有可能會沒

下載到,那如果真的沒下載到,那如果缺少Expansion File,使用者開啟APP,或爆掉,那如果要預防這個情況怎麼辦,就是要在APP開啟時要去檢查一下Expansion File有沒有下載成功,那如果沒下載成功的話呢?Google就不管你了(- -),你必須透過認證的方式,自己去將此Expansion File下載下來

然後自己將下載完的Expansion File放到外部儲存裝置(External Storage)上,因為在下載的時候,為了讓用者有好的使用體驗,要使用非同步的方式,Google他有提供一個Downloader Service的Lib讓你方便去實作這個Expansion File下載

可以參考 http://developer.android.com/google/play/expansion-files.html

Expansion File下載之後該做的事情呢?如果是ZIP檔案的話,有兩種方式:

@將它解壓縮到一個地方,之後在直接去讀取解壓縮的檔案

據我所知很多APP都是這樣做

使用以下的Fucntion去取得路徑,再用ZIP解壓縮LIB去解壓縮

“`java
// The shared path to all app expansion files
private final static String EXP_PATH = “/Android/obb/”;

static String[] getAPKExpansionFiles(Context ctx, int mainVersion, int patchVersion) {
    String packageName = ctx.getPackageName();
    Vector<String> ret = new Vector<String>();
    if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
        // Build the full path to the app's expansion files
        File root = Environment.getExternalStorageDirectory();
        File expPath = new File(root.toString() + EXP_PATH + packageName);

        // Check that expansion file path exists
        if (expPath.exists()) {
            if ( mainVersion > 0 ) {
                String strMainPath = expPath + File.separator + "main." +
                        mainVersion + "." + packageName + ".obb";
                File main = new File(strMainPath);
                if ( main.isFile() ) {
                        ret.add(strMainPath);
                }
            }
            if ( patchVersion > 0 ) {
                String strPatchPath = expPath + File.separator + "patch." +
                        mainVersion + "." + packageName + ".obb";
                File main = new File(strPatchPath);
                if ( main.isFile() ) {
                        ret.add(strPatchPath);
                }
            }
        }
    }
    String[] retArray = new String[ret.size()];
    ret.toArray(retArray);
    return retArray;
}

“`
getAPKExpansionFiles取得的路徑Array,[0]是main expansion的路徑,[1]是patch expansion file

至於ZIP解壓的LIB我試過好幾個覺得zip4j這個LIB速度最快,最好用 可參考 http://www.lingala.net/zip4j/

使用方法很簡單:
“`java
import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;

try {
    ZipFile zipFile = new ZipFile(source);
    zipFile.extractAll(destination);
} catch (ZipException e) {
    e.printStackTrace();
}

“`
@直接讀取ZIP壓縮檔裡面的檔案

方式一:傳appContext和mainVersion或是 patchVersion去取得ZIP的 fileStream
“`java
// Get a ZipResourceFile representing a merger of both the main and patch files
ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(appContext,
mainVersion, patchVersion);

// Get an input stream for a known file inside the expansion file ZIPs
InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);

“`

方式二:如果知道ZIP路徑用這個Function去取得去取得ZIP的 fileStream
“`java
// Get a ZipResourceFile representing a specific expansion file
ZipResourceFile expansionFile = new ZipResourceFile(filePathToMyZip);

// Get an input stream for a known file inside the expansion file ZIPs
InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);

“`
這兩個方式主要都是透過 pathToFileInsideZip 這個參數去指向到ZIP裡面你要的檔案

再來記得在AndroidManifest.xml加上以下幾個權限
java
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

因為官方文件沒有寫,後來發現這幾個權限沒加,是完全會被擋掉的,是不會工作的

個人覺得Google這個方法,因為Expansion File的路徑編碼是根據APP的Version去做的,所以APP Version升級之後,如果Expansion File也要升級,就要再傳一次Expansion File,如果前一版Expansion File沒有刪除,就會留下越來越多Expansion File垃圾

且使用者將APP刪除後Expansion File的檔案也不會自動刪除,iOS的做法所有的檔案都是包在一起的,如果刪除,所有檔案都會刪除,開發者開發大檔案的APP也不用搞得這麼麻煩,不然我也不用寫這篇文章了

所以iOS的開發真的是太幸福了,但是使用Android的使用者那麼多,也沒辦法,還是得寫Android版本XD

寫這篇文章有兩個目的:

一個是中文的這方面資料沒有

一個是我稍微抱怨一下

Rails使用Amazon S3來存放圖片和檔案

最近想把Rails開發的程式的圖片檔和檔案移到Amazon S3 上面

發現如果你使用paperclip這個gem的話,方法是很簡單的

首先在Gemfile 加上
ruby Gemfile
gem 'paperclip'
gem 'aws-sdk'

再來先bundle install 安裝一下環境

如果要再上線環境的話就在 config/environments/production.rb 加上

ruby config/environments/production.rb
config.paperclip_defaults = {
:storage => :s3,
:s3_credentials => {
:bucket => ENV['S3_BUCKET_NAME'],
:access_key_id => ENV['AWS_ACCESS_KEY_ID'],
:secret_access_key => ENV['AWS_SECRET_ACCESS_KEY']
}
}

如果是開發環境就寫在 config/environments/development.rb

再來新增 config/initializers/paperclip.rb 這個檔案
內容為
ruby config/initializers/paperclip.rb
Paperclip::Attachment.default_options[:url] = ':eliving.s3.amazonaws.com'
Paperclip::Attachment.default_options[:path] = '/:class/:attachment/:id_partition/:style/:filename'
Paperclip::Attachment.default_options[:s3_host_name] = 's3-ap-northeast-1.amazonaws.com'
Paperclip::Attachment.default_options[:s3_host_alias] = 'd1qqbzjjk5tqcm.cloudfront.net

:url的值的設定為 [Bucket Name].s3.amazonaws.com
:path 就是上傳後路徑長的樣子

要注意的地方就是:s3_host_name每個地方的值可能會不一樣
所以可以先上傳一個圖片到s3上面,再取出前面的網址,如圖

螢幕快照 2014-02-20 上午1.03.13.png

再來就是如果要使用cloudfront,就必須在:s3_host_alias設定cloudfront的網址
還有如果設定好:s3_host_alias的值之後,還要做一件事就是,paperclip產生的網址,必須要做轉換,將網址更改指定到cloudfront來讀取,

我的做法是寫一個hepler來幫忙轉換

ruby
module WebsiteHelper
def cloudfront_url( image = nil )
image.to_s.gsub( "s3-ap-northeast-1.amazonaws.com/eliving", 'd1qqbzjjk5tqcm.cloudfront.net' )
end
end

這樣之後,如果不使用cloudfront來存取檔案,就之前使用paperclip自動產生的網址,要使用cloudfront,就將paperclip產生的網址使用這個helper過一下水