Web Marina

日々の業務や勉強などで得た知識をアウトプットしていきます。

【Git】git reset --hard HEADを取り消したい!!

f:id:song-of-life1352607:20180113002051p:plain

こんにちは、マリンです。

今日Gitでやらかしました。

表題の通りなのですが、それにプラスしてDBのロールバックとかも関わってきて、

「ギャーーーー!!!」

ってなったので残しておきます。





概要

やらかした内容が多すぎるので、時系列で箇条書きします。

  1. 新規でDBのテーブルを作成するのにrails g migration ~~で作ってしまった。

  2. テーブルを作った後しばらく作業を進めてからModelが無いことに気づく。(この間何度かcommitしてる)

  3. Modelだけ作れば良いところ、わざわざrake db:rollbackしようとした。

  4. だがロールバックする前に何を思ったかCommitをテーブル作る以前まで戻してしまった。

  5. しかもGitの取り消しをgit reset --hard HEAD@{n}でやってしまった。(ここが一番問題)

  6. ロールバックしようにも当然マイグレーションファイルまで消えてしまったのでできない。




やはり一番のやらかしポイントは、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に切り出してみた。

f:id:song-of-life1352607:20170220105406j:plain

こんにちは、マリンです。

今回は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.alleachで回して表示しているとします。

さらにChildrenは複数いる可能性があります。


そこで問題となるのが、

  1. どのUserの子供であるかを特定するためにuserを渡す必要があること。

  2. Childrenはuser.childrenの配列になること。

  3. その配列から名前を取得して表示するには、ループを回す必要があること。




ポップオーバーの内容は複雑になる場合、別エリアに切り出すことも可能です。

しかし今回の場合、まず一番外側で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 ~イベントによって表示・非表示を切り替える~

f:id:song-of-life1352607:20180113005923j:plain

こんにちは、マリンです。

今回はボタンクリックなどのイベントによって

簡単に要素の表示・非表示を切り替えようと思います。





概要

任意の要素の表示・非表示をボタンのクリックで切り替えます。

ボタンを押すと要素を非表示に、もう一度押すと表示させます。

詳細

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-elementjQueryでこの部分を特定するためのセレクタです。

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アイコンを使う

f:id:song-of-life1352607:20180314224659p:plain

こんにちは、まりんです。

うちの会社ではFontAwsomeがマストなんですが、

結構クライアントからこんなアイコン使って欲しい!といわれ、

そんなのねーよ!!という事が多々あるようです。

そしてこの前唐突に、上司から

アイコン作るスキル身につけて欲しいんだけど。

と言われました。

マジかよ!?

という事で、今回ちょっとやってみました。

「独自アイコンをWebアイコン化し、それをRails5で使ってみた。」

です。





概要

環境

使用アプリ

  • icomoon


今回は元素材はこちらでお借りしました。

icooon-mono.com

f:id:song-of-life1352607:20180709125300p:plain

使いたいアイコンのページからSVG形式でダウンロードしてください。


本当にアイコン作るところからやる場合は、

IllustratorInkscapeなど、SVGで出力できるアプリを使って作成して下さい。

(イラレは高いので私はInkscapeにしようと思っています。本業じゃないし。)


Webアイコン使用までの流れ

  1. 素材(SVG)をicomoonでWebアイコン化する

  2. Webアイコンをダウンロードして必要ファイルをアプリケーションに移動

  3. ファイルの移動先に合わせてダウンロードしたstyle.cssを書き換え

  4. assets/stylesheets/application.scssからimportで読み込む


詳細

それでは一つずつの詳細を説明します。

素材をWebアイコン化する

今回はicomoonというアプリを使って行います。

元となる素材をアップしてそこからダウンロードするだけでできるのでとてもお手軽です。


f:id:song-of-life1352607:20180709135805p:plain

①まずは左上のボタンから素材のファイルをアップロードします。

今回は1枚だけですが、複数アップロードすることが可能です。


②Webフォント化する素材を選択します。

ここでも複数選択が可能で、今後アプリで使用したいアイコンをまとめてダウンロードすれば、

オリジナルのアイコンリストを作れます。

今回はアップロードしたものの他に、追加したBrandsと言うライブラリのニンテンドーswitchのアイコンと、

デフォルトで表示されているIcoMoonのHomeアイコンを一緒にダウンロードしてみます。

*ちなみに写っていませんが、ページの一番下にライブラリ追加のリンクがあります。


③選択すると、下部の真ん中のSelectionの数が増えます。

④右下のボタンでWebアイコンを作成。

Webアイコン化するファイルの確認ページに飛びます。


f:id:song-of-life1352607:20180709140410p:plain

①ここで付いている名前がそのままアイコン使用時のクラス名になります。

アップロードしたアイコンが日本語になっているので編集します。

他にもアイコン画像をクリックすれば、拡大縮小や反転などの簡単な編集も可能です。

②編集、確認が終わったら右下のボタンでダウンロードして下さい。


必要ファイルをアプリケーションに移動

ダウンロードしたフォルダを解凍したら、必要なファイルをアプリケーションに移動します。

f:id:song-of-life1352607:20180709142837p:plain

必要なファイルは上記のfontsフォルダ下のものと、style.cssです。


railsアプリのvendor下に置きます。

vendorの中にassetsをまず作り、その下にfontsstylesheetsを作ります。

他にも何かダウンロードしたものを使うことがあるかもしれないので、

一応専用のフォルダをそれぞれ作ってそこに入れます。

  • vendor/assets/fonts/icomoon → fonts下のファイル

  • vendor/assets/stylesheets/icomoon → style.css




style.cssの書き換え

先ほど移動した構造に合わせて、ダウンロードしたstyle.css内のurlの部分を書き換えます。

f:id:song-of-life1352607:20180709150059p:plain

私はここの指定が不十分だったがために余計な時間や手間を使ってしまいました。

assetsなので、追加した専用フォルダ名からで大丈夫です。

*今回はRails5なのでconfigの方の設定は入りませんでした。

確認はしていませんが、もしかしたらRails4以下ではプリコンパイルなどの設定がいるかもしれません。

こちらで大変詳しく説明して下さっています。

qiita.com




application.scssで読み込む

最後にassets/stylesheets/application.scssからimportでライブラリを読み込みます。

f:id:song-of-life1352607:20180709150950p:plain

ここでも専用フォルダ名が必要になるのでご注意ください。

*直置きの場合は入りません。

以上で実際に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>

f:id:song-of-life1352607:20180709151743p:plain

無事表示できました。

まとめ

Webアイコン化して表示させるまでは簡単ですね。

問題はアイコンを作れるかどうかです。

画力も計算もデザイン能力も全く自信がないのですが。。。

それはさておき、途中にも書きましたがこれはRails5での方法になります。

Rails4以下ではvendorに置いたものをデフォルトで探してはくれなかったりするようなので、

おそらくconfig/initializers/assets.rbなどに設定する必要があるかと思います。


今回はこちらの記事を参考にさせていただきました。

machida.github.io

【jQuery (CoffeeScript)】属性の値に変数や式を使う 〜式展開の色々〜

f:id:song-of-life1352607:20180113005923j:plain

こんにちは、マリンです。

今回は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】テーブル結合 ー外部結合ー

f:id:song-of-life1352607:20170220105406j:plain

こんにちは、マリンです。

前回の続き、次は外部結合についてです。

内部結合についてはこちらです⬇️

song-of-life.hatenablog.com




外部結合

外部結合は、結合したテーブルの指定したカラムの値が一致するものに加え、

どちらかのテーブルにしかないレコードも取得します。

どちらかというのは、基準とするテーブル(つまり左か右か)によって変わります。


例に使うテーブル構造を記述しておきます。

[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_loadpreloadなどもあります。

内部結合でも様々な方法がありますので、

ご興味のある方は調べてみてください。

なかなか奥が深い上に咀嚼に時間のかかるテーブル結合でした・・・

自分で色々試しながら理解してきましたが、

まだまだ自信がない部分も多々あります。

何か間違っているところなど御座いましたらぜひご教示ください。

最後に、今回こちらの記事に大変助けられましたのでご紹介しておきます。

qiita.com

【SQL】テーブル結合 ー内部結合ー

f:id:song-of-life1352607:20170220105406j:plain

こんにちは、マリンです。

今回は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_idowners.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 JOINownersの値も取得できます。

そちらも検証してみました。

INNER JOINの方のカラムを取得する場合にはSELECTでそのカラムを指定する必要があります。

 詳しくは補足をご覧ください。


【番外】結合した方のカラムの値を取得してみる

結合後のレコードからownersnameを取得してみます。

  • 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.nameowner_nameに変更して値を取得します。




次に、上で取得・連結したレコードの中からrailsowner_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 JOINownersの値も取得できます。

と書きましたが、

これはその後の番外で解説した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