rspecのdescribeとitを自作してみる
読書コード対象者
Rubyのコード少しでも読める方なら最後まで読了できるかと思います。
- Rubyの引数について詳しくなります。
- GrapeというAPIを簡単に作れるOSSのソースコードを少しだけ一緒に読んで行きたいと思うので毎日使っているコードが親近感でます
最終的には、下記の文法が何しているかわかります。
describe "RspecでこちらのコードがRuby内部でどういう文法で動いているか" do
puts "分かるようになります"
end
class Status < Grape::Entity
# またGrapeのこのあたりのコードにも詳しくなります
expose :style
expose :html, documentation: { type: String, desc: 'html' } do |f|
f.body
end
end
またこの do end や { } みたいなブロックと呼ばれるものへの理解が少し広がるかと思います。
背景
こんにちは僕です。この話は、Vue.jsからAPIを叩こう思い、Rails側にGrapeによるAPIを作っていたことから始まります。そのときに Grapeエラーでるよ〜〜あああああ〜〜 って思いソースコードまで追ってみたのでその時の話を順番にしていきます。( ドキュメント見てから実装開始しろってツッコミはなしで )
Grapeのコードを追ってみる
Entityのサンプル
class Status < Grape::Entity
expose :style
expose :html, documentation: { type: String, desc: 'html' } do |f|
f.body
end
end
と書いておくと
present s, with: Status
みたいな感じでJSONの返却をぬるっとやってくれるのがGrapeです。
今回はこの expose の引数に何を渡せばいいのか全くわからなかったので、そのあたりのコードを追っていきます。
Rubyの引数の振り返り
def x1(text)
puts text
end
def x2 text
puts text
end
x1("it calls x1") # it calls x1
x2 "it calls x2" # it calls x2
おもむろにTerminalでirbと打つとRubyが対話モードで使えるので(MacUser)上のコードは適当に自分で流し込んで試してください。!!
$ irb
$ irb(main):001:0>
こんな感じでRubyは () が省略できたりします。
参考: Ruby のコーディングスタイル
Grapeの expose の使い方をもう一度見る
前回はRubyの()の省略について学んだので次のコードも理解できるはずです。
expose :user_name
つまり expose はメソッドで、引数に :user_name というシンボルを渡している、という風に見えて来ましたね。(間違ってたら偉い人が教えてくれるのでコメント欄参照!!)
ここまでで、ちょっとRubyに詳しくなった気がします。
なので次は expose というメソッドを見に行きます。
Grapeの expose のソースコードを見る
ソースコードはこちら lib/grape_entity/entity.rb
def self.expose(*args, &block)
options = merge_options(args.last.is_a?(Hash) ? args.pop : {})
if args.size > 1
raise ArgumentError, 'You may not use the :as option on multi-attribute exposures.' if options[:as]
raise ArgumentError, 'You may not use the :expose_nil on multi-attribute exposures.' if options.key?(:expose_nil)
raise ArgumentError, 'You may not use block-setting on multi-attribute exposures.' if block_given?
end
raise ArgumentError, 'You may not use block-setting when also using format_with' if block_given? && options[:format_with].respond_to?(:call)
if block_given?
if block.parameters.any?
options[:proc] = block
else
options[:nesting] = true
end
end
@documentation = nil
@nesting_stack ||= []
args.each { |attribute| build_exposure_for_attribute(attribute, @nesting_stack, options, block) }
end
20行くらいなのでみんなすぐ理解できますよね(ちなみに私理解できなかったのでこの記事書きながら理解しています)
Grapeの expose のソースコードを適当に理解する
def self.expose(*args, &block)
options = "引数の *args からオプションをいい感じに整形する"
if args.size > 1
"問題あったらエラーだす"
end
raise ArgumentError, '問題ないよね…?'
if block_given?
"引数として &block が渡されてるとごにょごにょ"
end
@documentation = nil
@nesting_stack ||= []
args.each { |attribute| "いい感じにゴニョゴニョしてデータ作り出す" }
end
正直なんかよくわからないけど def self.expose(*args, &block) これの *args は 引数はたくさん渡せるよ!で、 最後の &block は 引数の最後にブロック渡せるぜ!!!! って意味だと思ったらいいと思います。(強引)
※ブロックとは do end とか {} のことで、ブロックが引数として渡されたのを判定するのは Rubyの block_given? というメソッドが使えます。
Grapeの expose の使い方をもう一度見る(3度目)
なので、こちらももう読めるようになったはずです。。
expose :html, documentation: { type: String, desc: 'html' } do |f|
f.body
end
exposeというメソッドの引数は
:htmldocumentation: { type: String, desc: 'html' })do |f| f.body; end
の3つで、最後の3つ目はブロック要素として認識されています。
def self.expose(*args, &block)
options = merge_options(args.last.is_a?(Hash) ? args.pop : {})
end
そして、もう一度exposeメソッドを見ると、1と2は(*args)に入って、3のブロックは(&block)に入っていくことがわかります。
args.last.is_a?(Hash) は *args の最後がハッシュなら という意味ですが、おそらくですが、この *args の最後には documentation のドキュメントをいれないとエラー起きるような気がします(今回はそこは追わない)
ブロックが渡されたか判断する (&block)
Rubyのメソッドにblock_given?で渡されたかチェックできます。
def self.expose(*args, &block)
if block_given?
"引数として &block が渡されてるとごにょごにょ"
end
end
なのでexposeには2つの書き方があります。
class Status < Grape::Entity
expose :style
expose :html, documentation: { type: String, desc: 'html' } do |f|
f.body
end
end
do; end;をつける書き方、何もつけない書き方、どちらも文法的に正しくなるように expose は設計されています。
普段なんとなく使っているコードをしっかり追ってみましたが、個人的にはRubyをちゃんと知らなくなんとなく使っているのはエンジニアとして成長するためにもだめだなあと思いました。
Rspecのコードは追わないが作ってみる
Rspec序文
ここまでで expose のコードを読んでrubyの使い方への理解が深まったと思います。
次は本題ですが Rspecの describe を自分で書いてみようと思います。
describe "xxxをテストする" do
puts "これで合ってるかな…"
end
ここまで読んだ読者はもうわかると思いますが、describe というメソッドは引数は (text, block) テキストとブロックだと判断がつきます。
Rspec作成編
# encoding: utf-8
def describe(msg, &block)
if block_given?
block.call
else
puts msg
end
end
puts "~~~~~~~~~~~~~~~~~~~~~~~~~~"
describe "こちらテストですよ!!!!!!"
puts "~~~~~~~~~~~~~~~~~~~~~~~~~~"
describe "こちらテストです" do
puts "ブロック渡したらどうなるかテスト"
end
# 返ってくる値
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~
# こちらテストですよ!!!!!!
# ~~~~~~~~~~~~~~~~~~~~~~~~~~
# ブロック渡したらどうなるかテスト
今回作成した describe はブロックを渡したかどうかで処理を分岐させてみました。
-
ブロックを渡した場合
-
block_given?がtrueになる -
block.callを実行させる
-
-
ブロックを渡さない場合
- ただ第一引数で渡したmsgをputsするだけになります。
ここまで自分で作れるようになったら、Rubyの引数マスターだと思います
block.call
渡したブロックを実行させられます。
block.call
Rspecのソースコードについて
たぶんレポジトリはこちらですが describe とかどこで実装されているか正直読んでないです。分かる方は記事を書いてくれると楽しんで読みたいと思うのでコメント欄でお願いします〜〜
最後に
自分が普段使っているソースコードを、気になったところだけでもいいので読んで見る。これだけでもOSS使ってるっぽいし(形から入る人)、ドキュメントにしっかりかかれていないこともなんとなく分かるのがすごくいいですね。(今回は expose の最後の引数はdocumentじゃね?って考えれたこととか)
Ruby詳しいですとは口がさけても言えないので詳しい方PRとかコメント欄でご指導いただけると幸いです。最後まで読んでいただきありがとうございました〜!!
