Rubyのcase文はwhen節に渡すオブジェクトによって振る舞いが異なる
今回は Ruby の case 文についてです。
Ruby の case 文はどのように値を比較するか
Ruby の case 文では case 節に比較対象となる値を、when 節に case 節の値と比較する値を記述します。
んで、when 節の値をレシーバとして、===
メソッドによって比較が行われ、最初に真となった when 節に続く処理が実行されます。
hoge = 'hoge' case hoge when 'foo' puts 'foooo!' when 'bar' puts 'baaar!' when 'hoge' puts 'hogeee!' else puts "わかりまへーん" end #=> hogeee!
このように===
で比較するわけで、数値や文字列を比較する時は単純に==
的な比較を行います。
しかし、実はいくつかのクラスでは===
の振る舞いが異なります。具体的にはRange
, Regexp
, Proc
, Module
とClass
です。
Range クラスのオブジェクトと比較する場合
score = 43 case score when (1..40) puts '追試ですよー' when (41..80) puts '微妙ですよー' when (81..1000) puts 'いい感じですよー' end #=> 微妙ですよー
Range
の===
では引数が自身の範囲内に含まれていたら真を返します。今回はscore = 43
なので追試は間逃れています。
Regexp クラスのオブジェクトと比較する場合
lang = 'ruby' case lang when /php/ puts 'phpですよー' when /ruby/ puts 'rubyですよー' else puts 'そんなの知りませんよー' end #=> rubyですよー
Regexp
の===
は引数の文字列がマッチした場合に真を返します。今回は簡単な例ですが、ちゃんと正規表現を書けばかなり柔軟な比較ができそうです。
Proc クラスのオブジェクトと比較する場合
obj = 'hoge' case obj when proc { |x| x.is_a? String } puts 'Stringでしたよー' when proc { |x| x.is_a? Numeric } puts 'Numericでしたよー' else puts 'よくわからないですよー' end #=> Stringでしたよー
Proc
の===
では右辺の値を引数にしてProc
の処理を実行します。
よって上記のように条件判断のためのProc
オブジェクトを仕込んでおくと、case に渡された値を引数として処理を実行するので、柔軟な条件分岐が可能になります。
Class や Module クラスのオブジェクトと比較する場合
obj = 'hoge' case obj when String puts 'Stringでしたよー' when Numeric puts 'Numericでしたよー' else puts 'よくわからないですよー' end #=> Stringでしたよー
Class
やModule
の===
では右辺のオブジェクトが左辺のクラス(もしくはそのサブクラス)のインスタンスだった場合、真を返します。
なのでString === String
はfalse
ですが、String === 'hoge'
はtrue
になります。
これは地味に間違えそうなやつですね。
まとめ
クラスによって比較の仕方が違うというのは、知らないとハマりポイントになりそうなので注意が必要です。また、例えばクラスの異なるオブジェクト同士を比較する場合は、どちらをレシーバにするかによって結果が変わる可能性もあるのでそこも注意が必要そうです。
今回も参考にしたのは安定のパーフェクト Ruby でございました。
Rubyのメソッド探索についてまとめてみた
今回はパーフェクト Ruby で「Ruby のメソッド探索」について学んだのでそのメモです。
オブジェクトに対してメソッド呼び出しが行われる際、どのようにメソッドが呼び出されているのか、書いてみたいと思います。
自身のメソッドを呼び出した時
class BaseClass def hello :hello end end base_object = BaseClass.new base_object.hello #=> :hello
これは最も単純なケース。
自身のクラスのhello
メソッドを探しにいって、あれば実行します。
親クラスのメソッドを呼び出した時
class InheritClass < BaseClass end inherit_object = InheritClass.new inherit_object.hello #=> :hello
この場合、まず Ruby のインタプリタはinherit_object
のクラスInheritClass
を参照しますが、ここにhello
メソッドはありません。なので、次にその親クラスであるBaseClass
を探しにいってそこで見つかったhello
メソッドを呼び出します。
特異メソッドを呼び出した時
def base_object.hello :singleton_method_hello end base_object.hello #=> :singleton_method_hello
base_object
の特異メソッドにhello
を定義した場合。これはbase_object
の特異クラスのメソッドなので上図のような関係になります。
特異クラスはオブジェクトのクラスよりも先にメソッド探索されるようになっています。よって今回の場合、インタプリタはまずbase_object
の特異クラスを参照し、そこにあるhello
メソッドを呼び出します。
ちなみにbase_object
の特異クラスにはbase_object.singleton_class
でアクセス可能(特異クラスはそれを定義したり、確認しようとしたタイミングで作成される)です。
クラスに include したモジュールのメソッドを呼び出した時
module HelloModule def hello_from_module :hello_from_module end end class InheritClass include HelloModule end inherit_object = InheritClass.new inherit_object.hello_from_module #=> :hello_from_module
モジュールをクラスにinclude
した場合、そのモジュールの機能を取り込んだクラスが自身と親クラスの間に挿入されます。そして自分、モジュールの機能を取り込んだクラス、親クラスの順に探索します。
今回の場合はモジュールの機能を取り込んだクラス内でhello_from_module
を発見し、それを呼び出しています。
ちなみに複数のモジュールをinclude
した場合は後からinclude
したモジュールが前に挿し込まれる感じになります。
クラスに extend したモジュールのメソッドを呼び出した時
module HelloModule def hello_from_module :hello_from_module end end class InheritClass extend HelloModule end inherit_object = InheritClass.new inherit_object.hello_from_module #=> NoMethodError: undefined method ... InheritClass.hello_from_module #=> :hello_from_module
モジュールをクラスにextend
した場合、レシーバ(今回はInheritClass
)の特異クラスに対してextend
したモジュールがinclude
されます。
よって上図のような関係になり、モジュールで定義しているメソッドがInheritClass
のクラスメソッドとして使えるようになります。inherit_object
のメソッド探索は直線部なので、破線部にあるhello_from_module
は見つかりません。
存在しないメソッドを呼び出した時
method_missing
メソッドが定義されているクラスを探し続けます。
それが見つからない場合は最終的にBaseObject#method_missing
を呼び出し、NoMethodError
が発生します。
まとめ
特異メソッド、特異クラス、mixin など何気なく使っているものの動作を図で整理できてよかったです。 なにか間違えていたらご指摘いただけると幸いです。
今回も参考にしたのはこちら。
Rubyのインスタンス変数、クラス変数、クラスインスタンス変数
Ruby のクラスには
があって何かとややこしい。。
特にクラス変数とクラスインスタンス変数の違いについてはいまいち理解しきれていなかったので、今回は頭の整理もかねてメモです。
それぞれの特徴について書いてみたいと思います。
インスタンス変数
オブジェクト固有の状態を保持するための変数。@をつける。
class Foo attr_accessor :instance_foo def initialize @instance_foo = 'instance_foo!' end end Foo.new.instance_foo #=> instance_foo!
外部からアクセスするにはFoo#name
とFoo#name=
を定義する必要があるが、attr_accessor
を使えば自動的に定義してくれる。
もしくはinstance_variable_get
とかinstance_variable_set
とか使う。
クラス変数
クラスとそのサブクラスの定義中、クラスメソッド、インスタンスメソッドで共有できる変数。
@@をつける。
class ParentFoo @@class_foo = 'class_foo!' def self.say @@class_foo end end class ChildFoo < ParentFoo def say @@class_foo end end ParentFoo.say #=> class_foo! ChildFoo.say #=> class_foo! ChildFoo.new.say #=> class_foo!
今回はsay
メソッドを用意してるけど、class_variable_get
とかclass_variable_set
とかで外部からアクセスもできる。
Rails を使ってるならcattr_accessor
でattr_accessor
的にクラス変数を外部に公開したりもできるはず。
クラスインスタンス変数
Ruby ではすべてがオブジェクトなので、クラスもClass
クラスのオブジェクト。つまりクラスオブジェクト自体もインスタンス変数を持つことができる。これがクラスインスタンス変数。
クラス定義式、クラスメソッドなど、self
がクラスを指すコンテキスト内で定義可能。
class ParentFoo @class_instance_foo = 'class_instance_foo!' def say @class_instance_foo end def self.say @class_instance_foo end end class ChildFoo < ParentFoo ; end ParentFoo.say #=> 'class_instance_foo!' ParentFoo.new.say #=> nil ChildFoo.say #=> nil ChildFoo.new.say #=> nil
クラス変数との明確な違いは以下の2点かなと思われる。
まとめ
それぞれの特徴を把握して、明確な意図を持って使い分けていきたいですね。
今回は以下の書籍を参考にしました。
Railsでfakerのバージョンを上げたらspecが壊滅したけど設定で乗り越えたメモ
最近、Rails プロジェクト内で使用しているfakerのバージョンを最新(v1.4.3
)に上げたら、今まで通っていた spec が壊滅したので、その際のメモです。
状況把握
こちらの記事に記述されているものと全く同じ状況に陥ったと思われます。
プロジェクト内の locale はデフォルトでI18n.locale = :ja
にしていたので faker の locale も:ja
にセットされ、その結果Faker::Internet.email
やFaker::Internet.url
が壊れたのだと思います。(前はv1.1.2
ぐらいだったのですが、faker の locale 設定まわり変わったんですかね。。)
対策
プロジェクト内でfactory_girlも使用していたので、config/initializers/factory_girl.rb
を用意して
if defined? Faker Faker::Config.locale = :en end
のようにしました。
わざわざfactory_girl.rb
に設定している理由はfactory_girl
を
$ rails c [1] pry(main)> FactoryGirl.create :user 以下略
のように develop 環境でも使うことを想定しているからです。
spec/factories/users.rb
などの factory の中で
FactoryGirl.define do factory :user do name { Faker::Name.name } email { Faker::Internet.url } end end
みたいにガンガン faker 使ってたので、rails c
で毎回、Faker::Config.locale = :en
を呼ばないといけないのもダルいし。。
もし test 環境だけでいいやーって感じなら、spec/support/faker.rb
などに
RSpec.configure do |config| config.before(:suite) { Faker::Config.locale = :en } end
とかでもいいんじゃないかと思います。
まとめ
テスト落ちまくって心臓に悪かったけど、おかげでバージョンの違いで使えなかった faker のメソッドが使えるようになりました。
同様の悩みを抱えている方のお役に立てれば幸いです。
参考
git-new-workdirを導入してGitのブランチを複数同時に扱えるようにした
今、僕の仕事環境では「エンジニアそれぞれが作業用ブランチを切って開発を進め、Github に pull-req を出してレビューもらって、それが通ったらマージされる」、といったよくありがちなフローで開発を進めています。
で、今までは自分が開発をしている最中に同僚から pull-req が飛んできてレビューをしなくてはならなくなったとき、自分の作業を一旦git stash
なり、git commit
なりしてキリがいい状態にしてから、git checkout
してレビュー、、のようなことをやっていました。
ですが、最近同僚にgit-new-workdir
なるものを使えばそんなだるいことしなくていいよーって教えてもらったのでさっそく導入!!今回はその導入メモです。
git-new-workdir コマンドを使えるようにする
僕はhomebrew
で git を導入してるのですが、git-new-workdir
自体はすでに/usr/local/share/git-core/contrib/workdir/git-new-workdir
にありました。
ただ参考記事を見る感じだと、コマンドはパスを通すなり、シンボリックリンクを貼るなりしないと使えないようなので今回は
$ ln -s /usr/local/share/git-core/contrib/workdir/git-new-workdir /usr/local/bin/git-new-workdir
して使えるようにしました。これで
$ git-new-workdir usage: /usr/local/share/git-core/contrib/workdir/git-new-workdir <repository> <new_workdir> [<branch>]
って出るようになりました!
ちなみにgit-new-workdir
がない人はこことかから落としたりすれば大丈夫そうです。
実際に使ってみる
$ git checkout git@github.com:project_name.git(てきとーなproject) $ git-new-workdir project_name for_develop $ git-new-workdir project_name for_review
これで開発用のブランチで開発をしつつ、pull-req レビュー用のブランチでレビューできる環境ができました!
$ tmux new -s for_develop $ tmux new -s for_review
みたいにして tmux で切り替えて管理できるようにするといいかもですね!
ちなみに.gitignore
に指定されているディレクトリ、ファイルはgit-new-workdir
でつくったディレクトリには反映されていないのでそこは注意。
まとめ
git-new-workdir
便利!!他にもここで紹介されているようにgit-contrib
には便利なツールがいっぱいあるみたいなので、また試していきたいです。