Rails + RSpec で FactoryBot(旧 FactoryGirl)を使う

FactoryBot

github.com

基本的には GETTING STARTED にしたがって導入します.

FactoryGirl が FactoryBot に変更された経緯はこちらの issue を参照してください.

github.com

Gemfile

Gemfile に factory_bot_rails を追加して bundle install してください.Rails の場合は factory_bot でないことに注意してください.

gem "factory_bot_rails", "~> 4.0"

.rspec

.rspec--require rails_helper を追記します..rspec が存在しない場合は rails rspec:install を実行します.プロジェクトのルートディレクトリに .rspec が生成されます.

なお,既存の .rspec--require spec_helper が記述されている場合はそれを削除してください.これは spec/rails_helper が下記のように spec/spec_helper を require するためです.

# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails'

spec/rails_helper.rb

spec/rails_helper.rbrequire 'factory_bot' を追記します.下記のように「# Add additional ...」に続けて追記するとよいでしょう.

# Add additional requires below this line. Rails is not loaded until this point!
require 'factory_bot'

つづけて,RSpec.configure do |config| 以下に下記を追記します.

config.include FactoryBot::Syntax::Methods

これにより下記のように FactoryBot シンタックスを省略できます.

# before
FactoryBot.build(:user)
FactoryBot.create(:user)
FactoryBot.build_stubbed(:user)
FactoryBot.attributes_for(:user)

# after
build(:user)
create(:user)
build_stubbed(:user)
attributes_for(:user)

FactoryBot の呼び出し

導入は完了です.FactoryBot は下記に配置されたファイルを factory として自動検出します.RSpec を使用する場合は spec/factories/**/*.rb に配置するのがよいでしょう.

test/factories.rb
spec/factories.rb
test/factories/*.rb
spec/factories/*.rb

factory は手動で生成することもできますし,次のように rails g を使用して自動生成することもできます.

rails g factory_bot:model User screen_name:string name:string email:string password:string

このとき,すべての factory ファイルは名前空間を共有することに注意してください.すなわち,同名の factory を別々のファイルで定義した場合,AttributeDefinitionError が発生します.

たとえば,tasks.rbuser.rb で次の factory を定義した場合 AttributeDefinitionError が発生します.

factory :admin do
  screen_name 'yurafuca'
  name  "ゆらふか"
  email "root@yurafuca.com"
  password "password"
  password_confirmation "password"
  admin true
end

動かない場合

FactoryBot.reload

rails_helper.rb に下記を追記することを検討してください.

config.before(:all) do
  FactoryBot.reload
end

この問題については下記のエントリが参考になります.

masawada.hatenablog.jp

config.generators.fixture_replacement

application.rb に下記を追加することを検討してください.

config.generators.fixture_replacement :factory_bot, dir: 'spec/factories'

この問題については下記のエントリが参考になります.

qiita.com

budle clean --force

下記の例外が発生する場合があります.

Unresolved specs during Gem::Specification.reset: minitest (~> 5.1)

この場合は bundle clean --force および bundle install を実行することを検討してください.

この問題については下記の issue が参考になります.

github.com

Yahoo! Japan主催のHack U 2017 NAGOYAで最優秀賞とHappy Hacking賞をダブル受賞しました

f:id:yurafuca:20171008142941j:plain

諸事情で公開が遅れてしまいましたが,Yahoo! Japan 主催のハッカソン『Hack U 2017 NAGOYA』に出場して,最優秀賞と Happy Hacking 賞をダブル受賞しました.

メンバーは @garicchi@drilldripper@yurafuca(僕)です.

「最高の夏にするぞ!」「やっていくぞ!」などと言いながら奇妙に速が上がった状態で開発を進めていたら本当に最高の夏を手に入れることができました.2017 年夏,本当にありがとう…….

 プロダクト紹介

『みずかけメモリアル』というゲームを作りました*1

f:id:yurafuca:20170908123054p:plain

最高の夏を最高の美少女と一緒に過ごしたい。そんな純粋な気持ちを叶えてくれるオンラインゲームを作りました。ゲームを始めると対戦相手(美少女)とマッチングし、バーチャル空間の砂浜で水をかけあうことができます。ゲーム中で水をかけられると、現実世界のあなたにも水がかかります。圧倒的なリアリティで忘れられない夏を送りましょう。(作品概要より)

スマートフォンにゲームクライアントをインストールして画面をタップすると,霧吹きから水が噴出されます.そして,顔に水がかかります(!).

遊び方

ゲームを起動します.名前を決めてログインをしたら「マッチング開始」ボタンを押しましょう.世界中のどこかの美少女とのマッチングが開始します.

f:id:yurafuca:20170908122953p:plain

マッチングが完了したらフィールドにいる女の子に向かって水をかけましょう.ライバルの女の子も同様に水をかけてきますから,移動しながらうまく避けましょう.このときフィールドにあるのは,美少女のあなたと,美少女のライバル,そして薄暮のような砂浜だけです.

ライバルの女の子がかけた水があなたにヒットすると,あなたの目の前に置かれた霧吹きから水が噴射されます.仮想世界のあなたと,現実世界のあなたは完全に繋がっています.

今回の Hack U のテーマは「UPDATE 夏休み!」でした.砂浜で女の子と水をかけあうことで夏を UPDATE しましょう.詳しい説明はプレゼンの動画をみてください.

技術スタック

f:id:yurafuca:20170908124155p:plain

ゲームクライアントは Unity で実装されています.プレイヤーの移動や水かけを同期するために WebSocket を使用して,Node.js で実装されたマッチングサーバと通信します.水がヒットすると Raspberry Piサーボモーターを回転させ,ノズルを引き,水を噴出させます.ユーザーの登録やスコアの更新などは Rails で実装した API を使用します.

C#RailsJavaScript が得意なメンバーが集ったので,それぞれゲームクライアント(@garicchi),API サーバ(@drilldripper),ゲームサーバ(@yurafuca)を担当しました.

WebSocket

日頃から JavaScript で『にこさぽ』のような Chrome拡張を作ったりしていたのですが,WebSocket を使ったプロダクトを開発したことはありませんでした.

開発期間を迎えて突然ハッカソン用のコードを書くのにはすこし不安がありましたし,比較的早い段階で WebSocket を使用することがわかっていましたから,練習をかねて CGIBOY 風の簡単なチャットアプリを作りました.このときの記事もいずれ書きたいです.

レトロチャット

これは WebSocket と直接の関係がない話ですが,サーバーに EC2 を,DB にDynamoDB を使用しています.

特に手詰まりすることもなく,比較的スムーズに実装できました.WebSocket のコツを掴めましたし,AWS の簡単な知見も得られたのでよい経験だったと思います.

余興

せっかくチームで開発するということで,マスコットキャラクターを作りました.『芋焼酎ちゃん』です.かわいいですね.ところで醸造酒はほとんど飲みません*2

f:id:yurafuca:20170908130848p:plain

ところでこれは,Tシャツ屋さんの様子です.インターネットっぽい.

f:id:yurafuca:20170908131249j:plain

「チーム開発だし T シャツを作ると盛り上がってよさそう」という理由で T シャツを作りました.最高ですね.使用したアイロンとアイロン台は自宅から大学に持ち運ばれたものです.

最後にプロダクトのウェブサイトを作りました.行き過ぎた合理化はメンバーの士気を下げますから,こういう「くだらないけどエモいから間違いないんじゃないか」みたいな遊びを取り入れていきたいですね.

みずかけメモリアル

受賞

実は開発段階では,最優秀賞を狙うのは難しいと感じていました.理由は,歴代の Hack U ではゲームが最優秀賞を受賞したことがほぼなかったからです.最優秀賞を受賞したほとんどのプロダクトは「ハッカソン的な遊びがあり,応用次第で真面目な製品やサービスとして展開できそう」といった種類のものでした.

それだけに Happy Hacking 賞と最優秀賞をダブル受賞できたのは大変嬉しかったです.Happy Hacking 賞を受賞した時点で「いやー満足,よくやったよな俺ら」などと言って脱力していましたから,最優秀賞を受賞したときは完全に狼狽していました.

賞品

最優秀賞の賞品として DJI のドローンをいただきました.

9月末日,まるであの日が戻ってきたかのような夏日に,僕らは手に入れたドローンを飛ばして,夏に別れを告げました.

f:id:yurafuca:20171008150613j:plain

これからもやっていきの気持ちを忘れないように暮らしをしたいですね.ありがとうございました!

他のメンバーの受賞レビュー

Hack U 2017 NAGOYAで最優秀賞とHappy Hacking賞をダブル受賞しました - drilldripper’s blog

HackU Nagoya 2017にゲームを作って出場して最優秀賞とHappyHacking賞のダブル受賞をしました – garicchi.com

*1:タイトルは僕の発案です.

*2:ウイスキーをよく飲みます.

ニコ生用Chrome拡張「にこさぽ」を作った話と,jQueryからReactに移行した話

フロントエンドの学習をかねて作りました. フォローしている放送中のコミュニティを一覧表示したり,自動で次枠移動に移動したりする機能があります.

chrome.google.com

スクリーンショット

https://raw.githubusercontent.com/tsuyuno/resources/master/docs/nicosapo_min.gif

f:id:yurafuca:20170525204636p:plain

f:id:yurafuca:20170525204654p:plain

f:id:yurafuca:20170525204721p:plain

f:id:yurafuca:20170525204715p:plain

機能一覧

  • フォローしている放送中のコミュニティを一覧表示
  • 自動次枠移動
  • (コミュニティ|チャンネル|番組)に自動入場
  • デスクトップ通知
  • 延長通知
  • 放送検索

使い方

README.md を参照してください.

github.com

作ったきっかけ

Chrome ウェブストアに公開されている既存の Extension が不安定だったことが動機です.

ニコ生関係の Chrome Extension にはいくつかのよく知られたものがありますが,それらは長らくアップデートされておらず,機能がただしく動作しないものがある状態です.動くものがほしい,という要望は多いようでしたし,フロントエンドの学習の題材に丁度よいと考えて開発に着手しました.


以下は技術の話です.

モダン化

動くものを作ることが最優先事項だったので,開発当初は jQuery/ES5 で開発していました.途中から学習とメンテナビリティの向上を目的に一気にモダン化しました.移行期間は 2 ヶ月程度だったと記憶しています.

  • yarn
  • Babel/ES2015
  • webpack
  • React

この記事では jQuery から React への移行した経験を実際のコードを参照しながら紹介します.yarn や webpack などの導入についての説明は他の多くの丁寧な記事にゆずります.

jQuery から React への移行

この記事では最も基本的なコンポーネントのひとつとして「ボタン」を紹介します.

1. 最初期

React 導入前の状態です.開発当初はボタンの状態遷移といえば,

  • 「自動次枠移動」(autoRedirect)ボタン

のことさえ考えていれば問題ありませんでした.牧歌的な時代ですね.ところがユーザーの要望を取り入れるにつれ,下記の3つのボタンを管理しなければならなくなりました.

  • 「自動次枠移動」(autoRedirect)ボタン
  • 「(このコミュニティに) 自動入場」(autoEnterCommunity)ボタン
  • 「(この番組に) 自動入場」(autoEnterProgram)ボタン

これらのボタンの状態を ON にする箇所のコードは下記です.

    toggleOn(buttonType) {
        const classes = {
            'autoRedirect': 'auto_redirect_button',
            'autoEnterCommunity': 'auto_enter_community_button',
            'autoEnterProgram': 'auto_enter_program_button'
        };
        const link = $('.' + classes[buttonType]).find('.link');
        $(link).addClass('toggled_on');
        $(link).removeClass('toggled_off');
        const labels = {
            'autoRedirect': '自動次枠移動',
            'autoEnterCommunity': '(このコミュニティに) 自動入場',
            'autoEnterProgram': '(この番組に) 自動入場',
        };
        $(link).text(labels[buttonType] + 'ON');
    }

満開や散華1を繰り返したり,直したい欲求よりも公開したい欲求が強かったりするとこのようなコードが残ることが知られています.

2. 下準備

コンポーネントを作るためにクラス設計をします.React におけるコンポーネントとは React.Component を継承したクラスなので,クラスに分割しさえすればコンポーネントを作るのが簡単になります.具体的におこなったことは下記です.

UIパーツに分割する

  • Button: ボタン
    • AutoRedirectButton: 自動次枠移動
    • AutoEnterCommunityButton: コミュニティへの自動入場
    • AutoEnterProgramButton: 番組への自動入場
  • InfoBar: 情報バー(放送の残り時間などを表示する)
  • Thumbnails: コミュニティ/チャンネルのサムネイル一覧
  • Thumbnail: コミュニティ/チャンネルのサムネイル
  • etc…
  • コンポーネントはあとでさらに細かく分割できる.とにかく分割するのが大事.

UIパーツの親子関係を考える

  • React はコンポーネント間のデータの受け渡しを親子間でおこなう
  • 適切に親子関係を決めるとデータの受け渡しがスムーズになる
  • 親から子へ向かって分割するのではなく,子から親へ向かってまとめあげるイメージで構成する方がうまくいく気がする
  • にこさぽでは AutoRedirectButton の親は Widgets
    • Widgets は子に AutoRedirectButtonInfoBar をもつ
  • Widgets の親は CastPage を継承したクラス
    • NormalCastPage || ModernCastPage || StandByPage

上記のコードは下記のようになりました.

export default class AutoRedirectButton extends Buttons {
  ・
  ・
  ・
  toggleOn() {
    const $link = $($(`.${this._className}`).find('.link'));
    $link.addClass('toggled_on');
    $link.removeClass('toggled_off');
    $link.text(`${this._label}ON`);
  }

  isToggled() {
    const $link = $($(`.${this._className}`).find('.link'));
    const isToggled = $link.hasClass('toggled_on');
    return isToggled;
  }

比較的マシになってきました.この時点では DOM 操作に依然として jQuery が使われています. とはいえ,この時点での目標はあくまで React.Component を意識してクラスを分けることなので jQuery が使われていてもよいこととします.

3. 現在

残る主な作業は下記です.

  • React.Component を継承したクラスに書き換える
  • コンポーネントに自身の状態(state)を保持させる
    • フィールドを2種類に大別する
      • 親から受け取るもの・自身が変更しないもの -> props
      • 親から受け取らないもの・自身が変更するもの -> state
    • DOM から状態を拾ってこない
  • render(){}を実装する

コードは最終的に下記のようになりました.

export default class AutoRedirectButton extends React.Component {
  ・
  ・
  ・
  onClick(e) {
    this.toggle();
  }

  toggle() {
    this.props.notify(!this.state.isToggled);
    this.setState({ isToggled:!this.state.isToggled });
  }

  render() {
    return (
      <span className={this._className + (' nicosapo_button')} onClick={this.onClick}>
          <a className={'link ' + (this.state.isToggled ? 'toggled_on' : 'toggled_off')}
            data-balloon={this._balloonMessage}
            data-balloon-pos={this._balloonPos}
            data-balloon-length={this._balloonLength}>
            {(this.state.isToggled ? `${this._label}ON` : `${this._label}OFF`)}
          </a>
      </span>
    );
  }

ようやく見慣れた React の外見になりました.初期と比較するとかなり見通しがよくなったのではないでしょうか.コードには示していませんが ES6 を使用する場合は contsructor 内で .bind(this) しなければならない点に注意してください2

this.props.notify(!this.state.isToggled)notifyは親コンポーネントからpropsで渡されたメソッドです3.)

何が変わったか

さて,このコードと初期のコードとの本質的な違いは何でしょう.

a. 自身の状態を自身のみが管理する

たとえば,自身が 押下状態である かは,React を使えば this.state.isToggled で管理できます.this.state.isToggled は外部から変更されることはないため,自身の state について疑心暗鬼にならずにすみます.

一方,もし最初期のコードのように hasClass('toggled_on') を使用して 押下状態である ことを判定するとどうでしょうか.DOM はグローバル変数と同等ですから .toogled_on は外部から不意に変更されてしまうかもしれません.

b. オブジェクトに対して一意のビューが出力されることが保証される

React を使用すれば className={this.state.isToggled ? 'toggled_on' : 'toggled_off'}this.state.isToggled ? 'ON' : 'OFF' のように記述すれば,任意の構造体の入力に対してかならず一意のビューが出力されることが保証されます.これはオブジェクトの状態とビューの関係を render で定義しているからです.

一方,最初期のような手法では,.toogled_on.text() の遷移の差分はプログラマが記述します.そのため 押下状態である にもかかわらず「OFF」と表示されてしまうかもしれません.

まとめ

一度に書き換えるのではなく,段階的に書き換え最後に React を導入すると比較的うまくいく気がします.「結局のところ何をすればいいのか」「何から手を付けるべきか」をイメージすることが難しい方の参考になれば幸いです.


  1. 勇者は満開を使用すると『散華』と呼ばれる現象を起こして身体の機能の一部を損失する.散華 (さんげ)とは【ピクシブ百科事典】

  2. React Without ES6 - React

  3. React ではこのように,propsで渡されたメソッドを通じて子コンポーネントstate を親コンポーネントへ通知します.そして親コンポーネントは受け取った子コンポーネントstate を必要に応じて自身の state に反映します.こうして変更された親コンポーネントstate は子コンポーネントたちの props として伝搬していきます.