Ruby

こんにちは、しきゆらです。
気が付くと、年が明けて2月です。

今回は、先輩に教えてもらった謎のコードを解読していきます。
謎のコードとは下記のもの。

[ruby]
ObjectSpace.each_object(ActiveRecord::Relation).each(&:reset)
GC.start
[/ruby]

いきさつ

AWS上にあるインスタンスでRailsを動かしていました。
その中で、DBのデータにミスがあったので、1.5万件ほどのデータに紐づく諸々の差し替え作業を行っていました。
このときにおこった問題としては「メモリ使い過ぎで怒られる」という状況でした。
コードを書き換えたりループを工夫したりしても解決せず困っていたとこで先輩が下記のようなコードを教えてくれました。

[ruby]
ModelClass.find_in_batches do |objects|
# 差し替える処理

ObjectSpace.each_object(ActiveRecord::Relation).each(&:reset) # ???
GC.start # ???
end
[/ruby]

このコードが入ると、途中で処理を止められることなく走らせることができるようになりました。

その当時は、先輩すげぇ~と思いながら後で調べておこうと思ってメモしておきました。
そのまま放置されていたものを思い出したので、時間のあるうちに調べてみよう、ということで調べてみました。

ObjectSpaceとは

そもそも、見慣れないクラス名です。
調べてみると、Rubyさんの組み込みモジュールでした。
リファレンスでは

全てのオブジェクトを操作するためのモジュール
module ObjectSpace (Ruby 2.7.0 リファレンスマニュアル)

とのこと。

わかるようなわからないような・・・という感じですが、定義されているメソッド類を見てみると
Rubyで定義したオブジェクトたちに対してあれこれしたり、プロファイルを取るときなどに使われるもののようでした。

ObjectSpace#each_object

その中に、ありました「each_object」メソッド。
「ObjectSpace」が何かわかれば、メソッド名で何をするものなのか大方予想が付きますね。

調べてみると、

指定されたクラスとObject#kind_of?の関係にあるすべてのオブジェクトに対して繰り返す
ObjectSpace.#each_object (Ruby 2.7.0 リファレンスマニュアル)

とのこと。
上記のコードでは「ActiveRecord::Relation」クラスのオブジェクトすべてに対する処理を行うということですね。

こいつは、ブロックを渡すとそのブロックを実行し、繰り返し回数を返すようです。
そして、ブロックが渡らない場合はEnumeratorオブジェクトを返すとのこと。
上記のコードでは、eachメソッドをつなげているので後者ですね。

なお、引数を与えなければすべてのオブジェクトに対して繰り返すようです。
また、Fixnumなどは対象外のようです。

つまり、上記コードの1行目はActiveRecord::Relationクラスのすべてのオブジェクト一つ一つに対して「reset」メソッドを呼び出していることになりますね。

ActiveRecord::Retation#reset

では、「ActiveRecord::Retation#reset」は何者でしょうか。

定義を確認してみると、内部で保持しているデータをすべて破棄しているようです。

つまり、上記コードの1行目が実行されたら、「ActiveRecord::Retationのオブジェクト」はすべて空のデータとなるようです。
1行目の内容は把握できました。
2行目を追っていきます。

GC

見たままGCでしょうね。
一応調べてみると、RubyのGCを制御するためのモジュールでした。
正確な情報は持っていませんが、イメージとしては使用していないデータを解放する仕組みという認識です。
メモリ上にあるいらないものを削除してきれいにしてくれる裏方さん。

GC#start

もう、見たままでしょう。
GC.start (Ruby 2.7.0 リファレンスマニュアル)
GCの処理を始めるためのメソッドです。

別メソッドでGCを禁止するようなこともできるようですが、このメソッドで実行した場合はGCを始めるようです。

すべてを見たうえで

[ruby]
ObjectSpace.each_object(ActiveRecord::Relation).each(&:reset)
GC.start
[/ruby]

たった2行のコードですが、知らないことが満載でした。
処理を簡単にまとめると、この処理の前までに作成されたすべてのActiveRecord::Relationオブジェクトを空にしてGCに削除してもらう感じでしょうか。

1.5万件のデータを処理していると、たくさんの不要なオブジェクトがたまっていき途中で利用できるメモリサイズをオーバーしてしまうということで、それを防ぐために一定の処理を終えると不要になったオブジェクトを削除して次に進む、ということを行っているようですね。

find_eachやfind_in_batchesなどである程度の粒度で処理することなどはわかっていましたが、それですら怒られるのでどうすればいいのかわからないところで、まさか自分でGCを動かして削除させることができるとは・・・。

まだまだ知らないことがいっぱいあるということですね。
まぁよく使うことではないとは思いますが、知っていると困ったときに役に立ちそうです。

まとめ

今回は、先輩が教えてくれた謎のコードを調べながら内容を理解してみました。
ActiveRecordなど、Githubで公開されているコードについては中身を読むことができるので、きちんと中身を知っておくことは大事だなと思いました。

 

今回は、ここまで。
おわり。

Ruby

こんにちは、しきゆらです。
今回は、文字列の先頭・末尾にある特定のものを正規表現で頑張らずとも判定する方法を知ったのでメモしておきます。

文字列の中に、特定のものが入っているかを判定するためには正規表現というものを使う場合があります。
そして、含まれているかどうかを判定するためのメソッドとして「String#match?」があります。
instance method String#match? (Ruby 2.6.0 リファレンスマニュアル)

しかし、文字列の先頭・末尾が特定のものかどうかを判定するためにmatch?を使うのはもったいないことでした。
というのも、この時にはより状況にマッチしたメソッドがあるからです。
それが「String#start_with?」「String#end_with?」です。
instance method String#start_with? (Ruby 2.6.0 リファレンスマニュアル)
instance method String#end_with? (Ruby 2.6.0 リファレンスマニュアル)

これらのメソッドは、機能はシンプルだが高速に処理できるようです。
Ruby: 文字列マッチは正規表現より先に専用メソッドを使おう
Ruby の文字列マッチ判定のパフォーマンス · Yuichi Takada

ということで、知らないメソッドシリーズでした。
ちなみに、これらはrubocop-performanceで怒ってもらえるようです。
Class: RuboCop::Cop::Performance::StartWith — Documentation for bbatsov/RuboCop (master)

今回、はここまで。
おわり

JavaScript,Ruby,Windows,環境構築

こんにちは、しきゆらです。
今回は、SimpackerというGemを使ってサーバ・フロント両方が良しなに動いてくれる環境を整えていきます。

今回はWSL2環境にUbuntuを入れ、そこで動かすことを想定しています。

Ruby,Windows

こんにちは、しきゆらです。
今回は、RSpecで同じ処理を書くことを避ける方法を知ったのでメモしておきます。

 

Railsにてテストを書いているときに、同じようなことを書いてるなぁと思うことがあります。
例えば「このAPIはユーザがいることが前提だ」となると、以下のようなことをいろんなテストで書くことになります。

[ruby]
before do
user = FactoryBot.create(:user)
end
[/ruby]

同じことを書くのは無駄ですよね。
何より繰り返し!修正面倒!!

ということで、このように特定の時に共通する処理を実行してほしい時にどうすればいいかわからなかったので、調べてみた結果をまとめます。

 

Ruby,Windows

こんにちは、しきゆらです。
今回は、RailsとRSpec、DatabaseCleanerを使った環境で起こったちょっとした問題と、(その場しのぎな)回避方法をメモしておきます。

Rails6環境で、DatabaseCleanerというGemを使ってテスト環境を毎回きれいにするようにしています。
そんな中、テストを複数実行すると2回目以降で以下のように怒られる現象に悩まされていました。

[bash]
ActiveRecord::NoEnvironmentInSchemaError:
Environment data not found in the schema. To resolve this issue, r
rails db:environment:set RAILS_ENV=test
[/bash]

 

調べてみると、これは「DBに保存されている環境情報がないから、それを設定してね」というメッセージとこのと。
https://blog.freedom-man.com/no-environment-in-schema-error

実際、上記のコマンドを実行した後にDBを確認するとしっかり情報が入っていましたが、テストを実行した後にはきれいさっぱりになっていました。

[sql]
# rails db:environment:set RAILS_ENV=test を実行した後
mysql> select * from db_name.ar_internal_metadata;
+————-+——-+—————————-+————–
| key | value | created_at | updated_at
+————-+——-+—————————-+————–
| environment | test | 2019-09-23 13:21:49.081104 | 2019-09-23 13
+————-+——-+—————————-+————–
1 row in set (0.00 sec)

# specを実行した後
mysql> select * from rts_test.ar_internal_metadata;
Empty set (0.00 sec)
[/sql]

これでは、確かに毎回設定しないといけません。

しかし、これはおかしな話です。
テストを回すたびに同じ環境情報を入れるなんて面倒でしかありません。

DBの情報がまっさらになっているので、DatabaseCleanerさんが悪さをしている可能性があります。
さらに調べてみると、(だいぶ古いですが)どうやらすでにissueとして報告されているようでした。
https://github.com/DatabaseCleaner/database_cleaner/issues/445

そして、最近これに関連したPRが動いているようです。
https://github.com/DatabaseCleaner/database_cleaner/pull/588
コードを見てみると、上記のar_internal_metadataを削除しないようにしてくれているようです。

しかし、執筆時点ではまだマージされていないので次のバージョンアップまで待ちましょう。
どうしても待てない!という場合は、diffを参考にコードを編集すればとりあえず回避することはできます。

今回はここまで。

おわり

Ruby,Windows

こんにちは、しきゆらです。
今回は、Railsと戯れている中で起こったちょっとした問題を何とかしていきます。

なお、環境としてはWSL2上のUbuntuにrbenvでRubyをインストールしているところで起きました。

状態としては、気が付くとタイトルの通り「rails c」とコマンドを打つと以下のような警告が出るようになっていました。

[bash]
/home/user_name/.rbenv/versions/2.6.4/lib/ruby/2.6.0/fileutils/version.rb:4: warning: already initialized constant FileUtils::VERSION
/home/user_name/.rbenv/versions/2.6.4/lib/ruby/gems/2.6.0/gems/fileutils-1.2.0/lib/fileutils/version.rb:4: warning: previous definition of VERSION was here
/home/user_name/.rbenv/versions/2.6.4/lib/ruby/2.6.0/fileutils.rb:1267: warning: already initialized constant FileUtils::Entry_::S_IF_DOOR
/home/user_name/.rbenv/versions/2.6.4/lib/ruby/gems/2.6.0/gems/fileutils-1.2.0/lib/fileutils.rb:1267: warning: previous definition of S_IF_DOOR was here
/home/user_name/.rbenv/versions/2.6.4/lib/ruby/2.6.0/fileutils.rb:1540: warning: already initialized constant FileUtils::Entry_::DIRECTORY_TERM
/home/user_name/.rbenv/versions/2.6.4/lib/ruby/gems/2.6.0/gems/fileutils-1.2.0/lib/fileutils.rb:1547: warning: previous definition of DIRECTORY_TERM was here
/home/user_name/.rbenv/versions/2.6.4/lib/ruby/2.6.0/fileutils.rb:1542: warning: already initialized constant FileUtils::Entry_::SYSCASE/home/user_name/.rbenv/versions/2.6.4/lib/ruby/gems/2.6.0/gems/fileutils-1.2.0/lib/fileutils.rb:1549: warning: previous definition of SYSCASE was here
/home/user_name/.rbenv/versions/2.6.4/lib/ruby/2.6.0/fileutils.rb:1595: warning: already initialized constant FileUtils::OPT_TABLE /home/user_name/.rbenv/versions/2.6.4/lib/ruby/gems/2.6.0/gems/fileutils-1.2.0/lib/fileutils.rb:1602: warning: previous definition of OPT_TABLE was here
/home/user_name/.rbenv/versions/2.6.4/lib/ruby/2.6.0/fileutils.rb:1649: warning: already initialized constant FileUtils::LOW_METHODS /home/user_name/.rbenv/versions/2.6.4/lib/ruby/gems/2.6.0/gems/fileutils-1.2.0/lib/fileutils.rb:1656: warning: previous definition of LOW_METHODS was here
/home/user_name/.rbenv/versions/2.6.4/lib/ruby/2.6.0/fileutils.rb:1656: warning: already initialized constant FileUtils::METHODS /home/user_name/.rbenv/versions/2.6.4/lib/ruby/gems/2.6.0/gems/fileutils-1.2.0/lib/fileutils.rb:1663: warning: previous definition of METHODS was here
[/bash]

 

特に大きな問題はないのですが、気持ち悪いので何とかならないかと調べてみました。
すると、同様の問題に直面している人がいました。

FileUtilsのGithubにissueとして挙がっていました。
その中に、暫定的としながらも解決法がありました。

temp solution
gem uninstall fileutils
gem update fileutils --default

fileutils conflict in Ruby 2.5.1 #22 | Github

ひとまず、エラーはなくなりました。
同じような問題で気持ち悪い思いをしている方は、お試しを。

 

今回は、ここまで。

おわり

SQL

こんにちは、しきゆらです。
今回は、DBにてselect文でデータを取得するときの取得順についてメモしておきます。

基本的に、取得順についてはorder句で指定した順番で取得されます。
しかし、order句を指定しない場合もデータは取得することができますね。
この時の取得順について触れる機会があったので、調べた結果をまとめておきます。

 

経緯

今回直面したことの簡単な経緯をメモしておきます。

データベースへランダムにデータを追加し、その結果をCSVとして吐き出す、ということをしていました。
シード値を固定した後、ランダムな値をDBへ追加してその結果問題なく何度も同じ値を取得できているかを確認したかったわけです。

実際、シード値を固定しているためランダム値は何度も同じ値を返すことは確認できました。
しかし、データベースからデータを取得しCSVへ吐き出した結果の差分をとると、何か所か結果が異なる部分がありました。

よく確認すると、データがおかしいわけではなく、単純に値の並び順が異なるだけであり、全く異なる値が入っているわけではないことがわかりました。

この時のSQL文は以下のような形でした。

[SQL]
select * from hoge;
[/SQL]

単純なselect文のみで、orderは指定していませんでした。

 

orderなし時の取得順について

これに関しては、SQL的にはどうにも指定されていません。
仕様等は見つけられませんでしたが、調べてみるとDBの実装依存となるようです。

Oracle Databaseの表データは、データベースへの挿入時の順序にかかわらず、特定の順序では保管されません。ある列の昇順または降順で行を取得するには、そうするようにデータベースに指示する必要があります。

Part 5:ソート順序 | ORACLE

結果の行の表示には特定の順序がないことに気付いたでしょう。

http://MySQL :: MySQL 5.6 リファレンスマニュアル :: 3.3.4.4 行のソート

きちんと書かれているわけではありませんが、順序を指定しない場合は特別何らかのソートがなされるわけではないようです。

MySQLでORDER BYがついていないときに返す結果セットの並び順は定められていない。

MySQLでORDER BYをつけないときの並び順 – かみぽわーる

ブログ等を見てみると、どこも同じようなことが書かれています。
データの取得順は、追加順やどこかのカラム順となるわけではないようです。

 

まとめ

データを取得した順番を考慮しないような場合は問題ありませんが、何らかの順序をもとに処理を行う場合は、しっかりorder句で順番を指定しなければいけないことがわかりました。

 

今回はここまで。

おわり

Ruby

こんにちは、しきゆらです。
今回は、最近意図的に行っている「良いといわれているライブラリのコードを読む」一環として、
Ruby on Railsの便利メソッドの一つ「blank?」の定義を見てみます。

 

なぜこんなことしているの???

私は、これまでライブラリ等を使わずに自分であれこれコードを書いてきました。
しかし、自分の中でいいコーディングの方法が確立できているわけではないので、設計や記述が悪くスクラップ&ビルド状態でうまく作り続けることができませんでした。

そんな中、先輩から「一般的に良いといわれているコードに触れておくと勉強になる」ということを伺い、実践してみようと思い立ったわけです。
読む者は、良く触れるものであれば何かあったときに対応しやすそうだと思ったのでRailsを選びました。
コードが複雑であり、Ruby自体を拡張するというRubyの機能をフル活用しているっぽい印象もあったのも選んだ理由ですね。

 

本題

ここから、実際に定義を見てみます。

今回見ていくメソッドは、ここにあります。

ターゲット:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/object/blank.rb

 

blank?メソッドは、Active Supportの中で定義されていました。
Railsでよく使われる便利メソッドの多くがここで定義されているようです。
Rubyで定義されているメソッドを拡張したり、あると便利な機能をオープンクラスを使って拡張したりと凶悪な側面もあります。

では、実際に中身を見てみます。

 

定義

[ruby]
def blank?
respond_to?(:empty?) ? !!empty? : !self
end
[/ruby]

定義はシンプルですね。
empty?メソッドが定義されていればそれを返し、そうでなければ自分自身の否定を返しています。
つまり、基本的にはempty?と似たような動きをしています。

「自分自身の否定」というのは、例えばRubyの場合は0もtrueとして扱うので「!0」はfalseとして返します。
Rubyのオブジェクトでfalse扱いになるのはfalseとnilだけであり、それ以外のオブジェクトはtrueとして扱われています。

挙動としては、メソッド名の通り「""」のような空文字や空配列など空白であるときにtrueを返します。
便利なところとしては、nilクラスにも定義されているので便利。

[ruby]
class NilClass
  def blank?
    true
  end
end
[/ruby]

NilClassと同様に、TrueClassやFalseClassにも定義されています。
また、ArrayやHashではempty?メソッドのaliasとしてblank?を定義しています。
つまり、どのクラスでも同じメソッドで呼べるようになっています。

注目すべきは、このblank?メソッドはObjectクラスで定義されているということですね。
Rubyで定義されているObjectクラスはすべてのクラスの親クラスなので、どのクラスでも使えるようになります。
便利ですね。

 

オープンクラス

どういうものか、ということは調べるとたくさん資料が出てきますが、簡単に言えば以下の通り。

オープンクラスとは、言語仕様として、クラスや継承やミックスインといった手続きなしに、既存のクラスを自在に拡張するようなコードを書けることを示します。

難しいが強力! Rubyのメタプログラミング、self、特異クラス/メソッド、オープンクラスとモンキーパッチ – @IT

ということで、ArrayやStringなどのRubyが提供しているクラス自体に独自に機能を追加することができるわけです。
頑張れば、メソッド定義などを動的に行うようなこともできてしまいます(いわゆる黒魔術)。
なお、Rubyの+や%など、ほかの言語では演算子と呼ばれるものもメソッドとして定義されているため、安易に上書きするとRuby自体を壊すこともできてしまいます。

Active Supportさんは、この強力であり怖さもあるオープンクラスを利用して、便利機能を追加しているわけです。

 

関連するメソッド

「blank?」に関連して、オブジェクトの存在確認に使われるメソッドとして「present?」があります。
このメソッドの定義も見てみましょう。

定義

[ruby]
def present?
  !blank?
end
[/ruby]

という感じで、blankでなければtrueを返します。
これもObjectクラスで定義されているために、すべてのクラスから利用することができるようになっています。

 

まとめ

さて、今回はRailsさんのコードを読んでみました。
本来であればactive_support.rbから読むべきなんでしょうが、処理が複雑だったり知らない書式が多いと先に進まないので簡単そうな部分から読んでみました。

メソッドの定義自体はシンプルなものであり、どこからでも同じように利用できるように工夫されていました。
こういうものを見ると、便利メソッドでも案外簡単な記述で書かれていることがわかりました。

また、Active Supportのコードを見てみると、実際の記述よりも説明に関するコメントが多いことがわかります。
どのような挙動をするのかを知ることができるので便利です。

今後も、不定期だとは思いますが適宜コードを読んでいこうと思います。
面白そうなものがあれば、またまとめます。

 

今回はここまで。
おわり

Linux,Windows,環境構築

こんにちは、しきゆらです。
Railsと戯れ始めたわけですが、MySQLを動かすために結構時間がかかってしまいました。
serviceで自動起動する方法がわからず、毎回service mysql startとして起動しています。

さて、今回はserviceコマンドを使ってMySQLを立ち上げようとしたときにおこった問題と解決方法をメモしておきます。

 

Ruby,Windows

こんにちは、しきゆらです。
今回は、Railsさんと仲良くなるための施策として、公式のチュートリアルを進めながら理解を深めていこうと思います。

Railsと戯れることが多いのですが、まだまだ仲がいいとは言えません。
デプロイで苦戦した記憶があるのと、Rails流のお作法をきちんと把握しきれていないことが要因です。
そこで、手軽にできて楽しそうなRailsチュートリアルを一通りふれながらRails流のお作法を学んで行きます。

今回は、Rails Guidesにある「Railsをはじめよう」というものをやっていきます。
基本はガイドにある通りですが、一部違うことをすることがあるかもしれません。
この時はきちんと記載します。

 

0. なぜ本ではなく無料のガイドを使うの?

Railsに強い先輩に、良さげな本はないかを聞いたところ、本よりも公式情報のほうがよい、ということを伺いました。
実際、少し見てみるとブログや技術系サイトの情報よりもわかりやすかった気がします。

あとは、大きい要因としては公式情報なので信用できるはずだからです。
発売されている本は、間違っていないか等をきちんと調査しているとは思いますが、動かなかった場合

  • 自分の環境・記述が悪い
  • 本の内容が間違えている
  • Rails、関連するライブラリのバグ

等々様々な要因が考えられるわけです。

たいていの場合、自分の環境や記述が間違えていることが多いですが、本の内容自体が間違えていることも意外と多くあります。
また、本の場合は情報の更新がされないので、本革のミスの場合は正誤表を確認しながら作業を進めないといけません。
これは面倒ですよね。

その点、Rails Guidesは(ほぼ)公式情報である上に、きちんと最新情報も書かれているようです。
安心感が違いますよね。
一応、公式サイトや定義を確認しようということもいわれたので、公式のものを使っていきます。

では、実際にガイドをもとに作業を進めていきます。