git rebase

git rebase の使い方

[git rebase]の存在は、知っていたが実際に使ったことがなかったので、備忘録としてこちらにまとめます。

誤ってローカルの作業ブランチをリモートのmasterブランチにpushしてしまい、且つローカルのコミットメッセージが誤っていたが、リモートのmasterブランチにマージされてしまった場合。

下記画面キャプチャのように、[add テストコミット1]、[add テストコミット2]、[add テストコミット_3]のコミットは、ローカルの作業ブランチのtest_branchブランチのコミットだが、正しくは、コミットメッセージを[add test_commit_1]、[add test_commit_2]、[add test_commit_3]とし、リモートのtest_branchブランチにpushしたいが、誤ってリモートのmasterブランチにpushしてしまい、リモートのmasterブランチにマージされてしまった。

対処法

ローカルのブランチをmasterブランチに切り替えて、git push origin master -fを実行する事で、ローカルのmasterブランチの内容でリモートのmasterブランチを上書きする事により、元のmasterブランチの内容に戻る

git switch test_branchでローカルをtest_branchブランチに切り替えて、git push --set-upstream origin test_branchでリモートにtest_branchブランチをpushしたが、コミットメッセージを変えたい。

ローカルのブランチをtest_branchブランチにチェックアウトして、git rebase -i origin/masterを実行すると、以下の画面になる。 これは、ローカルのブランチのコミット履歴をorigin/masterの上にきれいに乗せるという事になる。 以下の画面に出てくるコミットは、test_branchブランチを切ったmasterブランチの箇所からこれだけのコミットが積み上がっているということを意味している。

上記画面の内容のpickの部分をviのインサートモードにして、下記画面内容のように編集したいコミットをeと書き換えて、保存する。

上記を実行すると以下の画面のようになり、test_branchブランチの最初のコミットにチェックアウトされている状態になり、eに変えたことで、そのコミットの時点で処理が一時停止している。 今は rebase の途中段階で、選んだコミット(94f7b54... add テストコミット_1)を編集できる状態になっている。

git commit --amendを実行すると以下のような画面になるので、正しいコミットメッセージに書き換えて保存する。

※ 元のコミットメッセージは、ddで一気に消せる

正しいコミットメッセージに書き換えて保存するとgit graphの状態が以下のようになる。(コミットメッセージを書き換えたコミットがmasterブランチの上に新たにできる。)

git rebase --continueを実行すると、1つ目の編集対象コミットの処理が完了し、次のコミットがあればその処理に進む。

以下のようになり、2つ目のコミットのコミットメッセージも変えたいので、同じようにgit commit --amendを実行して、同じようにコミットメッセージを変更して、保存する。

git rebase --continueを実行すると、2つ目の編集対象コミットの処理が完了し、次のコミットがあればその処理に進む。

保存すると以下のようになるので、3つ目のコミットのコミットメッセージも変えたいので、同じようにgit commit --amendを実行して、同じようにコミットメッセージを変更して、保存してgit rebase --continueを実行する

ここまでやるとgit graphが以下のようになる。

※ rebase中だと以下のようになる。 その時は、git rebase --continueを最後のコミットまでやる

higmonta@higuchiyuunoMBP git_test % git branch                                 
* (no branch, rebasing test_branch)
  master
  test_branch

以下のようになっていたら、git push origin test_branch -fを実行すると、ローカルのtest_branchブランチの内容でリモートのtest_branchブランチの内容を上書きできるようになり、編集したコミットメッセージがリモートにも反映される。

higmonta@higuchiyuunoMBP git_test % git branch           
  master
* test_branch

 補足

GitのAuthorの名前変更とGitHubとの紐付け

git config --global user.name "test"を実行すると、コミットのAuthorの名前がtestに変わる。

git config --global user.email test@example.comを実行する。 GitHubに登録したメールアドレスと同じにすると、ローカルのコミットと、GitHub 上の自分のアカウントが “自動的に紐づき自分のアイコンや名前と結びつけられる。

git rebase -i origin/masterを実行する。

git commit --amend --reset-authorを実行して、そのままの内容でwqで保存して、git rebase --continueを実行して、次のコミットの処理に移るというのをコミット数分繰り返す。

上記を繰り返すと、git graphなどに表示されるAuthorがtestに変わる

参考記事

はてなID

apiのrspecを記載する時のjsonの記載方法

apiのrspecを記載する時のjsonの記載方法を備忘録としてまとめます。

rspecでapiをリクエストする際に以下の記載にするとidの数値が文字列になってしまう。

spec/requests/〇〇_spec.rb

    let(:headers){ { 'Authorization' => "Token token=\"#{admin.authentication_token}\"", 'Accept' => 'application/json' } }
    let(:api_url){ "/api/v2/admin/test.json" }

    context '時間を更新する場合' do
      let(:params) do
        {
          order: {
            time_id: 1
          }
        }
      end
      subject!{ put api_url, params: params, headers: headers }

上記specを実行してコントローラー側でデバッグすると以下のよう[time_id]が文字列の"1"になってしまう。

app/controllers/〇〇/api/v2/admin/〇〇_controller.rb

def update
  binding.pry
end

[1] pry> params
=> <ActionController::Parameters {"order"=><ActionController::Parameters {"time_id"=>"1"} permitted: false>

これは、パラメーターとして[time_id]が送られているが、json形式で送られていないので、文字列になってしまう。 integerとしてパラメーターを送るためには、json形式でパラメーターを送らなければいけない。 params はリクエスト(HTTP)を通して入ってくるので、全ての値は基本的に文字列(String)として扱われるため。

以下のようにパラメーターに[to_json]を記載することで、パラメーターをjson形式にする

spec/requests/〇〇_spec.rb

    let(:headers){ { 'Authorization' => "Token token=\"#{admin.authentication_token}\"", 'Accept' => 'application/json' } }
    let(:api_url){ "/api/v2/admin/test.json" }

    context '時間を更新する場合' do
      let(:params) do
        {
          order: {
            time_id: 1
          }
        }.to_json
      end
      subject!{ put api_url, params: params, headers: headers }

上記specを実行してコントローラー側でデバッグすると以下のようパラメーターの形式が通常と違う形になってしまう。

app/controllers/〇〇/api/v2/admin/〇〇_controller.rb

def update
  binding.pry
end

[1] pry> params
=> <ActionController::Parameters {"{\"order\":{\"time_id\":1}}"=>nil, "controller"=>"〇〇/api/v2/admin/〇〇", "action"=>"update", "id"=>"1381", "format"=>"json"} permitted: false>

これは、パラメーターとして[time_id]をjson形式で送っているが、headersに[Content-Type]として[application/json]を記載しないと、json形式で送れないし、受け取ることもできない為である。

以下のようにパラメーター記載することで、パラメーターをjson形式で送れるようになる

spec/requests/〇〇_spec.rb

    let(:headers){ { 'Authorization' => "Token token=\"#{admin.authentication_token}\"", 'Accept' => 'application/json' } }
    let(:api_url){ "/api/v2/admin/test.json" }

    context '時間を更新する場合' do
      let(:params) do
        {
          order: {
            time_id: 1
          }
        }.to_json
      end
      subject!{ put api_url, params: params, headers: headers.merge({ 'Content-Type' => 'application/json' }) }

上記specを実行してコントローラー側でデバッグすると以下のよう形式になっており、正しくパラメーターが送られている。

app/controllers/〇〇/api/v2/admin/〇〇_controller.rb

def update
  binding.pry
end

[1] pry> params
=> <ActionController::Parameters {"order"=>{"time_id"=>1}, "controller"=>"〇〇/api/v2/admin/〇〇", "action"=>"update", "id"=>"1382", "format"=>"json", "order"=><ActionController::Parameters {} permitted: false>} permitted: false>

Vueで複数の要素が同時に消える時のトランジション

Vueで複数の要素が同時に消える時のトランジションについて備忘録として、こちらにまとめます。

以下のようなコードがある時に、[item2]と[item3]が消えてトランジションする時の動きが意図しない動き方になる。

  <div id="app">
    <button @click="removeItems">アイテムを削除</button>
    <transition-group tag="ul" class="list">
      <li v-for="item in items" :key="item.id">
        {{ item.name }}
      </li>
    </transition-group>
  </div>

<style>
  .v-enter-active, .v-leave-active {
    transition: opacity 1s, transform 1s;
  }
  .v-leave-active {
    position: absolute;
  }
  .v-move {
    transition: transform 1s;
  }
  .v-enter, .v-leave-to {
    opacity: 0;
  }
</style>
new Vue({
  el: '#app',
  data: {
    items: [
      { id: 1, name: 'item1' },
      { id: 2, name: 'item2' },
      { id: 3, name: 'item3' },
      { id: 4, name: 'item4' }
    ]
  },
  methods: {
    removeItems() {
      // item2とitem3を同時に削除
      this.items = this.items.filter(item => item.id !== 2 && item.id !== 3);
    }
  }
});

gyazo.com

これは、以下のような挙動になっているため、このようなトランジションになる。
[item3]は、要素が削除されてから削除された要素が移動する挙動になっている。
[item3]には、[v-leave-active],[v-leave-to]に加えて[v-move]クラスが適用される。
削除された時に[v-leave-to]のopacity 0が適用され、その後の移動の際に[v-move]クラスが適用になるので、opacityが0の状態で移動する。

CSSを以下のいずれかに修正することで意図する挙動となる。

  .v-enter-active, .v-leave-active, .v-move {
    transition: opacity 1s, transform 1s;
  }
  .v-leave-active {
    position: absolute;
  }
  .v-enter, .v-leave-to {
    opacity: 0;
  }

[item3]は、消えた後に移動という挙動になる。
[v-leave-to]クラスでopacityが0になってから移動になるが、[v-move]クラスをここに定義することでopacityが1になる。

  .v-enter-active, .v-leave-active {
    transition: opacity 1s, transform 1s;
  }
  .v-leave-active {
    position: absolute;
  }
  .v-move:not(.v-leave-active) {
    transition: transform 1s;
  }
  .v-enter, .v-leave-to {
    opacity: 0;
  }

[v-leave-actiive]クラスを持っていない時(削除されていない要素)に対してのみ[v-move]を適用する。

gyazo.com

Vueのコンポーネント間での双方向データバインディング

Vueのコンポーネント間での双方向データバインディングについて備忘録としてまとめます。

コンポーネント間で双方向データバインディングする際の例は、以下になります。

  <div id="app">
    <my-calendar v-model="date"></my-calendar>
    <p>選択された日付: {{ date }}</p>
  </div>
Vue.component('my-calendar', {
  template: `
    <div>
      <input type="date" @input="updateDate($event.target.value)" :value="value">
    </div>
  `,
  props: {
    value: {
      type: String,
      required: true
    }
  },
  methods: {
    updateDate: function(newDate) {
      this.$emit('input', newDate);
    }
  }
});

new Vue({
  el: '#app',
  data: {
    date: '2022-01-01'
  }
});

動きの流れは、以下のようになります。
①親コンポーネントのv-model="date"のdateの値を子コンポーネントではpropsでvalueで受け取っている。

②子コンポーネントでは、this.$emit('input', newDate);により親コンポーネントにnewDateを渡し、親コンポーネントのdateのデータが書き変わる。

※
v-modelは、デフォルトの設定では、valueプロパティとしてinputイベントとして使用しているため、親コンポーネントでv-modelで記載した時に、子コンポーネントでthis.$emit('input', newDate);と記載することで、親コンポーネントのdataの値が書き変わる。

Vueのスロット機能

Vueのスロット機能について、備忘録としてこちらにまとめます。

親コンポーネント側から子コンポーネントに一部を差し込む機能がスロット

親コンポーネント側から子コンポーネントに一部を差し込む(slotタグ有り)

  <div id="app">
    <comp-child>
      フォールバックコンテンツ
    </comp-child>
  </div>
Vue.component('comp-child', {
  template: `
    <div class="comp-child">
      <slot>デフォルトコンテンツ</slot>
    </div>
  `
});

new Vue({
  el: '#app'
});

ブラウザには、以下のように表示される。(親コンポーネントの[フォールバックコンテンツ]が適用される)

gyazo.com

親コンポーネント側から子コンポーネントに一部を差し込む(slotタグ無し)

  <div id="app">
    <comp-child>
      フォールバックコンテンツ
    </comp-child>
  </div>
Vue.component('comp-child', {
  template: `
    <div class="comp-child">
      デフォルトコンテンツ
    </div>
  `
});

new Vue({
  el: '#app'
});

ブラウザには、以下のように表示される。(子コンポーネントの[デフォルトコンテンツ]が適用される)

gyazo.com

親コンポーネント側から子コンポーネントに一部を差し込む

  <div id="app">
    <comp-child>
      フォールバックコンテンツ
    </comp-child>
  </div>
Vue.component('comp-child', {
  template: `
    <div class="comp-child">
    </div>
  `
});

new Vue({
  el: '#app'
});

ブラウザには、以下のように表示される。(子コンポーネントが適用される)

gyazo.com

親コンポーネント側から子コンポーネントに一部を差し込む

  <div id="app">
    <comp-child>
    </comp-child>
  </div>
Vue.component('comp-child', {
  template: `
    <div class="comp-child">
      <slot>デフォルトコンテンツ</slot>
    </div>
  `
});

new Vue({
  el: '#app'
});

ブラウザには、以下のように表示される。(親コンポーネントで[フォールバックコンテンツ]が設定されていないので、子コンポーネントでslotタグを設定しており、slotタグの中の部分が適用される。)
slotタグに定義した記述は、フォールバックコンテンツとして表示される。
フォールバックコンテンツ: ある機能や要素がサポートされていない場合に、代替えとして表示されるコンテンツのこと。

値渡しと参照渡し

値渡しと参照渡しについて、備忘録としてまとめます。

[値渡し]
値渡しは、変数の値をそのままコピーして渡す方法
プリミティブ型(例えば、数値、文字列、真理値など)は、通常値渡しされる。

let a = 5;
let b = a; // bにaの値がコピーされる
b = 10;

console.log(a); // 5
console.log(b); // 10

※
この例では、b に a の値がコピーされます。b を変更しても、a の値には影響を与えません。

[参照渡し]
参照渡しは、変数が参照しているオブジェクトのメモリ位置を渡す方法
オブジェクト(配列や関数を含む)は、通常参照渡しされる。

let obj1 = { value: 10 };
let obj2 = obj1; // obj2はobj1と同じオブジェクトを参照する
obj2.value = 20;

console.log(obj1.value); // 20
console.log(obj2.value); // 20

※
この例では、obj2 は obj1 と同じオブジェクトを参照している。
obj2 を介してオブジェクトのプロパティを変更すると、その変更は obj1 にも反映される。

Vueでv-on:click.nativeを使う時

Vueでv-on:click.nativeを使う時とnativeが不要な時を備忘録としてこちらにまとめます。

[v-on:click.native を使うケース]

子コンポーネント

Vue.component('child-component', {
  template: '<button>クリックしてみて</button>'
});

親コンポーネント

new Vue({
  el: '#app',
  template: `
    <div>
      <child-component v-on:click.native="handleClick"></child-component>
    </div>
  `,
  methods: {
    handleClick() {
      alert('親コンポーネントでクリックイベントがキャッチされました');
    }
  }
});

※ 親コンポーネントが<child-component v-on:click.native="handleClick"></child-component>で子コンポーネントを呼び出している。
child-componentにv-on:click.native="handleClick"を渡しているので、子コンポーネントのボタンでは、ボタンをクリックした時に親コンポーネントのhandleClickが呼ばれる。

[v-on:click を使うケース]

子コンポーネント

Vue.component('child-component', {
  template: '<button @click="handleClick">クリックしてみて</button>',
  methods: {
    handleClick() {
      this.$emit('custom-click');
    }
  }
});

親コンポーネント

new Vue({
  el: '#app',
  template: `
    <div>
      <child-component @custom-click="handleClick"></child-component>
    </div>
  `,
  methods: {
    handleClick() {
      alert('カスタムイベントがキャッチされました');
    }
  }
});

ポイント

子コンポーネントでv-onを設定して、そのイベントをthis.$emitで親コンポーネントに渡す時は、nativeは不要になる。