読者です 読者をやめる 読者になる 読者になる

Slackのemojiをデカくする

こんにちは。デザイングループのミヤギ(@_ringogirl)です。

唐突ですが、先週めでたく最終回を迎えた『けものフレンズ』というアニメはご存知でしょうか。

上記の様なブログも書かれていて、かなり流行っていましたね。
僕自身もハマってしまい、11話〜最終回のアツい展開に深夜に1人で涙してしまいました。

f:id:ringo_girl:20170404120819p:plain

けものフレンズの emoji を使いたい

弊社では社内でのチャットに Slack を使っているのですが、Slackには独自のemojiを登録できる機能があります。今さら最終回を迎えたアニメの話で恐縮ですが、『けものフレンズ』の emoji を登録したくなり、素材を探していたところ以下のようなものが見つかりました。

けものフレンズアイコンまとめ【フリーアイコン】

@Kiguchi1902さんありがとうございます。

emoji が小さい

emoji を登録はしたものの、実際に使ってみるとサイズの問題でセリフが小さく、「すごーい!」という感情を上手く表現できないというもどかしさがありました。

f:id:ringo_girl:20170404120832p:plain

Slack の custom emoji は最大 128x128 でアップロードできますが、Developer Toolsで覗いてみると、サイズが縮められているようだったので、どうにか元の画像を表示できる方法を探していたところ、APIで画像のURLを取得できることがわかりました。

f:id:ringo_girl:20170404120841p:plain

Slack API | emoji.list method

このURLを attachments に設定することができれば、emojiをデカくするということが可能では、と思い実装をはじめました。

emoji をデカくする

実際に作ったものはGitHubに置いています。

Slash commandsを使う

Slash Commandsを使って以下のコマンドを発行することで、emojiを大きくするようにしました

/stamp :custom_emoji:

Slash commandsを入力すると、Outgoing Webhooksの要領で、 コマンド(/stamp)テキスト(:custom_emoji:) を受け取ることができます。それらを受け取った後で、emoji.list から該当する custom_emoji のURLを取得し、attachmentsに画像を埋め込んでメッセージを返すようにしています。

ユーザーの認証を行う

Slash Commandsだけでは不十分で、Private GroupやDirect Messagesではデカい emoji を投稿できる権限が無いので、ユーザー毎に認証してtokenを保存するようにしています。

SlackでAppを作り適切なScopeを与えて、チームにインストールしています。

たーのしー!

f:id:ringo_girl:20170404120906g:plain

これで custom emoji をデカくする機能の実装が完了しました。すごいことがあったら「すごーい!」と感情が表現できるようになりました!

その他のcustom emojiも使える

emoji.listというAPIを使っているので、custom emojiであれば全て元サイズで表示することができます。

f:id:ringo_girl:20170404120929g:plain

気をつけていること

この機能でデカいemojiが氾濫すると、本来のコミュニケーションが取れなくなりチーム内でストレスが溜まってしまう可能性もあるので、用法用量を守るようにしています。
弊社内のSlackではたまに使用される程度で、支障が出るほどではありませんでした。

まとめ

『けものフレンズ』のアイコンを使いたいという思いがことの始まりだったのですが、custom emoji全てを扱うことができるので、Slackでのコミュニケーションが賑やかになりました。

また、今回はじめて hapi という Node.js のWebフレームワークを使ったのですが、hapijs/joi というバリデーションのライブラリや認証周りがプラグインとして準備されていて便利でした。

デザイナー採用

株式会社groovesではデザインとフロントエンドがやりたいフレンズを募集中です!よろしくねー!

jobs.forkwell.com

www.wantedly.com

www.moreworks.jp

GitHubリポジトリが突然消えたときにやるべきこと

新年あけましておめでとうございます。ことよろ。
最近 Forkwell のポートフォリオ機能を弄っている@sinsoku です。

今日は弊社で起きた「リポジトリが突然消えた事件」について書きたいと思います。

事の始まり

昨年末の26日の朝、 fork したけど使っていないリポジトリがあったので、何気なく削除しました。 その30分後、同僚のapp2641に声をかけられました。

app2641:「なぜか(メイン)リポジトリが404なんですが、sinsoku さん何か知ってます?」
sinsoku:「え、いや、分からないです。私の方でも調べてみます。(もしかして...)」

自分でもリポジトリのページを表示してみました。

f:id:sinsoku:20170118110130p:plain

404 ですね。マジか...。Audit log を確認してみるか。

f:id:sinsoku:20170118110145p:plain

見覚えあるアイコンの横に repo.destroy て書いてあるじゃないか...o..rz

GitHub のサポートへ連絡

下記の内容で問合せました。

Hi, GitHub Team.

I accidentally deleted a "grooves/xxx" repository. Can you resurrect it?

その後、サポートの人から返ってきた返信を要約すると下記の通り。

  • GitHub ではリポジトリを復元できるようにアーカイブに保存している
  • grooves/xxx のリポジトリは検索したけど、アーカイブに存在しない
  • sinsoku/xxx のリポジトリはあるけど、これ?

GitHub では削除した後に問い合わせれば復元できるっぽいですね。 通常なら

I can't see a "grooves/xxx" repo.

https://github.com/grooves/xxx

And Audit log shows "repo.destroy".

と送ったところ、「技術的な問題があって復元できなかった。エンジニアチームに尋ねた」と返ってきた。

(´-`).。oO(もしかして GitHub のバグを踏んでしまいリポジトリが死んだのでは...)

そして新年

年末年始で返信もなく GitHub も休んでそうだったので、連休明けの 1/11(水) に進捗を確認してみた。

Could you tell us about the current status?
The repository is not restored yet. How long will it take?

翌日1/12(木)の夜には返信があって、無事にリポジトリが復活した。

Sorry for the delay on this one but the repository should now be restored.

リポジトリが無い期間の開発

初日

すぐ復活するだろうと思っていたのと小さい変更だったので、2つだけ Slack上で diff をレビュー してデプロイしました。

f:id:sinsoku:20170118110210p:plain

「手順書を用意 => バックアップ作成 => パッチ適用 => ホットデプロイ」を手動でやるとか、旧世代のデプロイに懐かしさを感じた1日。

2日目以降

リポジトリの復元に時間かかりそうな返信内容だったのと、さすがに 2017年にもなって手動デプロイとか無理 なので、一時リポジトリを作成して作業することになりました。

  • 一時リポジトリの作成
  • CI の環境構築
  • Capistrano の設定変更

こういうとき Git だとリポジトリの複製が楽で良いですね。

リポジトリが復活した後

Capistrano の設定を戻し、一時リポジトリの Pull Request, Review Comment はスクリプトを書いてバックアップを保存しました。

#!/usr/bin/env ruby
# frozen_string_literal: true
require 'fileutils'
require 'octokit'

OWNER = 'grooves'
REPO = 'tmp_repo'
FULL_NAME = "#{OWNER}/#{REPO}"
DOC_PATH = File.expand_path('../../doc', __FILE__)
PULLS_PATH = "#{DOC_PATH}/#{REPO}/pulls"
COMMENTS_PATH = "#{DOC_PATH}/#{REPO}/review_comments"

Octokit.auto_paginate = true

class RepoBackup
  attr_reader :token

  def initialize
    @token = ENV['TOKEN']
  end

  def save_pulls
    mkdir_p(PULLS_PATH)

    pulls = client.pulls(FULL_NAME, state: :all)
    pulls.each do |pull|
      write_json("#{PULLS_PATH}/#{pull[:number]}", pull)
      puts "saved pulls##{pull[:number]}"
    end
  end

  def save_comments
    mkdir_p(COMMENTS_PATH)

    comments = client.reviews_comments(FULL_NAME)
    comments.each do |comment|
      write_json("#{COMMENTS_PATH}/#{comment[:id]}", comment)
      puts "saved review_comment##{comment[:id]}"
    end
  end

  private

  def client
    @client ||= Octokit::Client.new(access_token: token)
  end

  def mkdir_p(path)
    FileUtils.mkdir_p(path) unless FileTest.exist?(path)
  end

  def write_json(path, obj)
    File.write(path, JSON.pretty_generate(obj.to_h))
  end
end

backup = RepoBackup.new
if backup.token
  puts 'Start backup script'

  backup.save_pulls
  backup.save_comments

  puts "Backup finished and saved to #{DOC_PATH}."
else
  puts 'USAGE: TOKEN=xxx ./script/backup_forkwell_tmp'
end

対策方法

通常なら GitHub に問い合わせれば復元できるっぽいので、厳重な対策はしなくて良いかもしれません。 ただ、公表されているサポートって訳でも無いですし、数時間でもリポジトリが消えたら困るってケースもあると思うので、いくつか方法を挙げておきます。

1. 削除時の確認

今回の件で初めて知ったのですが、リポジトリを削除するときの確認画面でリポジトリ名だけでなく ユーザー名 も入力することが可能です。

f:id:sinsoku:20170118110233p:plain

削除時は sinsoku/xxx のようにユーザー名(もしくは Organization 名)を含めて入力し、念のためスクリーンショットを保存しておくのが良さそうです。

2. 開発者に Admin 権限をつけない

Terraform の GitHub provider を使えば、Organization のリポジトリをコードで管理することが可能です。

  1. Admin を1人だけ作成する
  2. リポジトリを削除するときは Pull Request でレビューしてからマージする
  3. マージ後 Admin の token を使って terraform apply でリポジトリを削除する

上記のような運用を行うことで、より安全なリポジトリ管理ができるかと思います。

3. 外部サービス

f:id:sinsoku:20170118110246p:plain

BackHub という GitHub のリポジトリを定期的にバックアップしてくれるサービスを見つけました。 使ったことがないので詳細は不明ですが Issue, Pull Request などを定期的に保存するなら便利そうです。

まとめ

GitHub のバグなのか、私の操作ミスなのか最終的な原因は分かりませんでしたが、リポジトリが無事に復旧して良かったです。

この記事が皆様の 参考にならない事 を祈っていますが、もしリポジトリが消えてしまった時は本記事を参考にして頑張ってください。

f:id:sinsoku:20170118110254p:plain

Forkwell Jobs の求人編集画面で textlint の文章チェックができるようになりました

もう師走ですね。お疲れ様です @sinsoku です。

本日、 Forkwell Jobs で求人票の文章チェック機能をリリースしました。
求人票を作成・編集する人事様向けの機能です。

文章チェック機能

求人の編集画面で「文章チェック」のタブをクリックすることで、求人票の文章をチェックできるようになりました。

f:id:sinsoku:20161209110800g:plain

チェックしているルール

Forkwell Jobs で採用しているルールは現状だと下記の通りです。

今後の予定

今後もルールを追加・調整して、求人作成がもっと楽になるようにしていきたいと思っています。

この機能が Forkwell Jobs を利用している人事様の助けになれば幸いです。

Forkwell Scout のつくりかた 〜デザイナー視点〜

はじめまして、デザイナーの@711fumiです。

先日「Forkwell Scout」というスカウトサービスが爆誕いたしました。 (と言っても現在β版です)

f:id:blog_711fumi:20161110130641p:plain

ソフトウェアエンジニアがイキイキと働ける環境に出会えるよう、私達が運営するWebエンジニア向けの転職サイト「Forkwell Jobs」内の新機能としてリリースしたサービスです。 スパム(マッチしないスカウト)のない、エンジニアが正しく評価してもらえるサービスを目指して絶賛開発中です。

今年の春にプロジェクトが動き始め、現在β版までこぎつけることができました。 せっかくなので?デザイナーがどのようにプロジェクトに関わっていたかぼんやり振り返ってみようと思います。

企画段階

企画段階ではプロダクトオーナーを中心に、サービスが目指す方向性を固めていました。 そしてその方向性を元に本当に必要な機能(最低限必要な機能)を洗い出し、イメージを作っていく段階で具体的なデザインタスクが発生しました。

エンジニアや他のメンバーと相談しながら、画面遷移や導線などを確認をおこないました。

デザイン作成とレビュー

必要な機能がある程度固まったら、Sketchでデザインを作成しInVisionなどでレビューを行いました。 InVisionを使うことで、

  • レビュー内容(対応済みかどうか)が残る
  • レビュー会議みたいなのを開く必要がない

ってのはありがたいですね。

(InVisionで思い出しましたがCraftのPrototype機能はいつお目見えするんでしょうね…?)

動きのイメージ、導線の確認

今回はプロトタイピングツールのFlintoをつかって動きの確認をおこないました。 初めて使ってみたのですが、実際に動かせるものを用意して確認することで気がつく問題点や改善点があり、なかなか勉強になりました。 Sketchとの連携も簡単ですしね。 ただプロトタイプを作るのが目的(設計)ではないので、使い所を見極めないとなぁとは思います。

Sketchでのデザインやプロトタイプ作成は実装では無いので、何をどこまで設計(決定)する必要があるのかをデザイナーは最初に確認しておく必要がありそうです。

初期開発

必要となる機能・画面の設計が固まってきたらいよいよ開発です。 スモールスタートを目指すということで、「最低限必要な機能を最速で作る」意識で開発チームは動いていました。

エンジニアとの協業

URL設計などはエンジニアメンバーがやってくれているので、必要なページ(RailsでいうViewファイル)を用意してもらい、デザイナーがそこにガリガリコーディングしていくというスタイルで進めていきました。

「デザインだけ作っておいたからデータ取ってくるとこ実装よろしく」
「とりあえずデータが表示されてるだけのページ作ったんで、見た目はお願い」

というように得意な部分だけやって後は相手にガンガン投げるという方法で問題なく噛み合い、(おそらく)スムーズに初期開発が進んでいきました。

このエンジニア:デザイナーの境界みたいなものはメンバーのスキルセット(及び嗜好性)で決まるとおもうので、チームごとにいいやり方が見つかるよう調整していくのが良さそうです。

素早く形にする

「最低限必要な機能を最速で作る」という方針で進める上で助かったのは、Forkwll Jobsにはスタイルガイドがあるということです \(;∀;)/

CSSスタイルガイドを作って良かった話

トーンが統一された必要なコンポーネントがスタイルガイドで用意されているため、ある程度見た目を保証しつつ、素早くUI実装を進めることができました。

スタイルガイドと一口に言ってもwikiのようなものからコンポーネントを実装してまとめたものまで様々あると思いますが、デザインで迷う部分を少しでも減らすために何かしら準備しておくのをおすすめします。

こっそりリリース〜限定公開

最低限の機能が実装できた後、一部の人のみ利用できるようこっそりリリースがおこなわれました。 ここからはひたすら改善を進める日々です。

ユーザーテスト

開発チームに産技大で人間中心デザインを学んできたエンジニアがいるので、 彼主導で繰り返しユーザーテストを行い、貰ったフィードバックを参考に改修を行っています。

  • 社内チームメンバー
  • エンジニア採用を行っている企業の採用担当者
  • エンジニア

にテストをお願いしました。

実際のユーザー(ターゲットとなる方々)からリアルな意見をもらえることで、いろいろな気づきを得ることができました。 やっぱりそうなのね…と改めて実感させられる事もあれば、想定してたんとぜんぜん違う…みたいな使ってもらって初めて分かることなど様々でしたね。

ただ何よりも、自信を持って仕様の変更や機能の改修をおこなうことができるのが大きいです。 もちろんユーザーの意見をただサービスに反映させればいいというわけではないですし、優先度や大きさみたいなものも見極めなければならないのですが。

そして公開へ…

で、現在はβ版ではありますが全ての方に公開している状態になっています。

引き続きエンジニアのことを一番に考え、エンジニアが幸せになるためのサービスを目指して開発していきますので注目していただければ幸いですー

なんかデザインの話でもなんでも無くなっちゃいましたが… サービスについてご意見ありましたらぜひ教えてください! (ΦωΦ)/

Forkwell の開発で使っている rubocop の設定を公開しました

こんにちは、Forkwell 開発チームの @sinsoku です。

Forkwell の開発では RuboCop を使っていますが、その設定を onk/onkcopesminc/deka_eiwakun を参考に gem にして公開してみました。

github.com

コーディングスタイルについて

forkwell_cop というか、 Forkwell のコーディングスタイルは作成中です。 まだチーム内で議論していない項目は config/todo.yml に記載してあり、これから少しずつ対応していきます。

まだ作成中とはいえ、公開しても特に問題なさそうなので、gem にして公開してみました。

また、各社の Rubocop の設定がこのような形で公開されるとお互い参考になると思いますので、気が向いた方は公開してもらえると面白いんじゃないかなぁと思っております。

http://agile.esm.co.jp/news/2016-04-01-open-esm-ruby-code-convention.html

と永和さんの記事にもあるように、各社の Rubocop の設定がもっと公開されると面白くなると思うので、みんな公開したら良いと思います!

宣伝

forkwell.connpass.com

Forkwell を Rails 5.0.0.1 へアップグレードしました

こんにちは、Forkwell 開発チームの @sinsoku です。

Rails 5.0.0.1, 4.2.7.1, and 3.2.22.3 have been released! で Rails のセキュリティアップグレードが公開されました。
弊社で該当する処理はありませんでしたが、 Rails は上げられるときにアップグレードしておいた方が楽なので、さっさとアップグレードしておきました。

f:id:sinsoku:20160812141826p:plain

今回のアップグレード内容

CVE-2016-6316 を読むと分かるようにXSS脆弱性の対応です。 差分は rails/rails@v5.0.0...v5.0.0.1 を見ると tag_helper の修正だけでした。

# 5.0.0
content_tag('p', "content", title: '"'.html_safe)
#=> <p title=""">content</p>

# 5.0.0.1
content_tag('p', "content", title: '"'.html_safe)
#=> <p title="&quot;">content</p>

html_safe を使う機会は少ないかもしれませんが、使っている方は早めに上げた方が良さそうです。

最速で Forkwell を Rails 5 にアップグレードしてみました

こんにちは、Forkwell 開発チームの @sinsoku です。

本日の Riding Rails: Rails 5.0: Action Cable, API mode, and so much more で Rails 5.0.0 のリリースが告知されました。

f:id:tshotoku:20160701180502p:plain

Rails 5 ではいくつもの新機能・変更点があります。

  • Action Cable
  • Rails API
  • Railsコマンド
  • Turbolinks 5

各機能の詳細については、リリース前から既にいくつも記事が出てますし、そちらを参照ください。

そんな Rails 5 のリリース直後ですが、弊社では早速 Forkwell Jobs をアップグレードして、デプロイしてみました。

いくつかの変更は Rails 4.2.6 でも対応できますので、業務の合間などに少しずつ進めておくのがオススメです。 この記事が皆さんの Rails 5 アップグレードの参考になれば幸いです。

目次

  • Rails 5 へのアップグレード前の環境
  • gem の依存関係を解決する
  • 設定ファイルなどの更新
  • Rails Guide に従って更新
    • Active Record Models Now Inherit from ApplicationRecord by Default
    • Halting Callback Chains via throw(:abort)
    • ActiveJob Now Inherits from ApplicationJob by Default
    • Rails Controller Testing
  • schema.rb の更新
  • ApplicationMailer を継承するように修正
  • Turbolinks v5.0.0 対応
    • Progress bar の表示
    • イベントの変更
    • PhantomJS 1.9.2 対応
  • API の仕様変更への対応
    • [Error] Rails 3 時代の count(:xxx, distinct: true) を削除
    • [Error] Rails 3 時代の mass-assignment の修正
    • [Error] find_by(n) みたいな使い方していたので修正
    • [Error] ActiveRecord::Relation で slice が使えなくなっているので to_a.slice に修正
    • [Error] has_many の collection<<(object...) で select を使用したコードを修正
    • [Error] content_tag_for を使わないように修正
    • [Error] namespace 内の root でメソッドが生成されないので修正
    • [Warning] uniq を distinct に変更
    • [Warning] Controller の envを request.env に修正
    • [Warning] alias_method_chain を Module#prepend に修正
    • [Warning] alias_method_chain を Module#prepend に修正
    • [Warning] db/migrate/*.rb で ActiveRecord::Migration[4.2] を使うように修正
  • RSpec
    • [Warning] HTTP request methods の引数の変更
  • 調べる時間がなかった
    • [Error] routes の helper メソッドの引数の挙動が変
    • [Error] テストでミリ秒まで比較するとテストが落ちる
    • [Error] ActionController::Parameters#slice! がエラー
    • [Error] テストで使用していた params でエラー
    • [Error] ActiveRecord#touch が 2回走る?
    • [Warning] render :text を render :plain に修正
  • あとがき

Rails 5 へのアップグレード前の環境

Ruby: 2.3.1 Rails: 4.2.6

また、他の gem は tachikomatachikoma_ai を使い、毎週アップデートを実施してました。

gem の依存関係を解決する

まず rails 以外の gem を可能な限りアップデートします。

$ bundle update

次に rails をアップグレードします。

$ bundle update rails

エラーが出た gem を1つずつ対応していきます。

  1. beta, pre などのバージョンがないか確認
  2. gem 'rspec', '>= 3.5.0.beta3' などで対応
  3. GitHub の master ブランチが対応していないか確認する
  4. gem 'sinatra', github: 'sinatra/sinatra' のように対応
  5. Issue や Pull Request で対応されていないか確認する
  6. 対応されていれば fork されたリポジトリを使用する
  7. ソースコード読んで自分で直す
  8. プルリクを投げるチャンスですね!

地道に対応すれば、そのうち bundle update できるようになります。

設定ファイルなどの更新

rake app:update を実行し、設定ファイルなどを更新していきます。 ただ、競合したファイルはひとまず skip しておきます。

次に別ディレクトリに rails new app5 のように 5.0.0 で新しいアプリを作成し、 skip したファイルは新しいアプリと比較して、手で直しました。

比較方法の一例として、 vim を使った方法を紹介します。

$ vim -d config/environments/development.rb ~/tmp/app5/config/environments/development.rb

変更内容はアプリによって異なるので、各自で頑張ってください。

ちなみに、弊社では ActionCable はまだ未使用のため、コメントアウトしています。

  • config/application.rb
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
# require "action_cable/engine"
require "sprockets/railtie"
# require "rails/test_unit/railtie"

Rails Guide に従って更新

upgrading-from-rails-4-2-to-rails-5-0 を見ながら、各項目の対応をしました。

Active Record Models Now Inherit from ApplicationRecord by Default

ActiveRecord::Base を直接継承せず、 ApplicationRecord を継承するように直しました。

$ git grep -l ActiveRecord::Base -- app/models | xargs sed -i '' "s/ActiveRecord::Base/ApplicationRecord/g"

既存ファイルの継承元を sed で修正し、 ApplicationRecord を新規に作成します。

# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end

Halting Callback Chains via throw(:abort)

4.2 では before_save などで false を返すと、ROLLBACK されていました。 これが 5.0 では throw(:abort) と明示的に例外を投げる必要があります。

class User
  before_save { false }
  before_save { p 'Hello' } # 4.2 では実行されないが、 5.2 では実行される
end

git grep before_ -- app/models などで該当箇所を調べ、 return falsethrow(:abort) に修正しました。

ActiveJob Now Inherits from ApplicationJob by Default

ActiveJob::Base を直接継承せず、 ApplicationJob を使うようになりました。

$ git grep -l ActiveJob::Base -- app/models | xargs sed -i '' "s/ActiveJob::Base/ApplicationJob/g"

既存ファイルを一斉置換したあとに ApplicationJob を新規作成します。

# app/jobs/application_job.rb
class ApplicationJob < ActiveJob::Base
end

Rails Controller Testing

Rails 5 からコントローラーのテストで assigns などを使う場合は rails-controller-testing の gem が必要になりました。 gem がない状態でテストを実行すると下記のようなエラーが発生します。

Failure/Error: expect(assigns(:user)).to be_a_new(User)
NoMethodError:
  assigns has been extracted to a gem. To continue using it,
          add `gem 'rails-controller-testing'` to your Gemfile.

また、 rails-controller-testing には rails/rails-controller-testing#22 の問題があるため、 require: false にして読み込みを遅延させる必要があります。

# Gemfile
group :test do
  gem 'rails-controller-testing', require: false
end

# spec/rails_helper
RSpec.configure do |config|
  require 'rails-controller-testing'
  [:controller, :view, :request].each do |type|
    config.include ::Rails::Controller::Testing::TestProcess, :type => type
    config.include ::Rails::Controller::Testing::TemplateAssertions, :type => type
    config.include ::Rails::Controller::Testing::Integration, :type => type
  end
end

schema.rb の更新

Rails 5 で schema.rb の形式が変わっているので、更新しました。 一回 rails db:migrate を実行すれば更新されます。

$ rails db:migrate

Rails 5 から rake db:migraterails db:migrate でも実行できるようになっています。

ApplicationMailer を継承するように修正

ApplicationRecord, ApplicationJob と同様に ApplicationMailer になっていたので修正。

$ git grep -l ActionMailer::Base -- app/mailers | xargs sed -i '' "s/ActionMailer::Base/ApplicationMailer/g"

既存ファイルを一斉置換したあとに ApplicationMailer を新規作成します。

# app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: 'from@example.com'
  layout 'mailer'
end

Turbolinks v5.0.0 対応

Progress bar の表示

2.x では Turbolinks.enableProgressBar() を実行していましたが、 5.0.0 ではメソッドが無くなり、デフォルトで ON になっています。

https://github.com/turbolinks/turbolinks/tree/v5.0.0#displaying-progress

色を変える場合は下記のような CSS を用意します。

.turbolinks-progress-bar {
  height: 5px;
  background-color: green;
}

また、プログレスバーを非表示にする場合は visibility: hidden にします。

.turbolinks-progress-bar {
  visibility: hidden;
}

イベントの変更

turbolinks のイベントが変更されているので、対応が必要です。

https://github.com/turbolinks/turbolinks/tree/v5.0.0#full-list-of-events

- $(document).on 'ready page:load', ->
+ $(document).on 'turbolinks:load', ->

よくありがちな ready page:loadturbolinks:load に変更する必要があります。

PhantomJS 1.9.2 対応

PhantomJS 2系だと一部のテストが動かないため、 PhantomJS 1.9.2 を使っていたのですが、 PhantomJS 1.9.2 だと Turbolinks 5.0.0 は動きませんでした。

5.0.0.beta5 までは動いたので、 vender/assets/turbolinks_5_beta5.js を置いて、バージョンを固定して対応しました。

-//= require turbolinks
+//= require turbolinks_5_beta5

ただ、これだと turbolinks をアップデートできなくなるので、早めに PhantomJS をバージョンを上げたい。

API の仕様変更への対応

5.0.0 で API の挙動が色々と変わっていたので、対応しました。 一部、弊社では謎のコード・挙動にも遭遇しましたが、知見の共有として紹介します。

[Error] Rails 3 時代の count(:xxx, distinct: true) を削除

# 4.2.6
User.count(:name, distinct: true)
#   (0.2ms)  SELECT COUNT("users"."name") FROM "users"
#=> 0

# 5.0.0
User.count(:name, distinct: true)
#=> ArgumentError: wrong number of arguments (given 2, expected 0..1)

上記のように、エラーが発生します。 ただ、元のコードでは DISTINCT 効いていません。もしこんなコードを見つけたら、ちゃんと仕様を確認して、修正しましょう。

弊社の場合 使われていないページ でしたので、該当箇所のコードは全て消しました。直す場合は下記の通りです。

# 5.0.0
User.distinct.count(:name)
#   (0.6ms)  SELECT DISTINCT COUNT(DISTINCT "users"."name") FROM "users"
#=> 0

[Error] Rails 3 時代の mass-assignment の修正

# 4.2.6
User.new(params[:user], as: :admin)
#=> <User id: nil, name: "foo", ...

# 5.0.0
User.new(params[:user], as: :admin)
#=> ArgumentError: wrong number of arguments (given 2, expected 0..1)

上記のように、エラーが発生します。 Rails 4 からは Strong Parameters を使うべきなので、修正しましょう。

class UsersController < ApplicationController
  def create
    @user = User.new user_params
  end

  private

  def user_params
    params.require(:user).permit(:name)
  end
end

[Error] find_by(n) みたいな使い方していたので修正

# 4.2.6
User.find_by(1)
#  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE (1) LIMIT 1
#=> nil

# 5.0.0
User.find_by(1)
# ArgumentError: Unsupported argument type: 1 (Fixnum)

上記のように、エラーが発生します。 弊社の場合、運良く動いていましたがメソッドの使い方が謎なので、仕様を確認して直しましょう。

[Error] ActiveRecord::Relation で slice が使えなくなっているので to_a.slice に修正

# 4.2.6
User.all.slice(0)
#  User Load (1.9ms)  SELECT "users".* FROM "users"
#=> #<User id: 1, ...

# 5.0.0
User.all.slice(0)
#  User Load (1.2ms)  SELECT "users".* FROM "users"
#=> NoMethodError: undefined method `slice' for #<User::ActiveRecord_Relation:0x007fad46c2bcf8>

上記のように、エラーが発生します。 ActiveRecord 周りの仕様が変わったぽい。ひとまず to_a すれば元の挙動を維持できるので、それで対応しました。

# 5.0.0
User.all.to_a.slice(0)
#  User Load (0.1ms)  SELECT "users".* FROM "users"
#=> #<User id: 1, ...

[Error] has_many の collection<<(object...) で select を使用したコードを修正

モデルの構造は下記の通り。

class User < ApplicationRecord
  has_many :posts, through: :bookmarks
  has_many :bookmarks
end
# 4.2.6
u.posts << Post.select(:id)
#  Post Load (0.1ms)  SELECT "posts"."id" FROM "posts"
#   (0.1ms)  begin transaction
#  SQL (0.6ms)  INSERT INTO "bookmarks" ("user_id", "post_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["user_id", 1], ["post_id", 1], ["created_at", "2016-06-12 11:49:54.391603"], ["updated_at", "2016-06-12 11:49:54.391603"]]
#  SQL (0.1ms)  INSERT INTO "bookmarks" ("user_id", "post_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["user_id", 1], ["post_id", 2], ["created_at", "2016-06-12 11:49:54.401580"], ["updated_at", "2016-06-12 11:49:54.401580"]]
#   (0.9ms)  commit transaction
#  Post Load (0.2ms)  SELECT "posts".* FROM "posts" INNER JOIN "bookmarks" ON "posts"."id" = "bookmarks"."post_id" WHERE "bookmarks"."user_id" = ?  [["user_id", 1]]
#=> #<ActiveRecord::Associations::CollectionProxy [#<Post id: 1>, #<Post id: 2>]>

# 5.0.0
u.posts << Post.select(:id)
#  Post Load (0.1ms)  SELECT "posts"."id" FROM "posts"
#   (0.1ms)  begin transaction
#   (0.1ms)  rollback transaction
#=> ActiveModel::MissingAttributeError: missing attribute: user_id

上記のように、エラーが発生します。 ただ、そもそも元のコードで N+1 が起きていたので、弊社では zdennis/activerecord-import を使って collection<<(object...) を使わない形に修正しました。

[Error] content_tag_for を使わないように修正

<!-- 4.2.6 -->
<table>
  <!-- -->
  <%= content_tag_for(:tr, @user) do %>
    <td><%= @user.name %></td>
  <% end %>
</table>

<!-- 5.0.0 -->
<table>
  <!-- -->
  <%= content_tag_for(:tr, @user) do %>
    <td><%= @user.name %></td>
  <% end %>
</table>
<!--
ActionView::Template::Error (The `content_tag_for` method has been removed from Rails. To continue using it, add the `record_tag_helper` gem to your Gemfile:
  gem 'record_tag_helper', '~> 1.0'
Consult the Rails upgrade guide for details.):
-->

上記のように、エラーが発生します。 メッセージに従ってrecord_tag_helperを入れても良かったのですが、使用箇所も1つでしたので使わないように変更しました。

ちなみに、関連するIssueは↓です。

I remember adding RecordTagHelper as an experiment, but never felt happy with its use in real life. Let's kick it out in a gem and remove it from core. https://github.com/rails/rails/issues/18337

[Error] namespace 内の root でメソッドが生成されないので修正

# 4.2.6
Rails.application.routes.draw do
  root 'top#index'
  namespace :admin do
    root 'top#index'
  end
end
#         root GET    /                             top#index
#   admin_root GET    /admin(.:format)              admin/top#index

# 5.0.0
Rails.application.routes.draw do
  root 'top#index'
  namespace :admin do
    root 'top#index'
  end
end
#         root GET    /                             top#index
#              GET    /admin(.:format)              admin/top#index

上記のように、helper メソッドが生成されなくなります。 as オプションを使用して修正する必要があります。

Rails.application.routes.draw do
  root 'top#index'
  namespace :admin do
    root 'top#index', as: :root
  end
end
#         root GET    /                             top#index
#   admin_root GET    /admin(.:format)              admin/top#index

[Warning] uniq を distinct に変更

# 4.2.6
User.uniq
#  User Load (2.9ms)  SELECT DISTINCT "users".* FROM "users"
#=> #<ActiveRecord::Relation [#<User id: 1, name: nil, created_at: "2016-06-05 13:39:31", updated_at: "2016-06-05 13:39:31">]>

# 5.0.0
User.uniq
#DEPRECATION WARNING: uniq is deprecated and will be removed from Rails 5.1 (use distinct instead). (called from irb_binding at (irb):2)
#  User Load (0.2ms)  SELECT DISTINCT "users".* FROM "users"
#=> #<ActiveRecord::Relation [#<User id: 1, name: nil, created_at: "2016-06-05 13:39:19", updated_at: "2016-06-05 13:39:19">, #<User id: 2, name: nil, created_at: "2016-06-12 11:36:46", updated_at: "2016-06-12 11:36:46">]>

上記のように、警告が発生します。 修正方法は下記の通り。

User.distinct
#  User Load (0.2ms)  SELECT DISTINCT "users".* FROM "users"
#=> #<ActiveRecord::Relation [#<User id: 1, name: nil, created_at: "2016-06-05 13:39:19", updated_at: "2016-06-05 13:39:19">, #<User id: 2, name: nil, created_at: "2016-06-12 11:36:46", updated_at: "2016-06-12 11:36:46">]>

[Warning] Controller の envを request.env に修正

# 4.2.6
env['HTTP_USER_AGENT']
#=> "Mozilla/5.0 (Macintosh; Intel Mac OS X...

# 5.0.0
env['HTTP_USER_AGENT']
DEPRECATION WARNING: env is deprecated and will be removed from Rails 5.1
#=> "Mozilla/5.0 (Macintosh; Intel Mac OS X...

上記のように、警告が発生します。 修正方法は下記の通り。

request.env['HTTP_USER_AGENT']
#=> "Mozilla/5.0 (Macintosh; Intel Mac OS X...

[Warning] alias_method_chain を Module#prepend に修正

サンプルコードは下記の通り。*1

class User < ApplicationRecord
  def say
    puts 'Hello, World'
  end

  def say_with_name
    puts "Hi, #{name}"
    say_without_name
  end
  alias_method_chain :say, :name
end

各バージョンで試した結果は下記の通り。

# 4.2.6
User.new(name: 'sinsoku').say
#=> Hi, sinsoku
#=> Hello, World

# 5.0.0
User.new(name: 'sinsoku').say
#=> DEPRECATION WARNING: alias_method_chain is deprecated. Please, use Module#prepend instead. From module, you can access the original method using super.
#=> Hi, sinsoku
#=> Hello, World

警告メッセージに従い Module#prepend を使う形に修正しました。

class User < ApplicationRecord
  def say
    puts 'Hello, World'
  end
end

module SayWithName
  def say
    puts "Hi, #{name}"
    super
  end
end
User.prepend(SayWithName)

[Warning] db/migrate/*.rb で ActiveRecord::Migration[4.2] を使うように修正

migration のサンプルコード。

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :name

      t.timestamps
    end
  end
end

これは 5.0.0 で警告が出る。( rails db:migrate警告は出ませんでしたが、直した方が良さそうなので直しておきました)

追記: id:sue445 さんのコメントの指摘通り log/development.log に WARNING が出ていました。

DEPRECATION WARNING: Directly inheriting from ActiveRecord::Migration is deprecated. Please specify the Rails release the migration was written for:

  class CreateUsers < ActiveRecord::Migration[4.2] (called from require at bin/rails:4)

これは sed で一斉置換して対応しました。

$ git grep -l ActiveRecord::Migration -- db | xargs sed -i '' "s/ActiveRecord::Migration/ActiveRecord::Migration[4.2]/g"

RSpec

[Warning] HTTP request methods の引数の変更

RSpec.describe UsersController, type: :controller do
  describe "GET #show" do
    it "assigns the requested user as @user" do
      user = User.create!
      get :show, id: user.to_param #=> ①
      expect(assigns(:user)).to eq(user)
    end
  end
end

① の行で警告が表示されます。

DEPRECATION WARNING: ActionController::TestCase HTTP request methods will accept only
keyword arguments in future Rails versions.

Examples:

get :show, params: { id: 1 }, session: { user_id: 1 }
process :update, method: :post, params: { id: 1 }

警告メッセージに従って、メソッドの引数を修正しました。

 RSpec.describe UsersController, type: :controller do
   describe "GET #show" do
     it "assigns the requested user as @user" do
       user = User.create!
-      get :show, id: user.to_param
+      get :show, params: { id: user.to_param }
       expect(assigns(:user)).to eq(user)
     end
   end
 end

調べる時間がなかった

4.2.6 と 5.0.0 で比較するのに予想以上に時間かかったので、以下は現象だけ紹介。

(あとで再現できたら、追記します)

[Error] routes の helper メソッドの引数の挙動が変

# /users/:user_name
user_url(user_name, options)

上記のような書き方をしていた箇所がエラーになるケースがありました。

user_url(options.merge(username: user_name))

こんな感じで対応しました。

[Error] テストでミリ秒まで比較するとテストが落ちる

テストで時刻を比較しているテストが落ちていました。

it { expect(job.published_at).to eq now }

byebug で調べてみると、どうもミリ秒の部分が異なるのが理由みたい。 弊社では秒単位の比較で十分だったので、下記のように to_i で対応しました。

it { expect(job.published_at.to_i).to eq now.to_i }

[Error] ActionController::Parameters#slice! がエラー

params[:data].slice! のように書いていた箇所が動かなくなっていたので修正しました。

 def update
-   data = params[:data].slice!
+   data = params[:data].to_h.slice!
    # ...
 end

[Error] テストで使用していた params でエラー

テストで permit せずにアクセスしていたため、エラーになっていました。

it { expect(controller.params[:q].to_unsafe_h.symbolize_keys).to include(freeword: 'keyword') }

テストなので、 to_unsafe_h を使って対応しました。

[Error] ActiveRecord#touch が 2回走る?

Rails の Optimistic Locking を使っているコードで ActiveRecord#touch が2回走るような謎の現象に遭遇しました。

弊社では色々とあって、楽観的ロックを使うコードがなくなり、結果として対応が不要になりました。 ただ、発生原因がよく分からなかったので、アプリで使っている人は気をつけた方が良いかも。

[Warning] render :text を render :plain に修正

DEPRECATION WARNING: `render :text` is deprecated because it does not actually render a `text/plain` response. Switch to `render plain: 'plain text'` to render as `text/plain`, `render html: '<strong>HTML</strong>'` to render as `text/html`, or `render body: 'raw'` to match the deprecated behavior and render with the default Content-Type, which is `text/plain`.

まだ対応できていない。

あとがき

Rails 5 のアップグレード、動作検証、リリース、そして長いブログを書いて、本当に疲れた。ビール飲みたい。

そんな Rails 5 にアップグレードされた Forkwell Jobs で、ぜひ Ruby 求人を検索 してみてください。 あと、テストしているので大丈夫だとは思いますが、変な挙動を見つけたらこっそり教えてください。

*1:実際は Rails やその他 gem にモンキーパッチを当てる場合に使ってました