respond_to?でメソッドがあるかを確認する

タイトルの通りです。

respond_toの書き方

class Foo
  def foo
    p 'foo'
  end

  private

  def private_foo
    p 'private foo'
  end
end

x = Foo.new
x.respond_to?(:foo)
=> true
x.respond_to?(:foo, true)
=> true
x.respond_to?(:private_foo)
=> false
x.respond_to?(:private_foo, true)
=> true

private methodは第二引数にtrueを渡さないと判定してくれないようです。

public_methodはどっちでも反応しますね。

以上です。

shared_examples_forに引数を渡して処理をさらにまとめる

ここ最近はリファクタリングやより短い書き方を考えてます。

一行でもコードから文字が減るのは嬉しい。

そんなわけで、カラム名が違うだけで、内容同じじゃん・・・って場合のテストの書き方です。

前提条件

カラムの:login, :passwordは半角英数字のみで文字数制限を含んでいます。 ただし、文字数制限は違います。 けど、やってることは同じなので、処理をまとめます。

shared_examples_forの書き方

shared_examples_for 'alphanumeric' do |column_name, min_len, max_len|
  let(:factory_name) { described_class.name.underscore }
  let(:record) { build(factory_name) }

  context "#{column_name}" do
    context '登録できる' do
      context '半角英数字' do
        it '_を含むもの' do
          record[column_name] = 'foo_'
          expect(record).to be_valid
        end

      context '文字列の長さ' do
        it "#{min_len}文字" do
          record[column_name] = 'a' * min_len
          expect(record).to be_valid
        end

        it "#{max_len}文字" do
          record[column_name] = 'a' * max_len
          expect(record).to be_valid
        end
      end
    end
  end
end

こんな感じですかね。

ここでのポイントはrecord[column_name]で値を入れるようにすることです。

record.column_nameではno method errorになります。

あとはit_behaves_likeに引数を渡します。

it_behaves_like 'alphanumeric', :login, 4, 20
it_behaves_like 'alphanumeric', :password, 8, 20

これで同じ処理をまとめることができました。

ただ、passwordpassword_confirmationがいるよ!ってエラーになるので、そこはif文で分岐処理を書きました。

context '半角英数字' do
  it '_を含むもの' do
    value = 'foo_'
    if column_name == :password
      expect(record.update(password: value,
                           password_confirmation: value)).to be_truthy
    else
      record[column_name] = value
      expect(record).to be_valid
    end
  end
end

ここをうまく書ける方法があれば教えていただきたいですm( )m

返ってくる値を真偽値に変換する

例えば、matchを使用して、正しいかどうかの確認だけをしたいと思います。

REG_EXP = /foo/
x = 'foo'
x.match(REG_EXP)
=> #<MatchData "foo">
x.match('bar')
=> nil

通常だと判定した結果が返ってきます。

これを真偽値に変換します。

!!(x.match(REG_EXP))
=> true
!!(x.match('bar'))
=> false

以上です。

rspecでprivate methodを呼び出したい時の方法

privateメソッドを実行したいけど、できないよ!って場合の方法です。

sendを使えばできます。

class Foo

  private

  def foo
    puts 'hoge'
  end

end

x = Foo.new
x.send(:foo)
hoge
=> nil

x.foo
NoMethodError: private method `foo' called for #<Foo:0x007fa279135010>

こうなるので、sendを使ってメソッドを呼び出せば、private methodもテストできます。

これは便利だ。

オブジェクトのインスタンス変数名とメソッド一覧の取得方法

タイトルの通りです。

オブジェクトのインスタンス変数名とメソッド一覧の取得方法を探っていきます。

まずはインスタンス変数名からです。

インスタンス変数名の取得方法

instance_variables

class Foo
  def initialize
    @foo = 1
    @bar = 2
  end
end

x = Foo.new
x.instance_variables
=> [:@foo, :@bar]

このメソッドはインスタンス変数を配列で返してくれます。

メソッド一覧の取得

メソッド名 取得できるメソッド
Object#methods オブジェクトの持つプライベートメソッド以外
Object#public_methods オブジェクトの持つパブリックメソッド
Object#private_methods オブジェクトの持つプライベートメソッド
Object#protected_methods オブジェクトの持つプロテクテッドメソッド
Object#singleton_methods オブジェクトの持つ特異メソッド
class Foo
  def foo; end

  private

  def bar; end

  protected

  def baz; end
end

class Bar < Foo
  def foo_b; end

  private

  def bar_f; end

  protected

  def baz_f; end
end

x = Foo.new
y = Bar.new
def x.foo; end

x.methods
=> [:foo,
 :baz,
 :to_yaml,
 :to_yaml_properties,
...

x.public_methods
=> [:foo,
 :to_yaml,
 :to_yaml_properties,
 :pry,

 x.private_methods
=> [:bar,
 :DelegateClass,
 :default_src_encoding,
 :sprintf,

x.protected_methods
=> [:baz]

y.methods
=> [:foo_f,
 :baz_f,
 :foo,
 :baz,

y.public_methods
=> [:foo_f,
 :foo,
 :to_yaml,
 :to_yaml_properties,

y.private_methods
=> [:bar_f,
 :bar,
 :DelegateClass,
 :default_src_encoding,

y.protected_methods
=> [:baz_f, :baz]

x.singleton_methods
=> [:foo]

ちなみに特異メソッドのみを呼び出すのは、methodsからでも呼び出せます。

その場合は、引数にfalseを渡せばいいです。

参考

instance method Object#methods (Ruby 2.3.0)

x.methods(false)
=> [:foo]

まとめ

メソッド呼び出し方法を知っておけば、デバック時に役立つ可能性が生まれます。

以上です。

rubyのthreadについて

スレッドは同じプロセス上でメモリを共有しつつ、処理を並列に実行することができます。

スレッドを用いることで、単独で時間がかかる処理を早くすることができる可能性があります。

スレッドの生成

スレッドを作成し、処理を実行するには、Thread.fork/Thread.new/Thread.startを使用します。

下記は複数ファイルの行数を出力する例です。

files = %w(ruby.rb test.rb)
threads = files.map do |file|
  Thread.fork do
    num = File.readlines(file).length
    "#{file}: #{num}"
  end
end
p threads.map(&:value)

変数の扱い

スレッドは同じプロセス上のスレッドとメモリを共有します。

for item in %w(foo bar baz)
  Thread.fork do
    sleep 1
    puts item
  end
end

(Thread.list - [Thread.current]).each &:join

=>baz
=>baz
=>baz

メモリが共有されているので、最後に当たったものを参照しています。

他のスレッドと共有したくない値は、ブロックの仮引数として受け取るようにしましょう。

for item in %w(foo bar baz)
  Thread.fork item do |value|
    sleep 1
    puts value
  end
end

(Thread.list - [Thread.current]).each &:join
=>foo
=>baz
=>bar

以上です。

eval族

evalについてです。

|メソッド名|動作| |Kernel.#eval|selfが呼び出された式を評価する| |Module#class_eval|レシーバのクラスをselfとして式を評価する| |Module#module_eval|レシーバのモジュールをselfとして式を評価する| |Basic#Object#instace_eval|レシーバのオブジェクトをselfとして式を評価する|

Kernel.#eval

foo = 'bar'
eval 'foo'
=> "bar"

evalとBindingオブジェクト

Bindingオブジェクトを使うことで、コンテキストに名前がないものでも評価されるようになります。

class Foo
  def initialize
    @foo = 'foo'
  end

  def bar
    local_val = 'bar'
    binding
  end
end

x = Foo.new
y = x.bar
eval "@foo", y
=> 'foo'
eval "local_val", y
=> 'bar'

ただし、initializeにbindingをさしてもうまくいきませんでした。

eval "@foo", y
TypeError: wrong argument type String (expected binding)

以上です。

参考

www.amazon.co.jp

includeとprependで読み込む順番の確認

includeとprependで読み込む順番を確認していきたい思います。

moduleの複数読み込みをした場合に、どのような順番で読み込まれるのかを確認していきます。

includeの順番に依存

何も考えないでincludeしていく場合は、読み込む順番に依存していることがわかります。

module Foo
  def foo
    puts 'foo'
  end
end

module Bar
  def foo
    puts 'foo bar'
  end
end

class Baz
  include Foo
  include Bar
end

Baz.ancestors
=> [Baz, Bar, Foo, Object, Kernel, BasicObject]
x = Baz.new
x.foo
=>foo bar

ancestorsで確認しても、[Baz, Bar, Foo, Object, Kernel, BasicObject]下から読み込みされていることがわかります。

prependで優先的に読む

prependで試してみます。

module Foo
  def foo
    puts 'foo'
  end
end

module Bar
  def foo
    puts 'foo bar'
  end
end

class Baz
  prepend Foo
  include Bar
end

Baz.ancestors
=> [Foo, Baz, Bar, Object, Kernel, BasicObject]
x = Baz.new
x.foo
=>foo

これを見るとBazより先に読み込まれていることがわかります。

ということは、同じメソッドを用意したらどうなるか確認してみます。

module Foo
  def foo
    puts 'foo'
  end
end

module Bar
  def foo
    puts 'foo bar'
  end
end

class Baz
  prepend Foo
  include Bar

  def foo
    puts 'foo baz'
  end
end

Baz.ancestors
=> [Foo, Baz, Bar, Object, Kernel, BasicObject]
x = Baz.new
x.foo
=>foo

Fooの方が先にあるので、当然moduleのFooのmethodが読まれます。

まとめ

既存のメソッドをオーバーライドする・優先的に読む必要があるのなら、prependを使うのがいいでしょう。

lambdaについて

lambaはProcオブジェクトの別の書き方です。

Procオブジェクトやブロックに関してはこちらの記事をみてください。

ブロックについて - mikami's engineer diary

x = lambda { 'foo' }
x.call
=>foo

lambdaは->でも書けるので上はこのように書き直せます。

x = -> { 'foo' }
x.call
=> "foo"

railsだとこっちの方が見かけると思います。

ブロックを渡していたんですね。

引数も渡せます。

x = -> (x) { x + 100 }
x.call(5)
=> "105"

Procオブジェクトとの挙動の違いはあります。

詳細はこちらをご覧ください。

class Proc (Ruby 2.3.0)