ryota21silvaの技術ブログ

Funna(ふんな)の技術ブログ

これまで学んだ技術の備忘録。未来の自分が救われることを信じて

【初学者向け】Gitのコマンドの使い方をよく忘れるから、纏めてみた

はじめに

Git: もう怖くないGit!チーム開発で必要なGitを完全マスター 山浦 清透を受講した時のメモがあったので、今の知識をもとに整理してみました。 これから新しいコマンドの使い方を覚えた時には、この記事に纏めていこうと思います。


1. 基本コマンド

  • git init
    • ローカルリポジトリ(=.gitディレクトリのこと。gitに必要なものがほどんど入っている)の新規作成。
      ドット(.)は隠れフォルダを表している。
  • git clone URL
    • ローカルにリモートリポジトリのファイルをコピーする。
  • git add -A
    • 現在のディレクトリ配下の変更ファイルが、ステージング (Staging) という待機用リポジトリに置かれる。addしたファイルは本当にコミットの対象にすべきなのかgit statusgit diffとかで確認しておきたい。
  • git commit

    • 変更を記録する(=コミット)。ステージの内容をスナップショットとしてリポジトリに記録する(変更、新規作成、削除、複数ファイルの変更・作成・削除)。
  • git commit -m 'メッセージ'

  • git commit -v
    • エディターを立ち上げて、「どのファイルにどんな変更をしたのか」エディター上で確認できる。結構オススメ。
  • git status

    • 以下のような変更されたファイルを確認する。
      1. ワークツリーとステージの間で変更されたファイル(前回、ステージに追加してから変更したワークツリーのファイル)
      2. ステージとリポジトリの間で変更されたファイル(前回、コミットしてからステージに追加されたファイル)を確認できる。
  • git remote add origin URL名

  • git push -u origin master(ブランチ名)

    • ローカルリポジトリの内容をリモートリポジトリに送る(master=デフォルトのブランチ名)。
    • 最初に-uを付ければ、アップストリームブランチを指定してくれるので、次回からgit pushに省略可能。
    • featureブランチを切った時は、最初にgit pushとだけ実行し、アップストリームブランの指定を行うサジェスチョンを表示させるのが良い。

2. git diff系

  • git diff
    • git addする前の変更差分を確認する。ワークツリーとステージの間の変更差分を確認する。
  • git diff ファイル名
    • 特定のファイルの変更差分を見る。
  • git diff --staged
    • ステージしたけど、まだコミットしてない変更差分を見る。git addした後の変更内容を確認する。

3. git log系

  • git log
    • 今までの変更履歴(コミット)を確認する(最新のコミットから順に表示)。
  • git log --oneine
    • 変更履歴を1行で表示。
  • git log -p ファイル名
    • 指定したファイルの変更差分を表示する。
  • git log -n コミット数
    • 表示するコミット数を制限(最新順で表示)。よく使う。

4. git rm系

  • git rm ファイル名

    • ファイルの削除を記録する(削除された変更状態がステージに記録される)。
      これは、ファイルごと削除するということ(=コミットした記録だけではなく、ワークツリー内のファイルも削除)。ワークツリーからもローカルリポジトリからも削除したい時に使う。
  • git rm -r ディレクトリ名

  • git rm --cached ファイル名
    • コミットした記録は削除したいけど、ワークツリー内のファイルは残したい(ローカルリポジトリからだけ削除して、その変更状態がステージに記録される。
    • これらのコマンド実行後、lsでワークツリーのディレクトリ内を確認、git statusで変更されたファイルを確認をすれば、記録が分かる。

5. git mv系

  • git mv 旧ファイル 新ファイル
    • ファイルの移動(=ファイル名の変更)を記録する。つまり、ワークツリーのファイル名が変更されて、かつ、ステージにも変更が反映される。

6. ワークツリーのファイル内の変更を取り消したい

  • git checkout -- ディレクトリ名
  • git checkout --.
    • 全変更を取り消す。

7. addしたけど、その変更を取り消したい(=commit対象外にしたい)

更新内容自体は取り消さず、addしてインデックスに登録するのを取り消す。

  • git reset ファイル名
  • git resetだけだと、ステージングにあるファイル全てを取り消す。

8. git commitした後に、直前のコミットを取り消したい(git reset HEAD)

1. git logでコミット履歴を確認する。

$ gl -n 3                                                                                                                                                           【 feature/19_meal_record_crud_exception 】
commit 4419fad760dd4e703559660195577047d88f60c5 (HEAD -> feature/19_meal_record_crud_exception)
Author: ryota1116 <ryota@gmail.com>
Date:   Thu Aug 27 16:30:34 2020 +0900

    [Add]MealRecordの各ページを修正

commit 2d14341fb814920c1072bd0a1e09678ad43abcef (origin/feature/19_meal_record_crud_exception)
Author: ryota1116 <ryota@gmail.com>
Date:   Thu Aug 27 00:39:01 2020 +0900

    [Fix]MealRecord検索フォームのjsファイルを修正

commit 14e7eaaf4b36535e32b342b33b7e54120f11f9bb
Author: ryota1116 <ryota@gmail.com>
Date:   Thu Aug 27 00:17:06 2020 +0900

    [Add]MealRecordフォームのi18nを追記

2. 取り消したいcommitをrevertする。

  • git reset HEAD^で、HEADの1個前のコミットに戻る。git reset コミットIDでも可。
  • HEAD^^^で、HEADの3個前のコミットに戻る。
HEADの意味
  • HEAD -> aaa(ローカルブランチ名)が現在のブランチの場所を指定(記憶)しており、その指定されているブランチが最新のコミットの位置を記憶している。
  • origin/ブランチ名はリモートにpush済。

qiita.com

9. git pushした後に、直前のコミットを取り消したい(git revert)

  • git revert コミットID

revertするコミットを指定する感じ。
git logすると、commitとrevertのログが残ってるのが分かる。

  • ただし、revertしたコミットのファイルがローカルからも消えちゃう(前のコミットに戻ったから)ので、「revertしたコミットの変更」をrevertしてあげれば、変更内容をローカルに残せる。以下のように。

git revert <revertしたコミットID> -nで、revertしたコミットの変更を取り消す(直前のコミットIDの変更を削除したコミットを取り消す感じかね。

10. 直前のコミットを修正したい(=今のステージの状態をもとに、コミットの内容を上書きしたい)

  • git commit --amend
    • 例えば、ファイルの修正を忘れてた時に、ワークツリー内のファイルに変更を加え、git addgit commit --amendを行う感じ。

git commit --amendでaddし忘れたファイルを救済する - Qiita

注意点

  • リモートリポジトリにpushした後のコミットは絶対にamendで修正してはいけない!!!

    pushした後にそのコミットを修正したければ、git commitでもう一度新しいコミットを作って修正すること。

11. git remote系

リモートリポジトリの情報を確認する

  • git remote
    • 設定しているリモートリポジトリを一覧表示する
  • git remote -v
    • 設定しているリモートリポジトリのURLを一覧表示する。

リモートリポジトリを複数登録する

  • メリット

    • 複数のチームで作業するとき
    • 自分のリモートを持ちたいとき
  • コマンド

    • git remote add リモート名 リモートURL
      • リモート名(ショートカット)で、URLのリモートリポジトリを新規登録する。

12. git fetch系

  • リモートリポジトリから情報を取得する(フェッチ)

    • git fetch リモート名
    • git fetch origin
  • 特徴など

    • ローカルリポジトリに情報を保存するけど、ワークツリーには保存されない。
    • 具体的にはremotes/リモート/ブランチというリモートブランチに保存される。
      • git mergeでローカルリポジトリ(ローカルブランチ)に保存された情報を、ワークツリーに落とす。

13. git pull系

  • git pull リモート名 ブランチ名

  • リモートから情報を取得し、マージまでを一度にやりたい」ときに使う。

    • git fetchとgit mergeを同時に行っている

14. fetchとpull

  • fetchを使うのが基本オススメみたい。

    • pullは特殊な挙動を起こす。今自分がいるブランチに、pullしてきたブランチがマージされる。異なるブランチ同士が統合される恐れがある。ファイルがグチャグチャになる可能性があるということ。
  • 自分が「マスターブランチにいて、何の変更もしていないときに限って」、pullで情報を取得するなどを行うべき。

    • 安全なケースの時にだけpullを使うのが良さそう。

15. リモートの詳細情報を表示する

  • git remote show リモート名
    • fetchとpushのURL、リモートブランチ、git pullの挙動、git pushの挙動などが表示される。

16. リモート名を変更する、リモートを削除する

  • git remote rename 旧リモート名 新リモート名
  • git remote rm リモート名

17. ブランチとは

  • コミットを指すポインタのことで、並行して複数機能を開発するためにあるもの。
    • ブランチは分岐して開発するためにあるもので、他の人の行った開発が、自分の開発には影響されないメリットを持つ(複数の機能の共同開発が可能となる)。

18. HEADとは

  • 今自分が作業しているブランチを指し示したポインタのこと。
  • このブランチとHEADの2つ(リポジトリ内に保存されている)により、最新のコミットがどれか分かる(順にHEAD→ブランチ→コミットと指している)

19. branch関連のコマンド

ブランチの新規作成

  • git branch ブランチ名
    • ブランチを新規追加する。(ブランチの切り替えまでは行わない)

ブランチの表示

  • git branch
    • 今あるブランチの一覧を表示。
  • git branch -a
    • 全てのブランチを表示。リモートリポジトリも表示できる。

ブランチの切り替え

  • git checkout 既存ブランチ名
    • ブランチを切り替える。(HEADが指し示すブランチを切り替えるということ)
  • git checkout -b 新ブランチ名
    • ブランチを新規作成し、切り替えも行う。

ブランチ名を変更する

  • git branch -m 新ブランチ名
    • 自分が現在作業しているブランチの名前を変更する。

リモートにpushした後にブランチ名を変更する

git branch -m 新ブランチ名
git push origin :旧ブランチ名  # リモート上にある旧名のブランチを削除
git push origin 新ブランチ名

ブランチを削除する

  • git branch -d ブランチ名
    • masterにマージされていない変更が残っている場合、そのブランチは削除しないようになっている。安全。
  • git branch -D ブランチ名
    • 強制削除する。慎重に使うこと。

20. merge系

マージ・・・他の人の変更内容を自分のブランチに取り込む作業のこと。

  • git merge ブランチ名
    • 指定したブランチ名の変更内容を、自分が今いるブランチに取り込む。
  • git merge リモート名 ブランチ名
  • git merge origin master(リモートブランチ名)
    • origin(=GitHubのこと)のmasterブランチの内容を、自分の作業ブランチに取り込む。

21. コンフリクトとは

  • 複数人の人が、同じファイルの同じ行に対して、異なる編集を行った時に起こること。
    • マージしたら、コンフリクトしてるよと言われ、Autoマージが失敗する。コンフリクトしても、落ち着いてファイルの内容をきれいに書き換えればOK。

22. コンフリクトの事故が起きにくい運用ルール

  1. 複数人で同じファイルを変更しない。
  2. pullやmergeする前に変更中の状態を無くしておく(commitstash(変更中のファイルを一時保管するコマンド)で変更中のファイルを無くしておく)。
  3. pullするときは、pullするブランチに移動してからpullする(同じ名前のブランチに移動してからpullしろってこと)。

23. ブランチを利用した開発の流れ

  • masterブランチをリリース用ブランチとして使い、開発はトピックブランチを作成して進めるのが基本。
    • masterブランチを常に最新のリリース状態に保っているから、最新のリリース内容がどんなものかmasterを見れば分かるようになっている!
    • 何かバグが起きても、一つ前のマスターブランチのバージョンに戻せばいいだけ。
  • masterで開発すると、最新のリリース状態がわからなくなるし、前のバージョンに切り戻すのが難しくなる。
    • 開発するときは、最新のマスターからトピックごとにブランチを切って、そのトピックブランチで開発を行う。そして、開発が完了したらmasterにマージする。

24. プルリクエストとは

  • 自分の変更したコードをリポジトリに取り込んでもらえるよう依頼する機能。バグを発生させない、コードの質を保つために、コードレビューを行ってもらう。

25. rebaseで変更履歴を修正する

  • git rebase ブランチ名
    • 「ブランチの基点となるコミット」を別のコミットに移動する。コミットの履歴を一直線に綺麗に整えられる。

26. リベースとマージの違い

履歴が一直線なのか、枝分かれなのか

- rebase...作業の履歴を消したい場合

  • 履歴をきれいに保つことが可能
  • コンフリクトの解決が若干面倒(各コミットごとにコンフリクトが発生する)

- merge...作業の履歴を残したい場合

  • コンフリクトの解決が比較的簡単(コンフリクトが一度しか発生しない)
  • マージコミットがたくさんあると履歴が複雑化する

27. リベースとマージの使い分け

  • プッシュしていないローカルの変更には、リベースを使う
  • プッシュした後はマージを使う。コンフリクトしそうな時もマージを使う。

注意点

  • githubにプッシュしたコミットをリベースするのは絶対NG!!

    • git push -fは絶対NG

28. pullにはマージ型とリベース型がある

  • マージ型
    • マージコミットが残るから、マージしたという記録を残したいときに使う。
  • リベース型
    • マージコミットが残らないから、Githubの内容を取得したいだけのときは以下のコマンドを使う。
      • git pull --rebase リモート名 ブランチ名

29. 複数のコミットをやり直す(git rebase)

  • git rebase -i コミット名
  • git rebase -i HEAD~3
    • 直前3つのコミットをやり直す

30. Git管理対象外に置きたくないファイルをpushしてしまった時

秘匿情報の入ったファイル、アプリケーションとは関係のないスクショなどを.gitignoreの中に指定するのを忘れ、pushしちゃった時に、そのファイルをGit管理対象外にする。

git rm --cached ファイル名

このコマンドで対象にされたファイルは、.gitignoreが適用されるようになる。

git rm --cached ファイル名とすると、ローカルからファイルが一旦消えるため、どこかにバックアップを取っておく。
push→pullしたあと、ファイルがdeletedになるから、ローカルにコピーして書き直せばおk。

31. git pushrejectされた時の対処法

ローカルでコミットした後に、Githubへpushしようとすると以下のようなエラーが表示されることがある。

~/workspace/app/cat_meow_miaow
$ git push --set-upstream origin master 【 master 】
To https://github.com/ryota1116/cat_meow_miaow.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'https://github.com/ryota1116/cat_meow_miaow.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

これは自分がpushするまでの間に、他の人が同じブランチ(以下の例ではmasterブランチ)にpushしているなどリモートの内容が変更されている場合に表示されるエラー。解決方法としては、リモートの最新の内容をローカルに反映させてあげる必要がある。
git pull リモートブランチ名 作業ブランチ名としてあげればいいので、今回の例でいけば、git pull origin masterを実行する。

もしくは、git pull --rebase リモートブランチ名 作業ブランチ名としてあげると、remoteの変更をpullした上で、その後にローカル上の変更をマージしてくれるので、コッチのが手っ取り早くはある。

$ git pull --rebase origin master    【 master 】
From https://github.com/ryota1116/cat_meow_miaow
 * branch            master     -> FETCH_HEAD
First, rewinding head to replay your work on top of it...
warning: unable to rmdir 'sound': Directory not empty
Applying: [Add]キー入力で音声出力
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...

これでpushできるようになったはず。

その他Gitでよく使うコマンド

  • git stash
  • git stash -u
  • git stash apply
  • git stash pop
  • git reset -soft HEAD^
  • git reset -soft HEAD^^
  • git reset -soft HEAD2^
  • git reset -hard HEAD2^(危険)
  • git merge --abort

【Ruby】Date型で色んなメソッドを試してみる

今日、昨日、○日前、○日後とか

# 今日
[1] pry(main)> Date.today
=> Sat, 22 Aug 2020
[6] pry(main)> Date.current
=> Sat, 22 Aug 2020

# 昨日
[7] pry(main)> Date.current.yesterday
=> Fri, 21 Aug 2020

# 昨日(1日前)
[8] pry(main)> Date.current.ago(1.day)
=> Fri, 21 Aug 2020 00:00:00 JST +09:00

# 3日前
[9] pry(main)> Date.current.ago(3.day)
=> Wed, 19 Aug 2020 00:00:00 JST +09:00

# 10日前
[8] pry(main)> Date.today.prev_day(10)
=> Mon, 17 Aug 2020

# 10日語
[7] pry(main)> Date.today.next_day(10)
=> Sun, 06 Sep 2020

# 2ヶ月後
[9] pry(main)> Date.today.next_month(2)
=> Tue, 27 Oct 2020

日・週・月などを丸々取得

# 今日24時間丸々
[16] pry(main)> Date.current.all_day
=> Sat, 22 Aug 2020 00:00:00 JST +09:00..Sat, 22 Aug 2020 23:59:59 JST +09:00

# 今週丸々
[17] pry(main)> Date.current.all_week
=> Mon, 17 Aug 2020..Sun, 23 Aug 2020

[18] pry(main)> Date.current.all_month
=> Sat, 01 Aug 2020..Mon, 31 Aug 2020
[19] pry(main)> Date.current.all_year
=> Wed, 01 Jan 2020..Thu, 31 Dec 2020

日・週・月などの始まり・終わり

# 今日の0時
[21] pry(main)> Date.current.beginning_of_day
=> Sat, 22 Aug 2020 00:00:00 JST +09:00

# 今週最初の日(月曜日)
[13] pry(main)> Date.current.beginning_of_week
=> Mon, 17 Aug 2020

# 今週最後の日(日曜日)
[14] pry(main)> Date.current.end_of_week
=> Sun, 23 Aug 2020

# 今月最後の日 
[15] pry(main)> Date.current.end_of_month
=> Mon, 31 Aug 2020

# 今年最後の日
[20] pry(main)> Date.current.end_of_year
=> Thu, 31 Dec 2020

指定したオブジェクトが、どんなメソッド等を使用できるのか確認する

ある型を持つオブジェクト(今回で言えばDate型)が、どのようなメソッドを使用できるのか確認する癖は付けておいた方がいいと思う。

[16] pry(main)> today = Date.today
=> Thu, 27 Aug 2020

[18] pry(main)> today.class
=> Date

[17] pry(main)> ls today
ActiveSupport::ToJsonWithActiveSupportEncoder#methods: to_json
ActiveSupport::Dependencies::ZeitwerkIntegration::RequireDependency#methods: require_dependency
Comparable#methods: <  <=  ==  >  >=  between?  clamp
DateAndTime::Zones#methods: in_time_zone
DateAndTime::Calculations#methods:
  after?       all_year                 at_end_of_month    beginning_of_month    days_since          end_of_year   last_weekday  next_occurring  on_weekend?     prev_weekday  weeks_since
  all_day      at_beginning_of_month    at_end_of_quarter  beginning_of_quarter  days_to_week_start  future?       last_year     next_quarter    past?           sunday        years_ago
  all_month    at_beginning_of_quarter  at_end_of_week     beginning_of_week     end_of_month        last_month    monday        next_week       prev_occurring  today?        years_since
  all_quarter  at_beginning_of_week     at_end_of_year     beginning_of_year     end_of_quarter      last_quarter  months_ago    next_weekday    prev_quarter    tomorrow      yesterday
  all_week     at_beginning_of_year     before?            days_ago              end_of_week         last_week     months_since  on_weekday?     prev_week       weeks_ago
Date#methods:
  +                ajd                  at_noon                   cwyear           gregorian   jd            midday                  new_start              prev_month        step            to_s
  -                amjd                 beginning_of_day          day              gregorian?  jisx0301      middle_of_day           next                   prev_year         strftime        to_time
  <<               as_json              blank?                    day_fraction     hash        julian        midnight                next_day               readable_inspect  succ            tuesday?
  <=>              asctime              change                    default_inspect  httpdate    julian?       minus_with_duration     next_month             rfc2822           sunday?         upto
  ===              at_beginning_of_day  compare_with_coercion     downto           in          ld            minus_without_duration  next_year              rfc3339           thursday?       wday
  >>               at_end_of_day        compare_without_coercion  end_of_day       infinite?   leap?         mjd                     noon                   rfc822            to_date         wednesday?
  acts_like_date?  at_midday            ctime                     england          inspect     marshal_dump  mon                     plus_with_duration     saturday?         to_datetime     xmlschema
  advance          at_middle_of_day     cwday                     eql?             iso8601     marshal_load  monday?                 plus_without_duration  since             to_default_s    yday
  ago              at_midnight          cweek                     friday?          italy       mday          month                   prev_day               start             to_formatted_s  year

参照記事

Rails6にSemanticUIを導入

Rails6でのSemantic UIの導入

BootstrapのフレームワークであるSemantic UIをRails6で使ってみたので、以下に導入方法を記しておきます。

Semantic UI公式

以下の記事もよければ参考にしてください。

【Rails】Semantic UIを使って動的にフラッシュメッセージを表示する方法 - Qiita

前提

Webpackerでの導入方法が分からなかったので、従来のassetsフォルダでの導入にしました。。。
Webpackerでの導入方法が分かり次第、更新したいと思います。

追記

公式のisuueを追ったところ、semantic-ui-sassはwebpackerで導入する方法が見当たらなかった。

How to add semantic-ui-sass on Rails 6 · Issue #144 · doabit/semantic-ui-sass · GitHub

導入方法

  • Gemをインストールする。
(Gemfile)
gem 'semantic-ui-sass'
(app/assets/stylesheets/application.scss)
@import "semantic-ui";
(app/assets/javascript/application.js)

// Loads all Semantic javascripts
// = require semantic-ui

500エラーが表示されるので、エラーメッセージを確認。

ActionView::Template::Error - Asset `application.js` was not declared to be precompiled in production.
Declare links to your assets in `app/assets/config/manifest.js`.

  //= link application.js
and restart your server:
  app/views/layouts/application.html.slim:11:in `view template'

app/assets/config/manifest.js//= link application.jsを追記。

(app/assets/config/manifest.js)
//= link_tree ../images
//= link_directory ../stylesheets .css
//= link application.js

これだけでSemantic UIの記述を反映できる。

Rails6にjQueryを導入

Rails6でjQueryを導入する方法

yarn add jquery

package.jsonyarn.lockを確認すると、jQueryが追加されていることが分かる。

(package.json)
{
  "name": "zero_calorie",
  "private": true,
  "dependencies": {
    "@rails/actioncable": "^6.0.0",
    "@rails/activestorage": "^6.0.0",
    "@rails/ujs": "^6.0.0",
    "@rails/webpacker": "4.2.2",
    "jquery": "^3.5.1",
    "turbolinks": "^5.2.0"
  },
  "version": "0.1.0",
  "devDependencies": {
    "webpack-dev-server": "^3.11.0"
  }
} 
(yarn.lock)
jquery@^3.5.1:
  version "3.5.1"
  resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5"
  integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg== 
(config/webpack/environment.js)

const { environment } = require('@rails/webpacker')

module.exports = environment

以下のように変更

(config/webpack/environment.js)

const { environment } = require('@rails/webpacker')

const webpack = require('webpack')

environment.plugins.prepend('Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery'
  })
)

module.exports = environment

JS、jQueryのファイルを読み込む

(app/javascript/packs/application.js)
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")

// 追加
require("jquery")
require("jquery/submit_food_image")

htmlが記述されたslimファイル

h1 id="red"
  | 食事記録の一覧

h2 食事記録

jsファイルを作成。これでJS、JQueryで記述した内容が反映される。

(app/javascript/jquery/submit_food_image.js)
$(function () {
  $(document).ready(function(){
    $("#red").css("color", "red");
  });
});

slimで直書きVer.

h1 id="red"
  | 食事記録の一覧

h2 食事記録

javascript:
  $(function () {
    $(document).ready(function(){
      $("#red").css("color", "red");
    });
  });

参照サイト

Rails 6にjQueryとBootstrapを入れる - Qiita

【Rails】マイグレーションファイルをmigrateする前に戻す方法(その他マイグレーション関連のコマンド)

be rails db:migrate:statusでmigrateの状況を確認。 upになっているファイルがmigrateされている。

$ be rails db:migrate:status

database: zero_calorie_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20200802075924  Create meal records
   up     20200802080924  Create active storage tablesactive storage
   up     20200804123950  Create foods

be rails db:rollback STEP=3で3つのファイル(最新順)をmigrate前に戻す。

$ be rails db:rollback STEP=3 
== 20200804123950 CreateFoods: reverting ======================================
-- drop_table(:foods)
   -> 0.0920s
== 20200804123950 CreateFoods: reverted (0.0986s) =============================

== 20200802080924 CreateActiveStorageTables: reverting ========================
-- drop_table(:active_storage_attachments, {})
   -> 0.1429s
-- drop_table(:active_storage_blobs, {})
   -> 0.0940s
== 20200802080924 CreateActiveStorageTables: reverted (0.2376s) ===============

== 20200802075924 CreateMealRecords: reverting ================================
-- drop_table(:meal_records)
   -> 0.0528s
== 20200802075924 CreateMealRecords: reverted (0.0530s) =======================

Model files unchanged.

downになっているファイルは、migrateされる前の状態に戻ったということ。 down状態のファイルは直接編集おk。 (ただし、「新機能を実装するためにFoodテーブルと他のテーブルにアソシエーションを組みたい」といった場合、rollbackするのではなく新しくマイグレーションファイルを作成してあげればよい。マイグレーションファイルは歴史の積み重ね的なやつだから時系列で残してあげた方がよいと思われる)

$ be rails db:migrate:status
database: zero_calorie_development

 Status   Migration ID    Migration Name
--------------------------------------------------
  down    20200802075924  Create meal records
  down    20200802080924  Create active storage tablesactive storage
  down    20200804123950  Create foods
$ be rails db:migrate
== 20200802075924 CreateMealRecords: migrating ================================
-- create_table(:meal_records)
/Users/funesakisuke/workspace/app/zero_calorie/vendor/bundle/ruby/2.7.0/gems/migration_comments-0.4.1/lib/migration_comments/active_record/connection_adapters/mysql2_adapter.rb:39: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/Users/funesakisuke/workspace/app/zero_calorie/vendor/bundle/ruby/2.7.0/gems/activerecord-6.0.3.2/lib/active_record/connection_adapters/mysql/schema_statements.rb:80: warning: The called method `create_table' is defined here
/Users/funesakisuke/workspace/app/zero_calorie/vendor/bundle/ruby/2.7.0/gems/migration_comments-0.4.1/lib/migration_comments/active_record/connection_adapters/table_definition.rb:10: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/Users/funesakisuke/workspace/app/zero_calorie/vendor/bundle/ruby/2.7.0/gems/activerecord-6.0.3.2/lib/active_record/connection_adapters/abstract/schema_definitions.rb:364: warning: The called method `column' is defined here
   -> 0.0480s
== 20200802075924 CreateMealRecords: migrated (0.0481s) =======================

== 20200802080924 CreateActiveStorageTables: migrating ========================
-- create_table(:active_storage_blobs, {})
   -> 0.0524s
-- create_table(:active_storage_attachments, {})
   -> 0.0477s
== 20200802080924 CreateActiveStorageTables: migrated (0.1003s) ===============

== 20200804123950 CreateFoods: migrating ======================================
-- create_table(:foods)
   -> 0.0516s
== 20200804123950 CreateFoods: migrated (0.0517s) =============================

Annotated (3): app/models/meal_record.rb, spec/models/meal_record_spec.rb, spec/factories/meal_records.rb
$ be rails db:migrate:status 

database: zero_calorie_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20200802075924  Create meal records
   up     20200802080924  Create active storage tablesactive storage
   up     20200804123950  Create foods

その他コマンド

  • 全てのテーブルをドロップして、全てのmigrationを実行してテーブルの再作成を行う。
be rails db:migrate:reset

情報収集のやり方と好きなメディアサービスやブログを纏めておく

はじめに

Feedlyを使って効率的に情報収集しよう

私は10個くらいのメディアを使って情報収集しているのですが、全メディアのサイトを訪問しているようでは、時間がいくらあっても足りません。
そこで色んなサイトの記事を1つのプラットフォームで一元管理してくれるFeedlyというサービスを使っています。
Feedlyのページを見るだけで、各サイトの欲しい内容は大体漏れなくキャッチアップできますし、情報収集のハードルも下がります。控えめに言ってめっちゃオススメ。

f:id:ryota21silva:20200804193158p:plain
Feedlyのトップページ

この方の動画見たら使い方分かると思います www.youtube.com

好きなメディアやブログ

以下にザックリ纏めてみました。

はてなブログ

安定のはてぶ b.hatena.ne.jp

TechCrunch

スタートアップ企業の紹介やインターネットの新しいプロダクトのレビュー、そして業界の重要なニュースを扱うテクノロジーメディア jp.techcrunch.com

業界カオスマップもよく見ている jp.techcrunch.com

Monthly Pitch

創業期の企業家向けのピッチイベント monthly-pitch.com

スタートアップタイムズ

スタートアップのインタビュー記事 これめっちゃ面白いのでオススメ startuptimes.jp

R25

これからの時代を生きる若者の仕事や人生のバイブル的存在らしい。 読んでてワクワクする。 r25.jp

News Picks

学生時代から読んでる一番お世話になってるメディアかも。 難しいテーマをイラスト付きで解説してくれるまとめコラムみたいなのが面白い。 newspicks.com

CodeZine

プログラミングに役立つソースコードと解説記事が満載な開発者のための実装系Webマガジン codezine.jp

TechFeed

"地上最強"のエンジニア向け情報サービスらしい
TechFeed Pro - "地上最強"のエンジニア向け情報サービス

Menthas

プログラマ向けのニュースサイト menthas.com

エンジニア採用・育成・組織づくりラボ by Findy

Findy(ファインディ)Inc.が運営するCTO等のエンジニアマネジメント層や人事・採用担当者向けのメディア あんまり見ないけど一応。 blog.findy.us

https://techfeed.io/

クックパッド開発者ブログ

techlife.cookpad.com

食べチョク開発者ブログ

tech.tabechoku.com

【Rails】RailsでAPIを叩く基本編

APIの特徴などを軽く抜粋(dysonさんメモ)

  • APIインターフェイス=エンドポイントと思っておk
    →要はURL(http://localhost3000/users/1など)

  • APIを叩く=HTTPリクエストを送る

  • 決められたインターフェースに適合するようにリクエストを送ると、それに応じたレスポンスが返ってくる

  • 外部のAPIを使う
    →あるパラメータをあるエンドポイントに送ると、あるレスポンスが返ってくる。

  • データベースに情報を持っている
    →それを皆に使ってもらえるようにAPIを作る

  • 決められたインターフェースに適合するように=こういう条件を満たすように

  • JSON形式のデータを返すことが主流 render json: hoge

jsonのserializerとは?

jsonを生成する仕組みのこと。
→この生成したJSONのデータをレスポンスとして返す。

ActiveRecordは ActiveModel::Serializationをinclude している。なので、以下のようにrender jsonと、そのまま記述できる。

module Api
  module V1
    class ArticlesController < BaseController
      def index
        articles = Article.all
        render json: articles
      end
    end
  end
end
  • PostmanでAPIを叩くとJSONレスポンスが返ってきていることが分かる。
    f:id:ryota21silva:20200726122953p:plain
    PostmanでAPIを叩いた

今回実装してみる要件

fast_jsonapiというserializerを使って、 記事一覧のjsonレスポンスを返す。

  • Articleモデル。Userモデルと一対多の関係。
(app/models/article.rb)
class Article < ApplicationRecord
  belongs_to :user
  has_many :comments, dependent: :destroy

  has_one_attached :eye_catch
  enum status: { draft: 0, in_review: 10, published: 20, archived: 30 }
end
  • Articleモデルのシリアライザーを作る
$ be rails g serializer Article title contents status                                    
Running via Spring preloader in process 21798
      create  app/serializers/article_serializer.rb
(app/serializers/article_serializer.rb)
class ArticleSerializer
  include FastJsonapi::ObjectSerializer
  attributes :title, :contents, :status
  belongs_to :user
end
(app/controllers/api/v1/articles_controller.rb)
# frozen_string_literal: true

module Api
  module V1
    class ArticlesController < BaseController
      def index
        articles = Article.all
        json_string = ArticleSerializer.new(articles).serialized_json
        # 上と同じ
        # json_string = ArticleSerializer.new(articles).serializable_hash.to_json
        render json: json_string
      end
    end
  end
end

PostmanでAPIを叩いてみると、ちゃんとデータが取れた。

{
    "data": [
        {
            "id": "1",
            "type": "article",
            "attributes": {
                "title": "title-1",
                "contents": "contents-1",
                "status": "draft"
            },
            "relationships": {
                "user": {
                    "data": {
                        "id": "1",
                        "type": "user"
                    }
                }
            }
        },
        {
            "id": "2",
            "type": "article",
            "attributes": {
                "title": "title-2",
                "contents": "contents-2",
                "status": "draft"
            },
            "relationships": {
                "user": {
                    "data": {
                        "id": "2",
                        "type": "user"
                    }
                }
            }
        },
...
......
........

Userログインを叩くとこんな感じ f:id:ryota21silva:20200726123531p:plain


関連モデルのデータを返す

  • ArticleSerializer.new(@article).serialized_jsonの場合
def show
        # options = { include: [:user, :'user.name', :'user.email'] }
        json_string = ArticleSerializer.new(@article).serialized_json
        render json: json_string
      end

以下のようなJSONレスポンスが返ってくる。記事に関連付いたユーザーのidも返ってくる。

{
    "data": {
        "id": "1",
        "type": "article",
        "attributes": {
            "title": "title-1",
            "contents": "contents-1",
            "status": "draft"
        },
        "relationships": {
            "user": {
                "data": {
                    "id": "1",
                    "type": "user"
                }
            }
        }
    }
}
  • options = { include: %i[user user.name user.email] }ArticleSerializer.new(@article, options).serialized_jsonの場合
def show
  # options = { include: [:user, :'user.name', :'user.email'] }
  options = { include: %i[user user.name user.email] }
  json_string = ArticleSerializer.new(@article, options).serialized_json
  render json: json_string
end

記事に関連付いたユーザーのレコードも返ってくる。

    "data": {
        "id": "1",
        "type": "article",
        "attributes": {
            "title": "title-1",
            "contents": "contents-1",
            "status": "draft"
        },
        "relationships": {
            "user": {
                "data": {
                    "id": "1",
                    "type": "user"
                }
            }
        }
    },
    "included": [
        {
            "id": "1",
            "type": "user",
            "attributes": {
                "name": "user-1",
                "email": "user-1@example.com"
            },
            "relationships": {
                "articles": {
                    "data": [
                        {
                            "id": "1",
                            "type": "article"
                        }
                    ]
                }
            }
        }
    ]
}

qiita.com

リクエストスペックの書き方も

require 'rails_helper'
RSpec.describe "Api::V1::Articles", type: :request do
  let!(:user) { create(:user) }
  
  describe "GET /api/v1/article/:id" do
    let!(:article) { create(:article, user: user) }
    it "掲示板詳細を返す" do
      get api_v1_article_path(article.id), headers: {
        'CONTENT_TYPE': 'application/json',
        'ACCEPT': 'application/json'
      }
      json = JSON.parse(response.body)
      expect(response).to be_successful
      expect(response).to have_http_status(200)
      expect(json["data"]["id"].to_i).to eq(article.id)
      # to_iでid='1'という文字列を数値に変換する
      # digを使う
    # expect(json["data"]["relationships"]["user"]["data"]["id"].to_i).to eq(article.user_id)
      expect((json.dig("data", "relationships", "user", "data", "id")).to_i).to eq(article.user_id)
    end
  end

【Rails/Rspec】リクエストスペックでAPIをテストする際に、よくやること

リクエストスペックの特徴

  • HTTP 動詞に対応するメソッド(get 、post 、delete 、 patch)を使う。
  • capybaraは使わない。
  • HTTPのレスポンスのステータスコードと実際のデータを含んだレスポンスボディを返す
200: OK - リクエストは成功し、レスポンスとともに要求に応じた情報が返される。
401: Unauthorized - 認証失敗。認証が必要である。
403: Forbidden - 禁止されている。リソースにアクセスすることを拒否された。
404: Not Found - 未検出。リソースが見つからなかった。

リクエストスペックでよく使うヤツ

  • JSON.parse

    与えられた JSON 形式の文字列を Ruby オブジェクトに変換して返す。
    JSON形式の文字列からRubyのHashへ変換することで、WebAPIで取得したJSONデータをRubyで使える様にする

  • リクエストヘッダーをセットしておく。

    →ヘッダ情報がないとテストが通らないことがある。

get api_v1_articles_path, headers: {
      'CONTENT_TYPE': 'application/json',
      'ACCEPT': 'application/json'
    }
(spec/requests/api/v1/articles_spec.rb)
require 'rails_helper'

RSpec.describe "Api::V1::Articles", type: :request do
  describe "GET /api/v1/articles" do
    it "掲示板一覧を返す" do
      create_list(:article, 10, user: user)
      get api_v1_articles_path, headers: {
          'CONTENT_TYPE': 'application/json',
          'ACCEPT': 'application/json'
        }
      // JSON.parse(body)でもいけたけど、response.bodyにしておく
      json = JSON.parse(response.body)
      expect(response).to have_http_status(200)
      expect(json["data"].count).to eq(10)
    end

  end
end
  • letを使って、headers情報を格納しておく。

let(:headers) do
  { 'Content-Type' => 'application/json',
    'Accept' => 'application/json'
  }
end
  • letで数字を変数(numとか)に入れておくのもアリ

RSpec.describe 'Api::V1::Articles', type: :request do
  describe 'GET /articles' do
    let(:article_num) { 10 }

    before do
      user = create(:user)
      create_list(:article, article_num, user: user)
    end

    it 'returns fincle articles in json format' do
      get api_v1_articles_path, headers: { CONTENT_TYPE: 'application/json', ACCEPT: 'application/json', }

      expect(JSON.parse(body)['data'].count).to eq(article_num)
      expect(response).to be_successful
      expect(response).to have_http_status(:ok)
    end
  end
end

上記テストの、JSON.parse(response.body)["data"]の結果

JSON.parse(response.body)["data"]
=> [{"id"=>"1", "type"=>"article", "attributes"=>{"title"=>"title-1", "contents"=>"contents-1", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"1", "type"=>"user"}}}},
 {"id"=>"2", "type"=>"article", "attributes"=>{"title"=>"title-2", "contents"=>"contents-2", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"2", "type"=>"user"}}}},
 {"id"=>"3", "type"=>"article", "attributes"=>{"title"=>"title-3", "contents"=>"contents-3", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"3", "type"=>"user"}}}},
 {"id"=>"4", "type"=>"article", "attributes"=>{"title"=>"title-4", "contents"=>"contents-4", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"4", "type"=>"user"}}}},
 {"id"=>"5", "type"=>"article", "attributes"=>{"title"=>"title-5", "contents"=>"contents-5", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"5", "type"=>"user"}}}},
 {"id"=>"6", "type"=>"article", "attributes"=>{"title"=>"title-6", "contents"=>"contents-6", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"6", "type"=>"user"}}}},
 {"id"=>"7", "type"=>"article", "attributes"=>{"title"=>"title-7", "contents"=>"contents-7", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"7", "type"=>"user"}}}},
 {"id"=>"8", "type"=>"article", "attributes"=>{"title"=>"title-8", "contents"=>"contents-8", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"8", "type"=>"user"}}}},
 {"id"=>"9", "type"=>"article", "attributes"=>{"title"=>"title-9", "contents"=>"contents-9", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"9", "type"=>"user"}}}},
 {"id"=>"10", "type"=>"article", "attributes"=>{"title"=>"title-10", "contents"=>"contents-10", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"10", "type"=>"user"}}}}]
  • 記事詳細のレスポンスにJSON.parse

[7]  JSON.parse(body)
=> {"data"=>{"id"=>"1", "type"=>"article", "attributes"=>{"title"=>"title-11", "contents"=>"contents-11", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"2", "type"=>"user"}}}}}

[8]  JSON.parse(body)["data"]
=> {"id"=>"1", "type"=>"article", "attributes"=>{"title"=>"title-11", "contents"=>"contents-11", "status"=>"draft"}, "relationships"=>{"user"=>{"data"=>{"id"=>"2", "type"=>"user"}}}}

[9]  JSON.parse(body)["data"]["id"]
=> "1"
[10]  JSON.parse(body)["data"]["attributes"]
=> {"title"=>"title-11", "contents"=>"contents-11", "status"=>"draft"}

[11]  JSON.parse(body)["data"]["attributes"]["title"]
=> "title-11"

レスポンスボディ?の数
[12] JSON.parse(body).count
=> 1

relationship
[14] JSON.parse(body)["data"]["relationships"]
=> {"user"=>{"data"=>{"id"=>"2", "type"=>"user"}}}

ステータスコード
[3]  response.status
=> 200

attributes_for

POSTメソッドで、 - ファクトリからテスト用の属性値をハッシュとして作成する。複数のパラメータを送信する場合に、個々にパラメータ名を付けるのではなく、キーと値を組み合わせたハッシュを作成してくれる。 - buildならモデルオブジェクトを作成する。

https://qiita.com/_kotaro/items/adbc355bfb550b8b2150

FactoryBotのデータ

(spec/factories/users.rb)
FactoryBot.define do
  factory :user do
    sequence(:name) { |n| "user-#{n}" }
    sequence(:email) { |n| "user-#{n}@example.com" }
    password { 'password' }
  end
end
  • attributes_for
let!(:user_attributes) { attributes_for(:user) }

=> {:name=>"user-1", :email=>"user-1@example.com", :password=>"password"}
  • build
let!(:user_attributes) { build(:user) }

=> #<User:0x00007fcf5a68cff0 id: nil, name: "user-1", email: "user-1@example.com", crypted_password: nil, salt: nil, created_at: nil, updated_at: nil>
  • create
let!(:user_attributes) { create(:user) }

=> #<User:0x00007fb222ebcf18
 id: 1,
 name: "user-1",
 email: "user-1@example.com",
 crypted_password: "$2a$04$rslb/9FwCJVJFYmR.LeLR.8JjYOt2kEYCh8d2F5E1JJGRJ1lZ/BiS",
 salt: "hoCH88wcyx3PFVqxduem",
 created_at: Sun, 26 Jul 2020 02:15:36 UTC +00:00,
 updated_at: Sun, 26 Jul 2020 02:15:36 UTC +00:00>

to_json

RubyのHash形式のデータを、JSON形式の文字列に変換してくれる。

[1]  user_attributes
=> {:name=>"user-1", :email=>"user-1@example.com", :password=>"password"}
[2]  user_attributes.to_json
=> "{\"name\":\"user-1\",\"email\":\"user-1@example.com\",\"password\":\"password\"}"

【超Vue.js】フィルターとミックスイン【セクション11 】

※ セクション8~10はわざわざ纏める必要あるか微妙だったので、一旦パス。

144. フィルターを使用して、一般的なテキストフォーマットを作成する

フィルター…テキストをフォーマットするためのもの。文字を全部大文字にするみたいな。

今まではcomputedでやっていた。
→computedだと、dataの各プロパティごとに、dataの各プロパティごとに大文字にする設定を記述必要がある。
→大文字にフォーマットするプロパティをdata毎に作ってあげる必要がある。

main.jsに書く

1引数にフィルター名
2引数に関数→関数に必ず引数を取る→引数がフォーマットしたい値→フォーマットした値をreturnする
dataのプロパティ名 パイプ記号 フィルター名
(main.js)
Vue.filter("uppercase", function(value) {
  return value.toUpperCase();
}
{{ data名 | uppercase }}

145. コンポーネントのオプション内でローカルフィルタを定義する

ローカル登録

filters: {

  フィルター名(value) {

         return  value.toLower

146. 複数のフィルタ関数を連結させる

フィルターの特徴
  • フィルタを連結できる
    →パイプ記号で連結させる
    →左側のフィルターから実行される

147. フィルターでthisが使えないことに注意する

フィルター内ではthisは使えない

thisでアクセスしたいなら、computed、メソッドを使う。

148. computedとフィルターのキャッシュに対する違いを理解する

  • computed...リアクティブな依存関係に基づいてキャッシュする
    →必要な時だけ変わる

  • フィルタ...そのような機能はない。メソッドと同じで再描画される
    →頻繁に再描画される場合はフィルタを使うのを避けよう。

149. 共有できるコードを全てミックスインにする

ミックスインとは

export default, data, filters, directivesなどのoptionを、複数のコンポーネントで共有できる。コードの再利用。

150. 実際にミックスインを作って、オプションをコンポーネント間で共有する

共有化したいオプションを、オブジェクトとして別ファイル(js)に移し、そのファイルを使いたいコンポーネントファイルでimportする。
→mixinsは配列にして、importしたモノを指定する。

151. オプションが被ったときに、どのようにミックスインがマージされるのか

mixinファイルとコンポーネントファイルでオプション名が被った場合

→全てのオプションにおいてコンポーネントが優先される

※ created, mountedなどライフサイクルフックは別

→mixinのライフサイクルが先に実行される。その後にcomponentのふっくが実行される。

152. グローバルミックスインを作成する

グローバルミックスインのオプションは、全てのVueインスタンスに自動で適用されてしまう!
globalが最初に実行される。使う時は要注意。普通は使わない。

【記録】良記事とかをまとめておく

マインド

質問・学習などについて

各社研修

検索系

キャリア

効率系

英語

個人開発

デザイン

Mac

Linux

Vim

Git

GitHubのREADMEをサクッと高品質で書けるサービス作ってみました。 - Qiita

HTTP

ネットワーク

データベース

Ruby

Rails

テスト

RSpec

エラー、デバッグ

API

おもろい