のえら

技術備忘とかメモとか.間違いはつっこんでいただきたい所存.

rails new でバージョン指定したのに上のバージョンになって困った話

初歩的なミスなのですが。

バージョンを指定して新規プロジェクトするのに、以下のコマンドを実行。

rails _5.0.2_ new sample

(中略)

-> Installing rails 5.0.6

>>5.0.6<<

!?

rails new 実行すると続けて bundle install が走るため Gemfile にデフォルトで記述される 5.0.2「以上」が反映されてしまう。

bundle install をスキップして、バージョンを指定後 bundle install すればOK

rails _5.0.2_ new sample --skip-bundle
cd sample
vi Gemfile

gem 'rails', '~> 5.0.2'

gem 'rails', '= 5.0.2'

にする

bundle install --path=vendor/bundle

macOS Sierra(10.12.6) に rails をインストールした

開発用に MacBook Air を購入したので rails をインストールしたときのメモを残す。
今回は以下のツール・環境で構築した。

homebrew のインストール

homebrew をインストールするのに XCode(Command Line Tools for Xcode) をインストールしなきゃいけないなー、と思っていたら、以下のコマンドを叩いて homebrew をインストールすると、実行途中で Xcode ないやで?インストールする?と聞かれるんですね。

ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"
# 確認
brew doctor
rbenv のインストール
brew update
brew install rbenv ruby-build 
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
source ~/.bash_profile
ruby のインストール
# ruby のバージョン確認
rbenv install -l
# 2.4.2をインストール
rbenv install 2.4.2
# 環境全体で使用するバージョンを設定
rbenv global 2.4.2
# インストールの確認
ruby -v
rails のインストール
gem install rails

デフォルトでインストールされたもの

Done installing documentation for i18n, thread_safe, tzinfo, concurrent-ruby, activesupport, rack, rack-test, mini_portile2, nokogiri, crass, loofah, rails-html-sanitizer, rails-dom-testing, builder, erubi, actionview, actionpack, activemodel, arel, activerecord, globalid, activejob, mime-types-data, mime-types, mail, actionmailer, nio4r, websocket-extensions, websocket-driver, actioncable, thor, method_source, railties, bundler, sprockets, sprockets-rails, rails after 70 seconds

bundler のインストール
gem install bundler
設定の反映
rbenv rehash
source ~/.bash_profile

# インストールの確認
rails -v
> Rails 5.1.4

Groovyc: unexpected token: } と whereブロック

Spock(Groovy)のテストケースを書いていて、後からwhereブロックを追加したら以下のエラーが出たけど始めなんで怒られているのかわからなかった。

Groovyc: unexpected token: }

書いていたテストはこんな感じ。
API叩いて結果を確認するだけ。

def 'GET: /hoge/list Hogeの一覧を返す'() {
  when:
  // APIリクエスト クロージャ
  def request = {
    // Rest execute
  }
  
  then:
  with(request.call()) {
    it.status == xxx
    it.body == xxx
  }
}

と、シンプルなフィーチャーメソッドを作成して、後から色々なパラメータ与えたくなってwhereを追加した。
パラメータ自体は1種だけ。

def 'GET: /hoge/list Hogeの一覧を返す'() {
  when:
  def request = {
    // Rest execute(params) <-ここにwhereブロックの変数paramsが入る
  }
  
  then:
  with(request.call()) {
    it.status == xxx
    it.body == xxx
  }

  where:
  params || -
  1           || -
  2           || -
  null       || -
}

こんな感じで、データテーブルは1行では認識されないため、最低2列用意しないといけないのはわかっていたので、規約に沿って空白列を追加。
で、追加したところで、前述のエラーメッセージ。
閉じ位置は変えていないのになんでだろ?と思ってよく見直したら、空白列でアンダーバーを記述しなければならない箇所でハイフンを記述していたのでワイルドカードが認識されずにエラーになっていただけという。

正しくはwhereブロックはこうなる。

where:
param || _
1          || _
2          || _
null      || _

ハイフンは変数ではないので当たり前のことなんだけども、ぼーっとしながら書いちゃダメだなと思った。

テーブルデータが1つなら、空白列足すよりパイプ使った方がスッキリするやも。

where:
param << [1, 2, null]


ちなみに、whereブロックを1つだけしか定義しないと以下のエラーメッセージが表示される。めっちゃ丁寧。

Groovyc: where-blocks may only contain parameterizations (e.g. 'salary << [1000, 5000, 9000]; salaryk = salary / 1000')

Rspecの呼び出し回数チェックテストで全件通し実行するとコケていた件

rspecでrake task系のユニットテストで、onceなどのメソッド呼び出し回数チェックを使用していた。
それらのユニットテストをそれ単体で実行すると成功するが、テストを通して全件実行すると失敗する。
検証したところ、spec/lib/tasks/xxx_spec.rb で以下のようにrake taskを呼び出すためにロードしているが、 Rake::Task[task_name].invoke がこのコードを読み込んだ回数分、指定のタスクを実行していた。

before(:all) do
  Rails.application.load_tasks
end

このため、同様のrake task系テストをまとめているディレクトリ配下のユニットテストを通しで実行すると、回数を検証するonceなどを使用しているitブロックで複数回rake叩いた挙動になってしまい、テストに失敗する。

例)
spec/aaa_task_spec.rb

before(:all) do
  Rails.application.load_tasks
end

context 'HogeClass#foo' do
  it { expect(HogeClass).to receive(:foo).once.with(kind_of(RuntimeError)) }
end

spec/bbb_task_spec.rb

before(:all) do
  Rails.application.load_tasks
end

context 'FugaClass#foo' do
  it { expect(FugaClass).to receive(:foo).once.with(kind_of(RuntimeError)) }
end

spec/ccc_task_spec.rb

before(:all) do
  Rails.application.load_tasks
end

context 'HogeClass#foo' do
  it { expect(HogeClass).to receive(:foo).once.with(kind_of(RuntimeError)) }
end

rspec spec/

  • > 1) HogeClass#foo

Failure/Error: expect(HogeClass).to receive(:foo).once.with(kind_of(RuntimeError))

(HogeClass (class)).foo(kind of RuntimeError)
expected: 1 time with arguments: (kind of RuntimeError)
received: 2 times
...

※load_tasksを呼び出す回数ずつ増える

解決策の例

1)各ファイルでのタスク読み込みをやめてspec_helperなどで一回だけ呼ぶようにする
2)タスク読み込みをまとめて行うのではなく、都度Rake.application.rake_requireする

rails_helperにて一回だけ呼ぶように変更

IntelliJ の git 操作で Could not execute editor

IntelliJ で git 操作するのに Terminal からコマンド打ってぽちぽちしようとしたらエディタを実行できないというエラーメッセージが表示されてコマンドが実行できない。

起きたこと

IntelliJ, VSC は git を使用。
Terminal で git 操作をしていて、PR のコミットを整理しようと以下のコマンドを実行。

git rebase -i HEAD~4

実行するとテキストエディタが起動して対象のコミットを選択する表示になるはず。
が、以下のメッセージが。

Could not execute editor

git がテキストエディタを起動できないらしい。。
ターミナルで git 操作していた時は vi が起動していたので IntelliJ 経由でも起動してくれると思っていた。

解決した方法

git が起動するエディタを設定する。
ターミナルを起動して以下実行。

# 使用しているvimのパスを確認
$ which vim
-> /usr/bin/vim

# git が起動するエディタを、上記で確認した vim のパスに設定
$ git config --global core.editor "/usr/bin/vim"

再度 IntelliJ の Termina から git rebase コマンド打ってみるとエディタが起動、解決。

git のデフォルトエディタの設定について

https://git-scm.com/book/ja/v1/Git-%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA-Git-%E3%81%AE%E8%A8%AD%E5%AE%9A

core.editor
コミットやタグのメッセージを編集するときに使うエディタは、ユーザーがデフォルトエディタとして設定したものとなります。デフォルトエディタが設定されていない場合は Vi エディタを使います。このデフォルト設定を別のものに変更するには core.editor を設定します。
$ git config --global core.editor emacs

Groovy の式展開(埋め込み文字列)の型は GStringImpl

ruby の "hello #{str}" のような感じで Groovy でも式展開が使える。

"hello ${str}"
  • 文字列をダブルクォートでくくる
  • 展開したい変数を ${} でくくる

で、この展開された文字列の型は String ではなく org.codehaus.groovy.runtime.GStringImpl クラスとなるため、List<String>のように String で定義しているものに突っ込もうとするとこける。

String として扱うなら #toString() で変換する。

Spockをちょっと触ったので覚え書き

初めてGroovySpockを触ったので色々と覚え書き。

動作確認は Spock Web Console を使用。
https://meetspock.appspot.com/

基本的な検証の例と出力結果

  • フィーチャーメソッドの名称は自由
  • フィーチャーブロックにはコメントも書ける(出力はされない)
  • setup(given) -> expect or when, then [-> expect or when, then] -> cleanup (whereは後述)
  • when と then はセットで使用する、一つのフィーチャーメソッド内に複数書ける
サンプルコード
import spock.lang.*

class SampleSpec extends Specification {
    def 'given, when, then'() {
        given:
        def amount = 1000
        when: '合計値に0.5をかけた場合'
        def actual = amount * 0.5
        then: '1000が返ること'
        actual == 1000
    }

    def 'only expect'() {
        expect:
        Math.max(10, 3) == 3
    }

    def 'setup, cleanup'() {
        setup:
        def numbers = [1, 3, 4, 0]

        expect:
        numbers.isEmpty()

        cleanup:
        numbers.clear()
    }
}
実行結果

- maximum of two numbers, 0 or 0
- given, when, then FAILED

Condition not satisfied:

actual == 1000
| |
500.0 false

at SampleSpec.given, when, then(Script1.groovy:22)

- only expect FAILED

Condition not satisfied:

Math.max(10, 3) == 3
| |
10 false

at SampleSpec.only expect(Script1.groovy:27)

- setup, cleanup

whereブロックの話

同じ内容のテストをテストする値を変更して実行したい場合に使える。

  • Unrollアノテーションはwhereブロックの実行結果を行ごとで出してくれる(whereブロックの変数が使用できる)
  • フィーチャーメソッドの引数は任意、それぞれの引数の型を明示的に指定することもできる
  • whereブロックはフィーチャーメソッドの中身をまるっと実行する
サンプルコード
import spock.lang.*

class SampleSpec extends Specification {
    @Unroll
    def "maximum of two numbers, #first or #second"(int first, int second, int expected) {
        expect:
        Math.max(first, second) == expected

        where:
        first | second || expected
        1 | 3 || 3
        7 | 4 || 4  // ここは失敗する
        0 | 0 || 0
    }
}
実行結果

SampleSpec
- maximum of two numbers, 1 or 3
- maximum of two numbers, 7 or 4 FAILED

Condition not satisfied:

Math.max(first, second) == expected
| | | | |
7 7 4 | 4
false

at SampleSpec.maximum of two numbers, #first or #second(Script1.groovy:7)

- maximum of two numbers, 0 or 0

whereブロックの話その2

  • 何度目かは #iterationCount で取得できる(フィーチャーメソッド名の中だけで使用可能?)
  • テーブル形式とパイプ形式がある、後者はイテレーションが使えるオブジェクトならなんでも呼び出せる、外部リソースを使うようなテストで使用する?
サンプルコード
import spock.lang.*

class SampleSpec extends Specification {
    @Unroll
    def "like a table(#a, #b)[#iterationCount]"() {
        expect:
        Math.max(a, b) == b
        where:
        a | b
        1 | 7
        2 | 8
        3 | 9
    }

    @Unroll
    def "pipe(#a, #b)[#iterationCount]"() {
        expect:
        Math.max(a, b) == b
        where:
        a << [123]
        b << [789]
    }
}
実行結果

SampleSpec
- like a table(1, 7)[0]
- like a table(2, 8)[1]
- like a table(3, 9)[2]
- pipe(1, 7)[0]
- pipe(2, 8)[1]
- pipe(3, 9)[2]

フィクスチャーメソッド

テスト全体とフィーチャーメソッドの前後に実行される。
RSpec でいう before(:each), before(:all), after(:each), after(:all) のようなもの。

サンプルコード
import spock.lang.*
 
class MyFirstSpec extends Specification {
	def setupSpec() {
		println('call setupSpec')
	}
 
	def setup() {
		println('call setup')
	}

	def cleanup() {
     		println('call cleanup')
   	}
 
   	def cleanupSpec() {
     		println('call cleanupSpec')
   	}
 
   	def 'test1'() {
     		println('call test1')
     		expect:
     		assert true
   	}

   	def 'test2'() {
   		println('call test2')
   		expect:
     		assert true
   	}

	def 'test3'() {
        		println('call test3')
        		expect:
        		assert true
        		where:
        		a | b
        		1 | 2
        		3 | 4
    	}
}
実行結果(外部出力)

call setupSpec
call setup
call test1
call cleanup
call setup
call test2
call cleanup
call setup
call test3
call cleanup
call setup
call test3
call cleanup
call cleanupSpec