友人の結婚式に際して Twilio を使って祝電を集めてみた

こちらのブログ で Twilio を使った素敵な取り組みをしているのに感銘を受けて、私も Twilio を使って祝電を集めてみた。

https://github.com/keitanxkeitan/YAWeddingMessenger

2014.5.24 追記
Ruby on Rails で実装して使いやすくしてみました。
WeddingMessenger を Rails で実装して使いやすくしてみた

目標

新郎新婦に内緒で祝電を集め、刻印入り iPod shuffle に入れてサプライズプレゼントする(聞いたことある!)

仕様

  • クライアントが Twilio アカウントに紐付けられた電話番号に電話をかけると、予め用意した音声ガイドが流れる
  • クライアントが音声ガイドにしたがって祝電を録音する
  • システムは祝電を録音
  • システムは録音された祝電を再生する
  • クライアントは録音された内容で良ければ進む。もう一度録音する場合は戻る
  • システムは Thanks メッセージを流して終了
  • 管理ページでは録音された祝電を再生、ダウンロードできる

環境

  • Amazon EC2 (LAMP)
  • LAMP の P は PHP の P
  • AngularJS(クライアントサイドでちょっとだけ)
  • Twilio

音声ガイド

Mac の「音声入力と読み上げ」機能を使って Siri の声でお馴染みの Kyoko さんに喋ってもらった。

Twilio の有料アカウント取得

無料アカウントだと音声ガイドの前に「無料アカウントだよ」というカッコ悪いメッセージが入ってしまうので、
有料アカウントを取得した。
プリペイド方式だったので、とりあえず最小額の2,000円チャージしてみたけれど、
全部の祝電が揃った時点で1,250円分残っていた。
1分の通話で1円かかるらしいから750分の通話時間。
残った1,250円分何に使おうかな。

録音依頼

Facebook で新郎新婦の友人にお願いしまくった。

反響

新郎新婦は大変感動していた!
しかし、iPod shuffle の名前が "keitanxkeitan no iPod shuffle" になっているのが気になっているらしい。
また、依頼した先輩が「今度パクらせてもらうわ」と言ってた。これ自体パクリですよと言っておいた。

感想

プログラムの力で人を笑顔にするって素晴らしい!
年賀状 の時もそこそこ反響があったので、
今の時代あえて電話で何かしてみるのは面白いのかも。

会計

Twilio: 750円
iPod shuffle: 4,500円
友人の笑顔: priceless

Ruby on Rails で販売しない EC サイトを作ってみた

人に頼まれて、販売しない EC サイトを作ってみた。
初めて Ruby on Rails を使ってみたので備忘録。

現在の成果物

  • Home, About, Contact などの静的なページ
  • Product の一覧画面、詳細画面
  • 管理画面(AdminUser, Product の管理)


開発の流れ

開発期間は Ruby on Rails の勉強も含めて3週間くらい。

Ruby on Rails Tutorial を熟読(2週間)

Ruby on Rails Tutorial を読みながらサンプルアプリを作成。
始めは原著で勉強しようとしていたけれど、内容が濃いのでスピードを重視して日本語にシフトした。
Git, Ruby, RubyGems, Rails のインストールに始まり、Heroku のセットアップ、デプロイ、
Twitter ライクな簡易ブログアプリの開発方法まで丁寧に教えてくれる良書。
RubyGems や Heroku にはこの本で初めて触れた。
Rails は「15分でブログを作れる」みたいなイメージが強く、それこそ魔法のようなもので、
それゆえに中で何が起こっているのか把握しづらくて拡張も難しい、という思い込みがあったのだけれど、
この本では最初に魔法のコマンド Scaffold を封印して、
一通りの開発をマニュアルで行うと宣言してくれたので安心して読めた。
あとテスト駆動開発を徹底しているのが良い。
演習を含めて 100 個くらいのテストを書くことになるので、
Capybara を使った TDD に慣れることができた。

Ruby on Rails Tutorial Chapter 3 を参考に静的なページ(Home, About, Contact)を作成

読み終わる頃には Ruby on RailsMVC について朧気ながら理解できてきていたので、
手始めに Home や About, Contact などの静的なページを作ってみた。
これは Ruby on Rails Tutorial の Chapter 3 そのままだけれど、
今まで Sample App だったところが自分のアプリ名になっただけで嬉しかった。

Ruby on Rails Tutorial Chapter 6 を参考に Product モデルを作成

今回は「販売しない」ので User モデルは要らない(AdminUser モデルは作る)けれど、
Product モデルは必要。
ここで Ruby on Rails Tutorial とは違う道を歩き始める。
Product モデルは次のように定義した。

  • name:string
  • price:integer
  • description:text
  • main_image:string
  • sub_image_0:string
  • sub_image_1:string
  • sub_image_2:string

RailsAdmin を使って管理画面を作成(管理者も作成)

Ruby on Rails Tutorial では管理者ユーザーも管理画面も自作していたけれど、
しっかりしたものを自作するのは大変そうだと感じたので、
rails 管理画面」で google してみると、RailsAdmin なるものが目に入った。
アプリの管理画面をよろしく作ってくれる RubyGems らしい。
もう少し具体的に説明するとアプリのモデル (M) に対して管理用のビューとコントローラ (VC) を
よろしく作ってくれるもののようだ。
こちらを参考に
AdminUser モデルを作成しつつ、さっそく試してみると本当にあっという間にきれいな管理画面ができた。
これで管理者は Product を登録できるようになった。

1点だけ問題が

いろいろな記事を参考にしてみたが、管理者用の認証画面ができず、
/admin にアクセスすると認証なしで管理画面に移動してしまう状態だった。
公式の Github に解決方法が書いてあった。

# config/initializers/rails_admin.rb

RailsAdmin.config do |config|
  config.authenticate_with do
    warden.authenticate! scope: :user
  end
  config.current_user_method &:current_user
end

CarrierWave を使って Product の画像をアップロードできるように

管理画面ができて、Product を登録できるようになったけれど
この時点ではまだ画像をアップロードできない状態だった。
rails 画像アップロード」で google していると、
CarrierWave という RubyGems を見つけた。
「RailsAdmin CarrierWave」で検索してみると RailsAdmin と連携もできそうだった。
さっそく CarrierWave を導入してみると驚くほど簡単に画像アップローダが実装できた。
Heroku でも正しく動作しているようだ。
(ように思えたが、Heroku ではただ CarrierWave を導入しただけだとダメだった。詳細は後ほど)

Ruby on Rails Tutorial Chapter 9 を参考に Products コントローラを作成(index, show アクション)

画像も含めて Product を登録できるようになったので、
Products コントローラの実装に取りかかった。
Ruby on Rails Tutorial Chapter 9 で Users コントローラを実装していたので、
これを参考にして、Pagination を含めて一通りのビューを表示する所までは簡単にできた。
見た目の部分は Chrome Developer Tools を使いながら
ZOZOTOWN などの EC サイトを参考にさせてもらった。
ここで clearfix の意味を知る。

fog を使って CarrierWave の保存先を Amazon S3

できたと思っていたら、Heroku にアップロードした画像が消えていた。
「Heroku CarrierWave 消える」で google してみると、
push のタイミングで消えてしまうらしい。
無料で画像アップロードし放題なんてそんな虫のいい話があるはずなかった。
さっそくこちら を参考に、
fog という RubyGems を使って、CarrierWave の保存先を Amazon S3 にしてみた。
AWS のアクセスキーなどは環境変数に保存することにしたので、
上手い扱い方はないかと「Rails 環境変数」で google して
dotenv という RubyGems を見つけて導入してみた。

ここまでやってみて

Ruby on Rails Tutorial を一通り読み終わった段階で Rails の「魔法感(≒畏怖)」は半減し、
読みながら実際にアプリを作ってみて魔法感は 80% ほど減ったと思う。
RubyGems の充実ぶりがすごくて、「Rails ◯◯」で google してみたらやりたいことは大体できるという印象だった。
逆に言うと、ここに書いたくらいのことができてもエンジニアとしての付加価値はほとんどゼロに等しくて、
現存する RubyGems でできないようなことを思いついて実際に手を動かせるエンジニアにならないと、
この先生きのこれないなと強く思った。

ngRepeat を特定のフィールドについてフィルタリングする

app.controller('Ctrl', function($scope) {
  $scope.widgets = [
    { name: 'foo', description: 'basic model' },
    { name: 'bar', description: 'upper model of foo' }
  ];
});

name と description を持つ Widget というモデルがあって、これを ngRepeat でリスト表示しているとする。

このリストをフィルタリングするとき以下のようにすると、query が name か description のどちらかに
マッチするしたら表示される。

<input ng-model="query">
<div ng-repeat="widget in widgets | filter: query">

たとえば query が 'foo' のときは、foo は name で、bar は description でマッチするので、2つとも表示される。

name だけでフィルタリングをかけたければ以下のようにすると良い。

<input ng-model="query">
<div ng-repeat="widget in widgets | filter: { name: query }">

参考:Angular.js ng-repeat :filter by single field - stackoverflow

AngularJS の Service の考え方

AngularJS の Conceptual Overview にわかりやすく書いてあった。

ビューに依存しない再利用可能なビジネスロジック

コントローラにすべてのロジックを記述してアプリケーションを作ることも可能だけれど、それだとコントローラがどんどん肥大化してしまう。

ビューに依存しないロジックをサービスという形で切り出す。

まだ、factory() と service() の違いはわかっていない。

AngularJS のチュートリアルと Conceptual Overview には factory() を使った作り方しか出てきていない気がするので、躓くまでは factory() を使っておけばよさそう。

JSON と JSONP

AngularJS の勉強をしていたら $http.jsonp() が出てきて「!?」となったので JSONP について調べてみた。

こちらの記事がわかりやすかった。
JSONPで悩むある程度の人々へ

今まで違いを意識したことがなかったけれど、異なるドメインJSON データを取得する場合は JSONP を使うっていう認識でよいのだろうか。
ライブラリを使っているとラップされていてわからないけれど、同じ JSON データを取得するのでも、同一ドメインからの場合と異なるドメインからの場合で、内部の実装がだいぶ異なる。

jQuery

同一ドメインJSON データを取得する場合

$.getJSON('somedata.json', function(data) {
  console.log('name: ' + data.name);
  console.log('value: ' + data.value);
});

異なるドメインJSON データを取得する場合

$.getJSON('http://somecompany.com/somedata.json?callback=?', function(data) {
  console.log('name: ' + data.name);
  console.log('name: ' + data.value);
});

AngularJS

同一ドメインJSON データを取得する場合

$http.get('somedata.json', function(data) {
  console.log('name: ' + data.name);
  console.log('value: ' + data.value);
});

異なるドメインJSON データを取得する場合

$http.jsonp('http://somecompany.com/somedata.json?callback=JSON_CALLBACK', function(data) {
  console.log('name: ' + data.name);
  console.log('name: ' + data.value);
});

AWS EC2 インスタンスで Sinatra

簡単なウェブアプリケーションを作りたいときに Sinatra が便利そうだったので AWS EC2 インスタンス上で動かしてみた。
公式の手順通りに進めたらハマりどころがあったので記録。

Sinatra 公式の手順

http://www.sinatrarb.com/intro.html にしたがったけれど上手く行かなかった。
手順は以下の通り。

# myapp.rb
require 'sinatra'

get '/' do
  'Hello world!'
end

gem で Sinatra をインストール。

gem install sinatra

起動。

ruby myapp.rb

http://localhost:4567 を見よ!

上手く行った手順

gem をインストールする

そもそも EC2 インスタンスに gem をインストールしていなかったのでインストールします。

sudo yum -y install rubygems

sinatra インストールリトライ。

sudo gem install sinatra

ruby スクリプトにもちょっと手を加える

公式の手順通り ruby スクリプトを実行すると

`require': no such file to load -- sinatra (LoadError)

というエラーが出た。

ググったら以下の記事がヒット。
https://github.com/sinatra/sinatra/issues/640

ruby 1.8 系を使っているのなら

require 'rubygems'

が必要とのこと。

また、デフォルトではポート 4567 でアプリケーションが起動するので、これをポート 80 で起動するように指定。

set :port, 80

これで行けるだろーと思ってブラウザでアクセスしても Hello, world! 表示されず。

実は Sinatra は Development 環境のデフォルト設定だと localhost からのアクセスしか受け付けないらしい。
http://qiita.com/u1_fukui/items/b86b21f6ed39f4c10d5d

だので、スクリプトに以下の記述も追加。

set :environment, :production

最終的にスクリプトは以下のようになりました。

# myapp.rb
require 'rubygems'
require 'sinatra'

set :environment, :production
set :port, 80

get '/' do
  'Hello world!'
end