マイペースなRailsおじさん

Ruby、Ruby on Rails、オブジェクト指向設計を主なテーマとして扱います。だんだん大きくなっていくRuby on Rails製プロダクトのメンテナンス性を損なわない方法を考えたり考えなかったりしている人のブログです。

トランザクション中にrescueするとロールバックしないので注意!

トランザクション中のrescueはロールバックを発生させない

動画による説明

www.youtube.com

トランザクション中のrescue

このようにすると、create!で発生した例外をキャッチして、exec_transactionの返り値としてfalseを返すことができます。

def exec_transaction
  ApplicationRecord.transaction do
    User.create!(name: 'Duplicate')
    User.create!(name: 'Duplicate')
  rescue ActiveRecord::RecordInvalid
    false
  end
end

Userモデルは下記のようにバリデーションが設定してあります。

class User < ApplicationRecord
  validates :name, uniqueness: true
end

このコードを実行すると、二回目のcreate!でエラーが発生しますが、ロールバック処理が実行されません

Rollbackが起きる仕組み

transactionメソッドに与えられたブロックは、最終的にwithin_new_transactionメソッドの中で実行されます。 ここで、与えられたブロックで発生したすべての例外をキャッチして、ロールバックを発生させたあと、例外をもう一度送出します。

https://github.com/rails/rails/blob/291a3d2ef29a3842d1156ada7526f4ee60dd2b59/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb#L313-L318

この動作によって、transactionで例外が発生すると、ロールバックが暗黙的に実行されて、例外の創出も行われます。

Exceptionをキャッチするとロールバックしない

上述の通り、発生した例外をトリガーにしてロールバックが発生します。したがって、先に示したコードのようにブロックの中で例外をキャッチしてしまうと、ロールバックが起きません。

ロールバックしつつ例外をキャッチしたい場合

方法は二通り。明示的にロールバックするか、トランザクションの外側で例外をキャッチする。

明示的にロールバック

こちらの動画で紹介されている方法と同じ考え方です。 https://www.youtube.com/watch?v=jFBvEQhApKQ

def exec_transaction
  success = true
  ApplicationRecord.transaction do
    success &= User.create(name: 'Duplicate')
    success &= User.create(name: 'Duplicate')
    unless success do
       rescue ActiveRecord::RecordInvalid
    end
  end

  success
end

トランザクションの外側で例外をキャッチする

ロールバックが発生したら例外は再度送出される、という性質を利用します。

def exec_transaction
  ApplicationRecord.transaction do
    User.create!(name: 'Duplicate')
    User.create!(name: 'Duplicate')
  end
rescue ActiveRecord::RecordInvalid
  false
end

そもそもの例外処理の設計

こういう例外処理がある事自体が良くない可能性もあります。もちろん、すべてが悪いということではないです。 エラー処理の設計については、いとうじゅんいちさんが非常に丁寧にまとめてくれています。

Railsアプリケーションにおけるエラー処理(例外設計)の考え方 - Qiita

プロを目指す人のための例外処理(再)入門 / #rubykansai 2018-01-13 - Speaker Deck

Ruby CoreをCLionでデバッグモード実行する

CLionでRubyデバッグする

CLionは、JetBrains製のC、C++用の高機能なIDEです。このIDE上でCRubyをデバッグモードで実行すると、解析や試行錯誤が捗ります。デバッグの様子は、下記のビデオを見てください。

youtu.be

設定方法

1. Rubyソースコードをダウンロードする

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

2. Rubyをmakeする

autoconf
./configure
make

3. CLionでMakefileを開く

File -> Open

f:id:ytnk531:20210125093808p:plain

4. ビルドの設定

Execurableに、minirubyを指定します。
次に、Program Argumentsに、test.rbを指定します。 これでminirubyをコンパイルしてtest.rbをminirubyで実行するように設定できました。

f:id:ytnk531:20210125093929p:plain

5. .gdbinitの編集

rpなどのruby開発のマクロが使えるようにします。

まず、プロジェクトごとのgdb設定が有効になるようにします。 ~/.gdbinitを作成して下記内容を記述します。’

set auto-load local-gdbinit on
add-auto-load-safe-path /

これでruby用の.gdbinitが読み込まれるようになるのですが、このままだとCLionからgdbが起動できません。対策として、ruby用の.gdbinitの最初の行を、下記のようにコメントアウトします。

# set startup-with-shell off

実行してみる

以上で設定は完了です。test.rbにrubyコードを書いたら、そのコードが通るはずのCのコードにブレークポイントを置いて、デバッグ実行をしてみてください。デバッグは、ここの虫のマークを押すと実行できます。

f:id:ytnk531:20210125095040p:plain

参考サイト

RubyでWebスクレイピングしたい(ダウンロード編) ~Google画像検索で検索した画像をダウンロードしてくるgemを作りました~

ダウンロード編として、スクレイピングの方法を解説する記事を書く予定だったのですが、gemの公開をもって解説ということにします。 特にメンテしていく気は無いのですが、スクレイピングで色々とコツが必要だったので忘れないようにgemとして形にしました。

github.com

使い方

検索ワードと画像の数を入れて起動します。

$ gem install google_image_scraper
$ google_image_scraper 猫 10

裏で地道にダウンロードしているのでそこそこ時間かかります。 コマンドが正常終了すると、こんなかんじで画像が保存されています。

f:id:ytnk531:20210102230739p:plain

仕組み

下記6段階のステップを踏んでダウンロードします。

  1. Google画像検索で画像を検索
  2. 検索された画像をクリックして右側に詳細を表示
  3. 高画質な画像がダウンロードされるまで待つ
    1. 3.を繰り返す
  4. 画面をスクロールして追加の画像を読み込む
  5. 2に戻る

Google画像検索のHTMLの構造が複雑だったので、けっこう無理のある実装をしました。HTMLの構造が変わったらたちまち壊れることが想定されます。

  1. のステップは工夫が必要なところでした。 2.で表示された詳細ウィジェットに表示している画像は、最初は画質が荒いサムネイルなのです。遅延読み込みをしているらしく、少し待つと掲載元サイトにアップロードされている画像への直リンクに差し替えられて、高画質に表示されます。 f:id:ytnk531:20210102231553p:plain

かなり壊れやすい実装なのと、機能も最低限しか無いです。メンテしていくかは未定です。

RubyでWebスクレイピングしたい(ChromeDriverインストール編)

Seleniumを使って、Google画像検索から画像をスクレイピングすることにしました。

環境

  • Ubuntu 18.04.2 LTS
  • WSL2 on Windouws 10
  • ruby 2.7.2

selenium-webdriverをインストール

まずはruby経由でWebDriverへのリクエストを送信するためのgem、selenium-webdriverをインストールします。

selenium-webdriver | RubyGems.org | your community gem host

gem install selenium-webdriver

selenium-webdriverの主な役割は、WebDriverと通信するためのAPIを提供することです。WebDriverが含まれているわけではないので、操作したいブラウザとそのブラウザ用のWebDriverを別途インストールする必要があります。

Chromeをインストール

操作したいブラウザの本体をインストールします。今回はChromeにします。すでにChromeをインストール済みの場合はスキップしてください。

WSL2では、GUIを操作するためにWindows用のX ServerであるをVcXsrv利用します。インストール時に少し工夫が必要なため、下記のサイトを参考にしました。Ubuntuのバージョンは違いましたが、特に手順は変えずにインストールできました。

  1. WSL2+Ubuntu 20.04でGUIアプリを動かす | AsTechLog
  2. WSL2+Ubuntu 20.04にChromeをインストール | AsTechLog

この操作でWSL2からGUIが使えるようになるので、ついでにgnome-openできるようにしたい方はこちらを参考にしてみてください

WSL2の導入とGUI環境の構築とsshfsしたものもgnome-openしたい!! [Ubuntu18.04/20.04] - Qiita

chromedriverをインストール

WebDriverは各ブラウザごとに実装されています。Chrome用のWebDriverであるchromedriverを使います。

Googleが配布しているchromedriverをダウンロードして、PATHの通っている場所にchromedriverのバイナリを配置すれば完了です。

1. Chromeのバージョンを確認

$ google-chrome --version
Google Chrome 86.0.4240.183

2. chromedriverをインストール

unzip が必要なので、インストールされていなければインストールします。

sudo apt-get install unzip

公式サイトより、先程確認したChromeのバージョンとなるべく近いバージョンのものをダウンロードし、パスの通っているフォルダにはいちします。実行権限を付与するのも忘れずに。

wget https://chromedriver.storage.googleapis.com/86.0.4240.22/chromedriver_linux64.zip
unzip chromedriver_linux64.zip
sudo cp chromedriver /usr/bin
sudo chmod +x /usr/bin/chromedriver

3. 動作確認

irbを起動し、chromeを立ち上げます。

require 'selenium-webdriver'
driver = Selenium::WebDriver.for :chrome

f:id:ytnk531:20201116055649p:plain

無事起動したら、irbからブラウザを操作できることを確かめます。

driver.get 'https://google.com'

googleにアクセスできていれば成功です。 f:id:ytnk531:20201116055944p:plain

起動時にCapabilitiesというオブジェクトを渡すと、オプションを設定できます。ウィンドウを立ち上げないヘッドレス状態で起動するには下記のように指定します。

    caps = Selenium::WebDriver::Remote::Capabilities.chrome(
      'chromeOptions' => { args: %w[--headless --disable-gpu] }
    )
    Selenium::WebDriver.for :chrome, desired_capabilities: caps

ブラウザの状態を確認しつつプログラムを書いたりデバッグをする際はGUI付きで、自動実行する場合はヘッドレス状態で起動するのがよいでしょう。

続く

WSL2にChromeをインストールするところはVcXSrvの起動オプションなどに工夫が必要なので、ハマりポイントかもしれません。 次はようやく画像をダウンロードしていきます。

RubyでWebスクレイピングしたい(ツール選定編)

自作したスクレイピングツールで画像をあつめたい

現在開発中のアプリケーションで、エフェクターボードの画像が100枚くらい欲しかったので、Google画像検索から画像を集めることにしました。

画像収集は機械学習などでかなり需要があるらしく、自作せずとも利用可能なツールがいくらかあるようです。

せっかくですが、rubyではWebスクレイピングするようなプログラムは書いたことがなかったので、自作してみることにしました。

nokogiriかSelenium

rubyスクレイピングをする場合、nokogiriSelenium(あるいはcapybara経由でSelenium)を使うのが主流です。

nokogiriは、HTMLのパーサーです。nokogiriを使う場合は、対象のページのHTMLタグを解析して、所望のデータを取得することになります。テキストデータとして解析するだけなので、JavaScriptなどは実行されません。

Seleniumは、rubyプログラムからブラウザを操作するためのライブラリです。こちらを使う場合、対象のページをブラウザで開いて、rubyプログラムからブラウザ経由で画面内に表示されている要素に対して操作、データ取得を行うことになります。

nokogiriの得意・不得意

  • 得意
    • seleniumを使う場合に比べて余計なことをしないので、高速に動作させられる
    • 単純なページを簡単に解析できる
  • 不得意
    • 収集対象のデータがJavaScriptによって非同期でダウンロードされたり、動的にDOM要素が変化したりする場合、構造の解析が難しい。

Seleniumの得意・不得意

  • 得意
    • JavaScriptで動的に画面が動的に変化する場合でも、あまり大変な解析をせずに操作できる
    • ブラウザで見た感じで操作する手順を記述していけばいいので、nokogiriを使う場合に比べてDOM構造への理解が少なくて済む
  • 不得意
    • ブラウザを起動させて操作するためにかなりリソースを使うので、高速には動作しない

Seleniumにする

最近はJSでゴリゴリ動的にDOM変化させたり、REST APIやGraph QL使って非同期でデータ取得するページが増えている気がするので、Seleniumを使うほうが簡単だと思います。 nokogiriを使ったさい、難読化されたJSを読む羽目になる未来が恐ろしい。

下記の記事では、instagramからnokogiriを使って画像収集ため、ページ閲覧中に発生するHTTPリクエストを解析して、効率的な画像収集の方法を発見しています。こんなかんじで解析できるのが理想的ではありますが、私レベルではこういう作業は答えが見つかるまでどれくらい時間がかかるか読めないので、なかなか手が出しにくい方法です。 Instagramから大量の画像を集める - Qiita

SeleniumとWebdriver

Seleniumを使う場合、操作したいブラウザのdriverが必要になります。 この辺ややこしいのですが、こちらの記事の図が非常にわかりやすいです。

入門、Selenium - Seleniumの仕組み | CodeGrid

f:id:ytnk531:20201110072203p:plain
引用元: 「入門、Selenium - 第1回 Seleniumの仕組み」

Seleniumライブラリは、WebDriverの立ち上げと、WebDriverへのJSONの発行を行ってくれます。WebDriverは各ブラウザごとに用意されていて、基本的にOSごとのバイナリ実行ファイルを取得するかビルドしておく必要があります。

次はchromedriverをインストールして起動するところまで書きます。だいぶ進んでませんが、今日はこのへんで。

やる気が出ないエンジニア、目標がわからなくなってしまったエンジニア、伸び悩んでいるエンジニア…みんな今すぐ「情熱プログラマー」を読もう

とにかくおすすめな「情熱プログラマー

エンジニアのChad Fowlerさんが自身の経験を元に、エンジニアがどう生きていくべきか、について書いた本です。

技術革新によって自分の仕事がなくなるかもしれない、あるいは稼げなくなるかもしれない、という危機にさらされ続けるエンジニアという職業においては、戦略を持ってキャリアを築いて行くべきだ、というのが著者の主張です。

そのため、下記に示すようなエンジニアにとって必要な戦略を教えてくれます。

  • どんなスキルを磨くべきか
  • スキルの身につけ方、磨き方
  • スキルを仕事にどう活かすか
  • コネクションの作り方
  • などなど

とにかくおすすめな理由

どんなレベルのエンジニアでも学びがある(はず)

エンジニアとしての仕事を初めたばかりの人が読むのが一番学びがあると思うが、対象の読者は特に絞られていない。

おそらく、実務をある程度長くこなしてきた人から見ても、そんなの当たり前じゃんっというようなことはあまり書いていないと思う。それは、ジャズミュージシャンからシリコンバレーの第一線で働くことになったChad Fowlerさんならではの気付きと洞察が散りばめられているからだ。

ハートに火が点くことが書いてある

この本に書いてあることを、言われるがままに実践できる人は少ないと思う。それくらい、高い目標を目指すように訴えかけてくる。ただそれが嫌味っぽくなく、なるほどたしかにと思うような文面で書かれている。

本当にいろいろな面からエンジニアに必要な能力についてかいてあるので、人によって感銘を受けるポイントは違うと思う。 私が特に印象に残っている文章を引用する。

変容するIT業界で生き残るスペシャリストはこんな人だ。例えば.NETのスペシャリストは、.NET以外に何も知らないことのいいわけではない。.NETについては権威であるような人だ。IISサーバがハングして再起動する必要があるんだけど・・・・・・「お安い御用だ」Visual Studio .NETとソースコントロールの統合をしたいんだけど・・・・・・「方法を教えてあげよう」よくわからないパフォーマンスの問題で顧客がカンカンだ・・・・・・「30分くれ」
これが君の意味するスペシャリストと違うなら、どうかスペシャリストを名乗らないでほしい。

これは、スペシャリストについてのセクションに書かれた一文だ。スペシャリストとはどんな人を指すのかについて書いてある。この文章を読んで、どんな印象をもっただろうか?耳が痛い、そんなやついるわけない、そんなレベル求められてない…人によって感じることは様々だと思う。

わたしの場合は、「自分はまだまだ甘かった。」だった。 また、「あ、そうか、そこを目指せばいいんだ」とも思った。

目標が明確になる

キャリアって、一体どこを目指せばいいのかわからず、迷子になることがよくあると思う。この本は、あなたが次になにをすべきなのかのヒントをくれる。できるだけ迷わないように、自分が何を学ぶべきなのか的が絞れるように手助けしてくれる。

やる気ブースターとして活用しよう

情熱プログラマーに書いてあることは本当に意識が高いし素晴らしい。それだけ聞くと、そんな情熱ないよとか、自分には無理だとか、思うかもしれない。そういう人こそこの本を読んでほしい。

確かに、実行に移すのが難しいこともたくさん書いてある。でも、決してやれないことではない。勇気を持って人脈を作り、師匠を見つけ、弟子を取り、達人になっていくことはあなたにもできる。きっとハートに火がついてプログラミングを頑張れる一言が書いてあるはずだ。

Tennis-Refactoring-KataでRubyのリファクタリングの練習をやってみた

Refactoring Kataでリファクタリングを練習しています。今日も頑張りました。

Tennis-Refactoring-Kata

github.com

概要

  • テニスのスコアリングをするプログラムのリファクタリングを行う
  • テストは書いてある
  • 同じ仕様のプログラムが3パターン用意されていて、それぞれ違ったややこしさを持っている。

やってみた

やってみました。 github.com

差分はこちら。
Comparing emilybache:master...ytnk531:master · emilybache/Tennis-Refactoring-Kata · GitHub

以下、やったことや感想を書いていきます。

TennisGame1

Comparing emilybache:master...ytnk531:master · emilybache/Tennis-Refactoring-Kata · GitHub

全体の流れは複雑では無いものの、条件分岐がif文が多くて流れがわかりにくくなっている印象。メソッドに切り出すだけでだいぶ改善した。

  • scoreメソッドが長いので、とりあえずメソッド抽出した
  • メッセージを出し分ける部分をメソッドに抽出して、文字列の加工を簡単にした
  • 条件をメソッドにして、何を判定するのかわかるようにした

TennisGame2

Comparing emilybache:master...ytnk531:master · emilybache/Tennis-Refactoring-Kata · GitHub

文字列を生成するためにif文が並びまくっていて、とにかく流れが追いかけにくい。複数の条件に引っかかりながら進むパターンもあるので、条件分の前後関係も実は変えては行けなかったりする場所がある。わかりにくくいじりにくい凶悪なコード。

条件分岐をシンプルにしてやり、文字列の加工も単純にすることでだいぶ改善した。

  • scoreメソッドの分岐を単純にした。
  • 重複した条件の場所を探してまとめた。@p1points > @p2pointsは実は3つの条件に当たっているのを見つけた。
  • スコアの名前を取り出すのにif文を使っていて無駄が多いので、ハッシュから出すようにした。

TennisGame3

Comparing emilybache:master...ytnk531:master · emilybache/Tennis-Refactoring-Kata · GitHub

よくある、短くかくためにいろいろ省略しすぎて謎のコードになっているやつ。謎の数式があるので首をかしげる

リファクタリングは、ロジックの改善よりはメソッド名や変数名を使ってコメントを付けるような感覚だった。もっとわかりやすくできた気がするが、あまり変えなくてもそこまでわかりにくくなかったので、ほどほどで手を止めた。

  • 条件をメソッドに切り出した
  • 数式での条件判定を変数に入れて名前をつけた
  • 謎の変数にちょうどいい名前をつけた

感想

どれも頭が痛くなるコードだったが、TennisGame2がとにかく凶悪だった。こういうの、20~30分でかたづけられるといいんですけどねぇ。今回はテストがついていたのでとてもやりやすかったです。GildedRoseはテストも自分で書かないと行けなかったので。

他のアイディアややりかたについてもっと知りたいと思いました。きっともっといいやり方があるんだろうなぁという。こういうの一緒にできる知り合いがいるといいんですが、難しいですね。