くりにっき

フルスタックキュアエンジニアです

rubocopでreversibleなmigrationかどうかチェックしたかったので作った

僕がrubocopに送ったPRが v0.47.0 に取り込まれました。

f:id:sue445:20170116111818p:plain

個人的に便利機能だと思うのでこの場を借りて軽く紹介したいと思います。

Rails/ReversibleMigrationについて

Railsのmigrationファイルで change メソッドの中に書いたmigrationコマンドがreversible *1かどうかをチェックするためのcopです

github.com

具体例

https://github.com/bbatsov/rails-style-guide#reversible-migration より抜粋

これだと drop_table :users だけだと逆方向のmigration(create_table)でどのようなカラムでテーブルを作ればいいか分からないのでreversibleではありません。rake db:rollbackrake db:migrate:down した時にエラーになります。

# bad
class DropUsers < ActiveRecord::Migration
  def change
    drop_table :users
  end
end

こういう場合、upメソッドとdownメソッドでそれぞれmigrationを書いてあげないと rake db:rollbackrake db:migrate:down が正しく実行されません

# good
class DropUsers < ActiveRecord::Migration
  def up
    drop_table :users
  end

  def down
    create_table :users do |t|
      t.string :name
    end
  end
end

http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters.html#method-i-drop_table にも書いていますが、 drop_table にブロックを渡すとchangeで逆方向のmigrationを補完する時に create_table の引数扱いになりカラムが作られます。

# good
# In this case, block will be used by create_table in rollback
# http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters.html#method-i-drop_table
class DropUsers < ActiveRecord::Migration
  def change
    drop_table :users do |t|
      t.string :name
    end
  end
end

どうして作ったか?

慣れてくればreversibleなコマンドかどうかは Railsの気持ちになって考えれば だいたい推測はつきます。

例えば

add_column :users, :name, :string

の逆は

remove_column :users, :name

ですが、

remove_column :users, :name

の逆方向を考えた時にどんな型でカラムを作ればいいかの情報がこれだけでは推測できないのでreversibleじゃないといった感じです。

微妙に迷った時も全部 http://api.rubyonrails.org/classes/ActiveRecord/Migration/CommandRecorder.html に書いてあるのでこれを読めば分かります。

しかしコードレビューの度にそれをいちいち指摘するのは面倒なので自動化するために Rails/ReversibleMigration を作成しました。

検出精度について

さっきのリファレンスを全部読んだ上でchangeメソッド内の下記を検出するようにしています

  • 絶対にreversibleじゃないmigrationコマンド
    • change_table
    • change_table_comment
    • execute
    • remove_belongs_to
  • 特定の条件を満たさないとreversibleじゃないmigrationコマンド
    • drop_table
    • change_column_default
    • remove_column
    • remove_foreign_key

ただし特例として reversibleブロックがある場合は reversibleであるとみなして検出対象外にしています。

詳細は下記参照

railsguides.jp

auto correctについて

rubocopのcopでは autocorrectメソッドを定義すればauto correct時の実装を定義することができますが、down(逆方向)のmigrationは過去のmigrationファイルを探して人間が書く必要がありそうな気がしたのであえて実装していません。

Cop開発Tips

次回以降自分がrubocop触りやすくするために開発手順とか参照すべきソースをメモっときます。

前提知識

日本語だとここが参考になりました。

koic.hatenablog.com

tech.sideci.com

rubocop静的解析したいソースをruby-parseでS式を出す

ruby_parser というRubyソースコードを解析するためのgemを使います。rubocopのソースをcloneしていればbundle installで入るので別途gem installする必要はありません。

例えばこんなファイルを作っといて

class ExampleMigration < ActiveRecord::Migration
  def change
    # ブロックがあるのでreversible
    drop_table :users do |t|
      t.string :name
    end

    # ブロックがないのでrubocopで警告を出したい
    drop_table :users
  end
end

ruby-parseに食わせてS式を出します。

bundle exec ruby-parse /tmp/migration.rb
(class
  (const nil :ExampleMigration)
  (const
    (const nil :ActiveRecord) :Migration)
  (def :change
    (args)
    (begin
      (block
        (send nil :drop_table
          (sym :users))
        (args
          (procarg0 :t))
        (send
          (lvar :t) :string
          (sym :name)))
      (send nil :drop_table
        (sym :users)))))

rubocopのcopに on_defon_send などのメソッドを定義することによって、rubocopで解析中にS式のその要素と対応するメソッドが実行されます。

今回の場合はメソッド呼び出し drop_tablechange_table などのメソッド呼び出しをフックしたかったので on_sendメソッドを作りました。

binding.pryなどでとめてnodeの中身を確認

on系のメソッドに binding.prybinding.irb を書いて

def on_send(node)
  binding.pry

任意のspecを実行

$ bundle exec rspec -- spec/rubocop/cop/rails/reversible_migration_spec.rb:85
Run options:
  include {:focus=>true, :locations=>{"./spec/rubocop/cop/rails/reversible_migration_spec.rb"=>[85]}}
  exclude {:broken=>#<Proc:./spec/spec_helper.rb:32>}

Randomized with seed 63344

From: /Users/sue445/workspace/github.com/bbatsov/rubocop/lib/rubocop/cop/rails/reversible_migration.rb @ line 120 RuboCop::Cop::Rails::ReversibleMigration#on_send:

    119: def on_send(node)
 => 120:   binding.pry
    121:   return unless within_change_method?(node)
    122:   return if within_reversible_block?(node)
    123:
    124:   check_irreversible_schema_statement_node(node)
    125:   check_drop_table_node(node)
    126:   check_change_column_default_node(node)
    127:   check_remove_column_node(node)
    128:   check_remove_foreign_key_node(node)
    129: end

[1] pry(#<RuboCop::Cop::Rails::ReversibleMigration>)> node
=> s(:send, nil, :drop_table,
  s(:sym, :users))
[2] pry(#<RuboCop::Cop::Rails::ReversibleMigration>)>

慣れていないうちは1回1回 binding.pry で止めてnodeの中身を確認しつつデバッグしていくのがいいと思います。余談ですがデバッグに慣れていなさすぎて1日 1migrationコマンド対応が限界でした('A`)

S式にマッチさせるmatcherを書く

cop内に

def_node_matcher :drop_table_call, <<-END
  (send nil :drop_table ...)
END

のようなmatcherを書くことによって、

drop_table_call(node) do
  # drop_table メソッドの呼び出しがあればこの中が評価される
end

のように処理を挟むことができます。

matcherの書き方は https://github.com/bbatsov/rubocop/blob/master/lib/rubocop/node_pattern.rb のコメントを要熟読

matcherに $_$... を書くことにより、マッチした文字列を変数として取得することもできます。

$_ は単一nodeを取得

# remove_foreign_keyに渡された引数の2つ目のみを取得するmatcher
def_node_matcher :remove_foreign_key_call, <<-END
  (send nil :remove_foreign_key _ $_)
END
remove_foreign_key_call(node) do |arg|
  # $_ の内容がブロック引数のargに入ってくる
  if arg.hash_type?
    # remove_foreign_keyの第2引数がHash(テーブル名以外)の時に警告を出す
    add_offense(
      node, :expression,
      format(MSG, 'remove_foreign_key(without table)')
    )
  end
end

$... は以降のnodeを全部取得

# remove_columnに渡された引数を全部取得するmatcher
def_node_matcher :remove_column_call, <<-END
  (send nil :remove_column $...)
END
remove_column_call(node) do |args|
  # argsに引数全部入ってくる
  if args.to_a.size < 3
    # 引数が3個未満の時に警告を出す
    add_offense(
      node, :expression,
      format(MSG, 'remove_column(without type)')
    )
  end
end

*1:rake db:rollback や rake db:migrate:down した時に逆方向のmigrationを自動補完する

僕に「サザエさん」の話させたら長くなりますよ?

これは 僕に「○○」の話させたら長くなりますよ Advent Calendar 2016 - Adventar の25日目です。

www.adventar.org

僕に「サザエさん」の話させたら長くなりますよ?

サザエさんとは?

テレビ放送45年以上で老若男女誰でも知ってる圧倒的国民的アニメです。

www.fujitv.co.jp

サザエ実況採点ポイント

採点ポイントを細かく解説します

ちなみに毎週この採点表で採点して、ツイートしています。

採点表 | SuperSazaeTime History #sst_history

エイケン

サザエさんの制作会社です。エイケンが製作すると◯がつきます。

www.eiken-anime.jp

開幕雪室

サザエさんは30分間で3本の話で構成されていますが、「開幕雪室」はその1番目が脚本家の雪室俊一先生が担当する回だと◯がつきます。

テクマクマヤコン―ぼくのアニメ青春録

テクマクマヤコン―ぼくのアニメ青春録

城山先生生存確認

雪室先生と並んでサザエさん二大脚本家の1人、城山昇先生の脚本が登場するA〜Cパートのどこかで出ると◯がつきます。

レア脚本家

雪室先生と城山先生以外の脚本は出ると◯がつきます。

ハイエナ行為

「ハイエナ」ことノリスケがハイエナ行為(磯野家などからたかる)を行うと◯がつきます

shirts-custom.com

BACK COME ON

波平が「バッカモーン!(BACK COME ON)」と怒鳴ると◯がつきます

SAYOU

波平が「左様(SAYOU)」と言うと◯がつきます。

創価

波平が「そうか(創価)」と言うと◯がつきます。

ちなみに「BACK COME ON」「SAYOU」「創価」は波平の三大名言です。

花沢異常性欲

「カツオの嫁」こと花沢さんがカツオに対して異常な性欲を発揮すると◯がつきます。無限性欲とも呼ばれます

タラヲ氏ね

文字通り

サイコパス堀川

タラヲがウザいのは元からですが、近年頭角を現してきた堀川くん。そのサイコパスっぷりが発揮されると◯がつきます

news.merumo.ne.jp

otapol.jp

レアBGM

普段聴かないBGMが流れると◯がつきますが、よく聴くレアBGMもあります。

熱いカツオDIS

いたずら小僧として世間で認知されているカツオも最近は割とそんなこともなく良識派だと思います。

そんな中偏見だけででカツオがぬれぎぬを着せられたりDISられたりすると◯がつきます。

タラヲメイン

タラヲメインの話が1本でもあると◯がつきます。

じゃんけん

番組最後の予告でサザエさんが出した手です。

脚本家で振り返る2016年サザエさん

今年はサザエさんを実況しながらパートごとの脚本家の名前を全部スプレッドシートにメモしていました。

2016年版サザエ脚本家リスト - Google スプレッドシート

ここからいろいろ検証してみたいと思います。

開幕雪室率

開幕雪室率は98%。

今年全51週あったうち、3/27の1時間スペシャルを除いて全て雪室先生が先発脚本でした!

城山先生生存率

城山先生が全く出てこなかったのは計11回なので、今年の城山先生生存率は80%でした

レア脚本家は本当にレアなのか?

今年の各脚本家の回数を数えたら下記のようになりました

f:id:sue445:20161225190922p:plain

雪室先生と城山先生が圧倒的すぎて、他の脚本家が出るのはレアだということが証明されましたと思います

ネタ提供で雑に振り返る2016年

その週に個人的に話題になってたものを提供にしています

1月

2月

3月

4月

5月

5/29は別件で実況していなかったのでツイートは無し

6月

7月

8月

9月

10月

10/23は日本シリーズ第2戦でサザエさんの放送はありませんでした

11月

12月

予告

来年のサザエさんは2017/1/8(日)からです

rubicureのこれまでとこれから

これは プリキュア Advent Calendar 2016 - Adventar の16日目です。

www.adventar.org

12/16はキュアピースの中の人の金元寿子さんのお誕生日です。おめでとうございます!!!!

今回はこれまでのrubicureとこれからのrubicureについて書くポエムです

rubicureとは

プリキュアRuby実装です

github.com

最新情報は先日Qiitaに書いたのでこちらを御覧ください。

qiita.com

これまでのrubicure

リリースノート

この辺

https://github.com/sue445/rubicure/blob/master/CHANGELOG.md

rubicureのバージョニングポリシー

僕のgemはだいたいセマンティックバージョニングに則っています

セマンティック バージョニング 2.0.0 - Semantic Versioning

ただ、rubicureの場合若干変えていて、同一シリーズ内の場合は必ず +0.0.1し、シリーズが変わる時に +0.1するようにしています。

https://github.com/sue445/rubicure/blob/master/CHANGELOG.md

今まで割と曖昧なバージョニングをしていましたが、魔法つかいプリキュアで上記のバージョニングを思いついたので魔法つかいプリキュアは 0.4.x 系を保ち続けています

moongiftにとりあげられた

www.moongift.jp

正月に帰省先でファッ!?となった記憶があります。

周辺ツールとかができた

Rubyをインストールして gem install すればすぐに使えるというお手軽さから(推測)、周辺ツールがいろいろできています

github.com

igreque : Info -> ユナイトプリキュア!このあとVim! #cure_advent

これからのrubicure

rubicure v1.0.0

映画プリキュアがオールスターズからドリームスターズになるということは記憶に新しいと思います

www.precure-dreamstars.com

nlab.itmedia.co.jp

これを機にrubicureも1.0.0を考えています

今考えているのは主に

  • ドリームスターズ対応
  • 古いRuby(2.0系, 2.1系)の非サポート化
    • Travis CIでビルドしない(古いRubyでもインストールはできるけど動作は保証しない)だけにするか、required_ruby_versionを変更して古いRubyではインストールすらできなくするかまでは考えてない
    • 雑にやるなら前者でもいいと思うけど、2.1系でうまく動かないってってissueがきて対応するのも面倒なので個人的には後者でいい気もしている。(もうすぐ出るRuby 2.4から数えたら3世代以上前なので切り捨ててもバチは当たらないだろう)
  • deprecatedメソッドの削除

辺りです。

個人的にはactuvesupportも外したいところなのですが厳しそうな予感はしてる。。。

区切り的に次回作の「キラキラ☆プリキュアアラモード」でv1.0.0にしようと考えています

www.toei-anim.co.jp

最後に

これからもrubicureをよろしくお願いします m( )m

久しぶりにJenkinsプラグインをリリースしようとしたら謎のエラーで失敗した

Qiitaや会社ブログにはエントリ書いてたけどこっちのブログは1ヶ月間更新無しとかマジか。(挨拶)

前置き

先日自分がメンテしてるJenkinsプラグインにPRがきて「よっしゃ!マージしてリリースするぜ!」と思っていつものリリースコマンド *1 をたたいたらリリース時に謎のエラーがおきて途方に暮れていた時の出来事です。

日本語だと今回の事象が見つからなかったので日本語でメモ残しておきます

tl;dr

org.jenkins-ci.plugins.plugin(全てのJenkinsプラグインの親プラグイン)を2.5以降にしよう

エラー内容

[INFO] [INFO] --- maven-install-plugin:2.3.1:install (default-install) @ gitlab-logo ---
[INFO] [INFO] Installing /Users/sue445/dev/workspace/github.com/jenkinsci/gitlab-logo/target/checkout/target/gitlab-logo.hpi to /Users/sue445/.m2/repository/org/jenkins-ci/plugins/gitlab-logo/1.0.2/gitlab-logo-1.0.2.hpi
[INFO] [INFO] Installing /Users/sue445/dev/workspace/github.com/jenkinsci/gitlab-logo/target/checkout/pom.xml to /Users/sue445/.m2/repository/org/jenkins-ci/plugins/gitlab-logo/1.0.2/gitlab-logo-1.0.2.pom
[INFO] [INFO] Installing /Users/sue445/dev/workspace/github.com/jenkinsci/gitlab-logo/target/checkout/target/gitlab-logo.jar to /Users/sue445/.m2/repository/org/jenkins-ci/plugins/gitlab-logo/1.0.2/gitlab-logo-1.0.2.jar
[INFO] [INFO] Installing /Users/sue445/dev/workspace/github.com/jenkinsci/gitlab-logo/target/checkout/target/gitlab-logo-sources.jar to /Users/sue445/.m2/repository/org/jenkins-ci/plugins/gitlab-logo/1.0.2/gitlab-logo-1.0.2-sources.jar
[INFO] [INFO] Installing /Users/sue445/dev/workspace/github.com/jenkinsci/gitlab-logo/target/checkout/target/gitlab-logo-javadoc.jar to /Users/sue445/.m2/repository/org/jenkins-ci/plugins/gitlab-logo/1.0.2/gitlab-logo-1.0.2-javadoc.jar
[INFO] [INFO]
[INFO] [INFO] --- maven-deploy-plugin:2.6:deploy (default-deploy) @ gitlab-logo ---
[INFO] Uploading: http://maven.jenkins-ci.org:8081/content/repositories/releases/org/jenkins-ci/plugins/gitlab-logo/1.0.2/gitlab-logo-1.0.2.hpi
[INFO]
[INFO] Uploading: http://maven.jenkins-ci.org:8081/content/repositories/releases/org/jenkins-ci/plugins/gitlab-logo/1.0.2/gitlab-logo-1.0.2.pom
[INFO]
[INFO] [INFO] ------------------------------------------------------------------------
[INFO] [INFO] BUILD FAILURE
[INFO] [INFO] ------------------------------------------------------------------------
[INFO] [INFO] Total time: 3:57.988s
[INFO] [INFO] Finished at: Thu Oct 27 08:50:12 JST 2016
[INFO] [INFO] Final Memory: 71M/499M
[INFO] [INFO] ------------------------------------------------------------------------
[INFO] [ERROR] Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:2.6:deploy (default-deploy) on project gitlab-logo: Failed to deploy artifacts: Could not transfer artifact org.jenkins-ci.plugins:gitlab-logo:hpi:1.0.2 from/to maven.jenkins-ci.org (http://maven.jenkins-ci.org:8081/content/repositories/releases): Connection to http://maven.jenkins-ci.org:8081 refused: Operation timed out -> [Help 1]

うーん、http://maven.jenkins-ci.org:8081プラグインアップロードする時にタイムアウト????

エラー全文 https://gist.github.com/sue445/259d4c71d2e4d34b33948cb61c66a900

原因

プラグインのアップロード先が http://maven.jenkins-ci.org:8081 から https://repo.jenkins-ci.org に変わったため

ソース

原因が見つかったのは https://issues.jenkins-ci.org/browse/JENKINS-37423 の下記のコメントを見つけたのがきっかけでした。(このきっかけを見つけるまで約1週間かかった)

CrossBrowserTesting.com LLC added a comment - 2016/Aug/22 11:23 PM fixed it. had to update the parent to version of at least 2.5 in pom.xml https://wiki.jenkins-ci.org/display/JENKINS/Hosting+Plugins#HostingPlugins-Workingaroundcommonissues

で、リンク先には

Either overwrite the repository location in your POM to point to https://repo.jenkins-ci.org, or update to the parent plugins POM 2.5 or newer

って書いてた。親プラグインのバージョン上げずにPOMのアップロード先だけ変えるってのが分からなかったので正攻法に親プラグインのバージョンを上げることに。

実際に対応したのがこのPR

github.com

この修正でプラグインをリリースすることができました

org.jenkins-ci.plugins.pluginのバージョンを上げるとBetamaxがうまく動かない問題

Betamaxというのはhttp通信をモック化するGroovy製のライブラリです。自分のプラグインだとGitLabの通信部分をスタブにするために使っていました

betamax.software

上記PRでbetamaxを消した理由をメモ

  1. org.jenkins-ci.plugins.pluginのバージョンを上げるとつられてJettyのバージョンも上がって、betamaxの中で使ってるJettyのバージョンが古すぎてビルドでエラーになる
  2. betamaxのバージョンを1系から2系に上げると今度はSSLのモックがうまく効かない
  3. 公式のドキュメント 読むと「 keytool -importcert 〜 でbetamaxの鍵を追加すると動くようになるよ」って書いてある
  4. しかし自分で管理してるJenkinsならまだしもcloudbeesのJenkinsでそれは無理ぽ

ってことでbetamaxを捨てて昔ながらにStubクラスからテスト対象のクラスを継承してテストするようにしました

github.com

*1:mvn release:prepare release:perform

今更RubyKaigi2016振り返り

RubyKaigi 2016 に参加してたのですが、最近までISUCON予選あったり自分がメインで関わってるアプリのリリースとかあったりで今更振り返りです

rubykaigi.org

0日目(前日)

知り合い何人かとairbnbで宿をとってたんだけど(宿主の名前をとって通称モリタハウス)、ホストから当日朝にドタキャン喰らった。FBのモリタハウス連絡部屋が朝からハチャメチャ大混乱

詳細

chiastolite.hatenablog.com

1日目

聞いたセッション

最後のk0kubunさんのが一番気になってたんだけど、AWS完全依存なのでうちでは使えないかなーという印象。(アプリによってAWSやそれ以外使い分けたりするし、費用安くするために別クラウドに移行ってのもたまにあるので)

AWSしか使わない(他クラウドに絶対移行しない)のであればむっちゃよさそう

弊社ブースについて

どっかでコミュニケーションロスがあって弊社の付箋が貼られた空っぽのブースがあるという事態に。。。

当日行って初めて自社ブースがあるのを知るという超恥ずかしい状態だったので次回以降なんか協賛するのであればちゃんと準備したいというのを社内でしてました

2日目

聞いたセッション

個人的に気になったのは2番目の「How DSL works on Ruby」。自分が今作ってるやつのヒントがあってよかった(DSLじゃないけど)

RubyKaraoke 2016

rubykaigikaraoke.doorkeeper.jp

弊社がスポンサーだったのでいろいろ手伝ってました

初のカラオケスポンサーということで目立ってた*1んですが、会社的には何もアピールできてなかったのが残念

3日目

聞いたセッション

前日(翌日朝まで)のカラオケが祟って昼過ぎに起きました('A`)

その他

そういえば会期中会った人から何回か先日のエントリについて聞かれることがありました

sue445.hatenablog.com

中には「日本語だけじゃもったいないから英語でも書いてほしい!」ってのを言われたのですが、なにぶん自分の英語が拙いのと自分が英語で書いてもそういう層にリーチしないという悩みが。(まあ英語でググってヒットするだけでも価値はありそうだけど)

英語で発信してくれる人、募集中です。

*1:ある意味ゴールドより目立ってたのでは?w

gemspecにRUBY_VERSIONによるif文書くのは意味がないので今すぐやめるべき

自戒です

tl;dr

  • gemspecの中でRubyのバージョンによってインストールしたいgemのバージョンを変えたい時は、gemspecではなくGemfileでif文書くのがおそらく正解

発端

先月くらいのFacebook内のちょっとした会話がきっかけでした *1

f:id:sue445:20160901233138p:plain

間違った対処法

f:id:sue445:20160901233158p:plain

  if Gem::Version.create(RUBY_VERSION) >= Gem::Version.create("2.2.2")
    spec.add_dependency "activesupport", ">= 4.0.0"
  else
    # NOTE: activesupport 5.x supports only ruby 2.2.2+
    spec.add_dependency "activesupport", ">= 4.0.0", "< 5.0.0"
  end

https://github.com/sue445/rubicure/blob/v0.4.7/rubicure.gemspec#L23-L28

gemspecでこんなif文を書いておけば、ruby 2.2.2未満の時はactivesupport 4系がインストールされる、、、












そんなふうに考えていた時期が俺にもありました

f:id:sue445:20160901234657p:plain

Travis CIのビルドの各Rubyのバージョンのログを見ても意図したバージョンがインストールされていたし、間違いなんてあるわけないと思っていました。

だがしかし

f:id:sue445:20160901233532p:plain

https://rubygems.org/gems/rubicure/versions/0.4.7 の表示、てっきり表示だけこうなってて gem install はいい感じに分岐してくれるのかと思ってた。。。

sonots先生曰く

f:id:sue445:20160901233846p:plain

rubygemsjruby と cruby (platform) で別々の gem をリリースすることはできるんですが、残念なことに cruby のバージョンで別々の gem をリリースすることはできないのですよねぇ


リリースする gem 自体の依存は緩くして、アプリ側の Gemfile であなたの rubyバージョンに合わせてactivesupport 絞ってね、というしかないのかなぁと思ってました。rubygems に機能が足りてないと思うんですよねぇ。


travis を通す時も、gemspec は緩くしておいて、Gemfile 二つ用意して、build matrix を頑張る

検証結果

f:id:sue445:20160901234124p:plain

ruby 2.1.9で上記gemspecの分岐が入ったrubicureのバージョンをインストールしようとすると、「activesupport requires Ruby version >= 2.2.2.」とエラーが出ているのが分かると思います

gist.github.com

上記エラーは rubygems.orgからのインストールログですが、geminaboxホスティングされた社内gemでも検証をしましたが同様の結果でした。

所感

  • add_dependencyadd_runtime_dependency )で書くruntime dependency(アプリの実行時に必要なgem)に関しては上記の理由で意味が無い
    • もしバージョンを絞りたい場合はgemspecではゆるくしておいてGemfileでバージョンを絞る
    • 必要ならドキュメントにもそれを明記する。*2
  • add_development_dependency で書くdevelopment dependency(gemの開発時に必要なgem)に関してはgem installでインストールされないため、if文を書いても意味なくはない
  • とはいえ、add_development_dependency はOKで add_dependency はNGというのは普通分からないし混在すると紛らわしいため、gemspecに RUBY_VERSION のif文は一切書かずに Gemfileに書くよう統一するのが今の自分の中では正解だと思ってます。(異論は認める)

Rubyのバージョンによる分岐を全部Gemfileに寄せた結果

こんな風になりました

source "https://rubygems.org"

# Specify your gem's dependencies in rubicure.gemspec
gemspec

if Gem::Version.create(RUBY_VERSION) < Gem::Version.create("2.1.0")
  # NOTE: build is failed when use ruby 2.0 and rspec-parameterized 0.3.0+
  #   https://travis-ci.org/sue445/rubicure/jobs/114266855
  gem "rspec-parameterized", "< 0.3.0"
end

if Gem::Version.create(RUBY_VERSION) < Gem::Version.create("2.1.0")
  # NOTE: unparser v0.2.5 drop support ruby < 2.1
  gem "unparser", "< 0.2.5"
end

if Gem::Version.create(RUBY_VERSION) < Gem::Version.create("2.2.2")
  # NOTE: activesupport 5.x supports only ruby 2.2.2+
  gem "activesupport", ">= 4.0.0", "< 5.0.0"
end

if Gem::Version.create(RUBY_VERSION) < Gem::Version.create("2.3.0")
  gem "backport_dig"
end

https://github.com/sue445/rubicure/blob/v0.4.9/Gemfile

rubicureもそろそろRuby 2.0系サポートきってもいいかなぁ。。。

謝辞

上記Facebookの会話の転載を快諾してくださった id:koic さんと id:sonots さん、ありがとうございます m(_ _)m

*1:FBキャプチャ転載に関しては事前にお二人の許可はとっています

*2: rubicureだとこんな感じ https://github.com/sue445/rubicure/blob/v0.4.9/README.md#installation