Tallman

技術とか読書とかいろいろ

rails consoleに簡単にオプションを足せるようになるgemを作った

作ったgemはこれです

github.com

使い方

config/application.rbに下のようにオプションをセットします。
Rails::Applicationの上に書くようにしてください。

Bundler.require(*Rails.groups)

D4C::Console.add_option 'hello' do
  puts "hello"
end

module YourRailsApplication
  class Application < Rails::Application

これでrails consoleに渡せるオプションを追加できました。実際にオプションが呼ばれると渡したブロックがcallされます。

rails c --hello
Running via Spring preloader in process 7922
hoge
Loading development environment (Rails 5.2.3)
irb(main):001:0> 

なんで作ったか

rails consoleを叩くときに、「デフォルトで実行されたくないけど実行したいときはさっと実行したい」処理があったからです。
職場でact_as_tenantというgemを使っています。dbのレコードに制限をかけるgemです。
act_as_tenantの都合上、rails consoleからdbのレコードを叩くには

ActsAsTenant.without_tenant do
end

として作業しなくてはなりませんでした。
とはいえRailtieconsoleメソッドでせっかくかけたセキュリティをデフォルトで外してしまうことはしたくありません。
--sandboxみたいにオプションで選択できると良いと考え、このgemを作りました。 1

実装の話

そもそもrails console --sandboxをした時の流れ

bin/rails console自体の具体的な流れはRailsガイドにとっても詳しく書いてあるので読みましょう。

railsguides.jp

では--sandboxはどうやって処理されているのでしょうか。 (注 Railsのコードを載せていますが色々省いています。)

Rails::Command::ConsoleCommandクラスは下のようになっています。

      class_option :sandbox, aliases: "-s", type: :boolean, default: false,
        desc: "Rollback database modifications on exit."

      def perform
        Rails::Console.start(Rails.application, options)
      end

受け取れるコマンドラインのオプションをclass_optionで追加しています。
Rails::Command::ConsoleCommandはThorというコマンドラインツールの作成を助けるgemを継承しています。class_optionはそのgemのメソッドです。

Rails::Applicationクラス内では--sandboxが入力されたかを保存できるようにしてあります。

    attr_accessor :assets, :sandbox

Rails::Consoleクラスでは、コマンドラインから渡されたオプションの有無をアプリケーションのインスタンスに渡しています。

    def initialize(app, options = {})
      @app     = app
      @options = options

      app.sandbox = sandbox?
    end

    def sandbox?
      options[:sandbox]
    end

これによりif app.sandboxで条件分岐できるようになります。

実際にD4Cで行った拡張

module Rails
  Application.prepend D4C::PrependOptionAccessor
  Console.prepend D4C::PrependOptionSetter
  Command::ConsoleCommand.prepend D4C::PrependClassOption
end

D4CのmoduleはD4C::Console.add_optionに渡されたオプションをRailsのクラスが扱えるようにするものです。 それぞれprependしてmoduleの中でsuperすることで処理を足しています。
D4C::Consoleの中身はオプションを構造体にして保存するクラスのインスタンスです。

Gemの名前

ジョジョの奇妙な冒険のスタンド名です。
最初はbaby_faceって名前でした、コンソールなので。
ディ・モールトベネなgemの名前だと思いながら作成して、いざ公開しようと思ったら同じ名前のgemが既にあって泣きました。皆さんはこうならないようにちゃんとrubygemsで名前を検索してから作りましょう。
最終的にたやすくオプションを足せるってところからD4Cにしました。
最後にこれが一番書きたかったんですけど、D4Cってまさに「Ruby」だと思うんですよね。

f:id:sasa5740:20190819222624p:plain


  1. ruby-jpでgemを晒したら「pryrcirbcに書くのも手ですがよりチームでコミットしやすいメリットもあるかも。」というお言葉もらいました。pryrcirbc書くのも手ですね。ありがとうございます。ruby-jpは本当に神。

クラスメソッドをPryのコマンドにできるGemをリリースしました

作ったGemはこれです

github.com

使い方

まず.pryrc内で下のようにクラスをセットします。

PrySingular.make_commands HogeJob, FactoryBot

これでPryの中で設定したクラスの特異メソッドをコマンドとして使うことができます。

pry(main)> perform_now

引数を渡すこともできます(あくまでコマンドなので引数との間はスペースが必要)

pry(main)> create :user

コマンドにしたいメソッドを直接指定することもできます。

PrySingular.make_commands FactoryBot, only: [:build, :attributes_for]

コマンドにしたくないメソッドを指定することもできます。

PrySingular.make_commands FactoryBot, except: :create

コマンドなのでObjectを汚してメソッドを生やすことはありません。

[1] pry(main)> self
=> main
[2] pry(main)> self.respond_to?(:create)
=> false

また、内部ではsingleton_methods(true)を使っているので、classとかrubyのコアな部分まではコマンドを設定しません。

f:id:sasa5740:20190715124804j:plain
超絶怒涛の神issueとPRを立ててくれた@fursichさんありがとうございます!*1

作った理由

FactoryBotのcreateとかActsAsTenantのwithout_tenantとかをいちいちrails cの中で書くのめんどい〜みたいな話が周りで起こり、.pryrcの話がにわかに盛り上がりました。そのとき職場の人がPry::ClassCommandでゴニョゴニョといっていたのを聞いたのが始まりです。
最初はFactoryBotとPryを連携させたいな〜と思って作っていたのですが、書いていくうちに「これクラス設定してもらって全部できるようにしたほうが便利だな」と思って実装してみました。

実装

コアな部分はここです

def import_class_command(klass, options)
  singular_methods = adapt_options_singleton_methods(klass, options)
  set_pry_command do
    singular_methods.each do |klass_method|
      command "#{klass_method}", "#{klass}.#{klass_method}" do
        extend PrySingular::Slop
        klass.class_eval <<-EOS
          #{parse_singular_method_command(Readline::HISTORY.to_a.last)}
        EOS
      end
    end
  end
end

def set_pry_command(&block)
  commands = Pry::CommandSet.new &block
  Pry.config.commands.import(commands)
end
module PrySingular
  module Slop
    def parse_singular_method_command(items)
      method, args = items.split(" ", 2)
      method + ' ' + args.gsub(' ', '')
    end
  end
end

commandはStringオブジェクトをメソッドの名前、メソッドの説明の順に引数に取って、コマンドが呼ばれたらブロックの中身を実行してくれるPryのメソッドです。
仕組みとしてはコマンドが呼ばれたらReadline::HISTORYの最新のものを一度メソッドと引数に分けてから引数の空白を削除し、対象のクラスをレシーバーにclass_evalして直接実行しています。

pry(main)> create :user

と呼ばれたら最終的に

FactoryBot.class_eval <<-EOS
  create :user
EOS

が実行されるといった具合です。 なのでコマンドの実装の中身は全て、クラスにReadlineから受け取った文字列をズルっとわたしているだけです。

Gemを作ってみて

Gem作成は初めてでしたが、RubyGemsのおかげで簡単に公開することができました。
PryのDSLの柔軟性にも助けられましたし、オプションの設定方法もRailsのパクリです。 巨人の肩は高い 。
反省点としてはリリースしてはだめじゃんを繰り返して既にversionが0.1.7なことですね…
まだまだテストもカバレッジ足りないしリファクタもできると思うし、そもそもまだバグもあるかもしれないので、よいGemにできるようやっていきです。
よかったら使ってみてください、バグとかこれがあったら面白いとかあればissueやコメントで教えてくれたら泣いて喜びます。

2019/07/21 追記

インターフェースをmake_commandsにしたり内部のコードいろいろかえました

*1:ブログに乗せることも快く了承いただき本当にありがとうございます

個人開発用マシンをThinkPad X1 Carbon with Ubuntuにしました

経緯

個人の開発マシンはプログラミング始めた去年の6月にMBP2014の13インチをヤフオクで落札して以来ずっとそれを使っていたのですが、5月末くらいにコーヒーを盛大にこぼしてトラックパッドが使用不能に…。
最新のMBPの13インチか迷ったけどLinux使ってみたかったのでThinkPad x1 CarbonにUbuntu入れて使うことにしました。
ThinkPad x1 Carbonにした理由は

です。いろいろいれたら17万くらいになっちゃいました。

スペック

  • Coffee Lake Core i7-8550U
  • 16GB RAM
  • 14.0型WQHD液晶
  • M.2 SSD180 (ぶっちゃけ200もいらないので減らした)
  • 英字キーボード

こうみるとそんなに盛ってないですね

ふぁーすと・いんぷれっしょん

f:id:sasa5740:20190708001839j:plain
ロゴがかっこいい
カーボンの天板の高級感と手触りは◎。キーボードも噂に違わぬ押しやすさです。 そしてなんといっても軽量です。前回のMBP2014が13インチが1.57 kg、職場のMBP2017の15インチが1.83kg。比較してThinkPad X1 Carbon G6は1.13kg。持つとわかりますが本当に軽いです。あと端子がまとも
スピーカーが背面にあるのだけ悲しい

Ubuntu入れてみて

ThinkPadLinuxってポピュラーな使い方だし大丈夫だろ〜と思ってたのですが結構苦労しました…
トラックパッドトラックポイント(赤ポチ)が反応せず、ググりまくって色々試してもどこか治るとどこか動かなくなる状態で途方にくれていました。
結局18.04から19.04にアップデートしたら治ったので良くわからない。 18.04でも使いたいって人がいたらこれをいれるのがいいかと思います。 https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1791427/comments/94
スリープ状態でもバッテリーがガンガン消費される問題もありました。BIOSLinux用の設定に変えれば解決しますが。
現在もbluetoothの音質がかなり悪く、なんとかならんかと試行錯誤しています。

Macとの違い

そんなにないです。⌘キーって便利だったんだなぁというくらい。 tweak tool入れてaltをctrにするといい感じ。現状はかなり快適です。
Dockerいれるのに19.04だと公式通りにやってもうまく行かなくてちょっとハマりましたが、Dockerそのものは明らかにMacより軽いです。キビキビ。 ワークスペースやウインドウの管理なんかも楽ですしスムーズです。
とりあえず入れて正常に動くことを確認したもの

chrome入れれば環境はほとんど同期したも同然だし、シェルもfish使ってるのでチョチョっとプラグイン入れるだけですみました。 今はlinuxbrewもあるのでこれからも困ることはそんなにないかと思います。

f:id:sasa5740:20190708002905p:plain
テーマもデフォルトで十分いい感じだと思います

まとめ

やっぱり黒一色っていいですね。秋葉に黒い人が多いのもしょうがない。
Linuxのしくみ」とかこれを使って実践しながら読んでいきたいと思います。
後いざThinkPadで競プロや!ってABCでたらB以降さっぱりできなくて泣いた。

SQL実践入門を読みました

SQL実践入門──高速でわかりやすいクエリの書き方 (WEB+DB PRESS plus)

SQL実践入門──高速でわかりやすいクエリの書き方 (WEB+DB PRESS plus)

SQLアンチパターンや失敗から学ぶRDBの正しい歩き方を読んでいて、ウインドウ関数や結合などまだまだ浅い理解だなーと思って買いました。
以下、気になったことと感想を書きます。

where句で条件分岐するのは素人、プロはSELECT句で条件分岐する。

どういうことかというと 例えば 名前、性別、部署を持つテーブルがあったとして

社員名簿テーブル

名前 性別 部署
太郎 営業
花子 営業

男性と女性人数を部署ごとに出したいとき whereを使うと

SELECT  部署, 
COUNT(*)
FROM 社員名簿
WHERE 性別= ''
GROUP BY 部署;

SELECT  部署, 
COUNT(*)
FROM 社員名簿
WHERE 性別= ''
GROUP BY 部署;

という感じでUNION使っても二回テーブルスキャンしなくてはいけないところを

SELECT  部署, 
  SUM( CASE WHEN sex = '' THEN 1 ELSE 0 END),
  SUM( CASE WHEN sex = '' THEN 1 ELSE 0 END)
FROM 社員名簿
WHERE 性別= ''
GROUP BY 部署;

というように書くことができます CASE式はWHENに当てはまればそこで評価を終了するので先程のWHERE句での分岐より処理も早いです。

Joinの際に駆動表はなぜ小さくするのか

ネステッドループで結合する際に、駆動表はフルテーブルスキャンが行われますが内部表はインデックスが使われます。
どうせフルテーブルスキャンするなら小さいほうが良いし、大きなテーブルスキャンするならインデックス使ったほうが良いので、駆動表は小さいほうがいいんですね。
ハッシュで結合するのは結合するテーブルが同じくらい大きいサイズのときにも有効ですが、メモリを大量に消費するので注意ともありました。

ぐるぐる系とガツン系

ぐるぐる系というのはいわゆる繰り返し処理(ループ)のことです。 一行ずつレコードを引っ張ってきて簡単な処理する、という行為のことですね。
対してガツン系はCASEやウインドウ関数等を駆使して処理を一回で記述してしまうことです。
シンプルでトランザクションの制御が容易だが、チューニングしにくくパフォーマンスもでにくいぐるぐる系と、チューニングしやすくパフォーマンスもでやすいが、トランザクションの制御やオプティマイザに委ねる部分が大きいガツン系か、そのトレードオフを認識する必要があるとのことです。
もともとSQLはループをできるだけ排除したい、という思想の元で生まれたそうで、手続き型言語になれている人ほど処理を全部自分で書いてループさせがちなので、ガツン系のメリットをしっかり知っておくのが重要です。

インデックスが使えないとき、役にたたない時

「失敗から学ぶRDBの正しい歩き方」のありましたが、

  • IS NULL

  • 列に直接演算

これらで検索するときはインデックスが使えません。
なぜかというと、インデックスの中に存在する値はあくまでそのセルの中にある値であって、演算後の値でも、NULLでもないからです。
選択率が高い(取り出すレコードがテーブル全体に対して割合が高い)ときも要注意です。

感想

SQLの当たり前に使われている用語に対して説明、考え方が乗っている良書でした。
ウインドウ関数や結合についてはなんとなくで使っていることが多かったので、クロス結合やパーティショニングのカットのイメージから説明してくれる本書はありがたかったです。 また全体を通してトレードオフというものを意識して書かれてあるとも感じました。
それぞれのアルゴリズムや手法にメリットデメリットが書かれていて、それを把握した上で活用するのが大事だということが繰り返し述べられていた印象です。

SpotifyAPIとRuby2.7の新機能パターンマッチングで人気のある曲だけをフリーワードから検索して出力するスクリプトを書いた

Rubykaigi2019にてパターンマッチングについての発表がありました。

speakerdeck.com スライドにもある通りAPIからもらったJSONを扱うのに便利と感じました。
そこで実際に解析するスクリプトを書いてみたのがこちら。 github.com

スタンドの名前つけるの楽しいしモチベ上がるのでおすすめです。

Echoesのやること

Spotifyは内部で曲やアーティストにpopularityというスコアを100点満点でつけています。
Echoesはフリーワードで公開されているplaylistを検索して、その中に含まれているpopularityが90点以上のスコアを持ってる曲を出力してくれます。

f:id:sasa5740:20190522223424g:plain SpotifyAPIで提供されている検索機能を利用しています。
入力されたワードで検索して、受け取ったJson形式のレスポンスをパターンマッチングを使って解析しています。

playlists.each do |playlist|
  tracks = spotify_client.get_tracks_from_playlist(playlist)
  tracks[:items].each do |item|
    100.downto(90).each do |popularity|
      case item
      in { track:  { name: name, popularity: ^popularity } }
        result[name] = popularity
      else
        next
      end
    end
  end
end

具体的にパターンマッチング使ってるのはこの部分

    100.downto(90).each do |popularity|
      case item
      in { track:  { name: name, popularity: ^popularity } }
        result[name] = popularity
      else
        next
      end
    end

^popularityの部分は既に定義されている変数をパターンマッチに使いたいときに使う記法です。今回は100から90までのIntegerインスタンスを突っ込んでます。
elseも書かないとNoMatchingPatternErrorという例外が起きてしまうので注意。(このErrorいい感じになってほしい)
スコアがpopularity点だった場合にresultハッシュに突っ込んでいくことをやっているのですが、パターンマッチングを使うと変数に値を格納するのも直感的にかけます。
これがパターンマッチング使わないと

    100.downto(91).each do |popularity|
      if item[:track][:popularity] == popularity
        name = item[:track][:name]
        result[name] = popularity
      end
    end

という感じになります。今回はそれほどでもないですが、条件追加するごとにどんどん条件分岐が深くなっていくやつですね。

終わりに

パターンマッチングを使うと条件分岐もパターンでシンプルに設定し、かつ変数に値も入れてくれます。
こんな感じの単純な使い方以外にもクラスに構造を追加してパターンマッチングしたり色々遊べそうです。
実際遊んでる記事を上げておきます。

tech.medpeer.co.jp

developer.feedforce.jp

Ruby 2.7 楽しみですね

追記

@k_tsjさんにこう書くといいよアドバイスいただきました。

playlists.each do |playlist|
  tracks = spotify_client.get_tracks_from_playlist(playlist)
  tracks[:items].each do |item|
    case item
    in { track:  { name: name, popularity: 90..100 => popularity } }
      result[name] = popularity
    else
      next
    end
  end
end

rangeオブジェクトとASパターンを組み合わせるともっとわかりやすい:eye: すごいクールになりました。ありがとう御座います。

プロになるためのWeb技術入門を読みました O/Rマッピングフレームワークの目的

 先月、基本情報技術者試験をうけました。(受かってるといいな) 

午後の問題は現代文の試験かな?というのもありましたが、CPU、メモリ、仮想メモリページングとかアルゴリズム秘密鍵、公開鍵、プロトコルあたりは基礎体力をつけるのにいい内容が範囲に含まれていたかなと思っています。

その流れでWebサービスの技術のあやふやが多いな〜と感じたのでこちらの本を読みました。

「プロになるためのWeb技術入門」 ――なぜ、あなたはWebシステムを開発できないのか

「プロになるためのWeb技術入門」 ――なぜ、あなたはWebシステムを開発できないのか

 

HTTPやCookie、Sessionといった基本的なことについても丁寧に書かれていたのですが特に関心を持ったのは、O/Rマッピングフレームワークについての部分です。

日々O/RマッピングフレームワークとしてActive Recordを利用しているくせに、そもそもなんでO/Rマッピングフレームワークってあるの?というのはあやふやでした…

O/Rマッピングフレームワークの目的

本書ではRDBオブジェクト指向での表現をイメージで示したあとに

このようなリレーショナル・データベース上の表現と、オブジェクト指向言語におけるオブジェクトによる表現の違いを「インピーダンス・ミスマッチ」と呼んでいます。 

(中略)

このインピーダンス・ミスマッチを解決することが、「O/Rマッピングフレームワーク」の最大の役割になります。

として、RDBオブジェクト指向設計の構造上のミスマッチを解決するためにO/Rマッピングフレームワークを使っていると書かれています。 

オブジェクト指向での構造の表現は基本的に階層構造です。 オブジェクトは主従関係を持っています。対して、RDBの表現はというと、ちょっと自分ではうまく言語化できなかったので、こちらの記事から引用します。

インピーダンスミスマッチについて本気出して考えてみた - 酔いどれ設計ナイト2019 - Qiita

  • リレーショナルモデルは言うなれば項中心の世界で、その項が何者であるかはその定義域(ドメイン、データ型)によってのみ決まり、項の間に優劣や主従はない。
    • 項の間の関係はリレーショナル演算によって見出され、逆に言うとそれによってのみ項間の関係は決まる。

外部キー制約とかありますが、確かにRDBの表それ自体には明確な主従性はないですね。 

この表現のミスマッチを防ぐために登場したのがO/Rマッピングフレームワークということです。データベースとオブジェクトの対応を定義して、本来オブジェクト指向での表現で扱いづらいRDBのデータをオブジェクト指向に落とし込んでくれているんですね。

そもそもこの2つの表現の差異について考えたこともなかったので、自分はRDBに階層的なイメージを持っていたと思います。O/Rマッピングフレームワークを通してしかRDBのことをみていなかったとも言えますね…

Active RecordはO/Rマッピングフレームワークとしてなにが優れているのか

本書とは直接関係ないですが、Railsエンジニアが常に意識しているActive Recordというフレームワークは何が優れているのでしょう。

「プロになるためのWeb技術入門」ではO/RマッピングフレームワークとしてiBATISというフレームワークが紹介されています。SQLマップファイルというDBのデータとオブジェクトの対応と実際に発行するSQLを書いています。SQLを明示的に書くことで細かい注文にも対応できるよう設計されています。  

それに対してActive Recordは命名ルールやスキーマのルールに従う必要がありますが、設定用コードは最小限ですみます。レールにのっていればSQLをそれほど意識しなくてもO/Rマッピングフレームワークの恩恵に預かれるということなんですね。

まさに設定より規約を重視した設計でRailsらしいと言えます。*1もちろんこれに甘えずSQLおよびRDBの知識は絶対に必要だとは思いますが。

 

最後に

今回のブログではO/Rマッピングフレームワークについての部分だけ取り上げましたが「プロになるためのWeb技術入門」は様々なWebアプリケーション開発の技術が”なぜ”あるのかを丁寧に説明してくれる本でした。Webアプリケーションの歴史やHTTPからアプリケーションのアーキテクチャ、セキュリティにまで幅広く一冊で言及している本はなかなかないのではないでしょうか。根本の仕組みと考え方が大事ですね。なんでもそうですけど。

大規模サービス技術入門を読みました。ついでにRubyでVB Codeを実装してみた

大規模技術サービス入門という本を読みました。扱うデータの総量が大規模になってきたときに想定される問題、その問題に対処する具体的な方法がふんだんに書かれています。
株式会社はてなさんでやっている(いた?)インターンシップの内容がベースになっています。そのため、ある程度複雑な考え方や用語等も説明やイメージを織り交ぜて記載されています。

重要だと思った点は以下の3つです。

  • メモリ、OS、IOの動作といった低レイヤの知識
  • DBやアプリケーションのサーバーの構成
  • 計算量を減らすにはアルゴリズムの知識が必要

メモリ、OS、IOの動作といった低レイヤの知識は重要

まず本書の最初には、OS、メモリ、仮想メモリLinuxのページキャッシュ、そしてそれらにおける具体的な速度について丁寧な解説がありました。
「メモリ内で計算できないこと」が、いかにパフォーマンスに影響するかが具体的にイメージできるように解説されていました。
ディスクI/Oが多くなると重くなる。メモリの消費は少ないのがベターです。
ちょうど基本情報を受ける前に読んでいたので余計刺さりました。

DBやアプリケーションのサーバーの構成

スケールが難しいDBサーバーをスケールさせるための構成。その他にもボットのための専用サーバーを用意したり、冗長性のためのDBサーバーのマルチマスタ等具体的な構成が数多く紹介されています。
また、手法に対してメリットだけでなくデメリットも紹介されていました。例えばDBのパーティショニングを使えばDBの分散やキャッシュの効率は高まりますが、その分運用は複雑になります。メモリを増やすで対応できないかを検討する必要もありますし、運用を別の技術で解決できないかを考える必要もあります。(今はクラウドが主流だとは思うので別の知識も必要そうですが)

計算量を減らすにはアルゴリズムの知識が必要

二分木探索、n-gramインデックスの具体的な利用方法が紹介されていました。
また、メモリにキャッシュしやすくするための圧縮や計算量が大きくなりがちな全文検索については詳細に解説されています。 実際にPerlで書かれたサンプルコードとともにVBCodeや転地インデックスといって手法をコードに落としこんでいるところまで書いてあります。
自分も圧縮のときに紹介されていたVBCodeについてRubyで実装してみました。自分のコードで圧縮されたファイルっていいですね。

github.com IOクラスとかシリアライザとかまだまだ勉強不足ですね。

まとめ

大規模サービスについて想定される問題についての現実的な解決策が惜しげもなく公開されている良書でした。 一番なるほどと思った部分を引用します。

結局、原因がわかればその原因に対する対応方法は自明なのです。この自明になった対応方法を実践することが、チューニングにほかなりません。

サービスが大きくなってきたときにボトルネックになっているのはなんなのか、メモリにキャシュしきれてないのか、アルゴリズムが悪くディスクI/Oが多発しているせいなのか、はたまた外部のボット等全く違う要素なのか、正確に判断することが重要です。
そのためにはアプリケーションがどのように動いているか?という基本的かつ全般的なコンピューターやインフラの知識が必要だと感じました。
低レイヤやインフラだけでなく、ボットやリクエストの多いAPIに対して専用のサーバーを用意するといった、局所性を理解した構成のためには、サービスの理解も重要です。サービスを提供するって大変ですね。