Backbone.js + Rails + AWSでWebサービスを開発したお話(その3:SEO/OGP)

弊社では、新サービス「otacco – おたっこ|オトナになったオタク女子のための情報サイト」を先日ローンチいたしました。

otacco

その1では主に弊社のRailsでの開発手法を、その2ではotaccoのAWSおよびミドルウェアの構成に関してご紹介させて頂きましたが、今回はシングルページアプリケーションを開発する際に問題になりやすい、SEOとOGPのノウハウに関して解説させて頂きたいと思います。

SEO

結論から申し上げますと、一般的なAjaxページであれば、ここ数年間のGoogle botの機能向上により、URLにHashbang(#!)を使用してHTMLスナップショットを返すという、Googleが公式ドキュメントで推奨している方式もはや不要になったと考えて良いのではないかと思われます。詳細は下記の記事等をご参照ください。

AjaxやSPAのHTMLスナップショットをSEO向けに作る必要はなし

ただし、あらゆる種類のAjaxページでGoogle botがコンテンツを正しく認識出来るのかどうかは分かりません。Google Search ConsoleのFetch as Google機能を使用した際にコンテンツが正確にレンダリングされるかどうかが一つの目安になりそうな気もしますが、確証はありませんので、こちらはあくまで参考情報であると考えて頂ければと思います。

以下、hash fragment方式、Hashbang方式、そしてpushState(History API)を使用した方式に関して解説いたします。

(なお、この記事では「hash fragment」は「#だけで区切られたURLの形式」、「Hashbang」は「#!で区切られたURLの形式」という前提で説明させて頂きます。SEO用語に関してはこちらの記事が詳しいです)

hash fragment

Backbone.jsのデフォルトである「#だけで区切られたURL」は、Googleのインデックス対象外となります。

ネット上では「hash fragment形式でもGoogleはインデックス化することがある」というような情報も見受けられましたが、これはかなり特殊なケース(もしくはHashbangとの混同?)なようで、少なくともotaccoの場合、#だけで区切られたURLではGoogleはインデックス化してくれませんでした。

よって、Backbone.jsでシングルページアプリケーションを開発する場合、SEOを考慮するのであれば、少なくともhash fragment方式は不可ということになると思われます。

Hashbang

「#!」でURLを区切り、「?escaped_fragment=」というクエリ文字列の付加されたGoogle botからのリクエストに対してHTMLスナップショットを返すという方式は、Googleが公式に推奨している方式ではありますが、過去にこの手法を採用していたFacebookやTwitterが現在ではもうHashbangを使用していないこと、およびこのURL形式がかなり評判が悪い&将来的に廃止される可能性が高そうということもあり、otaccoではこの手法は採用しませんでした。

どうしてもHashbang&HTMLスナップショットで対応したいという場合、Railsでは下記のようなgemを使用する方法もあるようです。(内部でヘッドレスブラウザを使用してAjaxページをレンダリングし、それをGoogle botに返すというような方式になるようです)
google-ajax-crawler

pushState

pushState/popStateは、HTML5でHistory APIに追加された機能ですが、otaccoではこの機能を使用して、URLは全て「/」のみで区切る方式を採用いたしました。

Backbone.jsでのpushStateの使用方法に関しては下記をご参照ください。
試して学ぶ Backbone.js入門5

また、otaccoでは、アンカーがクリックされた際の挙動に関しては下記の記事を参考にいたしました。そのため、URLは全て「/」から開始する方式で統一しています。
Proper Link Handling With PushState

pushStateを使用する場合の大きな問題点は、IE8とIE9、および一部のAndroid標準ブラウザ(version3〜4.1)では動作しないということです。
Can I use history?

otaccoの場合、対象となるユーザー様でIE8やIE9を使用されている方はほぼいらっしゃらないと想定されること、および各ブラウザのシェアや工数等を考慮して上記は動作対象外とさせて頂きましたが、IE8とIE9に関しては、現時点(2015年9月時点)でもまだそれなりのシェアを確保しているため、pushState方式を選択される方はこの点をよくご確認された方がよろしいかと思われます。
Desktop Browser Version Market Share

蛇足ながら、現在IE8もしくはIE9を使用しているユーザーのOSは、既にMicrosoftのサポート対象外となっているXP、もしくは2017年4月でサポートが終了するVistaのいずれかと思われますが、ハードウェアの耐久年数等を考えれば、おそらく順次Windows 10やその他のOSに移行していくものと思われますので、IE8やIE9への対応に大きな工数を割くのは、一般的なWebサービスの場合あまり得策ではないのでは、という気はいたします。
各Windows OSで利用できるIEのバージョンを知る

OGP

FacebookまたはTwitterのbotは、残念ながらAjaxコンテンツを解釈してくれません。つまり、OGP用のタグをJavaScriptで動的に変更しても、Facebook/Twitterのbotはその変更を認識してくれないということになります。

よって、Facebook/Twitterのbotからアクセスされた場合、何らかの手段で「OGPタグがそのページ用にレンダリングされた状態の静的HTML」を返す必要があるということになりますが、otaccoではRails側に「ogpタグ専用のHTMLページ」を出力するためのコントローラを作成し、Facbook/Twitterのbotからアクセスされた場合はそのページ用のHTMLを動的に生成して返す、という方式を採用いたしました。

ちなみに、以下で説明いたしますが、このHTMLページに関しては、最低限のHTMLタグとOGPタグが記述されていれば、コンテンツは一切不要のようです。

titleタグやmeta descriptionタグは一応設定しておりますが、固定の文字列を出力しております。(もしかするとこのタグも不要かもしれません)

HTMLテンプレート

FacebookもしくはTwitterのbotからアクセスされた際に出力するHTMLのテンプレートは下記のようになります。(Twitter Cards用のOGPはページ毎に出力するタグを切り替えています)

# index.html.erb
<!DOCTYPE html>
<html>
<head>
<title>otacco</title>
<meta name="description" content="otacco">
<%= render 記事ページ?(@origin) ? {partial: "twitter_entry", locals: {entry: 記事を取得()}} : {partial: "twitter"}   %>
<meta property="og:locale" content="ja_JP">
<meta property="og:site_name" content="otacco">
<meta property="og:url" content="<%= get_ogp_url @origin %>">
<meta property="og:type" content="<%= get_ogp_type @origin %>">
<meta property="og:title" content="<%= get_ogp_title @origin %>">
<meta property="og:description" content="<%= get_ogp_description @origin %>">
<meta property="og:image" content="<%= get_ogp_image @origin %>">
</head>
<body>
</body>
</html>
# _twitter.html.erb
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="<%= Settings.twitter.account %>">
# _twitter_entry.html.erb
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="<%= Settings.twitter.account %>">
<meta name="twitter:title" content="<%= entry.title %>" />
<meta name="twitter:description" content="<%= entry.description %>" />
<meta name="twitter:image" content="<%= entry.image.url %>" />

nginxの設定

Facebook/TwitterのUser Agentを判定するために下記のような設定を行っています。(「Facebook/Twitter botからのアクセス」かつ「imagesディレクトリへのアクセスではない」場合に、Railsのbot用コントローラにURLをRewriteするという処理になります)

if ($http_user_agent ~* (facebookexternalhit|Twitterbot)) {
  set $bot A;
}
if ($uri ~* "^/(?!images)") {
  set $bot "${bot}B";
}
if ($bot = AB) {
  rewrite .* /bot?target=$uri break;
}

ご参考までに、フロントエンドが担当するURLと、バックエンドが担当するURLを切り分けるためのnginx設定の一部をご紹介いたします。下記のような設定を行わずに、全てのリクエストが常にunicorn等のアプリケーションサーバに渡されてしまうと、負荷の面で問題がございますのでご注意ください。

location ~* ^/(?!api|bot|index.html) {
  try_files $uri /index.html;
  break;
}

try_files $uri @unicorn;

location @unicorn {
  include conf.d/proxy.conf;
  proxy_pass http://unicorn;
}
正規表現に関する補足

上記の補足になりますが、nginxはPCRE(Perl Compatible Regular Expressions)をサポートしていますので、正規表現においてPerlと同様な構文で「否定先読み」が使用できます。
4. 正規表現

シングルページアプリケーションの場合、「フロントエンド側独自のURL」はどんどん増えていく可能性がありますが、それに合わせてnginxの設定を毎回変更するのは大変です。

これに対して、バックエンド側が担当するURLは滅多に増えませんので、「バックエンド側で担当するURLでない場合」という判定を行っておく方が作業工数を減らせるということになりますが、こういった判定は上記のような「否定先読み構文」を使うことでシンプルに対応出来ます。

以上になります。


「世界NO1のotakuマーケティングカンパニー」を目指す株式会社アリスマティックでは、インフラ・サーバーサイド・ネイティブ・フロントエンドなど、各職種でエンジニアを絶賛募集中です!

採用ページ



Backbone.js + Rails + AWSでWebサービスを開発したお話(その2:AWS/ミドルウェア)

弊社では、新サービス「otacco – おたっこ|オトナになったオタク女子のための情報サイト」を先日ローンチいたしました。

otacco

前回の記事では主に弊社のRailsでの開発手法に関してご紹介させて頂きましたが、今回はotaccoのインフラを構成しているAWSおよびミドルウェア周りの環境について解説させて頂きたいと思います。

AWS

まずは構成図をご覧ください。

otacco7

専用のVPCを構築し、Multi-AZ構成で冗長化、サブネットは「フロント層/アプリケーション層/データ層」に分割し、セキュリティグループもこの単位で分けています。

Webサーバーおよび全文検索用サーバーはELBでそれぞれ冗長化および負荷分散しています。踏み台(bastion)サーバー以外へのSSHアクセスは許可していません。

Webサービス用のAWS環境としては標準的な構成だと思います。

ここら辺の構成に関しては、AmazonのAWS構成事例のページや、下記のDevelopers.ioさんの記事等、色々な情報を参考にさせて頂きました。
【AWS】VPC環境構築ノウハウ社内資料 2014年4月版

サーバー監視は別会社様に外注させて頂いておりますが、VPCごとに監視元サーバーを用意するとコスト面で問題があるため、今後VPCの数が増えていっても監視元サーバーの数を増やさないですむように、VPC peering機能を活用した監視方式を採用しております。

EC2

EC2のAMIは標準のAmazon Linux AMIを使用しております。

特殊な設定は行っておりませんが、Capistranoでのデプロイ時に、AWS SDKを使用してデプロイ先のサーバーを環境に応じて動的に識別出来るように、Webサーバーには「Project: production」のようなタグを設定しています。

S3/CloudFront

本番環境用の静的アセットファイル、記事等の画像ファイル、ログファイル等は全てS3にアップロードしています。

アセットファイルや画像ファイルに関しては、デプロイ時や更新時に古いファイルは自動的に削除されますが、ログファイルはどんどん蓄積されていってしまうため、専用のRakeタスクを作成し、それをwheneverでcronに登録して、古いログを定期的に削除するようにしています。

RDS

弊社ではMySQL5.6を使用しています。

文字コードにutf8mb4を採用する場合は、新しいParameter Gropを作成し、下記のように設定を変更してRDSをReboot必要があります。

character_set_client = utf8mb4
character_set_connection = utf8mb4
character_set_results = utf8mb4
character_set_server = utf8mb4
character_set_database = utf8mb4
innodb_file_per_table = 1
innodb_file_format = barracuda
innodb_large_prefix = 1
skip-character-set-client-handshake = 1

セッションストレージおよびキャッシュとして使用しているInnoDB memcached plugin用のOption Groupsの設定に関しては下記の記事が参考になると思われます。
Amazon RDS MySQL 5.6と新機能を試してみた

Route53

「otacco.com」というzone apexをELBのエンドポイントにマッピングするために使用しています。詳しくは下記の記事等をご参照ください。
Zone apexとCNAME

ミドルウェア

nginx

nginxに関しては、要件ごとに下記のような設定を行っております。

常時SSL対応

otaccoでは全てのアクセスをhttpsで行う(常時SSL対応)というルールを採用しておりますため、下記のような設定を行い、httpでアクセスされた場合はhttpsにリダイレクトするようにしています。
(ELB経由の場合は、HTTP_X_FORWARDED_PROTOというヘッダで判定を行います)

if ($http_x_forwarded_proto != "https") {
  rewrite ^ https://$host$request_uri? permanent;
}
ヘルスチェック

ELBからのヘルスチェックは下記のような設定で対応しています。

server {
  listen 80;
  location = /healthcheck {
    empty_gif;
    access_log off;
    break;
  }
}
IPフィルタリング

特定のURLへのアクセスを特定のIPアドレス(オフィスの固定IP等)に限定するために、nginxのgeoモジュールを使用しています。

geo $geo {
    proxy VPCのネットワークアドレス;
    オフィスのIPアドレス OK;
    default NG;
}

server {
    ...
    location ~ ^/hoge/ {
      if ($geo = 'NG') {
        return 403;
      }
      ...
    }
    ...
}

unicorn

unicornの設定に関してはネット上に色々な情報が散見されますが、弊社では下記のような設定に落ち着きました。

# -*- coding: utf-8 -*-
worker_processes Integer(ENV["WEB_CONCURRENCY"] || 1)
timeout 15
preload_app true

listen "/tmp/hoge.sock"
pid "/tmp/hoge.pid"

current_path = ENV["RAILS_ENV"] == "development" ? "/hoge" : "/hoge/current"
working_directory current_path

before_exec do |server|
  ENV["BUNDLE_GEMFILE"] = File.expand_path("Gemfile", current_path)
end

before_fork do |server, worker|
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!

  old_pid = "#{server.config[:pid]}.oldbin"
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
      Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end

  sleep 1
end

after_fork do |server, worker|
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection
end

before_execBUNDLE_GEMFILE環境変数を設定する箇所は非常に重要で、これが記載されていないと、RailsのGemfileを更新した場合にunicornのreloadが失敗する場合がありますのでご注意ください。この問題に関しては下記の記事で解説されています。
capistrano + unicornではまった。

WEB_CONCURRENCY環境変数に関しては、弊社では/etc/environmentに設定する方式を採用しました。

# /etc/environment
WEB_CONCURRENCY=任意の値

fluentd

nginxとRailsのログを集約し、S3にアップしています。設定が重複しないようにfluent-plugin-forestプラグインを使用しています。

<match hogelog.**>
  type forest
  subtype copy
  remove_prefix hogelog
  <template>
    <store>
      type s3
      aws_key_id "hoge"
      aws_sec_key "secret"
      path ${tag}
      buffer_path /var/log/td-agent/buffer/${tag}.s3
      s3_bucket "hogelog"
      s3_region "ap-northeast-1"
      s3_object_key_format %{path}.%{time_slice}_%{index}.%{file_extension}
      time_slice_format %Y%m%d
      time_slice_wait 10m
    </store>
  </template>
</match>

NewRelic

パフォーマンス監視用にNewRelicを導入しています。ほぼ標準設定のまま使用しています。

ElasticSearch

全文検索用にElasticSearchを導入しています。AWS環境ではマルチキャストが使用できませんので、ノードのディスカバリ用に、下記のような設定を行う必要があります。
AWS Cloud Plugin for ElasticSearchを使う

RailsとElastiSearchの連携、特に「Modelに親子関係がある場合」の連携方法に関しては下記の記事がとても参考になりました。
既存のRailsアプリの検索にElasticSearchを導入してみる

Wercker

CIツールにはWerckerを採用しました。rubyおよびmysqlのdockerを使用する場合の設定ファイルは下記のようになります。ご参考までに。
(otaccoではDBのマイグレーションにridgepoleを使用しています。メッセージの通知先はChatworkです)

box: ruby
services:
  - id: mysql:5.6
    env:
      MYSQL_ROOT_PASSWORD: $MYSQL_ROOT_PASSWORD
      MYSQL_DATABASE: $MYSQL_DATABASE
      MYSQL_USER: $MYSQL_USER
      MYSQL_PASSWORD: $MYSQL_PASSWORD
build:
    steps:
        - bundle-install
        - rails-database-yml:
            service: mysql-docker
        - script:
            name: ridgepole
            code: (ridgepoleコマンド。省略)
        - script:
            name: rspec
            code: bundle exec rspec
    after-steps:
        - heathrow/chatwork-notify:
            (省略)
deploy:
    steps:
        - script:
            name: make .ssh directory
            code: mkdir -p "$HOME/.ssh"
        (以下、秘密鍵の生成用コード。省略)
        - script: 
            name: execute deploy
            code: (capistranoを起動するコード。省略)
    after-steps:
        - heathrow/chatwork-notify:
            (省略)

Capistrano

デプロイにはCapistranoを使用しています。かなり設定が多くて書ききれないので省略させて頂きますが、アセットのプリコンパイルに関しては下記の記事を参考にさせて頂きました。
Capistrano3 で asset_sync をローカル環境で実行してからデプロイする

次回は、シングルページアプリケーションを開発する際にハマりやすい、SEOとOGP関係の設定方法に関して紹介させて頂きたいと思います。


「世界NO1のotakuマーケティングカンパニー」を目指す株式会社アリスマティックでは、インフラ・サーバーサイド・ネイティブ・フロントエンドなど、各職種でエンジニアを絶賛募集中です!

採用ページ



Backbone.js + Rails + AWSでWebサービスを開発したお話(その1:Rails)

弊社では、新サービス「otacco – おたっこ|オトナになったオタク女子のための情報サイト」を先日ローンチいたしました。

otacco

サービスの仕様が、弊社の他のアプリケーションとかなり異なるということもあり、今回は新しい開発手法にチャレンジしてみようということで、フロントエンドにBackbone.js、バックエンドにRuby on Railsという組み合わせを採用いたしました。

今回から3回に渡って、SPA(シングルページアプリケーション)における、主にバックエンド側の開発方式やインフラ構成、およびSEO/OGPのノウハウ等に関してご紹介させて頂きたいと思います。

(Backbone.jsの情報はほんのちょっとしか出てきません。ごめんなさい)

基本情報

Rubyのバージョンは2.2、Railsは4.2.1、DBはMySQL5.6、セッションストレージやキャッシュ管理にはMySQL5.6から導入されたInnoDB memcached pluginを使用しています。

ソース管理はGithub、開発フローはGithub Flowを採用、コーディング規約は下記を参考にしました。

The Ruby Style Guide
The Rails Style Guide

スキーマ管理(マイグレーション)

RailsのDBスキーマ管理に関しては、ActiveRecordのマイグレーション機能で管理する手法がスタンダードだと思いますが、下記のような理由でこの方式は採用しませんでした。

  • 開発の初期段階ではスキーマ構造に頻繁に変更が入ることが予想されるが、その度にマイグレーションファイルを作成すると、ファイル数が無駄に多くなる。
  • ActiveRecordのマイグレーション方式のメリットである「任意の時点のスキーマを再現できる」という機能が必要になるケースは滅多にない。
  • マイグレーション用のコードを書くのが面倒。

ということで他の手法を探してみたところ、Cookpadさんが公開しておられる「Ridgepole」というツールが、我々の要望に非常にマッチしている感じでしたので、機能検証後、こちらのツールを使用させて頂くことにしました。

ridgepole

Ridgepoleを使うことで下記のようなメリットがあります。

  • 各テーブルごとに1つのschemaファイルを用意すれば良いだけなので管理するファイルの数が少なくて済む。
  • schemaファイルからDBテーブルを作成するだけでなく、DBテーブルからschemaファイルを生成することが可能。
  • schemaファイルがDB定義書を兼ねているので、DB定義書を作成/更新する必要がない。
  • マイグレーション用のコードを書く必要がない。

詳細に関してはCookpadさんの下記の記事をご参照ください
クックパッドにおける最近のActiveRecord運用事情

otaccoの「記事」用テーブルのschemaファイルは、例えば下記のようになっています。(テーブルごとにschemaファイルを分割しています)

create_table "entries", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC" do |t|
  t.integer  "category_id", limit: 4,               null: false
  t.string   "title",       limit: 255,             null: false, collation: "utf8_general_ci"
  t.string   "image",       limit: 255,                          collation: "utf8_general_ci"
  t.string   "description", limit: 255,             null: false, collation: "utf8_general_ci"
  t.integer  "status",      limit: 4,               null: false
  t.datetime "created_at"
  t.datetime "updated_at"
end
add_index "entries", ["category_id"], name: "idx_entries_01", using: :btree

こちらのschemaファイルと、DBの実際のスキーマ構造を照合して、自動的にSQL文を生成/発行したり、あるいはテーブルからschemaファイルを生成してくれたりします。ものすごく便利です。

# 差分をチェック
ridgepole -E development --enable-mysql-awesome --diff config/database.yml schemas/Schemafile
# DRY-RUN
ridgepole -E development --enable-mysql-awesome -c config/database.yml --apply --dry-run -f schemas/Schemafile
# CREATE文やALTER文実行
ridgepole -E development --enable-mysql-awesome -c config/database.yml --apply -f schemas/Schemafile
# DBからschemaファイルを生成
ridgepole -c config/database.yml -E development --export --split --enable-mysql-awesome --output schemas/Schemafile

ちなみに、弊社ではDBの文字コードにutf8mb4を採用しておりますが、この場合innodbのファイルフォーマットにはBarracudaを使用して、ROW_FORMATをDYNAMICに設定しないと、インデックスに使用する列のサイズによっては「Index column size too large」というエラーが出る場合があります。

この問題に対応するため、activerecord-mysql-awesomeというgemも併用しております。(上記のコマンドにはそのgemを併用する際のオプションも含まれています)

管理画面

管理画面に関しては、独自にゼロから開発するという方式も考慮したのですが、開発工数を鑑みてActive Adminを採用しました。

active_admin

ちょっと気の利いた機能を追加したいという場合にはあまり融通が効かないという弱点はあるものの、こちらも非常に便利なgemです。

ほとんど定型的な使い方しかしていないとは思うのですが、ちょっとしたtipsとして、「多対多」の関係にあるレコード同士を編集画面で関係付けるための設定をご紹介します。

f.has_many :entry_keywords, allow_destroy: true, heading: false, new_record: true do |entry_keyword|
  entry_keyword.input :keyword_id, label: I18n.t('activerecord.models.keyword'), as: :select, include_blank: false,
    collection: Keyword.all.map { |k| [k.title, k.id] }
end

ActiveRecord側で「記事 – 中間テーブル – キーワード」という多対多の関係が成立している場合に、記事の編集画面でキーワードを複数関連付けたい場合には上記のようなコードを記述します。(実際に表示される画面は下記のようになります)

active_admin_has_many

API

フロントエンドから呼び出されるAPIは、GrapeJbuilderを使用して開発しました。

こちらも基本的には標準的な手法で開発しておりますが、ローカル環境においては、WEBrickで起動したバックエンドを、タスクランナー(弊社ではGulp)で起動しているフロントエンドからいわゆる「CORS」(Cross-Origin Resource Sharing)を使用して呼び出せるようにする必要がありますので、rack_corsというgemを導入し、下記のようなコードをローカル開発環境用のファイルに記述して対応しました。

# config/environments/local.yml
config.middleware.use Rack::Cors do
  allow do
    origins 'http://localhost:3000', 'http://localhost:5000'
    resource '/api/*', :headers => :any, :methods => [:get, :post, :options, :put], :credentials => true
  end
end

画像管理

画像のアップロードに関してはCarrierWavefog-awsminimagickを使用しました。(rmagickはメモリ消費が激しいという情報がありましたので使いませんでした)

画像は、ステージング環境と本番環境に関してはS3にアップし、CloudFrontから参照出来るように構成してあります。設定は下記のように行いました。ご参考までに。

# config/initializers/carrierwave.rb
CarrierWave.configure do |config|
  config.fog_provider = 'fog/aws'
  config.fog_credentials = {
      :provider              => 'AWS',
      :aws_access_key_id     => Settings.aws.access_key_id,
      :aws_secret_access_key => Settings.aws.secret_access_key,
      :region                => 'ap-northeast-1'
  }
  config.storage = :fog
  config.cache_storage = :fog
  config.fog_directory = Settings.fog_directory
  config.asset_host = Settings.asset_host
  config.fog_public = true
end

アセット管理

Rails側で管理しているcss/jsファイル等の静的アセットファイルは、asset_syncを使用して、デプロイ時に画像と同様S3にアップしています。こちらも設定ファイルを記載しておきますのでご参考までに。

# config/initializers/asset_sync.rb
AssetSync.configure do |config|
  config.fog_provider = 'AWS'
  config.aws_access_key_id = Settings.aws.access_key_id
  config.aws_secret_access_key = Settings.aws.secret_access_key
  config.fog_directory = Settings.fog_directory
  config.fog_region = 'ap-northeast-1'
end

定数管理

定数管理にはrails_configを使用して、「Settings」というクラス名で定数を参照出来るようにしています。環境ごとに異なる定数値が必要な場合は、環境別のymlファイルを使用して定数を上書きします。例えば下記のような感じです。

# settings.yml
aws:
  access_key_id: 'HOGE'
  secret_access_key: 'hhhOOOGGGEEE'
search:
  index_name: "hoge"
# settings/local.yml
search:
  index_name: "hoge_local"

さらに、「設定値を連結したい」というケースに対応するため、下記のような処理を追加しています。

# settings/merge.yml
uri:
  origin: <%= format('%s://%s', Settings.uri.schema, Settings.uri.host) %>
# config/application.rb
Settings.add_source!("#{Rails.root}/config/settings/merge.yml")
Settings.reload!

上記により、Settings.uri.originというようなコードで、連結された文字列を取得することが可能になります。

区分値管理

テーブルの区分値用の列に関しては、MySQLのenum型を使用するという手法もあるのですが、「値の定義を追加したい場合にALTER文の実行が必要になる」というデメリットがあるため、この方式は採用しませんでした。

また、単に定数で管理するという手法だと、例えば「区分IDから区分名を取得したい」という場合にうまく対応出来ないため、区分値に関してはActiveHashを使って列挙型のように扱う方式で対応しました。

class SpanType < ActiveHash::Base
  include ActiveHash::Enum
  self.data = [
    {id: 1, name: I18n.t('activerecord.attributes.values.span.daily'), value: 1.day, span_type: 'DAILY'},
    {id: 2, name: I18n.t('activerecord.attributes.values.span.weekly'), value: 1.week, span_type: 'WEEKLY'},
  ]
  enum_accessor :span_type
end

enum_accessorを指定することで、SpanType::DAILY.idというコードで区分IDを、SpanType::DAILY.nameというコードで区分名を取得することが可能です。

また、ActiveRecordと同様なメソッドでコレクションを取得出来るので、例えばActive Adminのプルダウン型の項目を、下記のようにシンプルなコードで実装出来ます。

f.input :span, required: true, as: :select, include_blank: false,
  collection: Entry::SpanType.all.map { |s| [s.name, s.id] }

次回は、otaccoのインフラやミドルウェア構成に関してご紹介させて頂きたいと思います。

Backbone.js + Rails + AWSでWebサービスを開発したお話(その2:AWS/ミドルウェア)


「世界NO1のotakuマーケティングカンパニー」を目指す株式会社アリスマティックでは、インフラ・サーバーサイド・ネイティブ・フロントエンドなど、各職種でエンジニアを絶賛募集中です!

採用ページ