【Git】git reset --hard HEADを取り消したい!!
こんにちは、マリンです。
今日Gitでやらかしました。
表題の通りなのですが、それにプラスしてDBのロールバックとかも関わってきて、
「ギャーーーー!!!」
ってなったので残しておきます。
概要
やらかした内容が多すぎるので、時系列で箇条書きします。
新規でDBのテーブルを作成するのに
rails g migration ~~
で作ってしまった。テーブルを作った後しばらく作業を進めてからModelが無いことに気づく。(この間何度かcommitしてる)
Modelだけ作れば良いところ、わざわざ
rake db:rollback
しようとした。だがロールバックする前に何を思ったかCommitをテーブル作る以前まで戻してしまった。
しかもGitの取り消しを
git reset --hard HEAD@{n}
でやってしまった。(ここが一番問題)
やはり一番のやらかしポイントは、commitのやり直しで
git reset --hard HEAD@{n}
を使ってしまったことでしょう。
--hard
オプションをつけると、ワーキングツリーまで変更されてします。
では、これに対する対処を次から書いていきます。
対処法
1、2、3まではおそらくModelファイルだけ作ってあげれば良かった話だと思うので省略します。
4、5、6について詳細と対処法を記載しようと思います。
そもそも何が起こったのか?
私が不用意に打ったgit reset --hard HEAD@{n}
これによって何が起こったのかを解説します。
飛ばしていただいて結構です。
$ git reset --hard HEAD^
git reset
:これは任意のcommitを取り消すコマンドです。--hard
:resetコマンドのオプションです。コミットだけでなく、ワーキングツリーまで元の状態に書き換えます。HEAD@{n}
:HEADの位置をどこまで戻すかを指示しています。@{n}
で遡ることn番目のcommitを表します。
問題は真ん中の--hard
オプションです。
これによりワーキングツリーまで元の状態に戻ってしまったため、
テーブル作成のために書いたマイグレーションファイルまで消えてしまいました。
(今回はテーブル作成をする一つ前のコミットまで戻ったので・・・)
そしてこれをrake db:rollback
を実行する前にやってしまいました。
該当のマイグレーションファイルが無いのにロールバックしたってできるわけがありません。
なのでこの取り消し(git reset)を取り消して、マイグレーションファイルを復活させる!
それが今回の目標です。
ちなみに問題のオプションですが、
--soft
にを指定すればコミットだけをなかったことにしてくれます。
これにしていればアワアワすることもなかったでしょう・・・
他にもコミットとインデックスを取り消す--mixed
もあります。
オプション無しのデフォルトもこれです。
こちらでより詳しく説明してくださっています。
[git reset (--hard/--soft)]ワーキングツリー、インデックス、HEADを使いこなす方法
コミット取り消しの取り消し
ここからが本題です。
消してしまった事実をなかったことにして、
マイグレーションファイルを復活させます。
$ git reflog $ git reset --hard HEAD@{n} $ git log
こちらが一連の流れです。
git reflog
:
まずどこまでコミットを戻すか履歴を見ます。
コマンドがgit log
でない理由は、
リセットされたコミットはgit log
の履歴に出てこないからです。
git reflog
は消されたコミットも履歴として見ることができます。
git reset --hard HEAD@{n}
:
ここで取り消しを無かったことにします。
今回は--hard
オプションで大丈夫です。
消えてしまったワーキングツリーのファイルを復活させたいので。
HEAD@{n}
のnにはログで確認した該当コミットが現在地から何番目にあたるかの番号を入れてください。
reflog
の時に番号入りで表示されるはずなのでわかりやすいと思います!
git log
:
最後に念のため確認をしましょう。
この場合はこちらの履歴で大丈夫です。
以上が解説になります。
なお、実例を交えたより詳しい説明をこちらでしてくださっていますので、
ぜひご覧ください。
git reset --hardした内容を取り消す (git reset --hard, reflog, HEAD@{x}, 取り消してしまったコミットを元に戻す) - いろいろ備忘録日記
まとめ
今までadd、commit、pushくらいしかやってこなかったGitです。
正直他のことはよくわからないから触りたく無かったし、
そもそも何ができるのかも曖昧でした。
しかし、今回初めて「履歴が残っている」ということの有り難みを感じました。
そしてむやみに手元まで変えてはならない!!ということも・・・
基本は--soft
でやるのが安全だし、一般的な様ですね。
大変勉強になりました!
【Bootstrap/Rails】popoverのdata-contentの中身をHelperに切り出してみた。
こんにちは、マリンです。
今回はBootstrapのpopoverです。
popoverはtitle
に表示するタイトル。data-content
に表示する内容を書きます。
でも長い内容をタグ内に書きたくないでしょ!
ってなわけでRailsのHelperにメソッド切って表示内容を切り出してみました。
注)HTML上ではベタ書きになっちゃいますけど。
概要
- 環境 -
Rails5
Bootstrap3
- 例に使うテーブル構造 -
Usersテーブル
id
name
Childrenテーブル
id
name
user_id
- 概要 -
Userの名前をクリックすると、
ポップオーバーでChildrenの名前が表示されるようにします。
この時data-content
の中身が複雑になるので、
Helperにメソッドを切ってそれをdata-content
に渡します。
詳細
そもそもなんでこんなことになるのか?ちょっと詳細を説明します。
まず前提として、対象のページはindex.html.erb
で、
Userの名前はUser.all
をeach
で回して表示しているとします。
さらにChildrenは複数いる可能性があります。
そこで問題となるのが、
どのUserの子供であるかを特定するために
user
を渡す必要があること。Childrenは
user.children
の配列になること。その配列から名前を取得して表示するには、ループを回す必要があること。
ポップオーバーの内容は複雑になる場合、別エリアに切り出すことも可能です。
しかし今回の場合、まず一番外側でUser.all
をループしています。
切り出すにはこのループの外になるでしょう。
となると1番のどのUserかを特定するuser
を渡すのが大変です。
Ajaxを駆使してコントローラに値を渡して〜・・・なんてことが予想されます。
2、3番は表示内容が複雑になる理由です。
てことでHelperにメソッドを切ってみてはどうか?となったわけです。
ちなみに子の名前は一人一人改行して表示します。
HTML上はベタ書きになってしまいますが、コードはすっきりします。
ソースコード
さて前置きが長くなりましたが、ここから本題です。
まぁやってることは真新しいことではないのですが・・・
ControllerとModelは特に特筆することはないので、コードだけで解説は省略します。
Controller
# controllers/users_controller.rb def index @users = User.all end
Model
# user.rb has_many :children # child.rb belongs_to :user
View
<!-- views/users/index.html.erb --> <% @users.each do |user| %> <button class="btn" data-html="true" data-toggle="popover" title="子ども" data-cotent="<%= children_popover(user) %>"> <%= user.name %> </button> <% end %>
まずbuttonタグの中にあるdata-html="true"
をご覧ください。
今回のメソッド切り出しとは直接関係はありませんが、
これを入れることでポップオーバーの中で改行<br>
を使うことができるようになります。
次に本題のdata-content="<%= children_popover(user) %>"
です。
このerb表記の中身が、後述のHelperに切り出すメソッド名です。
引数にuser
を渡していることにご注目ください。
別エリアへの切り出しではないため、大枠のループのuser
をそのまま使うことができます。
Helper
# helpers/users_helper.rb def children_popover(user) users_children = [] user.children.each do |child| users_children << child.name end users_children.join("<br>") end
まず最初に子どもの名前を入れる空の配列を用意します。
そしてuser.children
のループを回し、
その中で名前を先ほどの配列に格納していきます。
最後にループの外で出来上がった配列を、
区切り文字に<br>
改行を指定して返します。
これを使うために先ほどViewでdata-html="true"
を指定しました。
解説は以上になります。
まとめ
今回は値の取得などにたいした時間がかからないものでしたのでこの方法を使用しました。
しかし、表示する値の取得に時間がかかるようでしたら、
やはり別エリアに表示内容を切り出し、
値の取得にはAjaxを使って行うなどの方法をとった方が良いかと思います。
私も機会がありましたら挑戦してみようと思います。
ちなみに、今回の検証でpopoverを使用するためにclass="btn"
が必ず必要だったのですが、
これは仕様でしょうか?それとも私の設定ミスでしょうか?
どなたかわかる方いらっしゃいましたら是非教えてください。
よろしくお願い致します。
【jQuery】show/hide ~イベントによって表示・非表示を切り替える~
こんにちは、マリンです。
今回はボタンクリックなどのイベントによって
簡単に要素の表示・非表示を切り替えようと思います。
概要
任意の要素の表示・非表示をボタンのクリックで切り替えます。
ボタンを押すと要素を非表示に、もう一度押すと表示させます。
詳細
Rails環境で行っているため、JSの記述はcoffee scriptです。
View
<!-- app/views/samples/index.html.erb --> <div class="target-element" data-display="on"> <p>この部分を切り替えます。</p> </div> <button type="button" class="btn btn-md btn-default switch-display">Switch</button> <script> samples.switchDisplay(); </script>
上から順番に解説します。
<div class="target-element" data-display="on"> <p>この部分を切り替えます。</p> </div>
この部分が表示したり非表示にしたりする要素です。
target-element
はjQueryでこの部分を特定するためのセレクタです。
data-display="on"
は、現状が表示中かどうかを判定するためのものです。
<button type="button" class="btn btn-md btn-default switch-display">Switch</button>
表示・非表示の切り替えボタンです。
Bootstrapを使っているので、btn
などのクラスでスタイルを当てています。
switch-display
はイベントの対象となるセレクタです。
CoffeeScript
class SamplesController switchDisplay: -> $('.switch-display').on 'click', ()-> if $('.targe-element').data('display') == 'on' $('.switch-display').data('display', 'off') $('.switch-display').hide() else $('.switch-display').data('display', 'on') $('.switch-display').show() return this.samples = new SamplesController
上から順番に解説します。(class ~~
については省略します)
$('.switch-display').on 'click', ()-> if $('.targe-element').data('display') == 'on'
.switch-display
(ボタン)がクリックされたら、
まず切り替え対象の要素の持つdata-display
の値が
on
であるかそうでないか検証します。
$('.switch-display').data('display', 'off') $('.switch-display').hide()
値がon
の時、つまり表示状態の時には
data-display
の値をoff
にし、
切り替え対象の要素をhide()
で非表示にします。
else $('.switch-display').data('display', 'on') $('.switch-display').show()
値がoff
ので非表示状態の時には
data-display
の値をon
にし、
切り替え対象の要素をshow()
で表示にします。
まとめ
要点としては、
切り替えの着火剤となるイベント
hide
による非表示show
による表示
この3点です。
今回はイベントをクリックにしましたが、
hover
などで自作のポップオーバーに使ったりなど
イベントを変えればいろいろと応用が効くと思います。
【Rails5】Rails5で自作のWebアイコンを使う
こんにちは、まりんです。
うちの会社ではFontAwsomeがマストなんですが、
結構クライアントからこんなアイコン使って欲しい!といわれ、
そんなのねーよ!!という事が多々あるようです。
そしてこの前唐突に、上司から
アイコン作るスキル身につけて欲しいんだけど。
と言われました。
マジかよ!?
という事で、今回ちょっとやってみました。
「独自アイコンをWebアイコン化し、それをRails5で使ってみた。」
です。
概要
環境
Rails5
使用アプリ
- icomoon
今回は元素材はこちらでお借りしました。
使いたいアイコンのページからSVG形式でダウンロードしてください。
本当にアイコン作るところからやる場合は、
IllustratorやInkscapeなど、SVGで出力できるアプリを使って作成して下さい。
(イラレは高いので私はInkscapeにしようと思っています。本業じゃないし。)
Webアイコン使用までの流れ
素材(SVG)をicomoonでWebアイコン化する
Webアイコンをダウンロードして必要ファイルをアプリケーションに移動
ファイルの移動先に合わせてダウンロードしたstyle.cssを書き換え
assets/stylesheets/application.scssからimportで読み込む
詳細
それでは一つずつの詳細を説明します。
素材をWebアイコン化する
今回はicomoonというアプリを使って行います。
元となる素材をアップしてそこからダウンロードするだけでできるのでとてもお手軽です。
①まずは左上のボタンから素材のファイルをアップロードします。
今回は1枚だけですが、複数アップロードすることが可能です。
②Webフォント化する素材を選択します。
ここでも複数選択が可能で、今後アプリで使用したいアイコンをまとめてダウンロードすれば、
オリジナルのアイコンリストを作れます。
今回はアップロードしたものの他に、追加したBrandsと言うライブラリのニンテンドーswitchのアイコンと、
デフォルトで表示されているIcoMoonのHomeアイコンを一緒にダウンロードしてみます。
*ちなみに写っていませんが、ページの一番下にライブラリ追加のリンクがあります。
③選択すると、下部の真ん中のSelectionの数が増えます。
④右下のボタンでWebアイコンを作成。
Webアイコン化するファイルの確認ページに飛びます。
①ここで付いている名前がそのままアイコン使用時のクラス名になります。
アップロードしたアイコンが日本語になっているので編集します。
他にもアイコン画像をクリックすれば、拡大縮小や反転などの簡単な編集も可能です。
②編集、確認が終わったら右下のボタンでダウンロードして下さい。
必要ファイルをアプリケーションに移動
ダウンロードしたフォルダを解凍したら、必要なファイルをアプリケーションに移動します。
必要なファイルは上記のfontsフォルダ下のものと、style.cssです。
railsアプリのvendor下に置きます。
vendorの中にassets
をまず作り、その下にfonts
とstylesheets
を作ります。
他にも何かダウンロードしたものを使うことがあるかもしれないので、
一応専用のフォルダをそれぞれ作ってそこに入れます。
vendor/assets/fonts/icomoon → fonts下のファイル
vendor/assets/stylesheets/icomoon → style.css
style.cssの書き換え
先ほど移動した構造に合わせて、ダウンロードしたstyle.css内のurl
の部分を書き換えます。
私はここの指定が不十分だったがために余計な時間や手間を使ってしまいました。
assets
なので、追加した専用フォルダ名からで大丈夫です。
*今回はRails5なのでconfigの方の設定は入りませんでした。
確認はしていませんが、もしかしたらRails4以下ではプリコンパイルなどの設定がいるかもしれません。
こちらで大変詳しく説明して下さっています。
application.scssで読み込む
最後にassets/stylesheets/application.scss
からimport
でライブラリを読み込みます。
ここでも専用フォルダ名が必要になるのでご注意ください。
*直置きの場合は入りません。
以上で実際にviewで使用するまでの準備は完了です。
アイコンを使ってみる
viewでの使い方は、Font Awesomeなどのようにクラスにicon-~
のアイコン名を入れて使います。
icomoonの場合は<span>
にクラスをつけるようにしてください。
<div> <span class="icon-nightsky"></span> </div> <div> <span class="icon-home3"></span> </div> <div> <span class="icon-nintendoswitch"></span> </div>
無事表示できました。
まとめ
Webアイコン化して表示させるまでは簡単ですね。
問題はアイコンを作れるかどうかです。
画力も計算もデザイン能力も全く自信がないのですが。。。
それはさておき、途中にも書きましたがこれはRails5での方法になります。
Rails4以下ではvendorに置いたものをデフォルトで探してはくれなかったりするようなので、
おそらくconfig/initializers/assets.rb
などに設定する必要があるかと思います。
今回はこちらの記事を参考にさせていただきました。
【jQuery (CoffeeScript)】属性の値に変数や式を使う 〜式展開の色々〜
こんにちは、マリンです。
今回はjQueryでの式展開についてです。
よくdata属性で要素を絞り込む際の値に
事前に定義した変数を使いたい時があるのですが、
そんな時にいつも職場で使われている記述が理解できず、
色々ググってみたのに見つからない。。。
でも調べているうちに「はっ!!」と気付いたのです。
「これ、式展開だ!!」と。
ってことでこういった副題が付いています。笑
例
まず今回もRailsの中で検証してますので、
基本、記述方式はCoffeeScriptです。
では今回検証で使いました例のソースコードです。
# HTML <ul id="lists"> <li class="a" data-index="1">AAA</li> <li class="b" data-index="2">BBB</li> <li class="c" data-index="3">CCC</li> </ul> <button type="button" class="has-element">HasElement</button> # jQuery $('.has-element').on 'click', () -> class_a = "a" index = 2
まあボタンはどうでもいいんですけど、
これ押すとコンソールに表示されるって使用でつけました。
このクリックイベントの中に後述の式展開を書いていく感じで。
以後そこのみ書いていきます。
jQueryでの式展開の色々
やり方は3つあります。
#{}
+
,
#{}
これが純粋な式展開ではないかと思います。
[ソースコード]
// class属性で検索 classA = $('#lists').find("[class = #{class_a}]") console.log(classA.text) // data属性で検索 classB = $('#lists').find("[data-index = #{index}]") console.log(classB.text) // 展開部分の中で計算する classC = $('#lists').find("[data-index = #{index + 1}]") console.log(classC.text)
[実行結果]
AAA BBB CCC
それぞれ属性値の指定に、予め値を入れておいた変数を使用しています。
もちろんその中でさらに計算させることも可能です。
JSやjQueryでは' '
と" "
を区別しないみたいなんですが、
これはcoffeeだからだなんでしょうね、
#{}
の式展開はダブルクォーテーションの中じゃないと使えませんでした。
+
こちらは前後の文字列と+
を使って連結する方法。
[ソースコード]
// class属性で検索 classA = $('#lists').find("[class = " + class_a + "]") console.log(classA.text) // data属性で検索 classB = $('#lists').find('[data-index = ' + index + ']') console.log(classB.text) // 展開部分の中で計算する classB = $('#lists').find('[data-index =' + (index + 1) + ']') console.log(classC.text)
実行結果は前回と同じです。
classB
に注目していただきたいのですが、
これは単純に一つの文字列を前後に分け、
その間に変数を入れて繋げる方法ですので、
クォーテーションはシングルでもダブルでも大丈夫です。
その次のさらに計算をする方法ですが、
こちらは計算部分をカッコで囲ってください。
計算の順番が関係するということですね。
これをしないと左から順番通りに計算され、
前半の文字列にindex
の中身が足されたものに+1
をすることになり、
「は?」と怒られます。
正直、ここまでやってきて初めて計算の順番を意識しました。
,
最後は文字列の後ろに展開したものをくっつけるときに使えます。
文字列の間には入れられませんので、表題とはズレた番外編です。
[ソースコード]
# 値取得部分 classA = $('#lists').find("[class = #{class_a}]") # 表示部分(展開) console.log("classA: ", classA.text)
[実行結果]
classA: AAA
今回の展開部分は後半の
("classA: ", classA.text)
の部分です。
文字列の後にカンマで区切ったclassA.text
を展開してくっつけています。
変数の中身などを展開して文字列に連結しているので、
多分式展開の一種ではないかと思います。。。
まとめ
今回そもそもの問題だった「記述が理解できない」部分に関しては、
おそらく式展開の問題ではなく、コードの読解力だと思います。
真ん中のclassB = $('#lists').find('[data-index = ' + index + ']')
の記述でハマったのですが、
find
のカッコ内が' '
で囲まれていた時点できちんと
文字列 + 変数という構造であることを理解できていればこんなことにはなりませんでした。
なぜか' + index +'
という塊で捉えてしまったんですよね。
まぁおそらく私のような間違いを犯す人はいないと思いますが、
式展開の方法は一つではないとわかっていれば、
他人が書いたコードを読むときなどにちょびっとだけ役立てるのではないかな?
なんて思ったりなんかして。。。
【SQL】テーブル結合 ー外部結合ー
こんにちは、マリンです。
前回の続き、次は外部結合についてです。
内部結合についてはこちらです⬇️
外部結合
外部結合は、結合したテーブルの指定したカラムの値が一致するものに加え、
どちらかのテーブルにしかないレコードも取得します。
どちらかというのは、基準とするテーブル(つまり左か右か)によって変わります。
例に使うテーブル構造を記述しておきます。
[cars]
belongs_to :owner
id | name | owners_id |
---|---|---|
1 | crown | 1 |
2 | vellfire | 1 |
3 | stepwagon | 3 |
4 | legacy | 2 |
5 | elgrand |
[owners]
has_many :cars
id | name |
---|---|
1 | ichiro |
2 | jiro |
3 | saburo |
4 | shiro |
基本構文
SELECT カラム1, カラム2,... FROM テーブル1 LEFT(RIGHT) OUTER JOIN テーブル2 ON 条件
LEFTまたはRIGHTはどちらのテーブルを基準にするかを示しています。
LEFT OUTER JOIN: 左側(テーブル1)を基準結合
RIGHT OUTER JOIN: 右側(テーブル2)を基準に結合
例えばLEFTであればテーブル1が基準となり、
並び順や、前述したこちらのテーブルにしかないレコードも
テーブル1の物が表示されます。
詳細
それでは例を使った詳細です。
なお、railsでの方法には私が職場でよく使用するincludes
をご紹介します。
*内部、外部ともに様々な方法がありますが、
現時点でよく使うもの以外は理解が浅いので、
内部はjoins
、外部はincludes
のみご紹介させていただきます。
[SQL]
SELECT * FROM cars LEFT OUTER JOIN owners ON cars.owner_id = owner.id
LEFT OUTER JOIN
なので、cars
が基準テーブルになります。
[Rails]
Cars.includes(:owner)
こちらが基本形。
includes
は厳密には外部結合ではないようですが、
同等の値を取得することができます。
上記を実行すると
Car Load (1.2ms) SELECT "cars".* FROM "cars" Owner Load (12.8ms) SELECT "owners".* FROM "owners" WHERE "owners"."id" IN (6, 1, 4)
と言うSQLが発行されます。
ご覧の通り、まずcars
の全カラムを呼び出し、
その後上記のowner_id
を元にowners
の該当レコードを取得しています。
ちょっと二度手間感がありますね。
そんな時はreferences
を使います。
includesとreferencesをセットで使う
先ほどの基本形でも取れる値は同じなのですが、
もっとスマートに1回のSQLでinclude先のレコードも取ってくることができます。
それがreferences
メソッドです。
Car.includes(:owner).references(:owner)
これを実行すると
SQL (0.2ms) SELECT "cars"."id" AS t0_r0, "cars"."owner_id" AS t0_r1, "cars"."name" AS t0_r2, "cars"."created_at" AS t0_r3, "cars"."updated_at" AS t0_r4, "cars"."dealership" AS t0_r5, "cars"."display_order" AS t0_r6, "owners"."id" AS t1_r0, "owners"."name" AS t1_r1,"owners"."created_at" AS t1_r2, "owners"."updated_at" AS t1_r3 FROM "cars" LEFT OUTER JOIN "owners" ON "owners"."id" = "cars"."owner_id"
となります。
ご覧の通り、SQLは1回のみでowner
のレコードも取得できています。
上記を見ると、
LEFT OUTER JOIN
になっていますね。
includes
だけではこうはなっていなかったので、
外部結合として使うのであれば、こちらがお作法的には正しいのかな?
なんて思ったりしました。
Ownerのカラムを使う
上記のまま、例えば
Car.includes(:owner).references(:owner).each do |car| car.owner.name end
とするとおそらく
NoMethodError: undefined method 'name' for nil:NilClass
と出てきてしまうと思います。
オーナーのいない車もテーブルに入っているため、
そこのオーナー名が引っかかってしまっているわけですね。
このような時はtry
を使うと便利です。
- try:対象がnilでない場合のみメソッドを呼び出す。
つまりowner ? owner.name
と同じことをやってくれるということです。
try
の方が短く済んで良いかなと思います。
Car.includes(:owner).references(:owner).each do |car| car.owner.try(:name) end
これできちんとオーナーの名前も取得でき、もしオーナーが空の場合には取得しない。
ということが実現できます。
まとめ
今回はincludes
&references
の方法をご紹介しましたが、
他にもeager_load
やpreload
などもあります。
内部結合でも様々な方法がありますので、
ご興味のある方は調べてみてください。
なかなか奥が深い上に咀嚼に時間のかかるテーブル結合でした・・・
自分で色々試しながら理解してきましたが、
まだまだ自信がない部分も多々あります。
何か間違っているところなど御座いましたらぜひご教示ください。
最後に、今回こちらの記事に大変助けられましたのでご紹介しておきます。
【SQL】テーブル結合 ー内部結合ー
こんにちは、マリンです。
今回はSQLの内部結合と外部結合についてです。
まさかこの私がSQLについての記事を書く日が来ようとは…
〇〇結合とは?
まずはそもそものお話しです。
〇〇結合とはなんぞ?
は?そこから?と思った方は飛ばして下さい。
データベースからデータを取り出す際、
基本は「一つのテーブルから条件に合致するデータを取り出す」
だと思います。
ではもし、「欲しいのはこのテーブルのデータだけど、
あっちのテーブルと一致するやつで検索したいんだよなぁ〜」
と言うときは?
SELECT * FROM sample_tables
だけでは探せないですよね。
この場合検索条件に出せるのはここで出たsample_tablesのカラムだけです。
こんな時に使えるのが〇〇結合です。
予め関連づけられたテーブルを一時的に結合させ、
それぞれのカラムを使った検索条件で絞り込んだデータを、
結合させて取得する事が可能になります。
では次からはその結合方法について解説しますので、
その前に例として使うテーブル構造をば。
[cars]
belongs_to :owner
id | name | owners_id |
---|---|---|
1 | crown | 1 |
2 | vellfire | 1 |
3 | stepwagon | 3 |
4 | legacy | 2 |
5 | elgrand |
[owners]
has_many :cars
id | name |
---|---|
1 | ichiro |
2 | jiro |
3 | saburo |
4 | shiro |
内部結合
内部結合は、それぞれのテーブルから
指定したカラムの値が一致するレコードだけを結合します。
ポイントは、「一致するレコードだけ」という点です。
一致しないものや、そのカラムがnilのものなどは除外されます。
基本構文
SELECT カラム1, カラム2,... FROM テーブル1 INNER JOIN テーブル2 ON 条件
テーブル1からカラム1、カラム2...を撮ってきた後、
結合の条件に従ってテーブル2からも値を取得し、
一致したものを結合します。
SELECT
を指定しなければテーブル1の全カラムが対象になります。
詳細
では例を使って詳細を解説します。
ここからはRailsでの書き方も追加します。
[SQL]
SELECT * FROM cars INNER JOIN owners ON cars.owner_id = owners.id
[Rails]
Car.joins(:owner)
carsテーブルとownersテーブルを結合し、
cars.owner_id
とowners.id
の値が一致するレコードをくっつけます。
これを実行すると
irb():001:0> Car.joins(:owner) Car Load (6.5ms) SELECT "cars".* FROM "cars" INNER JOIN "owners" ON "owners"."id" = "cars"."owner_id" => #<ActiveRecord::Relation [#<Car id: 1, owner_id: 1, name: "crown", created_at: "2018-04-26 03:41:31", updated_at: "2018-04-30 16:06:58">, #<Car id: 2, owner_id: 1, name: "vellfir", created_at: "2018-04-28 16:14:28", updated_at: "2018-04-30 16:07:50">, #<Car id: 3, owner_id: 3, name:"Stepwagon", created_at: "2018-04-29 06:33:21", updated_at: "2018-04-30 16:07:58", #<Car id: 4, owner_id: 2, name: "legacy", created_at: "2018-04-29 06:33:38", updated_at: "2018-04-30 16:08:08">]>
こんな感じで条件に一致するレコードが取得できます。
一致していない(owner_idがない)carsの
id: 5, name: "elgrand"
は除外されています。
基準となるのがFROM
の方のテーブルなのでcars
のレコードが表示されていますが、
結合されているのでINNER JOIN
のowners
の値も取得できます。
そちらも検証してみました。
*INNER JOIN
の方のカラムを取得する場合にはSELECT
でそのカラムを指定する必要があります。
詳しくは補足をご覧ください。
【番外】結合した方のカラムの値を取得してみる
結合後のレコードからowners
のname
を取得してみます。
name
というカラム名が両テーブルでかぶっているので、結合時に別名をつける。
[SQL]
SELECT cars.*, owners.name AS owner_name FROM "cars" INNER JOIN "owners" ON cars.owner_id = owners.id
[Rails]
def self.list_by_ownerid @cars = Car.joins(:owner).select("cars.*, owners.name as owner_name") end
SELECT
の中でowner.name
をowner_name
に変更して値を取得します。
次に、上で取得・連結したレコードの中からrailsでowner_name
を取り出します。
Car.list_by_ownerid.map {|car| car.owner_name}
上記を実行すると
SELECT cars.*, owners.name as owner_name FROM "cars" INNER JOIN "owners" ON "owners"."id" = "cars"."owner_id" => ["ichiro", "ichiro", "saburo", "jiro"]
と結果が出るはずです。
並び順などに注目していただくとわかりますが、
carsテーブルが基準となっているので、
車が2台登録されている"ichiro"は2回出て、
並び順はcars.id
の順番に並んでいます。
まとめ
最後に内部結合のポイントをまとめておきます。
値が一致したレコードのみを取得、結合する
基準となるテーブルは右側(FROM)のテーブル
基準テーブルは並び順などに関係してくる
基準テーブルについては結合全体で関係してくることです。
次は外部結合について書きたいと思います。
補足
「内部結合/詳細」の中で
基準となるのが
FROM
の方のテーブルなのでcars
のレコードが表示されていますが、結合されているので
INNER JOIN
のowners
の値も取得できます。
と書きましたが、
これはその後の番外で解説したSELECT
句に結合する方のカラムを指定した時のみ、
そのカラムの値を使用できるようです。
試しにOwnerモデルの方でCarsをjoinして以下のようにやってみましたが、
NoMethodError
が出ました。
# Model def self.owner_list @owner = Owner.joins(:cars) end # View # dealership: ディーラー名 <% Owner.owner_list.each do |owner| %> <%= owner.dealership %> <% end %>
モデルを以下のように書き換えるときちんと表示できました。
def self.owner_list @owner = Owner.joins(:cars).select("owners.*, cars.dealership") end