kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Faker のコードを読んだりテストを直したり

Faker::Company.name で企業名を生成する

GitHub - stympy/faker: A library for generating fake data such as names, addresses, and phone numbers.

FactoryGirl でテストデータを生成するときに Faker を使ってるんだけど,企業名を Faker::Company.name で出力すると人名をベースに生成されてしまっていて前々から少し違和感があった.

>> I18n.locale = :ja #=> :ja
>> Faker::Company.name #=> "松本 LLC"
>> Faker::Company.name #=> "清水-佐々木"
>> Faker::Company.name #=> "田中, 佐藤 and 鈴木"

ロケールを :en にすると少しは企業名っぽく見えるけど,それは「英語」の雰囲気であって,人名をベースに生成されているところは共通になってるように見えた.

>> I18n.locale = :en #=> :en
>> Faker::Company.name #=> "King LLC"
>> Faker::Company.name #=> "Wisozk-Dare"
>> Faker::Company.name #=> "Hegmann, Gulgowski and Brown"

Faker Gem を読んでみた

せっかくだから Gem を読んで仕組みを調べてみた.

1.

faker/company.rb at master · stympy/faker · GitHub

まず company.rb を読んでみると Faker::Company の名前空間には現時点で計9個のメソッドが用意されていることがわかった.

  • .name
  • .suffix
  • .catch_phrase
  • .buzzword
  • .bs
  • .ein
  • .duns_number
  • .logo
  • .swedish_organisation_number

2.

faker/faker.rb at master · stympy/faker · GitHub

Faker::Company.nameparse('company.name') を呼び出していて,その中で各言語ごとのロケールからパターンをサンプリングしていた.パターンは Array の .sample で1種類に限定していた.

なるほどー!ここで実行しながら推測していた「計3種類のパターンがあること」「人名を使っていること」を実際のコードで確認することができた.

company:
  name:
    - "#{Name.last_name} #{suffix}"
    - "#{Name.last_name}-#{Name.last_name}"
    - "#{Name.last_name}, #{Name.last_name} and #{Name.last_name}"

3.

GitHub - pigment/fake-logos

Faker::Company.logo は上のリポジトリから画像を取得してることがわかった.実際の URL は http://pigment.github.io だけど.

README にプルリクした

上記の 1. に書いた通り,コードを見ると Faker::Company の名前空間に計9個のメソッドがあるのに README に全てが書かれていなかった.せっかくなのでプルリクした.

github.com

するとなんと…!

テストが落ちてるのを直してプルリクした

なんと…!テストが落ちた!コードは変えてないのに!なんでー???

と思って Travis CI のログを見て調べてみると master ブランチで既にテストが落ちていることがわかった.さらに最近出てる PR も全て同じ原因で落ちていて,ずっと放置されてる感じがした.

原因は en-UG.yml の定義に問題があることで,一部の Ruby バージョンでエラーになっていた.そこを直して別のプルリクを出した.

窮地を救った!ヒーロー!w

github.com

マージ済のプルリクを見てみると「そもそも何で CI 通ってないのにマージしたんだろう?」っていう疑問は残るけど,オーナーの意図もあるだろうし,OSS は前に進むものだし深く考えないようにしてる.

Faker ってメンテされてる?

RubyGems を見てみると去年からリリースされてないし,GitHub を見てみると Issue も Pull Request も大量に残ってるし,状況から察するに Faker はもう積極的にメンテされてないのかも?今回のプルリクも特に反応が無いし.

最近だと FFaker の方が活発にメンテされてるの?

まとめ

最近,勉強のために週に1個 Gem を読むようにしていて,今回は実際に使っている Faker を読んでみた.

読んでみると毎回様々な気付きが得られて凄く効果的だなと感じている.

さらに今回は README を更新するプルリクをしたはずだったのに,気付いたらテストも直していたりして個人的にビックリな経験だった.勉強になった.ただしまだマージされてないので継続的に様子を見ておかないとって感じ.+1 欲しー!

あとこういうプルリクに関しては,先週バズってた以下の記事にも書かれてて素晴らしいなと思う.僕もどんなに些細なプルリクだって重要だと思っているし,引き続きコントリビュートしたいと思う.

ショボいPull Requestを積み重ねて、自分の中でOSS活動の敷居を下げる - not good but great

関連エントリー

kakakakakku.hatenablog.com

auto_strip_attributes を紙でコードリーディングしてみた

Rails でフォームのテキストフィールドを保存するときに

  • 先頭の半角スペース / 全角スペース
  • 末尾の半角スペース / 全角スペース

を自動で除去したいという要求があった.

別に Gem を使うまでもなく実現できそうだけど,調べたところ auto_strip_attributes が良さそうだなーという感じだった.

github.com

使ってみた

README に書いてある通り,モデルにフィールドを定義するだけで使えた.簡単!

ただし自動で除去されるのは「半角スペース」だけで「全角スペース」は除去されなかった.

auto_strip_attributes :name

Gem をコードリーディングしてみた

auto_strip_attributes.rb を見ると86行しかなくて,シンプルな Gem だったので,Ruby の勉強を兼ねてコードリーディングしてみた.

README を見たらわかることも多いけど,自分でコードを読むことも重要!わかったことを簡単にメモしておこうと思う.

1.

Gem のロード時に ActiveRecord に extend してから setup を呼び出していた.

ActiveRecord::Base.send(:extend, AutoStripAttributes) if defined? ActiveRecord
AutoStripAttributes::Config.setup

2.

この Gem の正体は指定されたフィールドごとに before_validation を定義していた.よって変換をした後の状態でモデルのバリデーションが走ることがわかる.

attributes.each do |attribute|
  before_validation do |record|
    # (中略)
  end
end

3.

README に書いてある通りだけど,計5種類のデフォルトフィルタが定義されている.

nullify ってデフォルトで ON なんだなーと思った.

フィルタ名 デフォルト設定 概要
convert_non_breaking_spaces OFF NonBreakingSpace を半角スペースに変換する
strip ON 先頭と末尾の半角スペースを除去する
nullify ON .blank? = true な場合自動で nil に変換する
squish OFF 文字列空白(タブなどを含む)を半角スペースに変換する
delete_whitespaces OFF 文字列全体の半角スペースとタブを除去する

4.

上記のデフォルトフィルタ以外に独自フィルタを定義できる.

instance_eval &block if block_given?

5.

README に独自フィルタの例が書いてあるけど,デフォルト設定が false なら set_filter にフィルタ名だけを指定することもできる.

:strip_html => false じゃなくて :strip_html とも書ける.

AutoStripAttributes::Config.setup do
  set_filter :strip_html => false do |value|
    ActionController::Base.helpers.strip_tags value
  end
end

ここを見ると filter.is_a?(Hash) で分岐していた.

if filter.is_a?(Hash) then
  filter_name = filter.keys.first
  filter_enabled = filter.values.first
else
  filter_name = filter
  filter_enabled = false
end

紙でコードリーディング

今回は RubyMine に頼らずに,コードをプリントしてメモしながらコードリーディングしてみた.

SIer 時代によくやったなー!と懐かしさを感じたけど,読みながら頭の中を整理できるし,エディタで読むよりも理解が早かったかも!

(時間使ってないでサクッと読めなんて言わないで!ビギナーだから!笑)

f:id:kakku22:20150716201210j:plain

独自フィルタを定義してみた

先頭と末尾の全角スペースを除去するフィルタの需要はあると思うので,本家にプルリクしても良いかなと思ったけど,独自フィルタの機能がせっかくあることだし使ってみた.

config/initializers/auto_strip_attributes.rb を追加して以下の独自フィルタを書いた.

AutoStripAttributes::Config.setup do
  set_filter strip_double_byte_space: false do |value|
    unless value.nil? || !value.is_a?(String)
      value.gsub(/(^(\s| )+)|((\s| )+$)/, '')
    end
  end
end

モデル側で strip_double_byte_space: true を定義するだけで使えるし簡単!

auto_strip_attributes :name, strip_double_byte_space: true

まとめ

  • Rails で先頭と末尾の半角スペースを除去したかったら auto_strip_attributes を使うと便利!
  • 先頭と末尾の全角スペースを除去したかったら拡張すれば良し!
  • コードをプリントして読んだら理解が早かった!(個人の感想)

Ruby Gold に合格するなら必読本だと思う /「Effective Ruby」を読んだ

Ruby 関連だと最近は Metaprogramming Ruby 2 を読んだり,Ruby Gold に合格したり,表参道.rb で LT したりしてるんだけど,今さらながら Effective Ruby を読んだら最高すぎてビックリした.実は6月末には読み終わってたんだけど,少しバタバタしててまとめられなかった.

Effective Ruby

Effective Ruby 最高!っていうエントリーはたくさんあるし,参考になった項目も結構似通ってる.だから少し違う目線のレビューを書きたいなーと思って,Ruby Gold に合格するなら必読本でしょ!っていう話を少し書く.

Effective Ruby

Effective Ruby

Effective Ruby と Ruby Gold

比較的苦労して Ruby Gold に合格した僕からすると,Effective Ruby を読みながら「えっ!ちょっと!ここまじで悩んだところ...」みたいに思う場面が相当に書いてあった.Ruby Gold の出題範囲を広くカバーしてるし,出題範囲からは読み取れないような細かな Ruby の挙動に関してもしっかりと解説されているし,至れり尽くせり.

今後 Ruby Gold を受験して合格するなら Effective Ruby は必読本だと思う.全て挙げてたらキリがないけど,特に Ruby Gold の参考になる項目を挙げてみた.こうやって見ると「第2章」と「第5章」を重点的に読むと良い感じがする.

  • 項目7. super のふるまいがひと通りではないことに注意しよう
  • 項目8. サブクラスを初期化するときには super を呼び出そう
  • 項目12. さまざまな等価の違いを理解しよう
  • 項目13. "<=>" と Comparable モジュールで比較を実装しよう
  • 項目14. protected メソッドを使ってプライベートな状態を共有しよう
  • 項目16. コレクションを書き換える前に引数として渡すコレクションのコピーを作っておこう
  • 項目30. method_missing ではなく define_method を使うようにしよう
  • 項目33. エイリアスチェイニングで書き換えたメソッドを呼び出そう
  • 項目34. Proc の引数の個数の違いに対応できるようにすることを検討しよう

個人的に参考になる項目も多かった

今まで知らなかった書き方やあまり意識せず使ってた書き方などに対して理解が深まって良かった.今は仕事で Ruby を書いてるから,日常的に読むベストプラクティス集として使える1冊かなと思う.例えばこのあたりなど.

  • 項目3. Ruby の暗号めいた Perl 風機能を避けよう
  • 項目10. 構造化データの表現には Hash ではなく Struct を使おう
  • 項目17. nil、スカラーオブジェクトを配列に変換するには、Array メソッドを使おう
  • 項目31. eval の多様な変種間の違いを把握しよう

まとめ

Ruby Gold に関してはココにも書いたけど,とにかく公式本が全然使えなくて試験対策がしにくいのが課題だと思う.そこでこの Effective Ruby をサブテキストとして読むことで理解が深まって,合格率がグッと上がるはず.本当にオススメ.Ruby Gold 受けましょう!

kakakakakku.hatenablog.com

send_data でダウンロードするファイル名をテストする

Railssend_data を使って CSV ダウンロードを実装してて,ダウンロードする CSV ファイル名を Capybara でテストしたかった.

response_headers から取れる

こんな感じで書けた.

実際には 'attachment; filename="xxx.csv"' ってなってるから eq ではなく include を使う.

expect(page.response_headers['Content-Disposition']).to include('xxx.csv')

stackoverflow.com

Rails で簡単に PV を計測できる impressionist を触ってみた

impressionist

Rails で簡単に PV を計測できる Gem の impressionist を触ってみた.

README にも書いてある通り,Google Analytics などの計測ツールを使わなくても自前で PV を集計できる点がメリット.逆に複雑な分析をしたかったり,高トラフィックなサービスなら計測ツールを使うべきかなって感じ.

The goal of this project is to provide customizable stats that are immediately accessible in your application as opposed to using Google Analytics and pulling data using their API.

github.com

導入手順

README の通りで OK!

  • Gemfile に追加して
gem 'impressionist'
  • マイグレーションを流す
bundle install
rails g impressionist
rake db:migrate

impressions テーブル

マイグレーションを流すと impressions テーブルが CREATE される.スキーマはこんな感じ.

アドテク系のプロダクトだと既に impressions っていうテーブルがあったりなかったりwww

CREATE TABLE `impressions` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `impressionable_type` varchar(255) DEFAULT NULL,
  `impressionable_id` int(11) DEFAULT NULL,
  `user_id` int(11) DEFAULT NULL,
  `controller_name` varchar(255) DEFAULT NULL,
  `action_name` varchar(255) DEFAULT NULL,
  `view_name` varchar(255) DEFAULT NULL,
  `request_hash` varchar(255) DEFAULT NULL,
  `ip_address` varchar(255) DEFAULT NULL,
  `session_hash` varchar(255) DEFAULT NULL,
  `message` text,
  `referrer` text,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `impressionable_type_message_index` (`impressionable_type`,`message`(255),`impressionable_id`),
  KEY `poly_request_index` (`impressionable_type`,`impressionable_id`,`request_hash`),
  KEY `poly_ip_index` (`impressionable_type`,`impressionable_id`,`ip_address`),
  KEY `poly_session_index` (`impressionable_type`,`impressionable_id`,`session_hash`),
  KEY `controlleraction_request_index` (`controller_name`,`action_name`,`request_hash`),
  KEY `controlleraction_ip_index` (`controller_name`,`action_name`,`ip_address`),
  KEY `controlleraction_session_index` (`controller_name`,`action_name`,`session_hash`),
  KEY `index_impressions_on_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

設定方法

本当に簡単で README の通りに進めてみた.

1. Controller

計測するコントローラーに以下を設定する.アクションメソッドを限定することもできる.

class AppsController < ApplicationController
  impressionist

or

class AppsController < ApplicationController
  impressionist actions: [:index, :show]

2. Model

show などリソースを特定するアクションメソッドを計測するならモデルに以下を設定する.

class App < ActiveRecord::Base
  is_impressionable
end

3. アクションメソッド

アクションメソッドの中に以下を設定して PV を計測できるようにする.

impressionist(@app)

ただし index など ActiveRecord::Relation を扱うアクションメソッドの場合は何も設定しないこと!逆に設定してしまうと RuntimeError が出てしまう.

impressionist(@apps)
ActiveRecord::Relation is not impressionable!

レコード確認

この状態でリクエストを流すと impressions に自動的に PV が計測される.

ちなみに @current_user にユーザー情報が設定されていると,自動的に impressions.user_id に保存されるのは便利だなと思った.

ザックリとこんなデータになる.ちなみに index の場合は impressionable_id = NULL になる.

mysql> SELECT id, impressionable_type, impressionable_id, user_id, controller_name, action_name, created_at FROM impressions;
+----+---------------------+-------------------+---------+-----------------+-------------+---------------------+
| id | impressionable_type | impressionable_id | user_id | controller_name | action_name | created_at          |
+----+---------------------+-------------------+---------+-----------------+-------------+---------------------+
|  1 | App                 |              NULL |       1 | apps            | index       | 2015-06-19 15:40:52 |
|  2 | App                 |                 1 |       1 | apps            | edit        | 2015-06-19 15:42:24 |
+----+---------------------+-------------------+---------+-----------------+-------------+---------------------+
2 rows in set (0.00 sec)

モデルごとに集計する

モデルオブジェクトから impressionist_count を呼べるようになっていて PV を取得することができる.

日付で絞り込むこともできる.

このメソッドを活用して PV 画面を別で作る感じになる気がする.

pry(main)> @app.impressionist_count
   (0.2ms)  SELECT COUNT(DISTINCT `impressions`.`request_hash`) FROM `impressions` WHERE `impressions`.`impressionable_id` = 1 AND `impressions`.`impressionable_type` = 'App'
=> 5

一覧画面を集計する

index の場合は特に方法が無さそうで,自分で取得すれば良いのかなぁ?

pry(main)> Impression.where(controller_name: :apps, action_name: :index).size
   (0.3ms)  SELECT COUNT(*) FROM `impressions` WHERE `impressions`.`controller_name` = 'apps' AND `impressions`.`action_name` = 'index'
=> 3

まとめ

  • お手軽に PV を計測できる
  • 可視化などは全て自前で実装する必要がある
  • リクエストの中で MySQL or MongoDB にレコードが挿入されるし,ある程度リクエスト数が多くなると厳しそう
  • もしくは任意の期間でレコードを削除するバッチと併用する?

関連エントリー