言語移行の経緯などが読みたい方はこちらを御覧ください。
こんにちは。RubyMine大好き!@vexus2です。
前回はnanapiをRailsにする際にやってよかったこと、やらなかったことというエントリでプロジェクト進行観点での注力したことを書いたのですが、今回はnanapiをCakePHP→Rails化をする上でハマったことや苦労したことなどを技術的観点でまとめてみます。
CakePHPから移行でやったこと
データベースの移行
最初に頭を悩ませたのがデータベースをどうするかということでした。
既存のnanapiはnanapi worksや管理画面など、用途ごとにデータベース分かれた構成になっていたので、そのままの構成で動かす形に実装することも検討しましたが、最終的にはRailsらしい形で動くことを目指し以下の形を取りました。
- CakePHP準拠のカラム名は全てRails準拠にリネーム
- created, modified→created_at、updated_atなど
- typeは予約語で使えないので別の名前に変更
- データベースを全て1つに統合
- create table→select insertを実行
ツリー構造の移行
nanapi.jpの各記事は1つのテーマに紐付いていて、テーマは19,000ほど分解してデータを持っています。
それぞれのテーマはいわゆる入れ子集合モデル構造になっていて、19個の最上位テーマの元に1〜8階層の深さを持った構成になっています。
今回ネックとなったのはCakePHPのTree Behaviorで生成されたデータを、Railsに移行した後も同じデータフローを引き継げるようにすることでした。
諸々検討した結果、Awesome nested setを使えばカラムに depthを追加するだけで比較的かんたんに移行できました。
実際にやった対応については別エントリにてまとめてありますので合わせてご参照ください。
Jenkins先生との決別
IGNITIONの開発の時から推し進めていたJenkins撤廃ですが、今回のタイミングで決別しました。
決してJenkinsが悪いというわけではなかったんですが、
- カスタマイズ性が高い分、メンテコストが高い
- メンテナが固定されて各ジョブが秘伝のタレ化し、Jenkins職人が発生してその人に依存する
- 極端な話、職人がいないとジョブを追加できなかったり、有事の際に対応できなかったりする
という点をnanapiでは問題視していました。
デベロッパープロダクティビティ的なチームで個人に属人化させずにJenkinsを運用し続ける余裕があるような組織なら良いと思います。
弊社の場合はそこまでリソースを割けなかったので、各サービスごとにCircleCIやTravisCIのような最適なCI as a Serviceを利用する、という方式を取ることにしました。
※とはいえ社内的には自分がJenkinsを導入して最終的に決別したので、一人で騒いでいた感はあります
Railsで新しく実装したこと
遅延ロード
nanapiではもともとViewやPartialをページ表示後に遅延ロードするWidgetという仕組みがあったんですが、Rails化に伴い汎用化してlazy_renderという名前でGemにしました。
例えばヘッダー内のログインユーザによって処理を分けている箇所や記事PVの取得処理など、同期的に表示する必要がない箇所や一部だけキャッシュを効かせたくない箇所に使っています。
以下に簡単な使い方をまとめます。まずは事前準備をします。
1 |
gem 'lazy_render' |
1 |
lazy_render_for 'lazy_render/load', to: 'lazy_render' |
1 |
helper LazyRender::LazyloadHelper |
次に、lazy_renderで呼び出すアクションとコントローラを定義します。名前はなんでもいいんですが、ここではLazyRenderControllerにします。
1 2 3 4 5 |
class LazyRenderController < LazyRender::LazyloadController def load super end end |
次に、このController内に遅延ロードしたいアクションとその処理を記述してきます。
試しにsample_actionというアクションを定義します。
1 2 3 4 |
def sample_action(locals) @data[:sample_text] = 'Hello LazyRender!' @data[:pass_value] = locals[:value] end |
引数はViewからHashを渡すことができます。
次に上で作成したアクションから呼び出すViewをviews/lazy_render/配下に作成します。
1 2 3 |
<h2>This is sample view.</h2> <%= data[:sample_text] %> <h3><%= data[:pass_value] %></h3> |
最後に、遅延ロードさせたいViewで以下のように呼び出します。
1 |
<%= lazy_render :sample_text, locals: { value: 'Sample Text' } %> |
これでページを表示すると、上記を埋め込んだ箇所に以下のHTMLが遅延ロードされます。
1 2 3 |
<h2>This is sample view.</h2> Hello LazyRender! <h3>Sample Text</h3> |
基本の使い方はこんな感じです。詳しい説明はREADMEや以下のリンクをご参考ください。
RailsでView/Partialを遅延ロードするGem lazy_render
メール配信
メールの処理はCakeEmailとAmazon SESを分けて送っていたんですが、今回の移行に伴いAmazon SES経由に1本化しました。
また、キューシステムは元々独自実装だったものを廃止し、アダプタにRails4.2から導入されたActive Job、バックグラウンドはResque、バックグランドプロセス化としてdaemon-spawnを使いました。
以下はActive Job+Resqueでのメール配信の設定です。
※daemon-spawnはIGNITIONの時とほぼ同じなので、以下の説明は割愛します。
1 2 |
gem 'resque' gem 'resque-scheduler' |
1 2 3 |
Resque.redis = Redis.new(url: 'http://localhost:6379') Resque.redis.namespace = "resque:任意のネームスペース:#{Rails.env}" Resque.after_fork = proc { ActiveRecord::Base.establish_connection } |
1 |
ActiveJob::Base.queue_adapter = :resque |
1 2 3 4 5 6 7 8 9 |
require 'resque/tasks' require 'resque/scheduler/tasks' namespace :resque do task :setup do require 'resque' require 'resque-scheduler' end end |
次に、実際にメールを送信するActionMailerのクラスを作成します。ここでは AsyncMailer という名前で作成します。
1 |
$ rails g mailer async_mailer |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class AsyncMailer < ActionMailer::Base default from: "info@nanapi.co.jp" def welcome_register(user) extract_email_params(user) mail( to: @email, subject: 'ナナピ | ユーザー登録が完了しました' ) end def extract_email_params(args) args.each { |k, v| instance_variable_set("@#{k}", v) } end end |
上記で定義したactionに対応するviewを作ります。
1 2 3 4 5 6 7 8 9 |
<%= @nickname %>さん この度は、nanapi(ナナピ) にご登録いただきありがとうございます! ご登録情報は以下をご確認ください。 【アカウント情報】 ログイン用メールアドレス: <%= @email %> ----以下省略---- |
最後に、送信したい箇所で遅延送信を実行します。
1 |
AsyncMailer.welcome_register(user).deliver_later! |
ざっくりですがこんな感じの流れで送信処理をやっています。
管理画面
前のエントリにも書きましたが、今回はactive_adminやrails_adminは使わずにフルスクラッチで管理画面を作成しました。
社内向けの管理画面系のツールは手を抜きがちですが、「カッコいいAdminってモチベーション上がるよね」ということでBootstrapのテーマはエンジニアにアンケートを取って、評判が最も高かったテーマを使いました。
管理画面の再実装については、極力シンプルな作りを目指し
- グラフなどの無駄にグラフィカルな要素は使わない
- 解析データなどは管理画面に持たない
ということを意識しました。
解析データのグラフなどは見栄えも格好良く、作るだけで仕事をした感が出がちですが、実際に管理画面上でそれらを使うのは稀&専門の各解析ツールに任せたほうが健全なので、今回は一切実装しませんでした。
テスト
移行前はユニットテストしかなかったので、移行に伴いE2Eテストを特に注力しました。
E2EにはPhantomJS+Poltergeistを使っています。
テストの観点としては細かなチェックをしだすときりがないので、 そのページを訪れたユーザにどういう体験をさせたいか という点にフォーカスして書いています。
例えば記事ページの場合、「ユーザが期待している内容の記事が表示されてること(読めること)」が最重要であるべきなので、「そのページが正しくシェアできること」のようなexpectは省くようにしています。
ちなみにPhantomJS+Poltergeistは問題が少なからずあって、特定のテストで稀にPoltergeistがDead Client落ちたりしてドハマりもしてます。
良い解決方法ありましたら教えてください。
デプロイ
基本的にGitHub Flowに則った開発スタイルを取っていて、Pull Requestのレビュー完了後はSlack上でHubot経由でデプロイを行っています。
内部的にはCircleCIでジョブをビルドしていて、通常のPUSHによるビルドの場合はテスト実行→Staging環境へリリース、
Slack経由からの場合のみProduction環境へリリース、のように処理を分けて実行しています。
また、今までなあなあになっていたデプロイ可能時間を明確にし、定時時間外にリリースしようとすると @nanapi_bot ちゃんに怒られる、というような仕組みも入れています。
最後に
本プロジェクトではとにかく、Railsらしく書く、Railsのレールに乗る ということを意識しました。規約に反したことをやろうとすると途端に面倒くさいことが多くなるフレームワークなので、迷ったらRailsらしい実装かどうかを問うことは大事だなあと思っています。
最後に、今回のプロジェクトメンバーで読んだ/共有したチュートリアルや書籍のうち、特に良かったと思うものを紹介して終わります。
Ruby on Rails Tutorial
Everyday Rails – RSpecによるRailsテスト入門
パーフェクトRuby
パーフェクトRuby on Rails
Ruby on Rails 4 アプリケーションプログラミング