2014/02/17

ランダムデータを作るときにちょっとは役に立つかもしれない gem を作った

先日お仕事で、ランダムなデモデータを作成する必要がありまして、そのとき感じたことをいくつか。

  • rand(0..2) と値ぐらいならいいが、 rand(0..2).zero? ? "foo" : "bar" みたいなコードを結構使うが、意味が汲み取りにくい。
  • 直接関係ない値はついつい固定値にしてしまいがち。おかげで、ほとんどのデータが同じメールアドレスとか誕生日とか。そして、あとで他の機能からそのデータを使うときに、またランダム生成する。
  • そもそもランダムなメールアドレスや日付などは一手間かかる。この手のデータ作成は一時的な使い捨てであることも多々あるので、ちょくちょく同じような処理を書くことになる。

というわけで、ランダムデータ作成用に gem を作ってみた。

pinzolo/tekido
tekido | RubyGems.org

適度にデータを作るようにということで tekido。
使い方は、 Tekido.integer とか Tekido.date とか Tekido.string でよく使うデータ型のランダムなデータを作成出来ます。
Tekido.percent や Tekido.email や Tekido.birthday みたいなちょっといい感じににしてくれるメソッドもついてます。
Kernel.rand のように引数で色々作成するデータが変わるので詳しくは README.md や USAGE_ja.md を見てください。

rand を多用するよりはメソッド名で意味を汲み取りやすいし、普段なら一手間かかるデータも一発で作れます。
これで、ランダムデータの作成も少しは楽になるかもしれません。

2014/02/09

自分流 Rails での区分値

Railsでの区分値の扱いについて考える - TIM Labs
ActiveHashを使ってRailsで区分値を扱う方法 - TIM Labs

このあたりを読んでの話。

自分の場合は、もっぱらDBに保存する派です。
まあ、理由はいくつかあって

  • SQLで使いやすい
    過去のしごとで「アプリケーションの機能を作る程ではないけどこういう統計情報がほしい」という依頼を受けることがわりとあって、その時に画面表示と同じ文言を JOIN だけで出せる。
    区分が増えるほど CASE が増えていく SQL は書くのも保守するのもしんどい。
    Rubyを通さないと区分と文言の定義を正しく取れないのは使いづらい。
  • 区分を増やしてもアプリケーションの更新が必要ない場合がある
    区分値というのはドロップダウンリスト、チェックボックスのリスト、ラジオボタンのリストなどで画面から選択させることがよくあります。
    そんな場合、マスタ管理の機能を組み込むなり、INSERT実行するなりでアプリケーションの更新なしで対応できます。
    プログラム内で区分値を特定して使用する場合に初めてアプリケーションの更新が必要というのは理にかなってると思う。
  • 区分の保存先は統一したい
    数が少ない区分はコードで定義したほうが使いやすいが、付随する情報が多い区分はDBに保存したくなりません?
    でも保存先が分散されるのは保守しづらいのでどこかに統一したい。そんな場合、大は小を兼ねるでDBを選択してます。
というメリットが大きいな、使いやすいなと感じています。

でもDBに保存した場合、二重管理というのは確かに頭を悩ませる問題ですね。

def self.male
  find(id: 1)
end
みたいなコードを過去には量産してしまったりしてました。
それはやっぱりよろしくないので、自分の場合はちょっとした gem を作って対応してます。

pinzolo/mastar という gem で、以下その紹介。

countries
id name code
1 日本 japan
2 アメリカ合衆国 usa

こんなテーブルに対して、

class Country
  include Mastar
  mastar.key :code
end

とすれば、Country.japan で find(1) の結果にアクセスできます。
code のような、一意なコードを扱う列を作らなければいけないですが、二重管理からは開放されるので個人的には重宝してますね。
機能だけ作って README.md 書くのがめんどくさくなって v0.9.0 としてひっそりリリースして放置してました。
せっかくなので Country クラスのオブジェクトに対して japan? で判別できるメソッドが生える機能を足して v1.0.0 としてリリースしました。 もし使えそうなら使ってみてください。

あ、あと view でドロップダウンリストのために f.select :country_id, Country.all.map { |c| [c.name, c.id] } やら f.collection_select :country_id, Country.all, :id, :name と書いていたのを f.select :country_id, Country.pairs と書けるようにもなります。地味に気に入ってます。

2014/01/26

Update rails-flog to v1.3.0

rails-flog(github, RubyGems.org) をバージョン 1.3.0 にあげた。

  • バージョン 1.2.0
    一時的にフォーマットを無効にする機能をパラメータとSQL個別に行えるようにした。
    どっちかのフォーマットだけ欲しい人も安心。
  • バージョン 1.3.0
    いくつかのフォーマットを行わないための条件を設定できるようにした。 「キャッシュされたクエリはフォーマットしたくない」とか「時間のかかるクエリだけ調査したいからフォーマットしてほしい」とか「キーが一つしかないパラメータをフォーマットすんなよ。行数増えて余計見づらいわ」みたいな人もニンマリ。
    ただし、デフォルトの挙動が変わっているので注意。

issue や pull request もお気軽にどうぞ

2013/12/29

今開いているファイルの *** をレジスタに

.vimrc にこんなのを書いておくと結構便利だった。
クリップボードと連携しておけば、さらに捗る。

2013/12/08

Redmineのプラグインで既存の文言を上書きする

この情報は Redmine 2.5 で不要になりました。→ tail -f pinzo.log: Redmine 2.5.0 からプラグインでの文言上書きが復活しています

Redmine のプラグインで本体の文言を上書きすることが出来ません。
昔はできたようなのですが、バージョンが違うのか I18n の仕様が変わったのか現在は出来ません。

ja:
  project_module_documents: ドキュメント
  project_module_calendar: 日程表

プラグインの ja.yml にこんな記述をしても画面はかわりません。

通常、プラグインをロードすると、I18n.load_path はこんな状態になります。

$REDMINE_HOME/vendor/bundle/ruby/2.0.0/gems/activesupport-3.2.15/lib/active_support/locale/en.yml
$REDMINE_HOME/vendor/bundle/ruby/2.0.0/gems/activemodel-3.2.15/lib/active_model/locale/en.yml
$REDMINE_HOME/vendor/bundle/ruby/2.0.0/gems/activerecord-3.2.15/lib/active_record/locale/en.yml
$REDMINE_HOME/vendor/bundle/ruby/2.0.0/gems/actionpack-3.2.15/lib/action_view/locale/en.yml
$REDMINE_HOME/plugins/<plugin_name>/config/locales/en.yml
$REDMINE_HOME/plugins/<plugin_name>/config/locales/ja.yml
$REDMINE_HOME/config/locales/ar.yml
$REDMINE_HOME/config/locales/az.yml
$REDMINE_HOME/config/locales/bg.yml
--- 延々と Redmine 本体の言語ファイルが列挙
$REDMINE_HOME/config/locales/vi.yml
$REDMINE_HOME/config/locales/zh-TW.yml
$REDMINE_HOME/config/locales/zh.yml

rails/railties/lib/rails/engine.rb at 3-2-stable · rails/rails にあるように、I18n.load_path の後ろのほうが優先度が高いので、本体の言語ファイルのほうが優先されてしまいプラグイン側で上書きすることができません。 プラグインの登録は config/initializers/30-redmine.rb で行われていて、その際にプラグインの辞書ファイルが I18n.load_path に登録されるのですが、この時点ではアプリケーション本来の辞書ファイルが登録されていないからでしょう。

じゃあどうすればいいのかというと、どこかで I18n.load_path を調整して、プラグインの辞書ファイルを後ろの方にもっていくのがいいのかなと。
確実に行うなら、初期化完了時がよさげですね。
Rails::Engine には after_initialize という目的そのままの機能があるのでこれが使えそうです。

こんなコードを lib あたりに突っ込んでおいて、init.rb で require すればOK。
I18n.load_path はこんな感じになります。

$REDMINE_HOME/vendor/bundle/ruby/2.0.0/gems/activesupport-3.2.15/lib/active_support/locale/en.yml
$REDMINE_HOME/vendor/bundle/ruby/2.0.0/gems/activemodel-3.2.15/lib/active_model/locale/en.yml
$REDMINE_HOME/vendor/bundle/ruby/2.0.0/gems/activerecord-3.2.15/lib/active_record/locale/en.yml
$REDMINE_HOME/vendor/bundle/ruby/2.0.0/gems/actionpack-3.2.15/lib/action_view/locale/en.yml
$REDMINE_HOME/config/locales/ar.yml
$REDMINE_HOME/config/locales/az.yml
$REDMINE_HOME/config/locales/bg.yml
--- 延々と Redmine 本体の言語ファイルが列挙
$REDMINE_HOME/config/locales/vi.yml
$REDMINE_HOME/config/locales/zh-TW.yml
$REDMINE_HOME/config/locales/zh.yml
$REDMINE_HOME/plugins/<plugin_name>/config/locales/en.yml
$REDMINE_HOME/plugins/<plugin_name>/config/locales/ja.yml

これで、本体の辞書ファイルをプラグインの辞書ファイルで上書きすることができました。

2013/12/07

Railsのログに出力されるパラメータとSQLを整形するgemを作った

最初に

この記事は Ruby on Rails Advent Calendar 2013 - Qiita [キータ] の7日目の記事です。
前日は a_suenami さんの Railsでサービスとフォームを導入してみる話 - assertInstanceOf('Engineer', $a_suenami) です。

本題

こういう gem を作りました。

pinzolo/rails-flog - Github
rails-flog - RubyGems.org

Gemfile に下記を追加して bundle install するだけで、ログに出力されるパラメータとSQLが整形して出力されるようになります。

gem 'rails-flog', :require => 'flog'

Redmine でのスクリーンショットはこんな感じ。

使用前

使用後

Railsアプリケーションに tmp/no-flog.txt を配置すれば一時的に整形をやめることもできます。

残念なところ

  • 実装が結構強引。二重の特異メソッドとかどうなんでしょうね。完全に元の実装依存だし。
  • パラメータはスペース整形だけど、SQLはタブ整形。こいつ のせい。

作った経緯

  1. 先日、西脇.rb & 東灘.rb 合同もくもく会 7th - 西脇.rb & 東灘.rb に参加した時に、@jnchito さんが、色々と awesome_print で出力するWebサービスを作成されてて、その際にログに出力される複雑なパラメータを手作業で整形するのがタルいみたいな事をおっしゃってまして、「あるある」と思っていた。
  2. んで、複雑なSQLもタルいよなー、整形してログに出力させるぐらいならできんじゃね?
  3. anbt-sql-formatter てのがあるじゃない。イケそう。
  4. 誰か作ってんじゃない?と調べてみると Railsのログに出力されるパラメータやSQLを整形する - ( ꒪⌓꒪) ゆるよろ日記 というそのものなものが見つかる。しかも自分でブクマ済み。
  5. もう少し調べてみるとどうやら同等の機能をもつ gem は存在していないみたい。
  6. 作ってみるか。

思うところ

我々エンジニアは誰しも素晴らしいプロダクトやサービスを作りたいと思っていると思います。
そして多くの人が時間、技術力、アイデア、ビジョン等々の不足であきらめているのでしょう。
でもエンジニアの本質は「素晴らしい物を生み出す」よりも、「一歩進める」ことだと思っています。
ひとが考えもしない方向へ一歩進められたり、一歩がとっても大きい人が素晴らしいプロダクトやサービスを生み出せるのでしょう。
出来なかったことをできるようにしたり、自動化したり、簡略化したり、みんな一歩進めた結果です。
rails-flogも、アイデアも実装手段も他の巨人に乗っかったものですが、gemにするという一歩だけ進めることが出来ました。
一歩進めるだけなら誰にでもできるでしょう、さああなたも身近なものを一歩進めてみてください。

Next

明日は znz さんです。

2013/11/28

Coverallsで特定のファイルのみ集計対象にする

なにか作って Github に置く場合、基本的に Travis と Coveralls を利用している。
Coveralls を利用するのは簡単で、基本的には下記の手順で問題ない。

  1. coveralls の gem をインストールする
  2. .coveralls.yml を作成
  3. spec_helper などの最初に require "coveralls" して Coveralls.wear! する
  4. push する

とはいえ、これだとテストでロードされた全てのファイルが対象になってしまう。
全てのファイルが対象になるとスペックファイルの fail とか、lib に突っ込んだ外部ライブラリとかも対象になってしまい、永久に 100% にならない。

それは困るので調べてみたら、ちゃんと公式に書いてあった。
なるほど、フォーマッタだけ Coveralls のを使用すればいいのね。

こんな感じ。

ちょっと、個人的に直感的じゃないなと思うのは、add_filter の引数がパスに含まれているファイルやブロックにて true を返すファイルを除外するというところ。
フィルタなんだから、マッチするものだけを対象にするかと思ったら逆なんだな。

2013/11/23

Redmine のプラグインを Travis で CI してみた

Redmine のプラグインの場合、プラグイン本体のみ Github に登録しているので、そのままでは Travis で CI することができない。
諦めていたんだけど、.travis.yml のオプションとにらめっこしたらなんとなく出来そうな気がしたので試してみた。
10数回の試行を経て、なんとか出来たのでスクリプトを紹介します。

  • .travis.yml
    before_install があるので、after_install があるかと思ったらなかった。

  • before_install.sh
    gem インストール前に実行させるスクリプト。
    Travis では bundle install 前に直下の Gemfile が BUNDLE_GEMFILE 環境変数に登録されてしまう。
    しかし、指定したいのはプラグインの Gemfile ではなく、Redmine のものなのである。
    なので、mv しまくりでなんとかディレクトリ構成を整えた。苦労した。
    Redmine のダウンロード URL がバージョンだけに依存してくれないのが少し残念。

  • exec_test.sh
    テスト実行用スクリプト。
    前述のとおり after_install がなかったので、bundle install 後の処理(Redmineの初期化など)とテストの実行を同時にすることにした。
    こちらはそんなに苦労しなかった。

これで、プラグインを複数バージョンの Ruby でテストすることができるようになった。
でも、複数バージョンの Redmine でテストすることは当然できない。
複数バージョンの Redmine を対象にテストするなら Jenkins 使うのがいいんだろうな。

2013/11/06

OS X Mavericks にすると gcc-4.2 がなくなるらしい

bundle installしたらエラーになった。

pinzolo@ileach [master] % bundle install --path vendor/bundle
Fetching gem metadata from https://rubygems.org/......... Fetching gem metadata>from https://rubygems.org/.. Resolving dependencies... Using rake (10.1.0) Using i18n (0.6.5) Using minitest (4.7.5) Using multi_json (1.8.2) Installing atomic (1.1.14) Gem::Installer::ExtensionBuildError: ERROR: Failed to build gem native extension. /Users/pinzolo/.rbenv/versions/2.0.0-p195/bin/ruby extconf.rb *** extconf.rb failed *** Could not create Makefile due to some reason, probably lack of necessary libraries and/or headers. Check the mkmf.log file for more details. You may need configuration options. Provided configuration options: --with-opt-dir --without-opt-dir --with-opt-include --without-opt-include=${opt-dir}/include --with-opt-lib --without-opt-lib=${opt-dir}/lib --with-make-prog --without-make-prog --srcdir=. --curdir --ruby=/Users/pinzolo/.rbenv/versions/2.0.0-p195/bin/ruby --with-atomic_reference-dir --without-atomic_reference-dir --with-atomic_reference-include --without-atomic_reference-include=${atomic_reference-dir}/include --with-atomic_reference-lib --without-atomic_reference-lib=${atomic_reference-dir}/ /Users/pinzolo/.rbenv/versions/2.0.0-p195/lib/ruby/2.0.0/mkmf.rb:431:in `try_do': The compiler failed to generate an executable file. (RuntimeError) You have to install development tools first. from /Users/pinzolo/.rbenv/versions/2.0.0-p195/lib/ruby/2.0.0/mkmf.rb:516:in `try_link0' from /Users/pinzolo/.rbenv/versions/2.0.0-p195/lib/ruby/2.0.0/mkmf.rb:814:in `try_run' from extconf.rb:26:in `
' Gem files will remain installed in /Users/pinzolo/projects/detox/vendor/bundle/ruby/2.0.0/gems/atomic-1.1.14 for inspection. Results logged to /Users/pinzolo/projects/detox/vendor/bundle/ruby/2.0.0/gems/atomic-1.1.14/ext/gem_make.out An error occurred while installing atomic (1.1.14), and Bundler cannot continue. Make sure that `gem install atomic -v '1.1.14'` succeeds before bundling.

mkmf.log見ろって言われたので見てみる。

pinzolo@ileach [master] % cat vendor/bundle/ruby/2.0.0/gems/atomic-1.1.14/ext/mkmf.log
"gcc-4.2 -o conftest -I/Users/pinzolo/.rbenv/versions/2.0.0-p195/include/ruby-2.0.0/x86_64-darwin12.4.0 -I/Users/pinzolo/.rbenv/versions/2.0.0-p195/include/ruby-2.0.0/ruby/backward -I/Users/pinzolo/.rbenv/versions/2.0.0-p195/include/ruby-2.0.0 -I. -I'/Users/pinzolo/.rbenv/versions/2.0.0-p195/include' -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -Wno-error=shorten-64-to-32 -fno-common -pipe conftest.c -L. -L/Users/pinzolo/.rbenv/versions/2.0.0-p195/lib -L. -L'/Users/pinzolo/.rbenv/versions/2.0.0-p195/lib' -lruby.2.0.0 -lpthread -ldl -lobjc " sh: gcc-4.2: command not found checked program was: /* begin */ 1: #include "ruby.h" 2: 3: int main(int argc, char **argv) 4: { 5: return 0; 6: } /* end */
gcc-4.2がないらしい。

どうやら Mavericks にしたら、gcc-4.2がgccになるらしい。

sudo ln -s /usr/bin/gcc /usr/bin/gcc-4.2したら、解決した。

2013/09/08

RedmineのプラグインをRSpecでテストする

Redmineは基本的にはxUnitなコードでテストが書かれています。
今のところ例に習いxUnit形式でテスト書いていたんだけど、RSpecで書きたいなという欲求は当然あるわけです。
なので今回RSpecを導入してみた。

まずは、$REDMINE/plugins/<plugin_name>/Gemfile を作成して、bundle install

# Gemfile
group :development, :test do
  gem "rspec-rails", ">= 2.8.1"
end

$REDMINE/plugins/<plugin_name>/spec/spec_helper.rbを作成し、設定を記述。

require File.expand_path(File.dirname(__FILE__) + '/../../../spec/spec_helper')
RSpec.configure do |config|
  config.mock_with :mocha
  config.fixture_path = "#{::Rails.root}/test/fixtures"
end

各種specファイルの冒頭で spec_helper をrequireする。

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
# require 'spec_helper' ではダメだった。

基本的にはこれで設定はいいはずだけど、なぜかspecタスクで実行できない。
実行できないというよりspecファイルが見つけられないらしい。
rspec コマンドを指定しても不可能。
ベースディレクトリを指定してやれば実行できた。

$ bundle exec rake spec                             # ダメ
$ bundle exec rspec                                 # これもダメ
$ bundle exec rake spec plugins/<plugin_name>/spec  # これでもダメ
$ bundle exec rspec plugins/<plugin_name>/spec      # これはOK