rista’s blog

株式会社リスタの技術?ブログ

ActiveRecordで保存時に自動で全角→半角等のノーマライズ処理をする

テキストを入力するフォームを作るとみなさんホントにいろんな文字を入れてくれます。
全角英数字やら各種記号、不要な空白・改行から、『-』(全角ハイフンマイナス)や『 』(EM SPACE)のようなややこしいものまで。

あまり無秩序だと見た目的にもよろしくないし、データ検索する時にパッと見つけられなくて困ります。
あとSJISに変換するときにも困ります。(EXCEL CSVもうイヤだー)

で、ある時カッとなって俺のDBに変なデータはいれさせねえ!と、保存時に自動でノーマライズする処理を作りました。

実装

こういうConcernを作ってます。

module StringNormalizable
  extend ActiveSupport::Concern

  # validate前にかってに変換
  included do
    before_validation :normalize_changed_attributes
  end

  private

  # string, textで変更のあったカラムを変換
  def normalize_changed_attributes
    changed_attributes.each do |key, _|
      self[key] = normalize(self[key]) if self[key].is_a?(String) && self[key].present?
    end
  end

  def normalize(str)
    str.strip                                    # 前後の空白除去
       .tr("0-9a-zA-Z  ()-−", "0-9a-zA-Z  ()-") # 全角英数字を半角に
       .gsub("\t", ' ').gsub(/ +/, " ")          # 空白を単一半角スペースに統一
       .gsub("\r\n", "\n").gsub("\r", "\n")      # 改行を\nに統一
       .unicode_normalize(:nfkc)                 # ㌧㌦とか
  end
end

使ってみる

例えばこういうモデルに対して、

bin/rails g model articles title:string body:text
bin/rake db:migrate

includeしてやると、

class Article < ApplicationRecord
  include StringNormalizable
end

レコード作成、更新時に自動でノーマライズしてくれます。

pry(main)> article = Article.create(title: "  ㌧\t\t㌦  ", body: "(AB)\r\n123")
pry(main)> article.title
=> "トン ドル"
pry(main)> article.body
=> "(AB)\n" + "123"

pry(main)> article.title = "㈱ Ⅲ"
=> "㈱ Ⅲ"
pry(main)> article.save
pry(main)> article.title
=> "(株) III"

実際に使ってみて

処理としては特に問題なく動いています。

ただユーザーが入力するところでは全角系などは意識的に使ってる人も多く、『勝手に変換しないでほしい』という声が多かったので、今はもっと限定的な処理にしています。

まとめ

EXCEL CSV(SJIS)の取り込み・ダウンロード機能が辛い。

JOBLISTがリリースから1年たち、求人数が3万件を突破していました

エンジニアの@mikedaです。

先週、JOBLISTがサービスリリースから1年たち、同じぐらいのタイミングで求人掲載数が3万件を突破していました。

f:id:mikeda:20170721112641j:plain

いつのまにやら。感慨深いです。

最初はクックパッド子会社として『クックパッド・ジョブ』を立ち上げて、 サービスリリース直前でゴタゴタがあってクックパッドを離れることになり、 社長含めた3人で社名をリスタートの『リスタ』にして名前通りにリスタートしました。

2ヶ月でシステムから契約からいろいろやりなおしてなんとかサービスをリリースし、 半年かけて継続的にサービスを成長させていく仕組みを整えて、 今は社員9人、在宅ライターを20人ほど抱える体制になりました。

細かい粒度で見るとほんとに思い通りにいかないことばっかりですが、 全体としてはけっこう予定通りに進んでいてなんか不思議な感じです。

とはいえまだまだやっとスタートラインという状況なので、 引き続きドタバタしながらボチボチやっていきます。
※締めのいい言葉が思いつかないのであとでなおす。

Google Analyticsのディメンションを使ってABテストをする

おはようございます、辻(@dim0627)です。 最近英語の勉強にと思って英語の本を読んでるんですが、1ヶ月かかってやっと読み終わりました。 心が折れそうです。

さて、今回は弊社で行っているABテストについて、Google Analyticsのディメンションについての説明から始めて書いてみたいと思います。

ディメンション?

Google Analyticsにはカスタムディメンション、カスタム指標というものがあります。 今回使うのはディメンションなので、指標については言及しません。

ディメンションと指標 - アナリティクス ヘルプ

ディメンションというのは、サイトに対するアクセスや、ユーザ、セッションといった単位に異なる側面を与える機能です。 指標の場合は更に細かい数値まで設定できるもの、くらいに考えておけばだいたいあってます。

ほとんどのアナリティクス レポートの表では、ディメンション値は行に、指標は列に表示されます。

ディメンションと指標 - アナリティクス ヘルプ

ものすごくわかりづらいと思うのですが、よく使われているのは「ログイン済みのユーザかそうでないか」というディメンションではないかと思います。

ディメンションのスコープ

ディメンションは結構ややこしいので、どこまでを理解しておくべきかが曖昧だったりします。 なので本記事では、スコープまで言及してあとは公式ドキュメントにおまかせしようかと思います。

ディメンションにおけるスコープというのは、「ディメンションを設定する単位」です。 実際に設定できるのは次の4つで、

  • 商品
  • ヒット
  • セッション
  • ユーザ

「商品」は拡張eコマースのプラグインを設定している場合のみなので、ECサイトでなければヒット、セッション、ユーザの3つが対象になります。

拡張 e コマース  |  ウェブ向けアナリティクス(analytics.js)  |  Google Developers

ヒットレベルのスコープ

ヒットレベルというのは、アクセスがあるたびに設定されるディメンションの単位です。 1回目のアクセスではAというディメンションがついても、2回目のアクセスでBがつけばそれぞれがそのまま適用されます。

セッションレベルのスコープ

セッションというのは、「1ユーザが当該サイトに訪れてから出るまで」を1とする単位です。 Webアプリでよくつかわれる「セッション」とは違うので気をつけましょう。

Webアプリでよくつかわれる「セッション」は、Google Analyticsのスコープでいうとユーザレベルのスコープに該当します。 ややこしいですね。

セッションレベルのスコープでは、1セッション内でAというディメンションとBというディメンションが設定された場合、後に設定されたディメンションが勝ち、過去に設定されたディメンションを打ち消します。

ユーザレベルのスコープ

ユーザレベルのスコープはユーザ単位に設定され、セッションをまたいでもディメンションの値が適用されるスコープです。

こちらもセッションレベルのスコープ同様、後に設定されたディメンションが過去に設定されたディメンションを打ち消します。

Google AnalyticsにおけるABテスト

実は、Google Analyticsには、わざわざディメンションを使わなくともABテスト用の機能がついています。

ウェブテスト - アナリティクス ヘルプ

弊社でも最初はこれを使おうとしたのですが、PCだけとか、MOBILEだけとかでテストをしたいときにうまく要件を満たすことができませんでした。

また、最近ではGoogle Optimizeもあるので、テストしたい内容によってはこちらを使うのが簡単かもしれませんね。

Website, AB Testing & Personalization Solutions - Google Optimize – Google

GUIでテスト内容が設定できるのがすごい!

Google AnalyticsでABテストを設定する

では、実際にGoogle Analyticsのディメンションを使ってABテストを設定してみます。

ディメンションでのABテストは、基本的にGoogle Analytics側の設定とシステム側の設定にわかれます。

Google Analytics側の設定

Google Analytics側ではディメンションを作り、テストの結果確認用のセグメントを作っておくくらいです。

ディメンションの作成

テストの単位となるディメンションを作成します。 弊社では改善対象のページ単位にディメンションを作っています。

f:id:dim0627:20170714094634p:plain

f:id:dim0627:20170714093220p:plain

スコープについては前述したとおりですが、ディメンションの割当処理はシステム側でセッションを用いるので、Google Analytics側ではヒットレベルにしています。

気をつけなければいけないのが、ディメンションは一度作ったら削除できないということです。 また、作成できるディメンションの数もプランによって異なるので、制限があるということだけ覚えておくと良いと思います。

各プロパティごとに、カスタム ディメンションのインデックスを 20、カスタム指標のインデックスを 20 利用できます。プレミアム アカウントの場合は、カスタム ディメンションのインデックスを 200、カスタム指標のインデックスを 200 利用できます。

カスタム ディメンションを削除することはできませんが、無効にはできます。ただし、再利用はお避けください。カスタム ディメンションの名前、範囲、値を編集する際は、新しい値と古い値の両方を、新しいディメンション名または古いディメンション名と組み合わせることができます。これにより、レポートのデータはフィルタによって細かく分割されない形式でまとめられます。

カスタム ディメンション / 指標 - アナリティクス ヘルプ

セグメントの作成

セグメントはGoogle Analyticsのグラフ描画におけるユーザの区分のことです。 標準で豊富な量のセグメントが用意されていますが、今回はABテストユーザを識別するための「Aユーザ」、「Bユーザ」というセグメントを作ります。

f:id:dim0627:20170714093308p:plain

f:id:dim0627:20170714093414p:plain

セグメントは他にもいろいろな切り口で追加できるので、サービスに応じて必要な切り口のセグメントを作っておくと便利です。

僕はだいたいどのサイトにもこんな感じのディメンションを追加してます。(標準で作っといてくれよ!)

f:id:dim0627:20170714093444p:plain

システム側の設定

ABテストをする上で必要になるシステム側の設定は、ユーザ単位にA/Bを判定することと、それによってViewを切り替え、ディメンションを適用するスクリプトを書き込むことです。

ユーザ単位のA or B判定

ABテストは要件によって、ヒットレベル、セッションレベルのいずれかになると思うのですが、弊社では基本的にセッションレベルでテストしてます。 1ユーザがページを見るたびに要素が変わるのは、あまり良いユーザ体験とは言えないと考えているからです。

というわけで弊社ではセッションIDをもとにA/Bの振り分けをしています。

def ab_id(*ids)
  ids[session.id.to_i(16) % ids.size]
end

ab_id(:a, :b)
=> :a

はじめはセッションにA/Bを格納してたんですが、@mikedaにそんなん無駄やろって突っ込まれたのでやめました😗(その通りです)。

Viewの切り替え

A/Bの振り分けをしたらViewの切り替えをします。 要件によって、Viewまるごと切り替えてもいいですし、Viewの一部だけをifで切り替えてもいいと思います。

スクリプトによるディメンション切り替え

A/Bの値によってGoogle AnalyticsにJSからディメンションの設定を行います。

ga('set', 'article_page_abtest', 'A');

ABテストの結果を見る

ABテストがある程度進んだら、先に設定したセグメントによってA/Bそれぞれをグラフに描画できます。 例えば直帰率改善をはかるABテストであればこんな感じに。

f:id:dim0627:20170714093643p:plain

サンプル数はある程度必要なので、アクセスが少ないページだと少し長い期間テストをしなきゃいけないかもしれないですね。

終わりに

さて、駆け足でGoogle AnalyticsでのABテストについて書いてみました。ちょっと概念的な要素が強いのでわかりづらかったかもしれないですね、すみません。

なぜ駆け足かというと、人が足りてないからです。しょうがねえなあ手伝ってやるよって方、助けてください。

ネイティブアプリエンジニアも募集してるよ!

営業や在宅ライターの募集はこちら!

求人張り紙投稿アプリで投稿数が2,000件、ポイント交換総額が10万円を超えました

エンジニアの@mikedaです。

先日公開した求人張り紙投稿アプリの投稿数が2,000件、ポイント交換総額が10万円を超えました。
正確に言うと投稿数が2,066、ポイント交換の総額が11.7万円です。

現状は拡散はこのブログのみで、テスト的に公開しながら開発を進めている、という状態なのですがけっこういいペースで増えています。

ダウンロード数・ユーザー数

実際のダウンロード数、ユーザー数はこのぐらいです。

  • ダウンロード数 : 102
  • 投稿ユーザー数 : 38 (スタッフを除くと28)

まだまだ少ないですね。

投稿状況

ユーザーごとの投稿数を見ると、上位2名で半数以上を投稿しています。
※スタッフではありません。

f:id:mikeda:20170707111528p:plain:w350

この2人は普段の生活圏だけでなく、このためにいろんなエリアをまわってくれているようです。
※自分も武蔵小山をまわったときは2時間足らずで4,000ポイント集められましたし、場所を選べばお小遣い稼ぎにはかなり良さそうです。

投稿されている場所は関東圏がメインで東京が70%をしめますが、愛知や広島、長野、鹿児島や沖縄からも投稿されています。

問題

実際に運用していくなかでいろんな問題も見えてきています。

1つは内部の投稿チェック・テキスト起こし・ポイント付与にかなり時間がかかってしまっていることです。
ここをもっとスムーズに、ユーザーにとってもわかりやすく納得しやすいようにと、運用を調整中です。

あとは重複投稿や小さくて見えない写真の投稿、禁止しているアダルト系の求人の投稿などがけっこうあるので、UI調整で改善していければなと思っています。

まとめ

求人張り紙投稿アプリの投稿数が2,000件、ポイント交換総額が10万円を超えました。

今後は使い勝手向上やSNS連携の他、投稿した求人張り紙に応募があるとユーザーにポイントが付与される仕組みなどを作りたいなと考えてます。

Android版の開発も開始しています。
Android(Kotlin)、iOS(Swift3)、どちらも開発者を絶賛募集中なので興味あるかたはぜひ!

武蔵小山で焼き鳥と寿司とコッペパン食べた

上級エンジニアの@mikedaです。

前回の『武蔵小山で求人張り紙を集めてきた』の時に、気になったお店がいっぱいあったので、週末に武蔵小山をブラブラしてきました。

やきとり 鳥勇

f:id:mikeda:20170630103102j:plain

食べログ / JOBLIST

立ち食いの焼き鳥屋さんで、焼き鳥はどれも1本150円。
置いてあるものを勝手にとってタレつけて食べます。

f:id:mikeda:20170624183135j:plain:w400

プラスチックのコップが屋台っぽくていいですね。

いやー、ここ最高です。 近くにあったら通いまくっちゃうな。

築地 銀一貫

食べログ / JOBLIST

お寿司が食べたくなって、行列が出来てて気になってたこのお店に。

f:id:mikeda:20170624203344j:plain

写真全くとってませんでした・・・
2貫で120円〜、すごくお得感のある回転寿司屋さんでした。

ヅケイカとか大トロが美味しかった。 注文が全部タッチパネルなのも面白かったです。

パンの田島 武蔵小山

食べログ / JOBLIST

コッペパンがメインのパン屋さんなんですが、売ってるメニューが面白い!!!

f:id:mikeda:20170624185136j:plain:w400

『チーズちくわ』ってww

今回は『つぶあん宇治抹茶クリーム』にしました。

f:id:mikeda:20170624213518j:plain:w400

f:id:mikeda:20170624213510j:plain:w400

モチモチでしっかりしたコッペパンですごく美味しかったです。

ついでに張り紙集め

求人張り紙投稿アプリで求人張り紙集めです。
前回途中でiPhoneの充電が切れてまわり切れなかった商店街の奥のほうへ。

最終的に武蔵小山の商店街には56件の求人張り紙がありました。

f:id:mikeda:20170630104605p:plain:w400

投稿数ランキングはこんな感じになりました。

f:id:mikeda:20170630104644p:plain:w400

上位2人に全く追いつける気がしない・・・

まとめ

武蔵小山サイコーですね。

お寿司屋さんの入り口に陣取っていた猫

f:id:mikeda:20170624191615j:plain

MacBook Pro 13inchの持ち運び電源にMacBook用の29W 電源アダプタを買ってみた

Android開発始めたらMacBook Airが重すぎたのでMacBook Pro 13incに買い替えた@mikedaです。
vimmerなのにESCキーが無くいのめちゃくちゃ辛いです。(Ctrl + [でがんばってます)

MacBook Proの電源でかい

MacBook Proに買ってまず思ったのは『電源でけぇな。持ち運び辛いよ・・・』でした。
※24/365でPC持ち歩くので

買い替えにともなって今まで使っていた会社用、持ち運び用の電源も泣く泣く買い換えることになったのですが、調べてみるとMacBookの電源はこの3つがあるようです。

それぞれ『MacBook』、『13インチMacBook Pro』、『15インチMacBook Pro』と組み合わせると最も優れた充電パフォーマンスを発揮します、と記載されていますが、61Wのはデカくて高いし、持ち運び用は29Wでいけるんじゃないかな。

ということで持ち運び用には29Wを買ってみました

f:id:mikeda:20170627080420j:plain

61Wと比べると圧倒的に小さい!!!

充電してみると

普通に使えました。

f:id:mikeda:20170627090456p:plain

61Wと比べると時間がかかっているのかもしれませんが、そんな急いで充電したいということがないので特に気になっていません。
すごく熱くなるとかもないです。

まとめ

けっこう常識でわざわざ書くようなことじゃないのかもですが、MacBook Pro 13incの持ち運び用電源に29Wのやつを買ったら『安い・小さい・軽い』で非常に良かったです、という話でした。