Rails + RSpec で FactoryBot(旧 FactoryGirl)を使う
FactoryBot
基本的には GETTING STARTED にしたがって導入します.
FactoryGirl が FactoryBot に変更された経緯はこちらの issue を参照してください.
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.rb
に require '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.rb
と user.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
この問題については下記のエントリが参考になります.
config.generators.fixture_replacement
application.rb
に下記を追加することを検討してください.
config.generators.fixture_replacement :factory_bot, dir: 'spec/factories'
この問題については下記のエントリが参考になります.
budle clean --force
下記の例外が発生する場合があります.
Unresolved specs during Gem::Specification.reset: minitest (~> 5.1)
この場合は bundle clean --force
および bundle install
を実行することを検討してください.
この問題については下記の issue が参考になります.
Yahoo! Japan主催のHack U 2017 NAGOYAで最優秀賞とHappy Hacking賞をダブル受賞しました
諸事情で公開が遅れてしまいましたが,Yahoo! Japan 主催のハッカソン『Hack U 2017 NAGOYA』に出場して,最優秀賞と Happy Hacking 賞をダブル受賞しました.
メンバーは @garicchi,@drilldripper,@yurafuca(僕)です.
「最高の夏にするぞ!」「やっていくぞ!」などと言いながら奇妙に速が上がった状態で開発を進めていたら本当に最高の夏を手に入れることができました.2017 年夏,本当にありがとう…….
プロダクト紹介
『みずかけメモリアル』というゲームを作りました*1.
最高の夏を最高の美少女と一緒に過ごしたい。そんな純粋な気持ちを叶えてくれるオンラインゲームを作りました。ゲームを始めると対戦相手(美少女)とマッチングし、バーチャル空間の砂浜で水をかけあうことができます。ゲーム中で水をかけられると、現実世界のあなたにも水がかかります。圧倒的なリアリティで忘れられない夏を送りましょう。(作品概要より)
スマートフォンにゲームクライアントをインストールして画面をタップすると,霧吹きから水が噴出されます.そして,顔に水がかかります(!).
遊び方
ゲームを起動します.名前を決めてログインをしたら「マッチング開始」ボタンを押しましょう.世界中のどこかの美少女とのマッチングが開始します.
マッチングが完了したらフィールドにいる女の子に向かって水をかけましょう.ライバルの女の子も同様に水をかけてきますから,移動しながらうまく避けましょう.このときフィールドにあるのは,美少女のあなたと,美少女のライバル,そして薄暮のような砂浜だけです.
ライバルの女の子がかけた水があなたにヒットすると,あなたの目の前に置かれた霧吹きから水が噴射されます.仮想世界のあなたと,現実世界のあなたは完全に繋がっています.
今回の Hack U のテーマは「UPDATE 夏休み!」でした.砂浜で女の子と水をかけあうことで夏を UPDATE しましょう.詳しい説明はプレゼンの動画をみてください.
技術スタック
ゲームクライアントは Unity で実装されています.プレイヤーの移動や水かけを同期するために WebSocket を使用して,Node.js で実装されたマッチングサーバと通信します.水がヒットすると Raspberry Pi はサーボモーターを回転させ,ノズルを引き,水を噴出させます.ユーザーの登録やスコアの更新などは Rails で実装した API を使用します.
C#,Rails,JavaScript が得意なメンバーが集ったので,それぞれゲームクライアント(@garicchi),API サーバ(@drilldripper),ゲームサーバ(@yurafuca)を担当しました.
WebSocket
日頃から JavaScript で『にこさぽ』のような Chrome拡張を作ったりしていたのですが,WebSocket を使ったプロダクトを開発したことはありませんでした.
開発期間を迎えて突然ハッカソン用のコードを書くのにはすこし不安がありましたし,比較的早い段階で WebSocket を使用することがわかっていましたから,練習をかねて CGIBOY 風の簡単なチャットアプリを作りました.このときの記事もいずれ書きたいです.
これは WebSocket と直接の関係がない話ですが,サーバーに EC2 を,DB にDynamoDB を使用しています.
特に手詰まりすることもなく,比較的スムーズに実装できました.WebSocket のコツを掴めましたし,AWS の簡単な知見も得られたのでよい経験だったと思います.
余興
せっかくチームで開発するということで,マスコットキャラクターを作りました.『芋焼酎ちゃん』です.かわいいですね.ところで醸造酒はほとんど飲みません*2.
ところでこれは,Tシャツ屋さんの様子です.インターネットっぽい.
「チーム開発だし T シャツを作ると盛り上がってよさそう」という理由で T シャツを作りました.最高ですね.使用したアイロンとアイロン台は自宅から大学に持ち運ばれたものです.
最後にプロダクトのウェブサイトを作りました.行き過ぎた合理化はメンバーの士気を下げますから,こういう「くだらないけどエモいから間違いないんじゃないか」みたいな遊びを取り入れていきたいですね.
受賞
実は開発段階では,最優秀賞を狙うのは難しいと感じていました.理由は,歴代の Hack U ではゲームが最優秀賞を受賞したことがほぼなかったからです.最優秀賞を受賞したほとんどのプロダクトは「ハッカソン的な遊びがあり,応用次第で真面目な製品やサービスとして展開できそう」といった種類のものでした.
それだけに Happy Hacking 賞と最優秀賞をダブル受賞できたのは大変嬉しかったです.Happy Hacking 賞を受賞した時点で「いやー満足,よくやったよな俺ら」などと言って脱力していましたから,最優秀賞を受賞したときは完全に狼狽していました.
賞品
最優秀賞の賞品として DJI のドローンをいただきました.
9月末日,まるであの日が戻ってきたかのような夏日に,僕らは手に入れたドローンを飛ばして,夏に別れを告げました.
これからもやっていきの気持ちを忘れないように暮らしをしたいですね.ありがとうございました!
ミラクル☆マジカル☆芋焼酎チームの皆さん、おめでとうございます🎉 #hacku pic.twitter.com/SvfWvbss5z
— Hack U🤘/Yahoo! JAPAN (@hackujp) 2017年8月21日
他のメンバーの受賞レビュー
Hack U 2017 NAGOYAで最優秀賞とHappy Hacking賞をダブル受賞しました - drilldripper’s blog
HackU Nagoya 2017にゲームを作って出場して最優秀賞とHappyHacking賞のダブル受賞をしました – garicchi.com
ニコ生用Chrome拡張「にこさぽ」を作った話と,jQueryからReactに移行した話
フロントエンドの学習をかねて作りました. フォローしている放送中のコミュニティを一覧表示したり,自動で次枠移動に移動したりする機能があります.
スクリーンショット
機能一覧
- フォローしている放送中のコミュニティを一覧表示
- 自動次枠移動
- (コミュニティ|チャンネル|番組)に自動入場
- デスクトップ通知
- 延長通知
- 放送検索
使い方
README.md を参照してください.
作ったきっかけ
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
は子にAutoRedirectButton
とInfoBar
をもつ
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 から状態を拾ってこない
- フィールドを2種類に大別する
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 を導入すると比較的うまくいく気がします.「結局のところ何をすればいいのか」「何から手を付けるべきか」をイメージすることが難しい方の参考になれば幸いです.