【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
を呼ぶこと。
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
を返します。
【API】Postmanの使い方(POST)
少し詰まったのでメモ
コントローラーとかルーティング部分は省略。
右の入力方式をJSONにしてなくて、送信できてなかった…
gem 'fast_jsonapi'を試してみる
fast_jsonapiとは
Netflixが提供しているgemで、JSONのserializerです。
This project is no longer maintained!!!! ⚠️ってなってるけど使っていいのかな…?
公式に沿って進めていきます。
serializerとは?
シリアライズ(serialize)とは、プログラミングでオプジェクト化されたデータを、ファイルやストレージに保存したり、ネットワークで送受信したりできるような形に変換することを言います。
逆に、シリアライズされたデータをプログラミングで扱えるようにオブジェクトの型に復元することをデシリアライズ(deserialize)といいます。
だいたいのプログラミング言語にはあらかじめシリアライズの仕組みが用意されています。たとえばRuby では Marshal という独自のクラスが用意されているほか、JSON形式やYAML形式でのシリアライズも簡単に行うことができます。
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形式でデータを取得できました
【Vue.js】【Vuex】Storeを分割してモジュール化し、namespacedを登録する
実装したいこと
Vuexのストアをモジュールに分割して読み込みたい。
アプリケーションの規模が大きくなると、ストアの規模も大きくなり管理が困難になるため、ストアを適切に分割し管理したい。
モジュールを読み込む
store/index.js
にまとめていたstore
をstore/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で調べる癖は悪なので反省
①名前空間をつけたモジュールが読み込めない…
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
にしたら直った
②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内でenumで定義した内容とリンクさせたい
セレクトボックスを生成するときに、enumで定義した内容とリンクさせたい。
enum status: { todo: 1, doing: 2, done: 3 }
option value
にenum
で定義した内容を渡せば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/tasks
へPOST
します。
※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
アプリケーション全体で使用されるデータ/コンポーネントでいうdatagetters
stateから別の値を算出するために使う。/コンポーネントでいうcomputedmutations
stateを更新するために使う。mutationは直接呼び出せないので、store.commit('ミューテーション名')
で呼び出す。 第2引数でデータも渡せるactions
非同期処理やAPIとの通信を行う。ミューテーション呼び出しはここで行う
Vuex内でAPIを叩くには
コンポーネント側は省略
action
→mutations
→state
のように順番に更新しています。
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) }) } }, })