ham-capのブログ

プログラミング学習の記録

Vimが好きだ

これはフィヨルドブートキャンプ Part 2 Advent Calendar 2022の7日目の記事です。

昨日はfuwa-syugyoさんの「輪読会はいいぞ、という話」でした。 ちなみにフィヨルドブートキャンプ Part 1 Advent Calendar 2022もあります。

目次

はじめに

せっかくのアドベントカレンダーですし僕もいろいろ考えたのですが、やっぱり自分が好きなものについて書かないと熱量が足りないよなぁということで、今回は自分が好きなものの中から技術に関連することについて好きなように書こうと決めました。

というわけで、今日は僕がフィヨルドブートキャンプのプラクティス(課題)でコードを書く際に使用しているテキストエディタVimの話をします。

この記事の方向性について

まず、この記事ではVimで使用できる具体的なコマンドの詳細までは深く立ち入りません。

なぜなら、ちょっとググれば僕のような若輩Vimmerでは及びもつかないようなVimマスターの方々の手によって生み出されたノウハウ満載の超有益記事が大量に見つかるからです。

なのでこの記事では、僕が思うVimの魅力を言語化することに集中します。 そもそも好きなことを好きに書くのが今回の個人的な趣旨でもあるので。

そうすることで僕と同じようにWeb開発に興味を持って勉強中の人や他のエディタを使っている人がVimに興味を持ってくれればそれはそれで嬉しいです。

一応、Wikiのリンクと基本的な説明がわかりやすいページだけ貼っておきます。

Vim - Wikipedia

はじめてのVim 〜 Vimはいいぞ!ゴリラと学ぶVim講座(1) | さくらのナレッジ

前置きが長くなりましたが、次から本題です。

そもそもなんでVimなん?VSCodeじゃダメなん?

ダメじゃないです。

むしろ、もし今VSCodeに限らずVim以外のエディタを使っていてなんの不満も無い!快適だ!と思うのであればわざわざVimに乗り換えるなんてナンセンスだと思います。はっきり言って面倒なだけですのでやめておきましょう。

今使っているエディタも悪くはないんだけどちょっとこの辺がなぁ…みたいな人や、自分の欲しい機能だけあれば余計なものはいらないという人、小綺麗な見た目がどうもしっくりこないんだよな、という人なんかはVimの中に答えがあるかもしれませんしないかもしれません。

僕がVimを使い始めた理由

ちなみに僕がVimを使い始めた理由は以下のとおり。

  • フィヨルドブートキャンプのカリキュラム序盤で触ってみてなんか良かったから
  • Microsoft社製品のおせっかい機能が腹の底から嫌いだから
  • ギークっぽくてかっちょいいから

今はVimの好きな部分がいろいろありますが、使い始めるきっかけはこれだけでした。

特に最後のは重要です。

エディタに限らずですが、自分にとってかっちょいいとか可愛いとか、使っているだけでテンション上がるとか、そういう要素がないとやってて面白くないです。

Vimのここが好きだよ!

ということで、Vimのいいところ、というか個人的お気に入りポイントを書いていきます。

ワープの使い手になれる

Vimはカーソル移動のためのコマンドがめちゃくちゃ充実していて、覚えれば覚えるほど素早く目的の箇所に辿り着くことができるようになります。また、コマンドと言っても長ったらしい呪文の詠唱は不要で、大抵はキーを何個か押すだけで済みます。

場合によっては初手でワープして、すぐにテキスト入力ができる状態になっていて、しかもそれがホームポジションから出ることなく完結しているということもザラにあります。

そうなると、わざわざ矢印キーやマウスに右手を移動させるのが億劫になってくるわけです。

マウスにいたっては近所のコンビニぐらいの距離ありますからね(体感)

画面スクロールなんかもなれるまではちょっと困惑しましたが、今ではマウスのホイールをジャッジャッ!ってやつもVimを使っている時にはほとんどやらなくなりました。

こういうことができるのは、Vimが移動やコピペ、範囲選択、テキスト入力、ファイル自体の操作をそれぞれ別のモードが担当し、どのモードにいるかによってキーバインドが全く異なるという特殊な仕様のおかげだと思うのですが、その特殊さが仇になってVimをとっつきにくいエディタにしてしまっているのも事実なんですよねぇ。

テキストオブジェクトが便利

Vimにはテキストオブジェクトという概念があります。(Vimだけじゃないかもしれないけど。)

これは何かというと、テキストのある範囲を一つの塊(オブジェクト)としてVimが認識してくれて、そのオブジェクトに対して操作ができる、という理解でいいと思います。

これを使うと、例えば''で囲まれた文字列をまとめてコピー/削除/置換したりということがかなり簡単にできます。 範囲については指定の仕方で1単語だったり文全体だったりします。

ちょっと分かりにくいと思うので実例を。

たとえば、''で囲まれた文字列をまとめて削除するときはdi'(Delete Inner ')と打てば一発。

一つの単語だけ消したいならdaw(Delete A Word)

プラグインを使えば置換も一発。僕の場合は置換を?に割り当てているので?aw

これだけだとちょっと分かりにくいかもしれないですが地味に便利。 もし置換ではなく自分で何か入力したい場合は、ci'(Change Inner ')とかcaw(Change A Word)のようにオペレーターをcに変えれば対象を削除後に自動的にインサートモードに入ってくれるのでスムーズに入力を開始できます。

リピート

Vimの機能の中にリピートという機能があります。 直前に行った操作を繰り返すというだけの単純な機能ですが、Vimではこれを.を押すだけでできます。

何が嬉しいかというと、例えば上で紹介したようなdi'などはキーを3つ叩いて入力していますが、その入力の直後であれば、対象となる単語まで移動して.を叩くだけで同じように''の内側を削除できます。

キー3つが1つになっただけじゃんと思うかもしれませんが、要は同じ操作が三分の一の労力でできるということなので、編集するコードの量が増えれば増えるほど恩恵を受けられることになります。

道具を使ってる感が楽しい

個人的にはこれが最大の推しポイントです。

あくまで感覚の話になってしまうのですが、コードを書いている時に4つのモードを行ったり来たりしながらあっちこっちにワープしてバッサバッサとまとめて編集していると、エディタでコードを書いているというよりは、何かの道具を使って工作でもしているような気分になって非常に楽しいです。

使えば使うほど手に馴染んできて更に使いやすくなっていく感じも、物理的な道具の扱いに習熟していくことと本質的には同じような気がします。

何かを使いこなすって楽しいんですよね。

Vimのここがよくないよ!

次にイマイチなところいってみましょう。

学習コストが高い

一番の欠点はなんと言ってもこれです。

とりあえず効率とかは一旦考えなかったとしても、各種モードの役割と切り替え方法、オペレーターとモーションの概念ぐらいは把握しておかないとまともにファイルの編集すらままなりません。

なんなら最初は上書き保存のやり方をググることになります。泣けてきます。

しかもVimの操作は思ったよりも奥が深く、多少覚えてももっと効率的なやり方や、上級者が使うような高度なテクニックをたくさん発見できてしまうので、胸を張って扱いに習熟したと言えるポイントが一向に見えてきません。

ただし、よく使うオペレーターとモーションの組み合わせやVimっぽい編集の手順なんかが手癖になってきたあたりで経験値が閾値に到達して一気に扱いやすくなるような気がします。(僕は最近やっとこれを感じ始めたところです。)

学習曲線が長く低空飛行を続けた後、極端に上昇していくイメージを持ってます。(当然個人差はありますが。)

カスタマイズが事実上必須

これも人によっては厳しいんじゃないでしょうか。

なんというか、Vimは大変に無愛想なやつでして、初対面では非常に素っ気ないです。

そもそもの見た目からして、縦に移動できるようになったターミナルって感じです。色もなんか変。たしか最初は行番号すらなかった気がする…。

どこにでもジャージとサンダルで来るようなやつです。

機能も同様です。例えば、入力の補完機能なんかはあるにはあるのですが、他のエディタが当たり前にしてくれるような親切心とかは微塵もありません。

この辺りでもう嫌になっちゃう人多数かと思います。

ただし、Vimはかなり素直なやつでもあります。

どうして欲しいかをしっかり伝えればちゃんとしてくれます。

具体的にいうと、.vimrcという設定ファイルにVimにしてほしいことをひたすら列挙したり、持っていてほしい便利道具(プラグイン)について書いておくことで、見た目も機能もどんどん自分の理想に近づけていけます。

子供の頃ガンダムのプラモデルなんかを素組みでは満足せずに塗装したりパーツを付け替えたりして改造していたタイプの人は楽しめると思います。

これ、捉えようによっては美点でもあるような気がします。

思わぬ副作用がある

これも個人差がありそうですが、Vimの扱いに慣れれば慣れるほど、Vim以外でのテキスト入力時にちょっとした支障が出始めます。

例えば僕の場合、Vimの使用中はテキストを入力している時以外、基本的にノーマルモードにいたいため、入力が終わるとすぐにインサートモードからノーマルモードに戻る操作(Ctrl + [)が手癖になっているのですが、このブログなどのようにVimを使わずに文章を書いている時にもこの手癖が暴発します。割と頻繁に。

今もここまで書き進めてくるあいだに何回Ctrl + [の素振りをしたか分かりません。

範囲選択もついvを押してしまったり、空行をいれたくてoを押してしまったりします。

日本語の間に不自然に挟み込まれたvoyyには哀愁すら感じます。

じゃあ文章書くのも含めて全部Vimでやったらええやんと思われる向きもあるかと思いますが、これには理由があります。次のよくないポイントです。

日本語入力と相性が悪い

これについてはVimを使用したことがない人には非常に伝わりにくいかと思います。

どういうことかというと、日本語入力の状態だとVimがコマンドを認識してくれません。 ノーマルモードからインサートモードへ移行するにはiを押す必要がありますが、日本語入力時はiではなくになってしまい、Vimが正しく認識しません。他のコマンドもすべて同じです。詳しくはわかりませんが、そもそも全角がダメっぽいです。

こうなると、そもそもカーソル移動もできないため、必然的にノーマルモード時は入力を半角英数に切り替えることになります。

そしてインサートモードに入ったら日本語入力に切り替えて入力し、終わったらノーマルモードに戻りますが、この時の入力は日本語入力になっています。そのままでは再度インサートモードに入ることすらできないためまた日本語入力を半角英数に切り替えてインサートモードに入ってまた日本語入力に切り替えて…。

頭に来ますね。

つまり、日本語入力とVimの操作が両立できないため日本語入力のオンとオフを頻繁に繰り返すことになり異常に煩わしいことになります。

実際には、キーバインド等の設定やプラグインを駆使することで上記の問題を解決する方法もあります。

ただ、それでVim上での日本語入力が多少楽になったとしても、コードを書いているときのように爆速な編集へ進化していけるかというと個人的には疑問です。

それならもういっそ日本語入力に関しては素直に割り切って、ブラウザへ直接入力するなりWordを使うなりするほうがよほど簡単だし早いと思います。

苦手なことをいくら頑張っても得意にはならないのです。

入れてよかったプラグイン

さて、先ほどからちょくちょく出てきているプラグインについて。

Vimプラグインというのは要するに拡張機能のことで、本当にさまざまなものがあります。

これらをインストールしてVimで使えるようにすることで、あんなに無愛想で不器用だったVimがどんどん洗練された親切なナイスガイになっていきます。

年季の入ったVimmerの中には100個や200個プラグインが入っている人たちもたくさんいらっしゃるそうで、奥の深さを感じます。(使用するプラグインの数が増えれば、それだけVimの動作も遅くなると思うので多けりゃ良いってもんでもないですが。)

というわけで、僕がインストールしてよかったと感じているプラグインを並べておきます。

有名なものばかりなので玄人感は皆無ですが、Vimに興味を持ってこれから使っていこうかなと考えている方の参考になれば幸いです。

NERDTree

Vimにサイドバーを表示させてディレクトリのツリーを表示してくれます。そのままファイルを選択して別のバッファで開くこともできます。これのおかげでVimが一気に便利になった。

github.com

Indent Guides

インデントを見やすくしてくれるのでネストの深さが視覚的に分かりやすくなります。

github.com

surround.vim

テキストの前後を囲んでいる括弧([],{}とか)や引用符(""とか)、はてはhtmlタグまで一発で削除したり置換したり追加したりできます。

github.com

vim-operator-replace

任意のテキストオブジェクトを置換することができるようになります。 意外ですが、Vimには検索なしで単語を楽に置換する手段が乏しいので助かります。

github.com

lexima.vim

閉じ括弧や引用符を補完してくれます。補完してくれた後はちゃんと括弧の間にカーソルが来てくれます。地味に便利。

github.com

closetag.vim

lexima.vimのhtmlタグ版。htmlの閉じタグを補完してくれます。

github.com

他にもたくさんありますが、キリがないのでこの辺で。

まとめ

なんだか本当に好きなものについてただ書き散らした感じで恐縮ですが、これでもだいぶ短くしました。 長々と書いておきながらなんですが、結局何が言いたかったかというと、Vimでコードを書くのは楽しいよってことです。

ここが好きだよ!のところでも書きましたが、ただのテキスト入力ソフトとしてではなく、道具として使っている感じがとても好きです。

こういう感覚は、僕がフィヨルドブートキャンプで学んでいるRubyというプログラミング言語や、そのフレームワークであるRuby on Railsにも同じことが言えるんじゃないかなぁと思っています。(こっちの方はまだまだ修行不足で全然使いこなせてないですが…。)

本当はその道具でどんなコードを書くかということの方が何倍も大事なのですが、プログラミングに限らず、自分が使う道具に愛着があるのは悪いことではないと思うので、今後もちまちまプラグインの剪定をしながら楽しんでいきたいなと思っています。

おすすめのプラグインとかあれば教えてください。

ちなみに、今回はVimの機能を網羅的に紹介するのが目的ではないので言及しませんでしたが、Vimにはまだまだ便利な機能や仕組みがあります。 バッファやレジスタ、アンドゥツリー、マクロといった仕組みを理解すればもっとできることが広がります。 興味のある方は是非一度、Vimを使ってみてください。

参考

はじめてのVim 〜 Vimはいいぞ!ゴリラと学ぶVim講座(1) | さくらのナレッジ

何も考えず~/.vimrcにこれを書くんだ! 〜vim初心者によるvim初心者のためのvim入門〜 - Qiita

脱初心者を目指すなら知っておきたい便利なVimコマンド25選 (Vimmerレベル診断付き) - Qiita

Vim幼稚園からVim小学校へ - Qiita

僕がVimで愛用しているプラグイン30連発 | 株式会社LIG(リグ)|DX支援・システム開発・Web制作

【Rails】hidden_fieldにユーザー情報は載せないほうがいい

フィヨルドブートキャンプの課題レビューで指摘されたシリーズ。

セキュリティに関わることなので大事。

form_withの中でユーザーに何かを入力させることなく何らかの値の受け渡しを行いたいときにはhidden_fieldを使う場合があると思いますが、データの中身によってはやめておいたほうがいいかもしれません。

例えばこんな場合。 メモのタイトルと本文をユーザーが入力するフォーム内に、hidden_fieldを設置してユーザーのidを一緒に送信しようとしてる。

<%= form_with(model: memo, local: true) do |form| %>
 
  <div class="field">
    <%= form.label :title %>
    <%= form.text_field :title %>
  </div>

  <div class="field">
    <%= form.label :body %>
    <%= form.text_area :body %>
  </div>

#メモを作ったユーザーのidを投稿者idとしてタイトルや本文と一緒に送る
  <div class="field">
    <%= form.hidden_field :user_id, :value => @user_id %> 
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

こうしてしまうと、@user_idの部分に開発者ツール等を使って任意の値を入れられた場合に他人になりすましてメモの作成ができてしまいます。

さすがにそれはセキュリティ的にアウトなので、こういう場合はhidden_fieldは使わず、送信先のcontrollerで対象となるモデルのインスタンスuser_idを紐付けるようにすると良いそう。

def create
  @memo = Memo.new(memo_params)
  @memo.user_id = current_user.id
  if @memo.save
  #以下略
  ・
  ・
  ・
end

まとめ

こういうの指摘される前に自分で気付いて潰せるようにならないといけないですが、ぶっちゃけ最初からそれは難しいので、指摘してもらえたものから順に覚えていければと思うております。

【Rails】findとfind_byとfind_by!についての概要

今回は、おそらくRailsの中でよく見かけるメソッドトップ10に入っているんじゃないかというぐらいよく見る'find'、'find_by'及び'find_by!'について、ぶっちゃけ何が違うのかよくわからんという人向けの記事です。

そもそもこのメソッド達は何なのか?

これら3つのメソッドはActiveRecordによって提供されており、その名のとおりデータベース内から任意のレコードを見つけ出して(取得して)くれる便利なメソッド。

ActiveRecordって何さ?

Railsガイドによると、

Active Recordとは、MVCで言うところのM、つまりモデルに相当するものであり、ビジネスデータとビジネスロジックを表すシステムの階層です。Active Recordは、データベースに恒久的に保存される必要のあるビジネスオブジェクトの作成と利用を円滑に行なえるようにします。Active Recordは、ORM(オブジェクト/リレーショナルマッピング)システムに記述されている「Active Recordパターン」を実装したものであり、このパターンと同じ名前が付けられています。

とのこと。 railsguides.jp

んー。 かなり大雑把に言うとRailsで作られたアプリケーションとデータベースの橋渡し役でありRubySQLの翻訳者、というように個人的には理解しています。

「システムの階層です。」という表現が分かるようで分からない。

データベース周りの便利機能を大量に提供してくれるライブラリみたいなものなのかなと思ったりもしますが、正直このあたりは理解が曖昧です。ライブラリではねーだろゴルァとかありましたら優しく教えてください。

使い方

では使い方です。 全てRailsガイドに基づいて書いていきます。

find

railsguides.jp

findメソッドを使うと、与えられたどのオプションにもマッチする「主キー」に対応するオブジェクトを取り出せます。 findメソッドでマッチするレコードが見つからない場合、ActiveRecord::RecordNotFound例外が発生します。

要は引数に渡した数値とIDが一致するレコードを取得してくれるという理解でいいと思います。

例えば、こんな感じ。

User.find(1) #usersテーブルからIDが1のユーザーを取得。

該当するレコードが見つからない場合は404エラーになります。

find_by

railsguides.jp

find_byメソッドは、与えられた条件にマッチするレコードのうち最初のレコードだけを返します。

findに対してこのfind_byは主キーではない任意のカラムを検索できます。

例えば、

User.find_by(first_name: 'Hoge') #usersテーブルのfirst_nameカラムの値が「Hoge」である最初のレコードを取得。

この場合、対象となるカラムに格納されている各レコードの値が一意である必要はありませんが、気を付けなければならないのはfind_byの場合はfindと違って該当するレコードがない場合の返り値がnilであることです。

もしfind_byがメソッドチェーンの途中にある場合、レコードが見つからずnilが返ってきてしまうと、nilクラスでは続くメソッドが呼び出せずに500エラーになったりします。

そこで次のfind_by!です。

find_by!

find_by! メソッドの動作は、マッチするレコードが見つからない場合にActiveRecord::RecordNotFound例外が発生する点を除いて、find_byメソッドとまったく同じです。

もうこの説明文のとおりです。

レコードが見つからない場合にnilではなく404エラーになります。

気を付けたい点

上記のように、これら3つのメソッドは挙動が微妙に異なります。

それぞれの特徴を把握したうえで、状況によって使い分けられるようになるのが目標です。

個人的には、対象となるレコードのidが分かるのであればfindを使っておけばそんなにおかしなことにはなんやろ、ぐらいの感じで考えています。

それと、大事な点としてこれらのメソッドはどれも、条件に該当するレコードのうち最初の1件だけを取得するメソッドだということです。

findについては主キーが一意なので問題ありませんが、他の2つを使う際に対象となるカラム内の値が一意でない(重複を許容する)場合には注意が必要で、該当するレコードが複数あっても、最初にヒットした1件のみが返るため、他に何件該当するか等は分かりません。

そのため、条件に該当する全レコードを取得したいというような場合は別のメソッドを使用しましょう。

また、レコードが見つからない場合の返り値が例外かnilかというのもポイントですが、何が返ってほしいかはケースバイケースだと思いますので、ひとまずfindfind_by!は例外、find_bynilを返すということを覚えておけば、目的によって使い分けられるかなと思います。

まとめ

というわけで、今回はfindfind_byfind_by!についてでした。

実は僕自身もこの3つの違いについては、先日コードレビューを受けたときにfind_byをメソッドチェーンの中で使っていて、それだとレコードが無い時に次のメソッドが呼び出しエラーになりますよと教えていただいたのがきっかけで初めてちゃんと調べました。

もう少し返り値が何かというのを意識しながらコードを書けるようにならないといけないなと強く思った出来事でした。

では、今回はこの辺で。

記載内容の誤りや事実誤認等に気付かれましたらこっそり教えていただければ幸いです。

【Rails】referencesを使って外部キーを設定する!

もくじ

やりたいこと

タイトルのまんまです。新しいテーブルのカラムに外部キーを設定します。

マイグレーションファイルの書き方

例えば、foobarsというテーブルを作成する際に、usersテーブルのidを外部キーに設定したい場合、マイグレーションファイルは以下のような書き方になります。 (それだけだとなんか寂しいのでt.timestampsも書いてますが今回は関係ないです。)

class CreateFoobars < ActiveRecord::Migration[6.1]
  def change
    create_table :foobars do |t|
      t.references :user, foreign_key: true
      t.timestamps
    end
  end
end

これだけ!

これでuser_idカラムにusersテーブルのidが外部キーとして設定されます。 なぜuserカラムではなくuser_idカラムなのかというと、references_idの部分を自動でくっつけてくれているからです。 また、referencesを使うと自動でindexも張ってくれます。 indexについてはこちらの記事がシンプルでわかりやすかったです。

カラム名を別名にしたい場合

例えば中間テーブルでusersテーブルのidを複数のカラムでそれぞれに名前を付けて外部キーに設定したい場合、以下のような書き方になります。

class CreateFoobars < ActiveRecord::Migration[6.1]
  def change
    create_table :foobars do |t|
      t.references :foo, foreign_key: { to_table: :users }
      t.references :bar, foreign_key: { to_table: :users }
      t.timestamps
    end
  end
end

上記の書き方だと、foo_idbar_idの2つのカラムが同じusersテーブルのidを参照していることになります。

まとめ

以上がreferencesを使った外部キーの設定方法です。 他にも方法はあるようですが、それはまた別の機会に。 _idの部分を補完してくれたり、indexを自動で張ってくれたりするのでマイグレーションファイルの記述も簡潔になるのがreferencesを使うメリットかなと思います。

【Rails・DB】DB反映済みのマイグレーションファイルを手元でサクッと修正する手順

もくじ

カラム名とか間違えるよね

DBに新しいカラムを追加するためにマイグレーションファイルを作り、意気揚々とrails db:migrateした後にカラム名のタイポに気付いたり、データ型を間違えていたりとか、絶対やっちゃうと思うんですよ(確信) それがまだプルリクエストを作っている段階で、自分の手元でしか反映されていないのであれば、割とサクッと修正できるよという話。

具体的な手順

  1. 反映済みのマイグレーションファイルをrails db:rollbackでいったん取り消して、DBを過去の状態に戻す
  2. マイグレーションファイルを修正する
  3. 再度rails db:migrateでDBに反映させる

こんだけ。

事前準備

実際にrails db:rollbackを実行する前に、どのマイグレーションファイルを修正するのかを確認したいので、rails db:migrate:statusでDBの状態を確認します。 するとこんな感じでマイグレーションの一覧が表示されます。

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     xxxxxxxxxxxxxxx  Create xxxxxxx
   up     xxxxxxxxxxxxxxx  Add xxxxx to xxxxx

左端のstatusがupになっている場合、DBに反映済みですよということです。 問題は、このままでは修正も削除もできないということ。 なので、次のrails db:rollbackでDBを過去の状態に戻します。 そうすることで、マイグレーションファイルも未反映の状態(rails db:migrateされていない状態)に戻ります。 例えば、DBを2つ前の状態に戻すということは全てのマイグレーションファイルのうち直近2つが未反映の状態に戻るということになります。

修正する

rails db:rollbackを実行すると、直近のマイグレーションファイル1つが未反映の状態になります。 先ほどと同様にrails db:migrate:statusで確認してみると、statusが1つだけdownになっていると思います。

この状態でファイルの内容を修正します。 修正が終わったら、あとは再度rails db:migrateマイグレーションファイルを反映させれば終了です。

rails db:rollbackの仕様

上にも書きましたが、rails db:rollbackをそのまま実行すると直近のマイグレーションファイル1つだけが未反映の状態になります。 つまり、何も指定せずに実行すると一段階ずつ過去に遡っていくことになります。 しかし、2つ以上前の時点まで一気に戻りたいこともあると思います。 そんな時はdb:rollback STEP=nで任意の時点まで一気にさかのぼることが可能です。 例えば、マイグレーションファイル3つ分戻りたければ、n3に置き換えてdb:rollback STEP=3として実行すればOKです。

また、特定のマイグレーションファイルのみ状態を変更したい場合、db:migrate:up VERSION=xxxxxxxまたはdb:migrate:down VERSION=xxxxxxxで操作できます。xxxxxxxの部分にはrails db:migrate:statusで確認したときのMigration IDを指定します。

まとめ

以上がrails db:rollbackを使ってマイグレーションファイルを修正する手順です。 ただし、これはあくまで自分一人で開発している場合や、開発用にブランチを切ってmergeされていない状態である場合に有効な手段だと思っておいたほうがよさそうです。チームで開発していてDBのカラムに齟齬が出たら他のメンバーに迷惑がかかる可能性が高いので。このあたりの運用はチームによってルールが異なると思いますのでその点はご注意いただければと思います。

【PostgreSQL・Ruby】異界と現世を繋ぐゾ!(RubyのコードからPGを使ってPostgreSQLにアクセスします)

もくじ

したいこと

Rubyで書いたプログラムからデータベース(以下、「DB」という)にアクセスしたい。

とりあえず今回はPostgreSQLで作成したDBからPGというgemを使って全てのレコードを取り出してみることにします。

PGのインストール、DB・テーブルの作成等は済んでいて、コードさえ正しく書けばDBを使える状態が前提になります。

DBとプログラムは別の世界

以前どこかで、DBというのはプログラムから見ると全く別の概念で構成された世界、異界であるときいたことがあります。

言われてみると確かにプログラムはコンピュータに話しかけるための言語(RubyJavaScript等)によって表現された世界ですが、DBに話しかけるためにはSQLという専用の言語を使わなくてはならず、コミュニケーションの方法からして全然違うわけです。

僕の場合、普段はRubyの世界でオブジェクトが~とかメソッドが~とか言って遊んでいるわけですが、DBの世界でオブジェクトだのメソッドだの言っても向こうでは「???」となってしまいます。そもそもそんな概念は向こうの世界にはありません。

そりゃそうです。 宇宙人に日本語で戦車道を嗜む女子高生の話をして通じるわけがないのと一緒です。

あくまで僕個人のイメージですが、Rubyの世界が僕たちの住んでいる「モノ」が存在する世界だとすると、DBの世界はふわふわした雲みたいなものしかない世界、という感じです。

ということは、DBからプログラムにデータをもってきていじくりまわすためには、(あくまでもイメージですが)そんなふわふわの世界に存在する雲のようなものを僕たちが住んでいる世界の「モノ」に変換して、手で触れられる存在にする必要があります。味もみておこう。

で、そんなすんばらしいことを実現してくれるのが本日の主役PGパイセンです。

PG

Rubyのコード内でSQLを発行してDBにアクセスするためのgemです。

deveiate.org

手順としては、

  1. DBとのコネクションを構築
  2. SQLを発行
  3. レコードをゲット!

という感じです。

順番にやってみます。

手順

1. DBとのコネクションを構築

これができれば勝ったようなもんです。

いくつか方法はあるようですが、今回はPG::connectionクラスに接続先のDBの情報を渡してインスタンスを作成するパターンでやってみます。

require 'pg'

#DBの情報を変数へ格納
host = '{IPアドレス}'
port = {ポート番号}
db = '{DB名}'
user = '{ログインユーザー名}'
password = '{パスワード}'

#上で定義した変数を`PG::Connection.new`の引数として渡してインスタンスを作成
connection = PG::Connection.new(host: host, port: port, dbname: db, user: user, password: password)

変数connectionがDBとのコネクションということになります。

なんだかイメージが湧きにくいのですが、PG::Connectionクラスのインスタンスワームホールみたいなものだと思っています。

余談ですが、ワームホールといえば個人的には映画『インターステラー』に登場したワームホールが印象に残っています。

普通ワームホールというと「地面に空いた穴」みたいなものや、ドラえもんに登場する通りぬけフープのような平面的なものを想像しがちなんですが、『インターステラー』のワームホールはなんと「球体状の穴」という摩訶不思議なものでした。要は360度どこから見ても穴なので、どこからでもその中に入っていけるという脳がパニックになりそうな代物です。

ちなみに、この記事の本筋とは関係ないですが『インターステラー』は傑作だと思うのでご興味があれば是非ご覧になってみてください。「ウラシマ効果」や「事象の地平面」あたりのワードに反応できる人は間違いなく楽しめます。

話がだいぶそれましたが、僕の頭の中では異界であるDBにつながっているPG::Connectionクラスのインスタンスはまさに「球体状の穴」のイメージがぴったりで、このコードを書きながら楽しんでいました。

だってただの穴より立体的な穴のほうがオブジェクトっぽいじゃないですか(?)

というわけで、異界につながる穴ができました。

あとはここに何かを投げ入れれば向こうの世界に情報が登録され、正しい呼び方をすれば向こうの世界にある情報がこちらの世界で使えるオブジェクトになって穴から出てくるわけです。

2. SQLを発行 → 3. レコードをゲット!

さて今回はDB内の全ての情報をまるっと取ってきたいと思います。 やり方は簡単で、先ほど用意した変数connectionexecメソッドを使ってSQLを投げてやればおkです。

all_data = connection.exec("SELECT * FROM テーブル名")

これでテーブル名で指定したテーブル内の全てのレコードの全てのカラムから情報を引っ張り出すことができました。 もう少し詳しく説明すると、connection.execの返り値はPG::Resultクラスのインスタンスです。

構造としてはハッシュを要素として持つ配列です。

ハッシュ1つで1レコード、各カラム名と値がそれぞれkeyとvalueになります。

例えばDBが以下のような構造の場合、

カラム1 カラム2 カラム3
1 foo hoge
2 bar fuga
3 buzz puni

返り値は

[ { カラム1 => 1, カラム2 => foo, カラム3 => hoge }, { カラム1 => 2, カラム2 => bar, カラム3 => fuga }, { カラム1 => 3, カラム2 => buzz, カラム3 => puni } ]

という形になります。

この返り値について一点気を付けなければいけないのは、実態としては配列であっても、あくまでもPG::Resultクラスのインスタンスだということです。

なので、Arrayクラスの全ての機能が使えると思っていると変なところで詰まるかもしれません。(未確認)

が、ご安心ください。

PG::ResultにはEnumerableモジュールがincludeされているため、to_aメソッドが使えます。

そのままでは扱いにくいという場合はto_aArrayクラスにしてしまいましょう。

まとめ

というわけで、これで異界と現世をつないでモノをやり取りすることに成功しました。

あとは煮るなり焼くなりお好きにどうぞ。

どの記事でもそうなのですが、例え話などはあくまでも僕のイメージや感覚的なものを文章にしているので、僕と感性が合わない人にとってはあまりピンとこない部分もあるかと思います。

そのあたりは各自の脳内で読み替えたり補完したりしてください。

では、今回はこんな感じで。

記載内容に間違い等があった場合にはこっそり教えていただければ幸いです。

最後までお読みいただきありがとうございました。

【Ruby】まだ空白文字で表示位置調整してるの?(文字列を右寄せにします)

もくじ

文字列の表示位置を空白文字で調整するととてもブサイク

ターミナル上にとある文字列を出力させるコマンドを作っていた時、画面左端からちょっとスペースを空けて表示させたいから先頭に空白文字を挿入して無理やり右に押し出すようにしていました。(以下は文字列の先頭に空白文字を4つ挿入。)

puts "    foo"

するとこんな感じに表示される。

f:id:ham-cap:20210710082035p:plain

表示させる文字列が完全に分かっていてかつ一行ならまぁ最悪これでもいいかなと思わなくもないけれど、例えばfooの部分を変数にして毎回文字数が変わるような場合、文字数の増減によって文字列の右端が伸び縮みするので、別の文字列や変数が右側に書かれていた日には表示位置が不規則に変化してしまいます。

そして何より、コード内の文字列に明らかに位置調整のためだけの空白文字が大量に書かれているととてもブサイクです。(個人の感想です)

今回はこういう場合に使えるメソッドについて書いてみます。

rjustメソッド

結論から言うとStringクラスのrjustメソッドを使うと解決します。 いつもどおりリファレンスマニュアルを読んでみます。

rjust(width, padding = ' ') -> String

長さ width の文字列に self を右詰めした文字列を返します。 self の長さが width より長い時には元の文字列の複製を返します。また、第 2 引数 padding を指定したときは空白文字の代わりに padding を詰めます。

docs.ruby-lang.org

微妙に言い回しが分かりにくいので以下で説明します。

最大幅を決める

考え方としては引数widthの部分で表示させたい最大の幅を決めて、その幅の中でレシーバの文字列が右寄せになります。

例えば、冒頭で書いたような先頭に空白文字4つ分のスペースを空けて表示させたい場合で、仮に文字列本体の最大の長さが6文字だとすると、rjustで設定するwidthは10になります。

puts "foobar".rjust(10) # => "    foobar"

空白文字以外も使える

このメソッドでは文字列を最大幅の中で右寄せにして、余ったスペースに空白文字を詰め込むというのがデフォルトの挙動ですが、マニュアルにも記載があるとおり第2引数に指定した文字列で余ったスペースを埋めることもできます。 例えば、

"foo".rjust(10, "*") # => "*******foo"

こんな感じで*で埋めたり、

"foo".rjust(8, "(^o^)") # => (^o^)foo

おじさんっぽい顔文字を挿入して文字列を鬱陶しくすることも思いのままです。fooって言ってるっぽくてめちゃくちゃイライラします。

まとめ

正直、なんとなくこういうメソッドがあるのはう〜っすら知っていたのですが使い所がよく分かりませんでした。 でも何かしら必要に迫られて改めて出会うとなんて親切なメソッドなんだとちょっと感動したので、結局は使い手次第というか、適材適所というか、僕が未熟だっただけですサーセン

今回はこんな感じです。

記載内容に間違い等があった場合にはこっそり教えていただければ幸いです。

最後までお読みいただきありがとうございました。