Programming Journal

学習したことの整理用です。

【Rails】【Ruby】雑多なメモ

知らないメソッドといくつか出会ったので、メモ

SecureRandom.uuid

UUID (Universally Unique IDentifier) を生成して返します。
SecureRandom.uuid (Ruby 2.7.0 リファレンスマニュアル)

例えば、ログイン認証で使うaccess_tokenを安全な乱数にするときに使います。

  def initialize(attributes = {})
    super
    self.access_token = SecureRandom.uuid #ここ
    self.expires_at = DEFAULT_EXPIRES_WEEK.after
  end

Initialize

インスタンスを生成するときに、初期設定として実行したい処理があるときに定義する。
意味は知ってたけど、実際に使おうと思ったら迷うことがあったので。
attributesにハッシュを渡すときは、superを呼ぶこと。

ActiveModel::Model

Also, if for some reason you need to run code on initialize, make sure you call super if you want the attributes hash initialization to happen.
attributesにハッシュを渡すときは、superを呼ぶこと。

class Person
  include ActiveModel::Model
  attr_accessor :id, :name, :omg

# インスタンスが生成されると呼ばれる
# attributesにはハッシュが引き渡される。何も渡されない場合は空のハッシュが渡される

  def initialize(attributes={})
    super
    @omg ||= true
  end
end

person = Person.new(id: 1, name: 'bob')
person.omg # => true

Compact

compact は自身から nil を取り除いた配列を生成して返します。 compact! は自身から破壊的に nil を取り除き、変更が行われた場合は self を、そうでなければ nil を返します。

Array#compact (Ruby 2.7.0 リファレンスマニュアル)

all?

Enumerable#all? (Ruby 2.7.0 リファレンスマニュアル)

すべての要素が真である場合に true を返します。偽である要素があれば、ただちに false を返します。

ブロックを伴う場合は、各要素に対してブロックを評価し、すべての結果が真である場合に true を返します。ブロックが偽を返した時点で、ただちに false を返します。

def valid?
  [user, user.address, user.phone_number].map(&:valid?).all?
end

モデルオブジェクトを配列に入れて、それぞれにvalid?メソッドを通して、true or false の形にしてからmapで新しい配列を生成→all?で配列の要素がすべて真だったらtrue を返します。

gem 'fast_jsonapi'を試してみる

fast_jsonapiとは

Netflixが提供しているgemで、JSONのserializerです。

This project is no longer maintained!!!! ⚠️ってなってるけど使っていいのかな…?

公式に沿って進めていきます。

GitHub - Netflix/fast_jsonapi: No Longer Maintained - A lightning fast JSON:API serializer for Ruby Objects.

serializerとは?

シリアライズ(serialize)とは、プログラミングでオプジェクト化されたデータを、ファイルやストレージに保存したり、ネットワークで送受信したりできるような形に変換することを言います。

逆に、シリアライズされたデータをプログラミングで扱えるようにオブジェクトの型に復元することをデシリアライズ(deserialize)といいます。

だいたいのプログラミング言語にはあらかじめシリアライズの仕組みが用意されています。たとえばRuby では Marshal という独自のクラスが用意されているほか、JSON形式やYAML形式でのシリアライズも簡単に行うことができます。

【用語解説】シリアライズ・デシリアライズとは – Cloud Cafe

Installation

gem 'fast_jsonapi'

bundle install

rails g serializer Article(モデル名) title contents status(カラム名を列挙)

Usage

さっきのコマンドで生成されるserializerのファイルに追記

class ArticleSerializer
  include FastJsonapi::ObjectSerializer

  attributes :title, :contents, :status  # 追記
  belongs_to :user # モデルの設定に合わす
end

コントローラー側で呼び出す 今回は、記事一覧を呼び出してみます。

class ArticlesController < BaseController
      def index
        articles = Article.all
        json_string = ArticleSerializer.new(articles).serialized_json
        render json: json_string
      end
end

json_string = ArticleSerializer.new(articles).serialized_jsonの部分は、一覧の取得でもnewです。
何を思ったか、allに変えてエラーを出しました。。

postmanで見てみる

ちゃんとJSON形式でデータを取得できました

f:id:Study-Diary:20201120190723p:plain
postman

【Vue.js】【Vuex】Storeを分割してモジュール化し、namespacedを登録する

実装したいこと

Vuexのストアをモジュールに分割して読み込みたい。
アプリケーションの規模が大きくなると、ストアの規模も大きくなり管理が困難になるため、ストアを適切に分割し管理したい。

モジュール | Vuex

モジュールを読み込む

store/index.jsにまとめていたstorestore/modues/task.jsに切り出す。

import Vue from 'vue'
import Vuex from 'vuex'
import tasks from './modules/task' // 分割したモジュールを呼び出す

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    tasks // 追加
  }
})
import axios from '../../plugins/axios'

// taskモジュールを定義
export default { // 注意(後述)
  namespaced: true,  // 名前空間 'namespaced'なことに注意
  state: {
    tasks: [],
  },

  getters: {
    tasks: (state) => state.tasks
  },

  actions: {
    fetchTasks({ commit }) {
      axios.get("tasks")
        .then(response => { commit('loadTasks', response.data)
        })
        .catch(error => console.log(error.status));
    }
  }
}

エラー

2時間位溶かす。エラーの切り出しが下手で時間がかかってしまいます…
今見たら、エラー文にちゃんと原因が書いてあった。すぐgoogleで調べる癖は悪なので反省

名前空間をつけたモジュールが読み込めない…

f:id:Study-Diary:20201117111905p:plain
エラー画面
f:id:Study-Diary:20201117111843p:plain
エラー画面

vuex.esm.js:345 Uncaught TypeError: Cannot read property 'getters' of undefined

ERROR in ./app/javascript/store/index.js 8:10-14
"export 'default' (imported as 'task') was not found in '../store/modules/task'
// taskモジュールを名前付きで定義
export const task = {
  namespaced: true,

名前付きのexportnamed exportでなく、export defaultにしたら直った

参考
https://stackoverflow.com/questions/58511705/uncaught-typeerror-cannot-set-property-render-of-undefined

namespaced: trueタイポ
namespace: trueにしてた…

呼び出し側

<script>
import { createNamespacedHelpers } from 'vuex' // 追加

const { mapActions, mapGetters } = createNamespacedHelpers('tasks') // 追加

export default {
  computed: {
    ...mapGetters(["tasks"])
  },

  created() {
    this.fetchTasks();
  },

  methods: {
    ...mapActions([
      "fetchTasks"
    ])
  }
}
</script>

createNamespacedHelpersというヘルパーを使ったら簡単にできました。
これを使わない場合は、第一引数にモジュール名を渡せばOK

 ...mapGetters( "tasks",["tasks"])

...mapActions("tasks",[
      "fetchTasks"
    ])

スタイルガイド

スタイルガイド — Vue.js

【Vue.js】Vue内でenumで定義した内容とリンクさせたい

セレクトボックスを生成するときに、enumで定義した内容とリンクさせたい。

f:id:Study-Diary:20201116133920p:plain
select

enum status: { todo: 1, doing: 2, done: 3 }

option valueenumで定義した内容を渡せばOK

<div class="form-group">
  <label for="status">ステータス</label>
  <select name="ステータス" class="form-control" id="status" v-model="task.status">
    <option value="todo">TODO</option>
    <option value="doing">DOING</option>
    <option value="done">DONE</option>
  </select>
</div>

【Vuex】雑多なメモ

編集時のデータの渡し方

タスク管理アプリで、タスクを編集するときのデータのやり取りについて詰まったのでメモ。
親からtaskデータをpropsで受け取り、v-modelでデータを入力値をバインドしたいです。
ただし、オブジェクトが格納された変数をコピーしても、参照がコピーされてしまうため、入力値をバインドすると参照元も変更されてしまいます。
何が起こるかというと、編集を「更新」しなくても、入力フォームに入力した時点でデータが書き換わってしまう。
v-modelじゃなくて:valueにして入力フォームに反映させる??などど1時間ほど詰まってしまってしまいました。

ポイントは、親から子へ渡すデータの時点で、 this.taskEdit = Object.assign({}, task)などとして、参照ではなく値をコピーして渡します。

assignメソッドについては、『JS本格入門』P163,165

※説明に必要な部分だけ抜粋

<template>
    <label for="title">タイトル</label>
    <input type="text" class="form-control" id="title" v-model="task.title">

    <button @click="handleCreateTask" class="btn btn-success" data-dismiss="modal">追加</button>
</template>

<script>
export default {
  name: 'TaskEditModal',
  props: {
    task: {
      title: {
        type: String,
        required: true
      }
    }
  },

  methods: {
    handleUpdateTask() {
      this.$emit('update-task', this.task)
    }
  }
}
</script>   
<template>

  <TaskEditModal
    :task="taskEdit" // 子へtaskを渡す
    v-if="isVisibleTaskEditModal"
    @close-modal="handleCloseTaskEditModal"
    @update-task="handleUpdateTask"/>

</template>

<script>
import TaskEditModal from '../task/components/TaskEditModal.vue'

export default {
  components: {
    TaskEditModal
  },

  data() {
    return {
      taskEdit: {}, // 定義しておく。
    }
  },

  methods: {
    handleOpenTaskEditModal(task) {
      this.isVisibleTaskEditModal = true;
      this.taskEdit = Object.assign({}, task)  // ポイント!子にtaskの値だけを渡す
    },

    async handleUpdateTask(task) { // 説明は略。Vuexのアクションへ飛ばしてAPIでPATCHします
      try {
        await this.editTask(task);
        this.handleCloseTaskEditModal();
      } catch (error) {
        console.log(error);
      }
    }
  }
}
</script>

VuexでAPIを叩く(PATCH DELETE)

※難しいところだけ抜粋

更新&削除後は、一覧を取得するアクションを呼んでいます。
それだと再び通信することになるので、あまりよくないかもしれないんですが、難しかったのでとりあえず、今回はこれで…

action内でactionを呼ぶには…?と悩みましたが、引数にdispatchを渡して以下の通りにやればOK

mutations: {
  loadTasks(state, tasks) {
    state.tasks = tasks
  },
  addTask: (state,task) => {
    state.tasks.push(task);
  }
},

actions: {
    // actionでAPIからtask一覧取得
    fetchTasks({ commit }) {
      axios.get("tasks")
        .then(response => { commit('loadTasks', response.data)
        })
        .catch(error => console.log(error.status));
    },
  // コンポーネントから受け取ったデータを引数で受け取り、UPDATEする
    editTask({ dispatch }, task) {
      return axios.patch(`tasks/${task.id}`, task)
        .then(response => {
          dispatch('fetchTasks', response.data)
        })
    },
// コンポーネントから受け取ったデータを引数で受け取り、DELETEする
    deleteTask({ dispatch },task) {
      return axios.delete(`tasks/${task.id}`)
        .then(response => {
          dispatch('fetchTasks', response.tada) // 一覧をgetするアクション
        });
    },
  }

【Vue.js】【Vuex】雑多なメモ

詰まりに詰まってしまったので、メモを残しておきます。

子から親へデータを渡すとき

まず、Vuexは使わない単純なやり方です。
コンポーネントでタスク名・タスク説明文を入力し、親にデータを渡します。
そして親がAPIでその内容をPOSTします。

※説明に必要な部分だけ抜粋

<template>
    <label for="title">タイトル</label>
    <input type="text" class="form-control" id="title" v-model="task.title">

    <label for="description">説明文</label>
    <textarea  name="説明文" class="form-control" id="description" v-model="task.description"></textarea>

    <button @click="handleCreateTask" class="btn btn-success" data-dismiss="modal">追加</button>
</template>

<script>
export default {
  data() {
    return {
      task: {
        title: '',
        description: ''
      }
    }
  },
  methods: {
    handleCreateTask() {
      this.$emit('create-task', this.task)
    }
  }
}
</script>       

$emitで子→親でデータを渡せる。
引数の渡し方で迷ったけど、第2引数で渡せばOK
これで、親側の@create-task="handleCreateTask"が発火する

<template>
  <TaskCreateModal
    v-if="isVisibleTaskCreateModal"
    @create-task="handleCreateTask"/> //子側から受け取る
</template>

<script>
import TaskCreateModal from '../task/components/TaskCreateModal.vue'

// 略
  methods: {
    handleCreateTask(task) {
      axios.post('tasks', task) //試してないのでもしかしたら動かないかも
    }
  }
</script>

handleCreateTask(task)で子から受け取ったデータ(引数)をapi/tasksPOSTします。
※baseURLを登録しているので、tasksと省略形で記述しています。

やらかしたことメモ

タスクのタイトル、説明文にそれぞれv-model:inputTitle v-model:inputDescription のように命名し、1つずつ渡していました。
オブジェクトとして渡せばよかった。ちなみに、オブジェクトは、ハッシュ、連想配列と呼ばれる場合もあるよう。Rubyと混ざって分からなくなる・・

// 子から渡す
this.$emit('create-task', inputTitle, inputDescription)

// 親が受け取る
handleCreateTask(inputTitle, inputDescription) {
   const tasks = { title: title, description: description }
   axios.post("tasks", tasks)

Vuex

Vuex とは何か? | Vuex
API リファレンス | Vuex
ストアの構成要素を理解するまでに時間がかかりました。

  • stateアプリケーション全体で使用されるデータ/コンポーネントでいうdata

  • getters stateから別の値を算出するために使う。/コンポーネントでいうcomputed

  • mutations stateを更新するために使う。mutationは直接呼び出せないので、store.commit('ミューテーション名')で呼び出す。 第2引数でデータも渡せる

  • actions非同期処理やAPIとの通信を行う。ミューテーション呼び出しはここで行う

Vuex内でAPIを叩くには

コンポーネント側は省略
actionmutationsstateのように順番に更新しています。

import Vue from 'vue'
import Vuex from 'vuex'
import axios from '../plugins/axios'

Vue.use(Vuex)

export default new Vuex.Store({
  // stateはcomponentのdataに相当
  state: {
    tasks: [],
  },

  getters: {
    tasks: state => state.tasks
  },

  // mutationsでstateを更新
  mutations: {
    loadTasks(state, tasks) {
      state.tasks = tasks
    },
    addTask(state,task) {
      state.tasks.push(task);
    }
  },
  actions: {
    // actionでAPIからtask一覧取得
    fetchTasks({ commit }) {
      axios.get("tasks")
        .then(response => { commit('loadTasks', response.data)
        })
        .catch(error => console.log(error.status));
    },
    // コンポーネントから受け取ったデータを引数で受け取り、APIに送信してタスクPOSTする
    createTask({ commit }, task) {
      return axios.post('tasks', task)
        .then(response => {
          commit('addTask', response.data)
        })
    }
  },
})