Wondershake 開発者ブログ

LOCARI(ロカリ)の運営会社の開発者ブログです。

LOCARI のテストで気をつけてること

この記事は Wondershake Advent Calendar 2日目の記事です。

先日分は間に合わなかったみたいですmm 代わりに謝罪しておきます 🙇

普段 RailsLOCARI のサーバーサイドを主に書いている mmyoji です。やっていきます。

話すこと

  • LOCARI のテストどーなってんの
  • 自分がテスト書く時に気をつけてること

Wondershake(LOCARI)のテスト環境

現在 Wondershake で一番コードベースが大きいプロジェクトが LOCARI という女性向けのメディアサービスのサーバーサイドを含む Rails アプリになってます。自分は最近までフリーランスだったのですが、ほとんどの現場でテストが書かれていませんでした。が、こちらは Coverage 85% 強となってて、比較的安心して開発が進められる環境となってます。(「低い!」と思われる方、幸せですね 😇 )

現状 Rails アプリはいくつかありますが基本的にすべて RSpec を使っています。 LOCARI に関して言えば本当は MiniTest 使いたいなーという意見(主に自分)もありますが RSpec のコードが結構沢山あってなかなか踏ん切りがつかないという状況です。

あと LOCARI アプリに関しては独特で以下の例でも見られると思いますが、 MiniTest に寄せたい思惑からか、expect ではなく assert を使って書いてたりします。このキモい感じが僕は割と気に入ってます 😈 ❤️ (読みづらいかもしれませんがご了承ください...)

で、本題に入りますが普段テスト書いてて気をつけてることとかを雑に書いていこうと思います。

こんなことを気をつけてます(個人的に)

賛否両論あるかと思いますがご容赦ください 🙇 あくまで 僕個人 が気をつけていることになりますmm

  • vcr ガンガン使う
  • 極力 example をまとめて test の実行速度を速くする
  • 意味が被るようなテストコード書かない
  • let(!)before を正しく使い分ける

vcr ガンガン使う

まず vcr の説明を簡単にしておくと、外部 API との通信を yaml ファイルにダンプしておいて、1回目以降はそちらのファイルを参照して実際に通信したかのようにテストを実行してくれる便利なライブラリです!

外部の API と通信を行う際に stub で済ませちゃうこともあると思うんですが、結構ガシガシ vcr に任せてしまいます(なので spec/cassettes が結構膨れてます...)

ただそうすることで少なくとも一回は実際のリクエストをテストできるので、例えば「ドキュメントに書いてあるレスポンスと違った!」みたいなことが防げたり、安心感があります。

あと小手先ですが、生成されるテストデータが微妙に異なるせいで、リクエストに含むパラメターが変わり、 vcr がうまく動かない(「異なるリクエスト」として扱われてしまう)こともあるので、そういう時は match_requests_on オプションを活用しています。このオプションに関しては以下を参考にしてみてくださいmm

以下のようなオプションの付け方してる箇所が多いイメージ

describe '#notify', vcr: { match_requests_on: [:host, :method] } do
  specify 'requests to external API' do
    # 処理
  end
end

極力 example をまとめて実行速度早くする

「どこでテストが落ちたのかわかりづらい!」と言う人もいるのでデメリットも多少ありますが、テストの初期化コストが大きく、且つまとめてしまえる場合などは特に example をまとめてしまう方がテスト実行時間減らせてオススメです。

API テストの例(あまりいい例ではないです。色々と省略)

# Before
describe 'GET index' do
  before do
    api_request
  end

  # HTTP status code と response の中身をそれぞれ別 example でチェックしている

  specify do
    assert_resposne 200
  end

  specify do
    assert_json_match(matcher, response.body)
  end
end

# After
describe 'GET index' do
  before do
    api_request
  end

  specify do
    assert_resposne 200
    assert_json_match(matcher, response.body)
  end
end

実際にベンチマーク取ってどれだけ速くなってるか、みたいなの出したかったですすいません

意味が被るようなテストコード書かない

「A がチェックできてたら B は保証されるから無くていいよね」という話です。

ある程度テスト書いてる人からすれば当然のことかもしれませんが、プログラミング書き始めた頃はよく指摘されてたので今でも意識してます。なんでもテストしたがる病ですね。

let(!) と before を正しく使い分ける

これは RSpec 特有の話になってしまいますが、RSpec 使い始めだとなんでもかんでも let(!) で書いちゃう人が一定数いると思ってて、テスト読む際に自分は意識して使い分けてます。

  • let! : example などで1回以上参照される
  • before : example などで参照されない

要するに let! に書いてあれば example 内で参照されたり、値をチェックしてたりするのでテストコード読む際に判断がしやすくなります。逆にそうでなければ before ブロックは読み飛ばして example だけ読めば大体わかるので無駄な時間使わずに済む、ということになりますね?(無理やり)

まとめ

テストはしっかり書きたいけどビルドが長いのは嫌だ、という気持ちを少しでも緩和するためこれからもやっていきます。

あと他の方のテストに関する気遣いを聞きたい今日この頃。

明日は Android エンジニア兼 PO/PM の shirokura です。ちゃんと書いてくれるかな? 😏

ESLint2からESLint3に上げて関連プラグインもアップデートした話

こんにちは。フロントエンドエンジニアの佐々木です。

先日、ESLintまわりを最新にアップデートしたのですが、結構修正することが多かったので備忘録も兼ねて残しておきます。

ちょっと古いですが、ESLintの導入に関してはこちらの記事でご紹介しました。

engineering.wondershake.com

ESLint3へのアップデート

f:id:sskyu:20161026225745p:plain

ESLint導入時には下記のバージョンを使っていました。

  "eslint": "^2.9.0",
  "eslint-config-airbnb": "^9.0.1",
  "eslint-plugin-import": "^1.12.0",
  "eslint-plugin-jsx-a11y": "^1.2.0",
  "eslint-plugin-react": "^5.1.1",
  "babel-eslint": "^6.0.4",

それぞれ最新のバージョンを調べてインストールしました。

  "eslint": "^3.8.1",
  "eslint-config-airbnb": "^12.0.0",
  "eslint-plugin-import": "^1.16.0",
  "eslint-plugin-jsx-a11y": "^2.2.2",
  "eslint-plugin-react": "^6.4.1",
  "babel-eslint": "^7.0.0",

この状態でESLintを走らせると ✖ 108 problems (99 errors, 9 warnings) と、結構な数のエラーが発生してしまいました。
バージョンを上げる前は 0 errors だったので、アップデートに伴ってルールが厳格になっているのがわかります。

.eslintrc を .eslintrc.yml に変換

ESLint3から設定をyamlで書けるようなので書き換えました。
これは好みの問題ですが、jsonよりもyamlの方がメンテしやすいです。

.eslintrc (変更前)

{
  "parser": "babel-eslint",
  "extends": "airbnb",
  "env": {
    "browser": true,
    "node": true
  },
  "globals": {
    "ga": true,
    "$": true,
    "I18n": true,
    "describe": true,
    "it": true,
    "before": true,
    "after": true
  },
  "rules": {
    "new-cap": [2, {
      "capIsNewExceptions": ["Map", "List", "Set", "OrderedSet"] // これはImmutableJS用の記述
    }]
  }
}

.eslintrc.yml (変更後)

---
  parser: babel-eslint
  extends: airbnb
  env:
    browser: true
    node: true

  globals:
    ga: true
    $: true
    I18n: true
    describe: true
    it: true
    before: true
    after: true

  rules:
    new-cap: [2, { capIsNewExceptions: [Map, List, Set, OrderedSet] }]

Lintをパスするよう修正

import/extensions

6:18  error  Unexpected use of file extension "jsx" for "./views/base.jsx"  import/extensions

このエラーは import Base from './views/base.jsx' のように .jsx を読み込んでいる箇所で起きてました。

eslint-plugin-import を見ると import/resolver が必要だとあるのでインストールします。

$ npm i -D eslint-import-resolver-node
# もし webpack を使っていたら eslint-import-resolver-webpack が必要そうです

.eslintrc.yml に追記します。

  # rules と同じインデントレベルに settings を追加
  settings:
    import/resolver: node

これでESLintを走らせれば import/extensions のエラーは回避できます。

class-methods-use-this

34:22  error  Expected 'this' to be used by class method 'comonentWillUnmount'     class-methods-use-this

このエラーの当該箇所は次のようなコードになってました。

class Base extends PureComponent {
  componentDidMount() {
    $(window).on('resize', throttle(this.handleResize, 200));
  }

  comonentWillUnmount() { // NG
    $(window).off('resize');
  }

  // 以下略
}

class methodとして定義されたメソッドのスコープ内で this への参照が無いとエラーになるようです。

componentDidMount でイベントを購読して、componentWillUnmount でイベントの購読を解除するコードなので、これ以上直しようが無いと思ったので warning を出すように変更しました。

.eslintrc.yml の rules に class-methods-use-this: 1 を追記するとエラーから警告に変わります。

根本的に解決するには、状態を持たないJSXは 関数として定義するといいです。

JSX In Depth - React

jsx-a11y/no-static-element-interactions

89:7   error  Visible, non-interactive elements should not have mouse or keyboard event listeners  jsx-a11y/no-static-element-interactions

このエラーは次のようなJSXを書くと発生します。

  render() {
    return <div onClick="this.handleClick" />
  }

div 要素などの本来ユーザーのインタラクションを想定していない要素に対して、onClick などでハンドリングしようとすると発生します。

この場合はHTMLのマークアップ上でクリッカブルな要素ということを示さないといけないので、div の代わりに button などで代替するのがいいでしょう。

  render() {
    return <button onClick="this.handleClick" />
  }

react/forbid-prop-types

6:5   error  Prop type `object` is forbidden    react/forbid-prop-types

ここが一番修正箇所が多いエラーでした。
このエラーは ReactComponent の propTypesReact.PropTypes.arrayReact.PropTypes.object を使っていると発生します。

props に React.PropTypes.array と指定されても、Array型なのはわかりますが配列の中身が何の型なのかがわかりません。
何の型を持った配列なのかを明示的に示さないとエラーになるようになりました。

このLintをパスするには PropTypes.array の代わりに PropTypes.arrayOf(), PropTypes.object の代わりに PropTypes.shape() を使います。

このあたりの書き方は本家のドキュメントが参考になりました。

Typechecking With PropTypes - React

例えば次のようなコードがある場合、

// @file sites.jsx
import React, { Component, PropTypes } from 'react';

export default class Sites extends Component {
  static propTypes = {
    sites: PropTypes.array.isRequired,  // NG
    currentSite: PropTypes.object,      // NG
  }
  // 以下略
}

このように直します。

// @file sites.jsx
import React, { Component, PropTypes } from 'react';

export default class Sites extends Component {
  static propTypes = {
    sites: PropTypes.arrayOf(PropTypes.shape({  // OK
      name: PropTypes.string.isRequired,
      url: PropTypes.string,
    })).isRequired,
    currentSite: PropTypes.shape({              // OK
      name: PropTypes.string.isRequired,
      url: PropTypes.string,
    }),
  }
  // 以下略
}

currentSitesite 型のオブジェクトを値に持つとすれば、sitessite 型の配列を値に持つと言えそうです。

上記のコードは冗長なので、型定義をまとめたファイルを constants/prop_types.js として作りました。

// @file prop_types.js
import { PropTypes } from 'react';

export const site = PropTypes.shape({
  name: PropTypes.string.isRequired,
  url: PropTypes.string,
});

export sites = PropTypes.arrayOf(site);

型定義ファイルを参照するように変更するとすっきりします。

// @file sites.jsx
import React, { Component } from 'react';
import * as pTypes from '../constants/prop_types';

export default class Sites extends Component {
  static propTypes = {
    sites: pTypes.sites.isRequired,
    currentSite: pTypes.site,
  }
  // 以下略
}

react/no-unused-prop-types

PropTypes.shape() を全て外部のファイル(constants/prop_types.js)で定義すればいいのですが、.jsx ファイル内で PropTypes.shape() を定義すると以下のようなエラーが出ます。

6:12  error  'item.key' PropType is defined but prop is never used    react/no-unused-prop-types

このエラーを抑制するためのオプションが用意されているので、.eslintrc.yml の rules に下記を追記して無効化しましょう。

  rules:
    react/no-unused-prop-types: [2, { skipShapeProps: true }]

Code Climate

ESLintのバージョンアップに伴い、CodeClimateの設定で少し悩んだので書いておきます。

CodeClimate上で実行するESLintのバージョンはデフォルトで1系が使われるようです。

Note:

If no channel is specified, ESLint v1.10.3 is used for analysis.

https://docs.codeclimate.com/docs/eslint

ということで、ESLint 3系を使って欲しい場合は .codeclimate.yml に追記します。

---
engines:
  eslint:
    enabled: true
    channel: eslint-3  # ESLint3系 を使うようにする

ちなみにこの eslint-3 で使われる ESLint のバージョンは現時点では 3.6.1 でした。

これでうまく行くはずだったのですが、ローカルでESLintが通っていても CodeClimate 上では import/no-unresolved のエラーが出る現象に悩まされました。

import/no-unresolved が出ている箇所は import React from 'react'; のように、npm module を読み込んでいる箇所で、参照先が絶対パスで始まるとエラーになるようでした。

これはCodeClimate上での結果が正しくないので、.codeclimate.yml に下記を追加してチェックを無効化しました。

---
engines:
  eslint:
    enabled: true
    channel: eslint-3
    checks:
      import/no-unresolved: # このチェックを無効化する
        enabled: false

まとめ

ESLintまわりを最新にアップデートして発生したエラーを無くしていく過程をご紹介しました。

記事が長くなるので紹介できなかったルールもあったのですが、React を使うなら eslint-config-airbnb は入れておいたほうがいいと改めて思いました。
このLint設定で書いていけば最低限のコードの品質は保てると思います。

Wondershake では Web エンジニアや iOSAndroid ディベロッパーを募集しています。 興味が湧いた方は是非こちらからご応募下さい!

www.wantedly.com

非iOSエンジニアが、iOSDC Japan 2016に当日スタッフで参加してきた

去る2016年8月19日&20日、iOSDC Japan 2016が開催されました。
記念すべき第1回目で、TrackBのTK(タイムキーパー)&司会を担当しました、安藤です。

「ブログを書くまでがiOSDC」By@tomzohさん・・・ということで。
今回は、iOSDC Japan 2016に参加するメリットと、スタッフをやった感想について書きます。 f:id:kikoando:20160926233832j:plain

iOSDC Japan 2016とは?

「iOSDC (iOS Developers Conference Japan) は、
iOSとその周辺技術に関するエンジニアのためのカンファレンスです。
iOS関連のカンファレンスを待っていた皆様、お待たせしました。ついにやってきました。

2016年8月20日はiOSエンジニアのお祭りです!
日本中、世界中から公募されたスピーカーがキレッキレのトークを繰り広げます。
トークは「iOSエンジニアが聞いて面白ければ何でもOK」という基準で選定されます。
iOSやSwiftといった王道テーマから、エモい話、デザインの話などもあるかもしれません。

そして、トーク終了後には同じ会場で懇親会が開催されます。

iOSDCはiOSエンジニアであれば誰でも楽しめるカンファレンスです。
皆様のご参加をお待ちしています。
カンファレンス未体験の方も、是非一度参加してみてください。
楽しく、エキサイティングな、忘れられない1日になるでしょう!」

公式サイトより。)

実際、トークの内容は

  • Swift誕生から現在までの軌跡を振り返る王道トーク
  • 個人の経歴をネタにしたトーク
  • アイコンデザインに関するトーク

さらにはコードに全く関係ない肉トーク(!)まで多岐に渡っていました。

※トークの内容は→こちらをどうぞ。

内容のバラエティさもさることながら、
イベント全体を包む空気がまさに「祭り」でした。 f:id:kikoando:20160926234602j:plain

そもそもなぜiOSDCの当日スタッフやろうと思ったの?

ことの始まりは、2016年7月11日。
こんなツイートが。

「イベントの運営経験値を貯めたい。iOSのことを知りたい」
そう思っていた自分には最高の機会だな。ということで、参加表明しました。

締め切り後、当選のメールが来てiOSDCスタッフSlackに招待され
その後は基本Slack上でやりとりをして当日を迎えます。

(8月16日に事前スタッフ顔合わせがありましたが、私は台風の影響で行けませんでしたorz)

参加者のメリット

  • 国内外で活躍されているエンジニアのトークが聞ける
  • 聞くだけじゃなくて、一問一答も出来る
  • 一問一答だけじゃなくて、会話もできる
    (スピーカーに話しかけるタイミングは沢山あります。
     普通に会場内をウロウロしています。懇親会なんて絶好のチャンス)
  • 充実したノベルティ、ビール← f:id:kikoando:20160926233948j:plain f:id:kikoando:20160926235446j:plain f:id:kikoando:20160926234003j:plain

結果

  • 新しい知見が手に入る
  • 悩んでいた問題が解決する
  • 開発へのモチベーションが上がる
  • 技術だけじゃなく働き方の指標にも出会える
  • ごきげんになれる(ビールでなれる人は)
  • 交流の輪が広がる f:id:kikoando:20161002215529j:plain

スタッフのメリット(主観)

  • 楽しい。仲間ができる。 f:id:kikoando:20160926234849j:plain

一言で言うと、これに尽きます。

実際に私がやったこと

1日目

時間の大半を机と椅子の設営に費やしました。
プロジェクターやカメラもほぼセッティングし終えたところで二手に分かれてみんなでランチ。

戻ってきてから、GitHubで配布されていたマニュアルを漁ります。
同じチームリーダーの@koogawaさんと打ち合わせしつつ
アナウンスの練習をしたら、あっというまに開場の時間。
この日はTrackBのTKが自分一人だったので、ずっとスピーカーと時間を気にしてました。
f:id:kikoando:20161002215717j:plain

最後のトークセッションを終えたら、受付に出てたシャツ等を部屋の中に入れ、
そそくさとスピーカーディナー(という名の飲み会)へ。

2日目

TrackBのTKが2人になったので、昨日より余裕が出てきました。
ただ、冷暖房や電気の明るさ調整など、気は抜けません。

この日は、後半TrackAと部屋を結合してからずっと懇親会の設営のことで頭がいっぱい。
1日目に3-4時間かけて並べた机と椅子を、30分で片付けるスケジュールでしたから。。。
実際は・・・スタッフのみなさんの協力のもと、スイスイことは進みました。 f:id:kikoando:20161002212825j:plain

懇親会では、ビールと料理を手に談笑する参加者&スピーカー&スタッフの皆さんを
端から見ては「みんな楽しそうだな。良い光景だな」と思ってました。 f:id:kikoando:20160926234016j:plain

さて、当日スタッフをやると、持ち場によってはほぼトークが見れなかったり(※)します。
また、コアスタッフは前日までの準備に何十時間という時間を費やします。
頭も体も使うから、どっと疲れる2日間です。

※配信管理やHQをされていたiOSエンジニアスタッフさんが「この人のプレゼン参考になるわ〜」とか「あ、そういうやり方あるのね〜」と漏らしてたので、見てなかったのは私だけかもしれません\/(トークは後ほど動画で配信されるので、見れなくても大丈夫ですが!)

でも、この祭りの楽しさを一番享受していたのはコア&当日スタッフだったのではと思います。
某スタッフさんも「こんなに楽しかったのは本当に久しぶり」と言ってましたしね!
みんなでひとつひとつ創っていく様子は、学生時代の文化祭準備を想起させてくれました。 f:id:kikoando:20161002214828j:plain

結果

もっとiOSのことが好きになりました。 f:id:kikoando:20160926234052p:plain (9/14の振り返りMTG後。ごきげん過ぎて画面に収まりきらない)
実際、私はiOSエンジニアではなかったのですが。この後、他のスタッフ主催の勉強会に参加したりして、iOSの世界に足を踏み入れかけています。

・・・iOSDC japan 2016が最高なイベントだったことは伝わりましたでしょうか。
少しでも楽しそうだと思った方は、来年のイベントスタッフ運営してみませんか?
スタッフになる方法?コアスタッフなら、@tomzohさんに聞いてみてください!
当日スタッフなら、きっと来年も公開応募があるかも?公式サイトとツイッターを要チェック!

まとめリンクたち

スピーカーや参加者、他のスタッフのブログはこちら
写真で振り返るiOSDC Japan 2016はこちら
トークセッションの映像はこちら

ここでちょっとお知らせ

11/11(金):ポッキーの日にエンジニア勉強会(もくもく会)を開催します。
もくもく内容は、何でもあり!前回は、お仕事する人や読書する人などもいらっしゃいました。

普段一人で家やカフェでもくもく作業してる人、11/11はみんなでもくもくやりませんか?
最初に今日何をもくもくするかを共有するので(任意)、きっと捗りますよ! daikanyama-mokumoku.connpass.com

また、WondershakeではiOSエンジニアを絶賛募集中です。
(無理に「来年一緒にiOSDCのスタッフやろう」とは言いません。)
一緒に会社を盛り上げてくれる方、Wanted。 www.wantedly.com

最後に

@tomzohさん、リードありがとうございました&大変お疲れ様でした!
f:id:kikoando:20161002215547j:plain

ProGuard Tips

こんにちは。Androidアプリを開発している代蔵です。 今回はアプリのサイズを削減するのに欠かせないツールProGuardに関する簡単なTipsを書いてみました。

アプリのサイズ

近頃、RxJavaやRetrofitなどAndroidアプリの開発に欠かせない便利なライブラリが増えてきています。こういったライブラリにより、開発速度・メンテナンス性は改善されている一方で、アプリの容量が大きくなってしまうという弊害が有ります。 この対策として、一般的にはProGuardを利用してアプリのサイズを小さくします。他にもredexといったツールを使うことでアプリの容量を小さくすることも可能です。

ProGuard

ProGuardを簡単に説明すると、使われていないクラスやメソッドを削除したり、クラス名やメソッド名などを難読化するためのツールです。これを利用することでコード量が削減されるので相対的にアプリケーションの容量が小さくなります。 ProGuardを利用する場合は以下のコードをbuild.gradleに追加し、proguard-rules.proに難読化したくないクラス名などを記述することで、ライブラリ内のクラス名などが削除・難読化されないようになっています。大抵のライブラリはProGuard用の設定を書いてくれています。

minifyEnabled true

ここで問題になるのは、利用するライブラリが増えれば増えるだけproguard-rules.proに記述する量が増えるので、可読性や管理コストが上がってしまいます。 例えば以下の様に一つのファイルに、様々なライブラリのルールを記述していくと管理するのが大変になっていきます。

# Retrofit
-dontnote retrofit2.Platform
-dontwarn retrofit2.Platform$Java8
-keepattributes Signature
-keepattributes Exceptions

# Picasso
-dontwarn com.squareup.okhttp.**

# OkHttp
-keepattributes Signature
-keepattributes *Annotation*
-keep class com.squareup.okhttp.** { *; }
-keep interface com.squareup.okhttp.** { *; }
-dontwarn com.squareup.okhttp.**

# RxJava
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
   long producerIndex;
   long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
    rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
    rx.internal.util.atomic.LinkedQueueNode consumerNode;
}

# ButterKnife
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }

-keepclasseswithmembernames class * {
    @butterknife.* <fields>;
}

-keepclasseswithmembernames class * {
    @butterknife.* <methods>;
}

proguard-rules.proを分割する

Locariでは、ProGuard用のルールをそれぞれのライブラリに対して作成することで管理を楽にしています。具体的には、proguard-rulesというディレクトを作成し、その中に各種ライブラリ用のルールを記述し、gradleでそのディレクトリの中身を読み込むような形にすることで、ルールファイルを分割しています。

proguardFiles getDefaultProguardFile('proguard-android.txt')
proguardFiles fileTree(dir: 'proguard-rules', include: ['*.pro']).asList().toArray()

f:id:takumiumiu:20160905153651p:plain

雑感

最近のライブラリはProGuardでkeepしなくても良いものが増えてきていますが、ルールとして記述しないといけないものが依然としてあるなか、このように管理するのは意外と便利です。 みなさんもぜひお試し下さい!

加えて、毎回書いていますが弊社ではAndroidiOS、サーバサイドエンジニアを絶賛募集しています。 ご興味の有るかた是非ご応募下さい! :)

https://www.wantedly.com/projects/60449

Rails 5 のマイナー機能の紹介

こんにちは、バックエンドエンジニアのダイクストラです。

Rails 5 がやっと出ました!ActionCable やTurbolinks 3など大きな機能が色々追加されました。 加えて,Ruby 2.2.1以上を使うことが必要になりました。 これらに関する記事はたくさんあるので、今回はあまり知られていない機能を紹介します。

便利な機能

APIベースのアプリケーションが作成可能

人気のジェム Rails APIRails 5 にマージされました。ActionController::APIAPI のみのアプリケーションを作成することが出来ます。新しいアプリケーション作る時、フラグを追加するだけで出来ます。

rails new hogehoge --api

このタイプのアプリケーションは、ActionController::Base ではなく、ActionController::API を使います。middlewareも少なくなり、コントローラーがもっとシンプルになるので、JSベースのアプリ作る時は、このような作り方が便利です!

PRリンク

Migration に コメントを追加できる

データベースにはあった機能ですが、今まで直接SQLのクエリで書く必要がありました。Rails 5 から MySQLPostgreSQL 使っていれば、コメントを追加することができます!

class CreatePosts < ActiveRecord::Migration[5.0]
  def change
    create_table :posts, comment: 'テーブルのコメント' do |t|
      t.string :title, comment: 'フィールドのコメント'
    end
  end
end

DBAのツールにも、schema.rbにもコメントでます。

PRリンク

create_join_table で UUID を使える

これはシンプルな機能ですが、今までは integer を扱えませんでした。

class CreateJoinTableFavorites < ActiveRecord::Migration[5.0]
  def change
    create_join_table(:users, :posts, column_options: {type: :uuid})
  end
end

PRリンク

使わないで欲しい機能

ActiveSupportArray#second_to_lastArray#third_to_last のメソッドが増えた

よく .last.first を使うと思ますが、 third_to_last などは本当に必要ありますか?マジックナンバー)のようなものなので、先にリファクタリングして欲しいです。Array 使わず Hash にすれば、コードが分かりやすくなる可能性があります。どうしてもこの使い方しかないと思ってたら、普通に Array[-2] 是非、使ってください。

PRリンク

ActiveSupportEnumerable#without のメソッド増えた

Array の場合は ary1 - ary2 でこれが実現できますし、Hash の場合は色んなやり方があるので、不必要なメソッドが増えたと思います。

Rubyの標準ライブラリで簡単に書けることをわざわざRailsだけのコードで書く意味がないです!

> [1,2,3,4,5] - [3,4]
=> [1, 2, 5]
> {a: 1, b: 2, c: 3}.reject{|x| [:a].include? x}
=> {:b=>2, :c=>3}

PRリンク

ActiveRecord のオブジェクト をsave する時、(touch: false) を入れれば、 updated_at はアップデートされない

今まで save した時、 updated_at がアップデートされたのに、急に touch: false をつけてしまうと、挙動が変わるので同じプロジェクトで働いてる人が困惑する可能性があります。save(touch: false) を使いたい時は、updated_at ではなく、新しいコラム作ったほうがいいでしょう。

PRリンク

その他の便利なアップデート

SQLのログにカラーが増えた

f:id:dykstra:20160817181904p:plain

PRリンク

アプリからログをいい具合にBigQueryに突っ込む険しい道

千葉です。基盤と雑用をしています。

弊社のログの解析はBigQueryに丸投げなのですが、それ以外はAWSさんにおんぶにだっこなので、いわゆる

  • Kinesis Firehoseからかんたんに Redshift にはいります!!
  • Mobile Analyticsからボタンひとつで Redshift にはいります!!

というのを敢えて使わずに茨の道を歩まねばなりません。辛いです。 また、BigQueryのStreaming Insertで100%安心です\(^o^)/ となれば良いのですが、前に障害のメールもありましたし、デバッグもしにくいという理由からこちらも避けて通っています。

ということで弊社の構成です。

  1. アプリはMobile AnalyticsのSDKを使ってログをいい具合に転送する
  2. Mobile AnalyticsのS3連携(Kinesis Firehoseのよう)で定期的にS3に書き出す
  3. Lambda が S3 Put Event を拾って DynamoDB に記録しつつ BigQuery に突っ込むjobをつくる

最近は俄然Kinesisファミリーが流行していますが、Mobile Analyticsも同じようなことができます。 また Cookpadさんのpureeのようなものがアプリではやはり必要なので、AWSさんの開発力に乗ってしまえるのも利点です。

ところが、いきなりの障害

上記の運用でしばらくうまくいっていたのですが、急にログがBigQueryにはいらなくなりました/(^o^)\ よくよく見ると Put のイベントの数がいきなり増えています。

f:id:tachiba1207:20160818050513p:plain

BigQueryはテーブル毎に1日に Insert する Job を作れる制限が決まっており、それが1,000件になっています。 それを超えてしまい、どうしようもなくなった!という状態でした。

仕方ないのでログを結合する

幸い、どのログが入ったのか入っていないのか、BigQueryのJobのIDがなんなのかというのはLambdaからDynamoDBに記録していたので、たどることができます。

なので、うまい具合にretryもしつつ、S3にたまったログを結合して(且つ、timestampなど特定のカラムをnormalizeして)BigQueryにいれるスクリプトを書きました。書きましたとも!

うまくいった

ということで、上記の対応でうまく対処できました。こうやって日々Locariのログを捌いています。

BigQueryはデータの投入もとても早いので(Steraming Insertの場合もすぐクエリーをうてます)かなりリアルタイムに近いデータにアクセスができるのは利点かなと思っています。もちろんBIのBackendとしても十分な速度とコストパフォーマンスがあると思います。

ちなみに汎用化すれば、BigQueryの普及に寄与できそうな Lambda(ES2015) を公開できそうなのですが、もうしばらく時間がかかりそうです...!

このスクリプトのメンテを中心とする基盤のメンテナンスやBigQueryに向けて一緒にクエリーを叩いてくれるような方を探しています! よかったらご応募ください!

jobs.lever.co

アイコンフォントライブラリを使ってみる

こんにちは、iOS開発担当の藤井です。

iOSアプリの開発を行うときにちょっと面倒なのがアイコン画像ファイルの追加だったりします。画像を切り出して、Images.xcassetsに1x, 2x, 3xをそれぞれ追加して… 特にモックレベルのものを作るときは、そもそもアイコン画像がなかったりするので自分で用意するとなると相当大変です。

先日の記事でも紹介したIoniconsiOS/Androidアプリ内で利用頻度が高そうなアイコンをカスタムフォントとして利用できるフォントライブラリです。

まずはインストール方法ですが、IonIconsはCocoapodsで配信されているのでいつものようにPodfileに以下を追加します。

pod 'ionicons'

UIImageとして利用したい場合は、

UIImage *icon = [IonIcons imageWithIcon:ion_ionic
                                  iconColor:[UIColor redColor] 
                                   iconSize:60.0f 
                                  imageSize:CGSizeMake(90.0f, 90.0f)];

という感じでUIImageとして生成します。色やサイズなども同時に指定可能です。 ただあくまでもアイコンフォントなので単色のみでしか指定できないので注意が必要です。 'ion_ionic'の部分はサイトから使いたいアイコンをホバーすると出てきます。'ion_ios_arrow_back'や'ion_ios_search'といったよく使いそうなアイコンから、'ion_social_twitter'や'ion_social_facebook'といった地味に便利なアイコンまでたくさんそろっています。

f:id:penpen-0704:20160803021212p:plain

UIImageだけでなくUILabelとしても利用できます。

UILabel *label = [IonIcons labelWithIcon:ion_ionic size:20.0f color:[UIColor blackColor]];

という形で生成してあげればOKです。

また、カスタムフォントなので文中に混ぜるといったことも可能です。例えばLocariでは記事一覧画面でのお気に入り数の横にあるハートアイコンをその方法で追加しておます。

これは

NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@ %@", ion_ios_heart_outline, likeCount]];

[attributedText addAttributes:@{NSFontAttributeName:[IonIcons fontWithSize:13]} range:NSMakeRange(1, 1)];

cell.likeLabel.attributedText = attributedText;

という感じにstringWithFormatなどで連結してあげて、NSFontAttributeNameでIonIconsを該当箇所に指定するとうまい具合に反映されます。

f:id:penpen-0704:20160803024032p:plain

文字内に含むのは若干レアケースかもしれないのですが、覚えておくと何かと便利かもしれません。

このようにLocariではIonIconsを利用しているのですが、もちろんこれ以外にもたくさんのフォントライブラリがあり、まとめて入れたい場合はFontAwesomeKit辺りを入れると便利なので気になる方はこちらも検討してみてください。

またSwiftの場合は別な方がSwift版をCocoapodsで配布しているので、そっちを利用すると良さそうです。

またWondershakeではアイコンフォントライブラリを使いこなせるiOSエンジニアも募集中です。 www.wantedly.com