ベルリンのITスタートアップで働くジャバ・ザ・ハットリの日記

日本→シンガポール→ベルリンへと流れ着いたソフトウェアエンジニアのブログ

RubyのblockやProcを分かったつもりになっていて見事にハマった

反省した。RubyのblockやProcを分かったつもりになっていて、しょうもないところでハマった。自戒を込めてブログに残しておくことにした。

$ ruby -v
ruby 2.3.3p222 (2016-11-21 revision 56859) [x86_64-darwin15]

例1

def method_1
  if block_given?
    puts 'Yes'
    yield
  else
    puts 'No'
  end
end

method_1 { puts 'I am a block' }

つまりmethod_1にI am a blockの出力というブロックを渡して、ブロックが有ればYesと共にそれを出せと。
method_1にはブロック引数が無いが、この例のようにそれが問題になることもなく、yieldすればちゃんと実行される。

実行結果

$ ruby block_sample_1.rb
Yes!
I am a block

例2

def method_1
  if block_given?
    puts 'Yes :method_1'
  else
    puts 'No  :method_1'
  end
  method_2
end

def method_2
  if block_given?
    puts 'Yes :method_2'
    yield
  else
    puts 'No  :method_2'
  end
end

method_1 { puts 'I am a block' }

blockが渡されたmethod_1からmethod_2を呼び出す例。

実行結果

$ ruby block_sample_2.rb
Yes :method_1
No  :method_2

渡されたblockはmethod_1まで。method_2には到達していない。

例3

つまり&引数名にしてブロックをProcオブジェクト化して渡す必要がある。その対策後のコード例がこれ。

def method_1 &block
  if block_given?
    puts 'Yes :method_1'
  else
    puts 'No  :method_1'
  end
  method_2 &block
end

def method_2
  if block_given?
    puts 'Yes :method_2'
    yield
  else
    puts 'No  :method_2'
  end
end

method_1 { puts 'I am a block' }

実行結果
これでしっかりblockがProc化されてmethod_2にまで到達していることが分かる。

$ ruby block_sample_3.rb
Yes :method_1
Yes :method_2
I am a block

例4

def method_1 &block
  if block_given?
    puts 'Yes :method_1'
  else
    puts 'No  :method_1'
  end
  method_2 plus_one 1, &block
end

def plus_one number
  number + 1
end

def method_2 number, &block
  if block_given?
    puts 'Yes :method_2'
    puts number
    yield
  else
    puts 'No  :method_2'
  end
end

method_1 { puts 'I am a block' }

実行結果

$ ruby block_sample_4.rb
Yes :method_1
No  :method_2

method_2にまでブロックが到達していない。これでハマった。とくに例3と変わったことをしているようにも思えない。ただdef plus_one numberを加えただけで、そこにblockはまったく関係無さそう。なぜmethod_2にまでブロックが到達しないのか、しばらく分からなかった。

見つけた答えがこれ。

例5

def method_1 &block
  if block_given?
    puts 'Yes :method_1'
  else
    puts 'No  :method_1'
  end
  method_2 plus_one(1), &block
end

def plus_one number
  number + 1
end

def method_2 number, &block
  if block_given?
    puts 'Yes :method_2'
    puts number
    yield
  else
    puts 'No  :method_2'
  end
end

method_1 { puts 'I am a block' }

実行結果

$ ruby block_sample_5.rb
Yes :method_1
Yes :method_2
2
I am a block

例4との違いはここの()だけ。

  method_2 plus_one(1), &block

つかれた。。。

tango-ruby.hatenablog.com

tango-ruby.hatenablog.com

tango-ruby.hatenablog.com

tango-ruby.hatenablog.com

Railsの生みの親、DHHのロックな発言に惚れた

Railsの生みの親であるスゴ腕エンジニアでしかもプロのカーレーサーで、イケメンで、嫁さんは超美人で、もう金も才能も成功も全部持っていってしまっているDHHのイキな発言が心に響いた。

元ネタはこちら。
https://hashnode.com/ama/with-david-heinemeier-hansson-cizf90u8w000ro353hnz8v4f5

私なりの意訳

= 質問者の投稿 =
なんでエンタープライズ向けの製品はASP、.NET、Javaのプラットフォームに偏ってるんですかね?なんでエンタープライズ向けでRails、Node.js、Pythonなんかの素晴らしいフレームワークを使った製品って出て来ないんでしょうか?近々これらの状況に変化はあるのでしょうか?


= DHHの回答 =
エンタープライズ向けの製品って技術的な優位性だけで決まるって訳じゃないんだよ。長期的なサポートとか、なんかあった時の言い訳や責任逃れのしやすさとか。売り込むにもそれを本当に必要としている人に売り込むんじゃなくて、そこから遠い所にいるマネージャー様に売り込む必要があったり。
そういう状況だと「ここはIBM製品を買っておけば、誰もクビにならずに済む」ってなるんだ。そんな基準では技術的優位性なんてちっとも重要でなかったりする。重要なのは箱にオラクルやマイクロソフトのマークが付いているってことなんだ。
まー彼らは自由を愛する俺達とは違うってことだね。

Rubyの顔がまつもとゆきひろ氏になっているようにRailsの顔はDHHになっている。そうなると単にコードを書くことだけが仕事ではなく、その思想や態度がRubyやRailsのコミュニティに影響する。

DHHのこうした発言に「あ、おもしろ」と思う人がRailsプロジェクトなんかに賛同してRailsが活気付いていく。

「おたくの製品はエンタープライズ向けにはちっともウケてませんね。ダメねー」なんて突っ込まれても「あんなダサい連中は放っとくに限るわ」とロックンローラーみたいな受け答えをする人がRailsのリーダーであることってまーまー幸運なことだな、と。
並の人間なら「いえいえ、エンタープライズのお客様にもご満足いただけるようにRailsのサポート体制も強化中でして、、」とやってしまうだろうが、我らがRailsのDHHはイケメンゆえにそんなダサい発言はしないのだ。惚れました。その調子でいってください。


tango-ruby.hatenablog.com
tango-ruby.hatenablog.com
tango-ruby.hatenablog.com
tango-ruby.hatenablog.com

Rubyのクラスで定義されたattr_accessor一覧をそのクラスのインスタンス変数から取り出す方法

一応題名の通りで「Rubyのクラスで定義されたattr_accessor一覧をそのクラスのインスタンス変数から取り出す方法」なのだが、何を言ってるのか書いた本人でも「?」となりがちなのでまずはやりたかったことから説明する。

例えばBookクラスがあって、そこに attr_accessorが定義されている。

class Book
  attr_accessor :id, :title, :author

end

Bookクラスを作ってインスタンス変数を作る。

@book = Book.new

attr_accessorに定義されているように以下のように変数にアクセスできる。

> @book.title
"STAR WARS"

やりたかったのはこの@bookを使って中で定義されているattr_accessorの一覧を取り出すこと。
こんな感じ。

> @book.something_something
[:id, :title, :author]

結果的にできた方法はこれ

class BaseModel
  
  def self.attr_accessor(*vars)
    @@attributes ||= []
    @@attributes.concat vars
    super
  end

  def attributes
    @@attributes
  end
end

class Book < BaseModel
  attr_accessor :id, :title, :author
end

こうしておくと以下のように一覧が出る。やってみれば単純だけど、最初ちょっと悩んだ。

> @book.attributes
[:id, :title, :author]

<解説>

クラスメソッドのattr_accessorの処理を上書きし、クラス変数として@@attributesを定義して、そこに一覧を格納しておくようにする。

  def self.attr_accessor(*vars)
    @@attributes ||= []
    @@attributes.concat vars
    super
  end

インスタンス変数@bookからインスタンスメソッドのattributesが呼ばれたら、クラス変数を返す。

  def attributes
    @@attributes
  end

もっといい方法をご存知でしたらぜひコメントください。

なんでこんなことをやっているのかというと個人プロジェクトで作っているRailにGoogle Cloud Datastoreをつなげようとしたのがことの始まり。Google Cloud PlatformはJavaやGo、Pythonなんかには初期から対応していたのにRubyはかなり後回し。最近になってやっと対応してくれたと思ったら、その公式サイトから出ているコードがなんとも微妙。

例えばGoogle Cloud DatastoreをつなげたRailsのModelのコード例がこんな感じ。

class Book

  attr_accessor :id, :title, :author, :published_on, :description

  # [START to_entity]
  # ...
  def to_entity
    entity                 = Google::Cloud::Datastore::Entity.new
    entity.key             = Google::Cloud::Datastore::Key.new "Book", id
    entity["title"]        = title
    entity["author"]       = author       if author
    entity["published_on"] = published_on if published_on
    entity["description"]  = description  if description
    entity
  end
  # [END to_entity]
end

Bookクラスが1つだけならいいけど、後から作るモデルクラスにも全部いちいちentity["なんやら"]って定義する訳にいかない。
初心者向けチュートリアルでわかりやすいといえばわかりやすいけど。

tango-ruby.hatenablog.com

tango-ruby.hatenablog.com

tango-ruby.hatenablog.com