旧gaaamiiのブログ

間違ったことを書いている時があります。コメントやTwitter、ブコメなどでご指摘ください

「平成Ruby会議 01」を見に行った

同僚の id:atomiyama さんがRustでgemを書く話をするそうで面白そうだったので行ってきました。

heiseirb.github.io

各セッションの感想など

RubyJVMを実装してみる

speakerdeck.com

Javaコンパイルして出てくるバイトコード読んで云々という話だったけど、ぼけーっとバイトコード眺めてる間に話が進んですぐについていけなくなってしまった。あとスライドのコードとかあまり見れなくて視力の衰えを感じた...。

rustで拡張モジュールを作成してgemにする

speakerdeck.com

  • 順を追って説明してくれてありがたかった。
  • Rustで速くなるんだったら一部だけRustで書いてgemにするのはよさそう。どんなモジュールだとRustにするメリットが大きいんだろうと興味が湧いた。
  • 以前はffiを通じて他言語の実装を使うみたいなのはなんかすごい特殊なイメージがあったけど、最近(というか去年の今頃)ElmでPortを通じてJSのライブラリ呼び出すみたいなことをしてからは、必要があってインタフェースがちゃんとしてるんだったらやっていいんじゃないのという気持ちになっていたのでなおさら興味深かった。

やわらか増税 〜はじめての増税対応〜

真のREST

www.slideshare.net

自分は正直RESTといえば「URLでリソース表してHTTPのメソッドでCRUDするやつ、Railsがやってるやつ」みたいな雑認識があったので、その雑認識がとりあえずは潰されてよかった。特にREST APIハイパーテキスト駆動ではないといけないというのが全然自分の頭の中になく、なんなら驚いてしまった。ちゃんと勉強しよう...。RESTful Web APIsという本が良いらしいので読んでおきたい。

全体の感想

どのセッションも自分が知らなかったことが多く、勉強になりました。どんなプラットフォーム向けに何を書こうにも、コンピュータそのものとか言語処理系のしくみとかWebの歴史とか、そういう基礎的なこと(だけど実務で学ぶ緊急性があまりないこと)をしっかり理解しておかないといけないな...と同世代のできる人たちを見ていて感じました。

RubyWorld Conference 2018 参加記(2日目)

shgam.hatenadiary.jp

1日目に続き、2日目も参加しました。

Chad Fowler氏の基調講演 Don't Stop Moving

エンジニアのキャリアなどについての話でした。そういえばまだちゃんと情熱プログラマー(Chadさんの本)を読んだことなかったので読みたくなりました。

mrubyについて

2日目はmrubyの話が多かったです。おもむろにrbenv install mruby-1.4.1してから話を聞いていましたが正直あまりついていけませんでした...。後でmrubyについて調べつつ発表の動画を見返したい。

2日間通しての感想

運営も会場も内容もすべてしっかりしたカンファレンスでした。

とても貴重な経験ができました。様々なRubyistと話すことができましたし、島根県松江市Rubyの関係とか雰囲気とか、そういったものも感じることができました。プログラミング言語で町おこしというのは先例もなかったと思うのですが、市がRubyの価値を理解してそれを利用してるというのがすごい。

また、自社のスタディプラスブースで好きなRuby本のアンケートを取っていたんですが、これがとても面白かったです。 Rubyのしくみ -Ruby Under a Microscope-が思ったより得票数多くて、さすがRubyWorld Conferenceだ...!という感じでした(青い付箋がRubyのしくみ)。

RubyのしくみはRuby構文解析とかの説明をした本なので、Rubyそのものに興味がある人達が集まっているこのカンファレンスでは人気が出るのもそれはそうか、とも思いましたが、それにしてもすごい。

自分はRubyのしくみをけっこう序盤で挫折してから読んでいないのですが、なんとかまた読み直そうと思いました。

2日間、自分は講演の聴講と自社ブースの案内だけで発表などはしていませんが、それでもとても貴重な経験ができました。 関係者の方々、スポンサーになってくれた自社に感謝です。ありがとうございました。

RubyWorld Conference 2018 参加記(1日目)

会社がスポンサーになったので、こちらに出席させていただいてます。

2018.rubyworld-conf.org

1日目のプログラムはまつもとゆきひろ氏の基調講演から始まり、Ruby Prizeの発表で終わりました。

Matz基調講演(The power of the community)

Rubyの父、Matzことまつもとゆきひろ氏の基調講演。

この基調講演では、Rubyがいつどのように生まれて、今日のように人気のある言語として広く使われるようになったのかを聴くことができました。

放置していたUTF-8対応、ドキュメント整備、ミートアップ開催、本の出版などはどれも、まつもとさん個人の力ではなくコミュニティの力で行われたことであり、特に人にプログラミングを教えるのはまつもとさんが苦手な部分だった(プログラミングできない人の気持ちがわからない)という話がありました。

また、こういったコミュニティの力を借りるために、コミュニティに対して面白いものを提供し続ける必要があるというようなことをおっしゃっていました。OSSコミュニティは止まったら死ぬ鮫のようなものだ、とも。

私はユーザーとして普段からOSSを使って仕事をしている身ですが、未だにOSSコミュニティは不思議に感じる部分が大きいです。仕事とも個人の趣味とも言えない、独特の雰囲気があります。

この基調講演でまつもとさんも、コミュニティに関わる人のモチベーションは知的好奇心、承認欲求、そこで発生するコミュニケーション、責任感、お金など人それぞれだとおっしゃっていました。

Ruby Prizeを受賞したk0kubunさんの発表

Ruby Prizeを受賞したk0kubunさんの発表も凄まじく良かったです。途中までブースにいて、内容を途中から聴いたのでYouTube動画あとで見返したい。

感想とか

今、Rubyはメジャーなプログラミング言語で、仕事はたくさんあり、学ぶ価値があるというのはすぐわかります。私も仕事で扱っているシステムはRubyで書かれていて、その開発や保守に携わって、日々生活するためのお金を頂いてます。Rubyで書かれたシステムが動いて、実際に価値を生み出していることは感じています。

しかし、そういったことが可能になる前からRubyそのものに携わってきた、作者のまつもとさんやコミュニティの中心で関わっている方々には、それとはまた違う気持ちがありそうです。

今日の講演などを聴いた上での私の想像ですが、知的好奇心を満たす手段として、純粋に楽しくてRubyに関わっていたのかなと、そんな風に思います。

何か技術的なことを学んだり、ものを作ったり、プログラムを書くことが、今の私にとっては仕事をしてお金を稼ぐためのものであり、それ自体が目的にはなっていません。しかし、もしそれ自体が目的になったり、少なくともそう感じられることが増えれば、とても幸せなことだと思います。

すごいエンジニアになってたくさんお金を稼ぐとか、偉くなりたいとか有名になりたいとか、そういったことではなく(もちろんそれも大事なんだけど)、やっていること作っているものそのものに没頭して、楽しさや喜びを見いだせるようなエンジニアになろう。基調講演やほかの素晴らしい発表を聴きながら、そんなことを考えることができました。

RubyのブロックとProcとlambdaとMethodについて

RubyのブロックとProcとlambdaとMethodについて書きたいけど自信がないです。容赦のないマサカリやツッコミをお願い致します。

ブロック

ブロックははじめてRubyを触ったときから慣れ親しんでいるものです。

%w(:Yoshida, :Tanaka, :Suzuki).each do |name|
  puts "Hello, #{name}"
end

# または

%w(:Yoshida, :Tanaka, :Suzuki).each { |name| puts "Hello, #{name}" }

do と end というキーワード、あるいは波括弧{}で行いたい手続きを囲みます。

yield

yieldは渡されたブロックを実行します。 例として、「誰かに何かする」というメソッドを定義してみます。

def do_for(name)
  yield(name)
end

do_for(:Tanaka) { |name| puts "Hello, #{name}" }
do_for(:Suzuki) { |name| puts name.upcase }

Proc

1回きりならdoとendで囲んどきゃいいけど、その手続きを変数に入れたりして保持したいこともあります。 Procクラスのコンストラクタに同じブロックを渡せば、その処理をいつでも呼び出せるようになります。

hello_proc = Proc.new { |name| puts "Hello, #{name}" }
hello_proc.call :Nishikori
%w(:Yoshida, :Tanaka, :Suzuki).each { |name| hello_proc.call name }

lambda

「ラムダ」と発音します。Procとほぼおなじですが、違いもあるようです。ただlambda式を実行して返ってくるものはProcクラスではあります。

hello_lambda = ->(name) { puts "Hello #{name}"}
hello_lambda.call :hoge
hello_lambda.class #=> Proc

Procとの違い

「Procとほぼおなじ」というからには、何か違いがあるわけですね。コメントを頂きましたのでそれをここでも共有させていただきます。

引数チェック

# Proc は引数チェックしない
hello = Proc.new { |name| "Hello, #{name}" }
hello.call :Tanaka, :Suzuki #=> "Hello, Tanaka"

# lambda は引数チェックする
hello_lambda =  ->(name) { "Hello, #{name}" }
hello_lambda.call :Tanaka, :Suzuki #=> ArgumentError

returnの処理

[TODO: サンプルコード]

Method

自信が無いのでドキュメントそのまま引用します。

Methodとは、

Object#method によりオブジェクト化され たメソッドオブジェクトのクラスです。

メソッドの実体(名前でなく)とレシーバの組を封入します。 Proc オブジェクトと違ってコンテキストを保持しません。

とのこと。


まとめ

利用の場面を経験しないとなんとも言えない感じがするので、Ruby熟練者が書いたコードをたくさん読んでいきたいです。

なにかいろいろ物足りないのでちょこちょこ書き直します。

参考リンク

参考書籍

Ruby技術者認定試験(Gold)落ちた

前からRuby触ってるし大丈夫だろうと思ってて、前日にちょろっとやって受かる予定だった。結果は56点だった。馬鹿だった。

ポイント

リンク

試験前に読んだものや、試験終わった後にもやもやを解消するのに役立ったリンクなど。

kakakakakku.hatenablog.com

qiita.com

qiita.com

本はこの三冊がいいと思う。3冊ちゃんと読み倒せばGoldなんて簡単すぎるくらいかもしれない(自分はというと、Effective Rubyは読みかけ、メタプロRubyは読んでない。あと頭が悪い)。

公式問題集はいろいろひどかったから載せない。

もう1回くらい今月末に受けて受かりたい。1万5000円もかかるのが苦しい。とはいえ勉強やはり楽しい。しかし1万5000円...(以下無限ループ)

頭字語コマンドを展開するwdimというコマンドラインツールをgemとして公開しました

wdimという名前でgemを公開しました。ダブリューディムとかダブディムって読んでください。意味は What does it mean の略です。

シェルでwdim ls と打つとlist segmentsが返ってきます。つまり、頭字語やら略語やらジャーゴンになっていてひと目では理解しづらいUNIX/Linuxコマンドを展開してくれるコマンドラインツールになります。

使い方

インストール

gemコマンドが使える環境で以下を打ち込んでください。

$ gem install wdim
(権限がないとかいわれる場合は)
$ sudo gem install wdim

つかう

$ wdim cd
change directory

経緯

こちらに書きました。

shgam.hatenadiary.jp

今後

正直、まともに使えるようになる前にgemとしてリリースしてしまいました。ローカルでインストール作業(git cloneしてgem build してgem install)してくれる人は少ないと思うので、ちょろっと試す際に試しやすくするためだけに公開しています。

今後はディクショナリを充実させたり、シェル作業中にパッと目につくような装飾をつけたり、また、起動の速度が気になってきたりしたらrubyではなくCで書いてHomebrewで配付したりとかそういうことを考えています。

ディクショナリ(現状はlsのようなコマンドに対してlist segmentsのような意味が記述されているymlファイル)がなにより大事なのです。この頭字語コマンドの意味知ってるよ~という方からのプルリクお待ちしてます。頭字語の意味は検索すればたいてい出てくると思いますが、議論があるものもあり、微妙なものはIssuesで話しあえたりしたら楽しいなと思ってます。

UNIX/Linux初学者にやさしい世界をつくりましょう。

github.com

Ruby製のウェブサーバライブラリWebrickのソースコードを読む

そういや一年前、「ウェブサーバーのこと知りたい -> Rack読もう。」となって挫折した。少しずつ暖かくなって意識が高まってきたし、今やっている仕事がずいぶんと(すくなくともRailsアプリ書くのと比較すると)低レイヤーなところなので、良い感じの相乗効果を狙って今のうちにサーバーについて理解を深めておきたい。そういうわけで、今春はWebrick読もう。

読みかた

今度は挫折したくない。以下の手法に従って読む。

また、Cのソースにも触れることになると思うので以下も参考にする。

使うもの

動的解析

基本的に解析は動的解析から始めるのがよい。 静的解析とは、多かれ少なかれ、プログラムの動作を予想することである。 対して動的解析で見るのは事実である。 まず事実を見ておいたほうが方向付けがしやすいし、間違いも減る。 最適化する前にプロファイルを取れ、というのと似ているだろうか。 事件解決はまず現場から、というのでもよい。

とのこと。まずは動かす。

サーバー起動

 ruby -rwebrick -e 'WEBrick::HTTPServer.new(:DocumentRoot => "./", :Port => 8000).start'

なお、これは以下と同等(r オプション使うの初めてだ)。

ruby -e 'require "webrick";WEBrick::HTTPServer.new(:DocumentRoot => "./", :Port => 8000).start '
[2016-03-26 14:03:45] INFO  WEBrick 1.3.1
[2016-03-26 14:03:45] INFO  ruby 2.2.3 (2015-08-18) [x86_64-darwin14]
[2016-03-26 14:03:45] INFO  WEBrick::HTTPServer#start: pid=4563 port=8000

参考:ワンライナーWebサーバを集めてみた - Qiita

リクエストを投げる

クライアントとして、別のシェルを開いて以下を実行する。

https://i.gyazo.com/10287afed859f10d8aa06f1fa223d8c7.gif

curl localhost:8000

(クライアント側)

$ curl localhost:8000
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<HTML>
  <HEAD>
    <TITLE>Index of /</TITLE>
    <style type="text/css">
...(以下略)

(サーバー側)

localhost - - [26/Mar/2016:14:04:04 JST] "GET / HTTP/1.1" 200 4076
- -

クライアントからHTTPリクエストを投げたらちゃんとハイパーテキストを返してくれたので、ウェブサーバーとして機能したと言える。サーバー側のログには時刻やHTTPメソッド・プロトコルのバージョン・ステータスなどが出されている。

静的解析

前準備

さすがにGithubのウェブサイト上でファイルを一つ一つブラウジングして理解できる気がしないので、「ソースコード完全解説ガイド」に従っていくつかの前準備を行う。

ドキュメントを読む

今回は http://docs.ruby-lang.org の説明を参照する。

汎用HTTPサーバーフレームワークです。HTTPサーバが簡単に作れます。

WEBrickサーブレットによって機能します。サーブレットとは サーバの機能をオブジェクト化したものです。 ファイルを読み込んで返す・forkしてスクリプトを実行する・テンプレートを適用する など、「サーバが行なっている様々なこと」を抽象化しオブジェクトにしたものが サーブレットです。サーブレットWEBrick::HTTPServlet::AbstractServlet の サブクラスのインスタンスとして実装されます。

WEBrick はセッション管理の機能を提供しません。

ソースコード入手

WebrickRuby の標準ライブラリなので、Githubruby リポジトリから入手する。

git clone git@github.com:ruby/ruby.git

tags ファイルつくる

webrickはlib/下にある。そこでctagsコマンドを実行する。

cd ruby/lib/webrick
ctags -R

参考:ctagsと連携するように環境を構築する - Qiita

ファイル構成を見る

MacBook-Pro:webrick gaaamii$ tree .
.
├── accesslog.rb
├── cgi.rb
├── compat.rb
├── config.rb
├── cookie.rb
├── htmlutils.rb
├── httpauth
│   ├── authenticator.rb
│   ├── basicauth.rb
│   ├── digestauth.rb
│   ├── htdigest.rb
│   ├── htgroup.rb
│   ├── htpasswd.rb
│   └── userdb.rb
├── httpauth.rb
├── httpproxy.rb
├── httprequest.rb
├── httpresponse.rb
├── https.rb
├── httpserver.rb
├── httpservlet
│   ├── abstract.rb
│   ├── cgi_runner.rb
│   ├── cgihandler.rb
│   ├── erbhandler.rb
│   ├── filehandler.rb
│   └── prochandler.rb
├── httpservlet.rb
├── httpstatus.rb
├── httputils.rb
├── httpversion.rb
├── log.rb
├── server.rb
├── ssl.rb
├── tags
├── utils.rb
└── version.rb

読むの開始

先ほどのワンライナーをふたたび見る。

WEBrick::HTTPServer.new(:DocumentRoot => "./", :Port => 8000).start

まずは以下からざっくりと追う。

  • HTTPServer.new
  • HTTPServer#start

HTTPServer.new

インスタンス初期化時に何をしているのか。mountというメソッドでパスにサーブレットを割り当てているようだ。あとは @virtual_hosts という配列を初期化している。

HTTPServer#start

HTTPServerのインスタンスメソッドstart見ようとしても、ない。継承元のGenericServerのものだとわかる。

GenericServer#start

さて、このstartメソッドは何をしてるのか。コメントがあるので、それを見る。

137     ##
138     # Starts the server and runs the +block+ for each connection.  This method
139     # does not return until the server is stopped from a signal handler or
140     # another thread using #stop or #shutdown.
141     #
142     # If the block raises a subclass of StandardError the exception is logged
143     # and ignored.  If an IOError or Errno::EBADF exception is raised the
144     # exception is ignored.  If an Exception subclass is raised the exception
145     # is logged and re-raised which stops the server.
146     #
147     # To completely shut down a server call #shutdown from ensure:
148     #
149     #   server = WEBrick::GenericServer.new
150     #   # or WEBrick::HTTPServer.new
151     #
152     #   begin              
153     #     server.start     
154     #   ensure             
155     #     server.shutdown  
156     #   end   

意訳する。

「サーバーを起動してコネクション毎にブロックを実行する。このメソッドはシグナルハンドラあるいは他のスレッドの#stop#shutdownによってサーバーが停止するまでreturnしない。(以降、例外捕捉の説明)」

スレッド <--(コネクション)--> クライアント
スレッド <--(コネクション)--> クライアント
スレッド <--(コネクション)--> クライアント

という感じで、スレッドとコネクションとクライアントが1対1対1の関係になるのがこのstartメソッドを見るとわかる。

この処理はbegin節で囲まれてた中で無限ループしていて、先のコメントにあったようにシグナルハンドラなんかによって停止されたときにこの節を抜ける。するとensure節が実行されて、サーバーが停止する。

というわけで、実際のコードの中身について、大事そうなものをかいつまんで見てみていく。

  1. server_type.start
    1. setup_shutdown_pipe
    2. svrs = IO.select([sp, *@listeners], nil, nil, 2.0)
    3. sock = accept_client(svr)
    4. th = start_thread(sock, &block)
    5. thgroup.add(th)
server_type.start

server_type にはデフォルトではWEBrick::SimpleServerとなるので、このstartメソッドをみる。

(server.rb)
 27   class SimpleServer       
 28 
 29     ##
 30     # A SimpleServer only yields when you start it
 31 
 32     def SimpleServer.start 
 33       yield                
 34     end
 35   end            

yieldの一行しかない。渡したブロックそのまま実行するということがわかる。

setup_shutdown_pipe

よくわからない。パイプってなんだ(あとでみる)

svrs = IO.select([sp, *@listeners], nil, nil, 2.0)

IO.selectが何をしているのか。Rubyの組み込みライブラリなので、まずドキュメントにあたった方がよさそうだ。

http://docs.ruby-lang.org/ja/2.2.0/class/IO.html

IOクラスとは、

基本的な入出力機能のためのクラスです。

とのこと。では、このselectというメソッドはなにか。

http://docs.ruby-lang.org/ja/2.2.0/class/IO.html#S_SELECT

与えられた入力/出力/例外待ちの IO オブジェクトの中から準備ができたものを それぞれ配列にして、配列の配列として返します。 タイムアウトした時には nil を返します。

ソースも見てみる。IOクラスはwebrickの中には見つからないので、今回は pry-doc というgemを使ってpry(対話環境)から場所を見つけることにする。

[1] pry(main)> show-source IO.select
From: io.c (C Method):
Owner: #<Class:IO>
Visibility: public
Number of lines: 22

static VALUE
rb_f_select(int argc, VALUE *argv, VALUE obj)

Cのソースで、rb_f_selectという関数だということがわかった。

start_thread
ThreadGroup#add

ThreadGroup の定義元に飛びたいけど、これは組み込みライブラリ、つまりCのソースになるので、webrickの中にはいない。ということで、今回は pry-doc というgemを使ってpry(対話環境)上から場所を見つけることにする。

pry> show-source ThreadGroup#add

とすると、このメソッドの定義元がわかる。どうやらthread.c にある thgroup_addという関数らしい。

IO.select([sp, *@listeners], nil, nil, 2.0)

書き途中です。

大江戸Ruby会議行ってきた

寝坊して体調も悪かったので午後から行ってきた。 東京駅の八重洲口からまっすぐ歩いてったら、途中で「Perlが〜Pythonが〜」という話をする人たちの集団がいたので付いていったら着きました。

いろいろ

  • ライブコーディングのときとかに見えないので前に座った方がお得
  • ぼっちでも意外とちゃんと楽しめる
  • 知らないことが多くて笑いどころをいくつも逃した
  • telnetが台湾ですごいらしい
  • Rubyを広めること」を使命とする会社があることに驚いた
  • 登壇者全員話すの超うまい
  • 江渡浩一郎さんの特別講演がとにかくすっごい良かった。

まとめ

モチベ高まるのでたまにはこういう大きい勉強会行くのもいいなと思いました。

【Ruby】接客と委譲

(さらに追記)不十分なところを指摘していただきました!ありがとうございます!

nomnel.net

(追記)オープンクラスの理解が間違っていたためタイトルを変更しました。今回あげている例はNoMethodErrorを起こさずに他のオブジェクトに処理を委譲する流れを説明したものです。

パーフェクトRubyの7章で、存在しないメソッドを呼び出したときにNoMethodErrorを発生させる代わりに他のオブジェクトに処理を任せる、という内容の節がありました。これを接客業に当てはめて説明してみたくなったので、ブログ記事にしています。

接客でやってはいけないこと

派遣バイトとしてコンサート会場などの案内スタッフになると、業務前にほぼ必ず以下のような説明を受けます。

みなさんは本日、◯◯(イベント会社)のスタッフです。そのためお客様に対して「わからない」と言ってはいけません。必ず、「お待ちください」と伝えて、近くのチーフに尋ねてください

接客において、お客様に対してわからないと言ってはいけないのは基本です。しかしわからないものはわかりません。会場に慣れていない僕らのような日雇いアルバイトは、現場をよく知るチーフに確認をすることで解決します。

日雇いバイトは業務をチーフへ委譲することでエラーを回避することができるのです。

委譲をコードで書く

まず、チーフはお客さんの質問に答える能力があります。それはもう完璧な回答をしてくれます。

(staff.rbというファイルにコードを書いていきます)

class Chief
  def initialize
    @info = { "トイレ" => "2F東3扉前", "喫煙所" => "1F西門外"}
  end
  def answer(target)
    puts "#{target}#{@info[target]}にございます。"
  end
end

日雇いバイトは、今日来たばっかなので会場のことを何も知りません。ただ、自分が所属するチーフの情報は持っています。ああ、あの口が悪くてタバコくせえ人か、ということがわかっています。

class PartTimer
  def initialize
    @chief = Chief.new
  end
end

この状態でバイトがお客さんに質問され、答えようとしても、あたふたしてエラーとなってしまいます。

undefined method 'answer' for #<PartTimer:0x007fe22b088d00 @chief=#<Chief:0x007fe22b088cd8>> (NoMethodError)

これでは困ります。業務説明を思い出してみましょう。

そのためお客様に対して「わからない」と言ってはいけません。必ず、「お待ちください」と伝えて、近くのチーフに尋ねてください

なるほど、わからなければチーフに尋ねればいいのか。

class PartTimer
  def initialize
    @chief = Chief.new
  end
  def method_missing(name, *args)
    @chief.__send__ name, *args
  end
end

これで、バイトくんはお客さんに「わからない」などと答えずに、チーフへ業務を投げ渡すことができるようになります。追加したコードが何をしているのかを以下で詳しく見ていきます。

BasicObject#method_missingを上書き

def method_missing(name, *args)

method_missingはBasicObjectに定義されている、メソッドが無かった時の振る舞いを決めるものです。本来なら先ほどのようにNoMethodErrorが発生します。ここではエラーを発生させずにチーフに尋ねて欲しいので、メソッドがない場合の振る舞いを上書きしています。こうやって定義済みのクラスを上書きできることを、Rubyではオープンクラスと呼びます。 (追記)ここでは定義済みのクラスBasicObjectではなくそれを継承したPartTimerクラスのメソッドを上書きしているだけなので、オープンクラスの説明として不適切でした。

Object#__send__で業務を委譲

@chief.__send__ name, *args

上書きしたmethod_missingの中で、バイトくん(PartTimerオブジェクト)がインスタンス変数に持っているチーフへと、回答という業務を委譲するようにしています。

(追記)nameはメソッドの名前、*argsはメソッドに渡す引数です。PartTimer.new.answer("トイレ")でエラーが発生したとすると、nameには:answerが、*argsには"トイレ"が入ります。*がついていると可変長引数なので、メソッドの引数が2つでも3つの場合でも対応できるようになっています。

ここで実際にバイトくん(ここではtanakaとします)にanswerしてもらいましょう。ちゃんと答えが返ってくるでしょうか。

tanaka = PartTimer.new
tanaka.answer("トイレ")
tanaka.answer("喫煙所")

トイレと喫煙所の場所を尋ね(引数に渡し)ます。

$ ruby staff.rb
トイレは2F東3扉前にございます。
喫煙所は1F西門外にございます。

tanakaくんはanswerメソッドを持たないのに、きっちりと答えが返ってきました。チーフへの委譲は上手くいったようです。


何を伝えたいのかわかりづらくなってしまいましたが、ポイントはmethod_missingを上書きしているというところです。

railsコマンドを追う

Ruby on Railsというフレームワークを使うとrails new Hogeとかでアプリケーションのひな形ができちゃって、rails serverでサーバーが立ち上げられたりするわけですが、これは一体どうなってるんだというのを追っていけたらなと思います。誰にでもわかるように書きたいです。今回こそはくじけずに書ききりたい。

railsとbin/railsの違い

railsはシステムにインストールされたrailsコマンドを呼ぶ(/Users/ユーザー名/.rbenv/shims/railsみたいな)。

bin/railsはそのプロジェクト下のbin/railsのコマンドを呼ぶ。

bin/rails

Railsプロジェクトを作ると、binというディレクトリの中にrailsというファイルがある。これをエディタで開いてみる。

$ vim bin/rails

中身はこんな感じ。

#!/usr/bin/env ruby
APP_PATH = File.expand_path('../../config/application',  __FILE__)
require_relative '../config/boot'
require 'rails/commands'

たった4行だ。

1行目

#!/usr/bin/env ruby

いきなり意味がわからない。

シバン

この記述、「シバン」(shebang)と呼ばれるもので、インタプリタを指定するために書かれるらしい。普通にRubyスクリプト実行するときはruby rails.rbみたいに実行するんだけど、今回はrailsというファイルをrubyで解釈してもらいたいわけなので、ファイル内で「Ruby使うよ」っていうのを言ってあげる必要がある。そのため、このshebangが必要になっている。

要は、#!という2文字でOSに対して「これshebang行だからね」と伝え、/usr/bin/env rubyrubyインタプリタを使うと伝えてる。

2行目

APP_PATH = File.expand_path('../../config/application',  __FILE__)

APP_PATHに入るのがなにか分からない場合はこの行の後にp APP_PATHというコードを挿入してbin/railsを実行すれば見れる。

"/Users/ユーザー名/プロジェクト名/config/application"

このAPP_PATH、どこで使うことになるんだろう...?

3行目

require_relative '../config/boot'

これは普通に、config/boot.rbをrequireしてる。require_relativeを使うと、相対パスを指定してファイルを読み込むことができる。

boot.rb

では、このboot.rbの中身を見てみる。

1行目
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)

ENVというのはRuby環境変数を表すオブジェクト。ハッシュと違う点はキーにも値にも文字列しか指定できないというところ。

何やらプロジェクトのGemfileのパスをENV['BUNDLE_GEMFILE']という環境変数に入れているようだ。||=という演算子は「無かったら(厳密には「偽か未定義なら」)入れてね」という意味。

2行目
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])

ENV['BUNDLE_GEMFILE']にはGemfileのパスが入っている。「そこにGemfileあったらbundler/setupをrequireしてね」というような意味。

requireはどこを探しに行くのか

この記事がわかりやすい。

requireは引数のファイル名のRubyファイルを読み込んで実行するメソッドです。引数が絶対パスだったときはそのファイルを、そうでない場合はロードパスを優先順位上位から辿って最初に見つかったファイルをロードする。

じゃあ上記のrequire 'bundler/setup'はどうなってるのか。

rails consoleirbを立ち上げて、以下を実行する。

$LOAD_PATH.each {|path| p path };nil

ぐあーっとパスが表示されるので、bundlerがありそうなところを探す。ちなみに表示する際、返り値として$LOAD_PATHが返ってくるととても見づらいのでここではセミコロンで区切ってnilを返り値にしてる。ぐあーっと表示されるのはほとんどgemのファイルなので、そういうの(kaminariとかjquery-railsとか)は無視してざっと見ると、bundlerのディレクトリが見つかる。

"/Users/ユーザー名/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/bundler-1.6.3/lib"

ここに移動する。

cd /Users/ユーザー名/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/bundler-1.6.3/lib

で、lsで見ると、bundlerというディレクトリとbundler.rbというファイルが見つかる。 ls bundlerでbundlerディレクトリの中身を表示。すると、setup.rbというファイルが見つかる。

setup.rb

setup.rbを開くと、430行くらいのコードが書かれている。全部見るのきついので「ああセットアップするんだろーな」という雰囲気だけ感じて、ファイルを閉じる。参照元のboot.rbが、GemfileがあればBundlerをセットアップするというのはわかった。setup.rbももう少し読んだ方がいいんじゃないかと思いつつ逃げるようにbin/railsに戻る。

4行目

4行目はなんだかとても楽しそう。commandsっていう名前からして明らかにこのエントリの本筋に迫る感じだ。

require 'rails/commands'

Railsrailtiesっていうライブラリにぎっしりと機能が詰まっているというのが記憶のどこかにあったので、それを見てみる。

cd /Users/ユーザー名/プロジェクト名/vendor/bundle/ruby/2.1.0/gems/railties-4.1.4/lib

案の定、railsというディレクトリとrails.rbというファイルがある。今はcommands.rbを探しているので、それを見てみる。

vim rails/commands.rb

こんな感じ。

ARGV << '--help' if ARGV.empty?

aliases = { 
  "g"  => "generate",
  "d"  => "destroy",
  "c"  => "console",
  "s"  => "server",
  "db" => "dbconsole",
  "r"  => "runner"
}

command = ARGV.shift
command = aliases[command] || command

require 'rails/commands/commands_tasks'

Rails::CommandsTasks.new(ARGV).run_command!(command)

おお!実行してる!コマンド実行してる! rails serverとタイプした時の流れとしては、このcommandという変数に"server"が入り、CommandsTasksインスタンスのメソッドがこれを受け取って処理してくれるみたい。インスタンス初期化の時に渡してるARGVの中身は、コマンド後のオプション。


というわけで、railsコマンドをタイプした時に内部で何が起きてるのかをざっくりと見てみました。ちょっとざっくりしすぎている部分はまた書き足すかもしれません。

【AOJ】花札シャッフル問題(Hanafuda Shuffle)

ICPCというプログラミングコンテストではJavaC++かCを使わないといけないので)サークルの方ではJavaでやっているんですが、普段はRubyで書いていきたいと思います。

問題

カードの山を混ぜて切る方法はいろいろとある. 日本のカード遊びである「花札」における花札混ぜ切りは,札を切る方法の一つ である.

n枚のカードの山がある.図1に示すように,山の一番上からp枚 目の札から連続したc枚の札を抜き取り,それをそのまま山の上に置く. この操作(カット操作という)を繰り返し行う.

花札混ぜ切りをシミュレートし,最終的に山の一番上にくる札を答える プログラムを書きなさい.

書いたコード

class Deck

  def initialize(n)
    @cards = 1.upto(n).to_a
  end 

  def cut(r)
    r.times do
      p,c = gets.split.map(&:to_i)
      bundle = @cards[-p-c+1..-p]
      @cards.slice!(-p-c+1..-p)
      @cards.concat(bundle)
    end 
    @cards
  end 

end

loop do
  input = gets.chomp
  break if input == "0 0"
  n,r = input.split.map(&:to_i)
  deck = Deck.new(n)
  puts deck.cut(r).last
end

山札のクラスを作ってインスタンス変数に配列を入れて、それをごにょごにょする感じで書きました。こういうのを定期的にやってないとメソッド名とかいつまでたっても覚えないですね。

気持ち悪い部分見つけたらツッコミください。

追記

修正しないと気持ち悪いので追記していきます。

gets.split.map(&:to_i)

p_c = gets.chomp.split(" ")
p = p_c[0].to_i
p = p_c[1].to_i

と書いてしまった部分。気持ち悪さは感じてたけど時間制限あるから「これでいいや」的なノリでサブミットしてしまいました。

まず、splitの使い方。引数を省略すれば空白文字で区切ってくれる。つまり、.split(" ").splitでオーケー。で、引数無しのsplitメソッドは先頭・末尾の空白文字も取り除いてくれるのでchompを併用する必要がない。

さらに、splitして返ってくる配列を一度保存してからその1つ1つにアクセスしてる。これはきもい。p_cという変数名もきもい。Rubyではa,b = [1,2]みたいな代入ができるのでそれを使った方がいい。

すると、こうなる。

p,c = gets.split.map(&:to_i)

こちらの記事を参考にさせていただきました。

@cards.concat(fetch_cards(p,c))

これ、2行もいらないのでは。

cards_bundle = fetch_cards(p,c)
@cards.concat(cards_bundle)

ということで1行に。

@cards.concat(fetch_cards(p,c))

fetch_cardsメソッドはいらないのでは

def fetch_cards(p,c)
  bundle = @cards[-p-c+1..-p]
  @cards.slice!(-p-c+1..-p)
  bundle
end

def cut(r)
  r.times do
    p,c = gets.split.map(&:to_i)
    @cards.concat(fetch_cards(p,c))
  end
@cards
end

cutメソッドとfetch_cardsメソッドを分けて書いていたのを、id:sekaiyaさんにご指摘頂きました。fetch_cards単体で使う場面は無いので、この処理はcutメソッドに書いてしまった方がすっきりして良いですね。

def cut(r)
  r.times do
    p,c = gets.split.map(&:to_i)
    bundle = @cards[-p-c+1..-p]
    @cards.slice!(-p-c+1..-p)
    @cards.concat(bundle)
  end
  @cards
end 

Rubyのirbでなんとなく「クラス」とか「メソッド」とかに触れる

追記:なんか後になって読み返してみたら、オブジェクト指向の説明として全く適切じゃないように思えてきました。ただ、プログラミング初めてみたけど何がなんだかよくわからんというレベルの人(この記事を書いた時の僕くらいの人)がRubyのクラスやらメソッドやらを初めて扱う上でこういう記事があってもいいんじゃないかなと思ってタイトルを変えて残しています(変更前はオブジェクト指向入門みたいな記事タイトルでした)。

ソフトウェア設計としてしっかりとオブジェクト指向を学びたい人には、オブジェクト指向のこころ (SOFTWARE PATTERNS SERIES)という本がおすすめです。


最近、オブジェクト指向をどう教えればいいのかという記事をよく目にします。僕も昨年の今頃はオブジェクト指向なんてさっぱりだったし未だにちゃんと理解しているか怪しいくらいなのですが、自分の理解のために一度こういう文章にしておきたいなと思い立ち、この記事を書いています。

まず前提として、この記事が目指すのは確実な理解とか完璧な定義ではありません。この記事は、オブジェクト指向になんとなく触れて感触を確かめるためのものです。

また、今回はRubyが使える環境がある前提で書いてしまったため本当に初心者向けと言えるのかな...という不安があります。irb使えれば入門は果たせるかもしれないけど、irb使える環境が無いとこの記事は読んでも無駄になるかもしれません。 コメントやTwitterで「Rubyどうやったら使えるのさ」という声があれば、初心者がすぐに使える環境も紹介したいと思います。

オブジェクト指向はなんでわかりづらいのか

なんでオブジェクト指向はわかりづらいのかというと、抽象化しすぎだからではないかと思います。たい焼きの説明は話としてはとてもわかりやすいけれども、問題はプログラミングでたい焼きを作る場面が無いということです。たい焼きはたい焼き器やあんこで作るものです。

実際のところプログラミングで扱うのはたいてい数値と文字列なので、素直にそれらを扱った方が親切じゃないでしょうか。そこで、文字列や数値もオブジェクトの一種として扱われるRubyオブジェクト指向を学ぶ上で最適だと思います。

文字列クラスで学ぶオブジェクト指向

Rubyにおいて、文字列はオブジェクトです。クラスがあり、インスタンスが作れます。と、この意味がわからない人のためにirbを使って解説します。irbを立ち上げてください。ターミナルでirbと打てば使えるはずです。

以下では、基本的に一緒に試してもらいながら読んでもらうのを想定しています。ただ読むだけではわからないかと思いますので、まずはirbを立ち上げましょう。

文字列の正体

irbを立ち上げたら、こうしましょう。

hoge = "ほげ"

うん、変数に文字列を代入しただけっぽいです。ではこのhogeの正体を探るとこから始めます。hogeの正体を知るにはhoge自身が持ってるメソッドから選ぶのがいいでしょう。以下の様にhogeが持ってるメソッドを尋ねます。

hoge.methods

いろいろ返ってきましたね。hogeの正体を知るには、クラスを尋ねるためのclassメソッドを使います。

hoge.class

Stringと返ってきました。ここから、「hogeはStringクラスのインスタンスだ」ということが言えます。文字列の正体はStringクラスのインスタンスだったのです。

クラスとは?

ここで、クラスの説明をします。クラスとは、設計書です。Stringでいうと、大文字にするためのメソッドとか数字を整数オブジェクトに変換するメソッドが書かれていたりします。そのおかげで、以下の様なことができるわけです。

hoge.upcase # 大文字にした文字列を返す1”.to_i # 整数オブジェクトにして返す

上のように、メソッドはオブジェクト.メソッドという記法で使えます。上からわかるように、hogeの中身は"ほげ"という文字列であると共に、いろいろと小便利なメソッドを持った実体だと言えそうです。

インスタンスとは?

インスタンスとはクラスという設計書を元に作られた実体を指します。つまり、hogeのことです。実体っぽさをつかむには、それぞれがちゃんとアイデンティティを持っていることを確認しましょう。

hoge1 = “ほげ”
hoge.object_id

hoge2 = “ほげ”
hoge2.object_id

object_idというメソッドを使うことで、オブジェクトのIDを返してもらえます。ちゃんと違う数値が返ってきたはずです。入ってる文字列は同じでも、違う実体だということがわかりましたね。

文字列クラスを自分で設計してみる

次に、クラスをどう作るのかというのを体験するために自分なりに工夫して一風変わった文字列クラスを実装してみます。

  • 4文字に略したものを返すメソッド
  • 語尾に「ぽよ」を付けて返すメソッド
  • 語頭に「マジで」を付けて返すメソッド

では早速、テキストエディタを開いて以下のコードを書いてMyClass.rbのようなてきとーなファイル名で保存しましょう。

class MyString

  def initialize(s)
    @string = s 
  end 

  def abbreviate
    @string[0] + @string[1] + @string[@string.size / 2] + @string[(@string.size / 2) + 1]
  end 

  def poyonize
    @string + "ぽよ"
  end 

  def majide
    “マジで” + @string
  end 

end

s = MyString.new("愛のままにわがままに僕は君だけを傷つけない")
puts s.abbreviate
puts s.poyonize
puts s.majide

書いたコードは、

ruby MyClass.rb

で実行できます。

なんだかセンスが無いメソッドですが、まあふんわりつかんでもらえたら幸いです。これをもとにもう少し説明を続けます。

コンストラクタとは?

ここで何気なく登場したのが、initializeメソッドです。これはMyString.newのようにインスタンスを作るときに実行されるメソッドで、一般的にはコンストラクタと呼ばれたりします。

MyString.new(“愛のままにわがままに僕は君だけを傷つけない”)

とすることで、@stringというインスタンス変数に文字列を代入しています。

インスタンス変数とは?

インスタンス変数とは、その名の通りインスタンスが持つ変数です。今回は、この@stringというインスタンス変数に入れたものに「ぽよ」とか「マジで」を付け加えたものを返すメソッドを書きました。全く実用的じゃなくて申し訳ありません。

インスタンスが持つ変数」という表現ではよくわからないという場合には、これまた実際に中身を確認してみましょう。先ほどのコードに、以下の一行を追加して実行してみましょう。

p s

pはオブジェクトをそのまま出力するためのメソッドです。ここではsに入れたMyStringのインスタンスをそのまま出力されます。

すると、MyStringというクラスのインスタンスの@stringというインスタンス変数に文字列が入っているのがわかりますね。

継承

さらにもう少し、個性的な文字列クラスを設計してみます。先ほどのMyStringとは別に、HyperStringというクラスをつくってみましょう。

class MyString

  def initialize(s)
    @string = s 
  end 

  def abbreviate
    @string[0] + @string[1] + @string[@string.size / 2] + @string[(@string.size / 2) + 1]
  end 

  def poyonize
    @string + "ぽよ"
  end 

  def majide
    "マジで" + @string
  end 

end

class HyperString < MyString

  def mitsuonize
    @string + "なあ。みつを"
  end 

  def funassize
    @string + "なっしー!!"
  end 

end

s = HyperString.new("愛のままにわがままに僕は君だけを傷つけない")
puts s.abbreviate
puts s.poyonize
puts s.majide
puts s.mitsuonize
puts s.funassize

語尾に何かつけるだけなので若干飽きてきたかもしれませんが…。 新しいクラスHyperStringのインスタンスに、先ほどのMyStringのメソッドを使うために継承を使っています。継承をするには、クラスの定義を以下のように書きます。

class HyperString < MyString
end

簡単ですね。「<」という記号は矢印だと思って大丈夫だと思います。Rubyはこういった直感的にわかりやすいシンタックスが多いのが特徴です。他の言語だと「<」ではなく「extends」と書いたりします。

まとめ

Rubyを通してクラスとインスタンス、コンストラクタ、継承といった基本的な概念に触れてきました。

僕はアホなので、オブジェクト指向についてわかりやすい説明をしてもらったりしても全然理解できませんでした。なんとなく「そういうことか」と感じたのは、手を動かしている時、パーフェクトRubyに書いてあることをirbで確認している時でした。

今回の記事はできるだけ初心者にわかりやすく書いたつもりですが、やはり手を動かさないと何にも面白くならないと思います。この記事を読みながら「語尾を相田みつをさんぽくして何が面白いんだ」と感じて自分なりにメソッドをアレンジした時にクラス設計が楽しくなるのではと思います。

参考書籍

一応、僕が読んだ本を載せておきます。

概念だけじゃなくて、オブジェクト指向に至るまでのプログラム設計手法の変遷がわかる一冊。

Rubyで遊びながらプログラミングの初歩に触れる。オタクっぽい文章が特徴的。

Rubyプログラミングのバイブルとも言える本。分厚い。

Ruby on Railsでブログを作成するときに役立った情報まとめ

検索から来た人へ: ここに書かれている情報は古いので、最新の書籍やドキュメントを読むことを強くおすすめします。

(最終更新日:2013/12/19)

Ruby on RailsでちょっとしたCMSを作ろうとしていて、単純なブログ程度の機能はとりあえず作れました。ここらで使ったものやら何やらをまとめておきます。少しずつ書いていきます。

下書きにしておくとだるくなって書かなくなるので最初から公開しておきますが、一段落ついたらTwitter(@gaaamii)で通知します。はてブ付けてくれるとモチベが上がって更新が早くなるかもしれません

書き終えました。

Ruby on Railsの導入

こちらでまとめました。2013/10/13時点での情報です。何かあれば修正しますのでお知らせください。

周辺ツール

Git,GitHub,Heroku(コード管理、デプロイ)

「ローカルで試しに作ってみる」という段階では絶対に必要なものではありませんが、いずれ必要になります。とりあえずは存在を知って、必要になった時にRailsTutorialの該当箇所を読むと良いと思います。

vimテキストエディタ

vimプラグインを管理するツール。まだあまり使いこなせてない。NeoBundleというツールの方が最近のものらしいというのを後から知りました。

vimでツリー表示するためのものです。これがないといちいちディレクトリをcdで移動しまくることになります。

f:id:shgam:20131219011131j:plain

Bundler(gem管理)

gemをBundlerで管理しているとbundle install の遅さにいらいらすることがあると思います。「みんなあの時間なにしてるんだろ」ということをTwitterでつぶやいたところ、id:yss44さんが上記の記事を紹介してくれました。

Bundlerを最新にして設定をちょこっといじると爆速になるよ、という内容です。

便利なgem

テキストエリアのためのgem

テキストエリアに入力されたものをそのまま出しちゃうのはもちろん(scriptタグとか埋め込まれたりして)セキュリティ的にやばいので、出力するときにはエスケープすることになります。

そのときに何かしら置換してあげないと見栄えが悪いです。かといってリッチエディタを用意するのは頭使いそうだし...

そんなお悩みを解決するのがマークダウンです。(qiitaのように)ユーザーとして想定する人が開発者なら、マークダウンさえ使えればオッケーなのではなのではないでしょうか。

タグ付けのためのgem

ブログなんかではたいてい必要になるのがこのタグ機能です。記事を書いて、それに[Ruby]とか[Rails]とか、タグを付ければ情報が整理できますよね。

ところが、これを自分で実装しようとしたら思いのほか難しい。そこで見つけたのがまさにタグ機能のためのgem、act-as-taggable-onです。以下のようなコマンドで簡単にマイグレーションファイルを作ってくれるって言うんだから便利なものです。

rails generate acts_as_taggable_on:migration

デザインのためのgem

おなじみTwitterBootstrapです。最近はCSSフレームワークにYahooの「Pure」を使うという話もちらほら聞きますが、とりあえずはBootstrapを味わい尽くしたいです。

セキュリティ

CSRFとは

Wikipediaでどんなものか把握。Railsではmetaタグにこんな形でtokenを発行することによってCSRF対策を施してる。

<meta content="xxxxxxxxxxxxxx" name="csrf-token">

Strong Parameters

Rails 4からの新機能。コントローラ側で受け取るハッシュを制限するためのものです。具体的にどう危険なの?というのはRailsTutorialのこの部分を見るとわかります。

この脆弱生を突くことはMass Assignmentと呼ばれていて、Rails3でも対策はあったものの管理者次第では穴を作ってしまうケースがあり、何度か問題になっていたようです。

教材・参考資料

インターネット

まずはドットインストールですよね。

じっくりしっかり勉強するのに最適。企業の研修でも使われているほどのクオリティ。「ローカルで何か作る」という意味での手っ取り早さではドットインストールの方が良いかも。

便利なgemの使い方とかが動画チュートリアルで学べる。無料エピソードだけでもそこそこ役立つものが見つかる。

書籍

良書だと言われている通り、良書だった。ただ、書かれている内容はRails3向けなので、MassAssignment対策にはattr_accessibleを使いましょうみたいになっててRails4との違いはところどころに見られる。

検索してよくお世話になったサイトやブログ

情報収集ツール

はてブはやっぱ便利です。Railsの情報を見つけたら[Rails]というタグをつけてひたすらぶっこむという使い方がおすすめです。

感想

情報がたっくさん見つかるので、やっぱRailsを使うことにして良かったなと感じてます。

参考書籍

検索からの訪問が多いので、参考書籍を追記しておきます(2014/12/26)

パーフェクト Ruby on Rails
すがわら まさのり 前島 真一 近藤 宇智朗 橋立 友宏
技術評論社
売り上げランキング: 99,852

nokogiriを使ったスクレイピング入門

今回はnokogiriを使ったスクレイピングについて書きます。

スクレイピングとは

ウェブスクレイピング(Web scraping)とは、ウェブサイトから情報を抽出するコンピュータソフトウェア技術のこと。
ウェブスクレイピング - Wikipediaより

Webプログラミングやるのであればどっかしらで必要になるものですよね。要はウェブサイトから情報をガーーーッと取得したいときにプログラムを書いて自動化しようぜ、というものです。こういうことができるようになると、インターネットがもっと楽しくなります。

たのしいRuby 第3版に分かりやすく解説されていたのでそれを参考にしました。

やり方

簡単なことならjQueryの初歩的なDOM操作みたいな感じで出来ます。

使うもの

言語
gem
  • open-uri
  • nokogiri

今回のスクレイピングrubyでやっていきます。PHPとどっちが簡単かわからないけど多分Rubyの方が簡単なんじゃないですかね。

ということで、まずは

gem install nokogiri

してから、

require 'open-uri'
require 'nokogiri'

しておきましょう。

早速、抽出する

全部出す

さっそくHTMLをとってきましょう。

uri = "http://shgam.hatenadiary.jp"
doc = Nokogiri::HTML(open(uri),nil,"utf-8")
puts doc

HTMLを全部とってきて、それをまんま出力するにはこれでおっけーです。
HTMLがばーーっと出力されるはずです。HTMLをとってくるだなんて、ブラウザになった気分ですね。

CSSの記法で欲しいものをとってくる
doc.css("h1").each do |h1|
  puts h1
end

これでh1タグのものが全部出力されます。でも、例えば記事タイトルだけ欲しいんだよなということでクラス名を指定してあげることもできます。

doc.css("h1.entry-title").each do |title|
  puts title
end

まんまCSSですね。jQuery使える人ならもうウハウハだと思います。
あれ、でもタグもそのまんまじゃなくて中身のテキストだけが欲しい時ありますよね。

doc.css("h1.entry-title").each do |title|
  puts title.text
end

.textでとれます。便利ですね。説明の必要が無いですね。あと、記事のリンクも欲しいでしょう。

クラス名で指定するとか、

doc.css(".entry-title-link").each do |link|
  puts link["href"]
end

「記事タイトル下のリンク」という感じで指定してもいいんじゃないでしょうか。

doc.css("h1.entry-title > a").each do |link|
  puts link["href"]
end


とまあ、こんな感じです。

スクレイピングが必要になった経緯

今年の春ごろからあるスポーツのウェブサイト運営をお手伝いしているのですが、その作業の一つに「当月のブログ記事のタイトルと更新日とリンクをエクセルファイルにまとめる」というものがあります。

ブラウザから月ごとのページに行って、5記事ずつ表示されるそれを手作業でコピー・ペーストするのは辛いです。ChromeJavaScriptコンソールから対応するDOMをとってきてみても、十分面倒くさい。ページに表示されるのは5記事ずつなので、その度ページ遷移が必要になります。

Rubyでちょっとしたスクリプトを書いてターミナルから1発でとってこれるようにしたら良いのでは、というのがnokogiriによるスクレイピングの個人的な目的でした。

書いたコードそのものは大したことないんですが、こういう考え方ができるようになってきたことにプログラミングやっててよかったなーとしみじみ感じています。今度はRubyからエクセルに書き込む方法を調べてまとめたいです。

追記

なお、nokogiri使えばいいというのはTwitter@ToraDadyさんに教えて頂きました。本当にありがとうございます。

このブログは親切なフォロワーさん,読者さんに支えられています。