【Vue】シャッフルし続ける文字列を実装する方法を✅

わたくし暖房器具がない家に住んでいるのですが、ついに同居人が「俺耳から体温あげてくわ」と言い出し沖縄の民謡を流し始めて困惑している23期中野昴こと酒ケジュール作成中です。

#おじい自慢のオリオンビール~♪

現在の進捗

なんかiphoneだと診断がカクカク動いてしまう現象に見舞われています。どうしたものか。。

https://zeroken.herokuapp.com/

今日は「下戸証明書」と「酒ケジュール」の作成に取り掛かっていました。

まだ本番環境に反映できていないので使うことはできません。早く正式リリースしたいです、、!

下戸証明書とは

アルハラから身を守るお守り。診断結果が下戸だった場合のみ現れるため、真の下戸にしか与えられない由緒正しき証明書

下戸証明書の進捗

真の下戸に飲み表示される下戸証明書。

Image from Gyazo

下戸証明書の中身

絶対飲ませるな?私のバックには弁護士と警察官がいるかんな?という雰囲気を伝えたい。

Image from Gyazo

酒ケジュールとは

1軒目で飲むお酒の順番。その人のお酒の強さや1軒目でなりたい気分に応じて変動する。

モーダルが反応してないため、ただbooleanがtrueになって画面に表示されただけになっている。

背景に設定した木目が滲んでガチャガチャしている。

Image from Gyazo

今回はtransitionを使って文字に躍動感を加える方法を学習していきたいと思います。

transitionを使ったら何ができるか

transitionで囲んだ要素に対して動きをつけることができる。modalやpop、fadeなどのモードがある。

transitionを制するものはweb制作を制しそう。普通にいじっていて楽しい。

文字に躍動感をつけてみる

コンポーネントの全体像

コンポーネント

frontend/pages/ZerokenTop.vue

template部分

親データautoplayをautoplayとしてprop downしている。

<template>
<ZerokenAbout :autoplay="autoplay" />
</template>

script部分

ZerokenAboutコンポーネントをインポートしている。

<script>

import ZerokenAbout from '../components/top/ZerokenAbout';
export default {
  components: {
    ZerokenAbout,
  },
data() {
    return {
      autoplay: true,

frontend/components/top/ZerokenAbout.vue

template部分

算出メソッドのeditor内でgetしたtextをリストレンダリングしている。

<template>
  <div class="black--text about text-center">
    <transition-group tag="div" class="title">
      <span v-for="el in text" :key="el.id" class="item" v-text="el.text" />
    </transition-group>
  </div>
</template>

script部分

<script>
export default {
  props: {
    autoplay: Boolean,
  },
  data() {
    return {
      timer: null,
      index: 0,
      // オリジナルメッセージ
      original: [
        'ZEROKENは、あなたが1軒目で飲むお酒の順番を提供するアプリです。お酒の強さを診断する機能や、アルハラ防止機能を搭載しています。',
        'ZEROKENを使うことによって、1軒目では自分のペースでお酒を飲むことができます。',
        '診断で出てきた酒ケジュールや下戸証明書はそのままTwitterで拡散したり、お守りとしてとっておくことができます。',
      ],
      // 分解したメッセージ
      messages: [],
      text: '',
    };
  },
  computed: {
    editor: {
      get() {
        return this.text.map((e) => e.text).join('');
      },
      set(text) {
        this.text = this.convText(text);
      },
    },
  },
  watch: {
    autoplay(val) {
      clearTimeout(this.timer);
      if (val) {
        this.ticker();
      }
    },
  },
  methods: {
    // デモ用のオートタイマー
    ticker() {
      this.timer = setTimeout(() => {
        if (this.autoplay) {
          this.index = this.index < this.messages.length - 1 ? this.index + 1 : 0;
          this.text = this.messages[this.index];
          this.ticker();
        }
      }, 5000);
    },
    // テキストを分解してオブジェクトに
    convText(text) {
      const alms = {};
      const result = text.split('').map((el) => {
        alms[el] = alms[el] ? ++alms[el] : 1;
        return { id: `${el}_${alms[el]}`, text: el };
      });
      return Object.freeze(result); // 監視しない
    },
  },
  created() {
    this.messages = this.original.map((el) => this.convText(el));
    this.text = this.messages[0];
    this.ticker();
  },
};
</script>

まずcreatedが実行され、最初の導線が発動される。具体的に下記の三つが実行される。

messagesの中に、convTextで加工されたoriginalが格納される。

textの中にmessage内の最初のオブジェクトが入る。

そしてtickerが走り、5秒ごとにtext配列の中身が変わる。

次にwatchによってtimerがリセットされ、仮引数の中身がまだあれば再度tickerが実行される。

これが繰り返し行われている。

style部分

<style scoped>
.about {
  margin: 0, auto;
  background-color: aliceblue;
  display: flex;
  align-items: center;
  justify-content: center;
}
.title {
  font-size: 2rem;
}
.item {
  display: inline-block;
  min-width: 0.3em;
}
/* トランジション用スタイル */
.v-enter-active,
.v-leave-active,
.v-move {
  transition: all 1s;
}
.v-leave-active {
  position: absolute;
}
.v-enter,
.v-leave-to {
  opacity: 0;
  transform: translateY(-30px);
}
</style>

v-enter-active,v-leave-active,v-move

transitionが1秒かけて行われている。

v-enter,v-leave-to

y軸に-30pxの方向へ消えていっている。

最終的なアウトプット

Image from Gyazo

もっとスタイリッシュに表示させたいですね〜

基礎から学ぶ Vue.js

【Rails】クエリーインターフェイスを✅

RUNTEQ卒業生が所属する会社のweb meetupを観覧して、つよつよはつよつよを引き寄せるんだなと感じ、一体どうやってそのつよつよの土俵に上がれば良いんだ嘆いている23期中野昴こと酒ケジュール作成中です。

さて、今回は、エビングハウス曲線やレミニセンス効果を狙うために、今のうちからActiveRecordについて学習していきたいと思います。

クエリーインターフェイスとは

データベースに問い合わせる際の窓口のこと

まんま日本語訳に直してみた。

APIがソフトウェアやプログラム、Webサービスの間をつなぐインターフェースのことを指すなら、ActiveModelのクエリーインターフェイスはDBとRailsを繋げる際の接合部分を指すのだろう。

どんなものが用意されているのか

公式を見ながらどんなSQL文が発行されるのかを確認してみる。

一つのレコードを取得したい時

find

引数に入れた値にマッチする主キーを持つレコードを一つ取り出す。マッチするレコードが無い場合は例外を吐き出す。

例外を出すということは、例外が起きないことが前提とされているからだろうか。感嘆詞の時は少なくともそうだったような。

irb > Alcohol.find(3)
  Alcohol Load (2.3ms)  
SELECT `alcohols`.* 
FROM `alcohols`
WHERE `alcohols`.`id` = 3 
LIMIT 1
=> #<Alcohol id: 3, name: "11500ボックス", alcohol_percentage: 0.0, alcohol_amount: 11500, description: "アルコール総量が11500mlのボックスです。", created_at: "2021-12-09 07:43:15.003750000 +0900", updated_at: "2021-12-09 07:43:15.003750000 +0900", image: nil>

引数に配列を入れることで複数のオブジェクトを取り出すことができる。

irb> Alcohol.find([3, 12])
  Alcohol Load (4.0ms)  SELECT `alcohols`.* FROM `alcohols` WHERE `alcohols`.`id` IN (3, 12)
=> [#<Alcohol id: 3, name: "11500ボックス", alcohol_percentage: 0.0, alcohol_amount: 11500, description: "アルコール総量が11500mlのボックスです。", created_at: "2021-12-09 07:43:15.003750000 +0900", updated_at: "2021-12-09 07:43:15.003750000 +0900", image: nil>, #<Alcohol id: 12, name: "7000ボックス", alcohol_percentage: 0.0, alcohol_amount: 7000, description: "アルコール総量が7000mlのボックスです。", created_at: "2021-12-09 07:43:15.038544000 +0900", updated_at: "2021-12-09 07:43:15.038544000 +0900", image: nil>]

find_by

与えられた条件で最初に該当したレコードを一つ取り出す。該当するオブジェクトが存在しない場合は、nilを返す。

irb(main):006:0> Alcohol.find_by name: "ビール"
  Alcohol Load (2.7ms)  SELECT `alcohols`.* FROM `alcohols` WHERE `alcohols`.`name` = 'ビール' LIMIT 1
=> #<Alcohol id: 25, name: "ビール", alcohol_percentage: 5.0, alcohol_amount: 350, description: "「醸造酒」の一つ。とりあえずビールでお馴染みのビール。乾杯、そしてキンキンに冷えた
  ールを流し込もう...", created_at: "2021-12-09 07:43:15.504200000 +0900", updated_at: "2021-12-09 07:43:15.504200000 +0900", image: "beer.png">

Active Record クエリインターフェイス - Railsガイド

take

モデルのレコードを一つ取り出す。そもそもDBにレコードがあるのかを確認するときに使用する。

irb(main):003:0> Alcohol.take
  Alcohol Load (1.7ms)  SELECT `alcohols`.* FROM `alcohols` LIMIT 1
=> #<Alcohol id: 1, name: "12500ボックス", alcohol_percentage: 0.0, alcohol_amount: 12500, description: "アルコール総量が12500mlのボックスです。", created_at: "2021-12-09 07:43:14.973709000 +0900", updated_at: "2021-12-09 07:43:14.973709000 +0900", image: nil>

複数のレコードを取得して処理を実行したい時

each

全てのレコードを取得した後、一件一件に対して丁寧にオブジェクトを生成する。めっちゃメモリを食うから推奨されていない。

find_each

全てのレコードを取得した後、一つのブロックに納めて丸っとオブジェクトを生成する。デフォルトで1000件処理することができる。

特定のカラムを取得したい時

Alcohol.findの時は、select *が実行されているため、対象のオブジェクトにあるカラムを全て取得していた。いやいや、この名前と度数だけでいいんよ、他のカラムはいらないっすというときはselectを使う

Alcohol.select("name,alcohol_percentage").find(1)
  Alcohol Load (1.4ms)  SELECT name,alcohol_percentage FROM `alcohols` WHERE `alcohols`.`id` = 1 LIMIT 1
=> #<Alcohol id: nil, name: "12500ボックス", alcohol_percentage: 0.0>

重複するレコードはいらないんすよという場合は、distinctを使う

irb(main):015:0> Alcohol.select("alcohol_percentage")
  Alcohol Load (2.6ms)  SELECT `alcohols`.`alcohol_percentage` FROM `alcohols` /* loading for inspect */ LIMIT 11
=> #<ActiveRecord::Relation [#<Alcohol id: nil, alcohol_percentage: 0.0>, #<Alcohol id: nil, alcohol_percentage: 0.0>, #<Alcohol id: nil, alcohol_percentage: 0.0>, #<Alcohol id: nil, alcohol_percentage: 0.0>, #<Alcohol id: nil, alcohol_percentage: 0.0>, #<Alcohol id: nil, alcohol_percentage: 0.0>, #<Alcohol id: nil, alcohol_percentage: 0.0>, #<Alcohol id: nil, alcohol_percentage: 0.0>, #<Alcohol id: nil, alcohol_percentage: 0.0>, #<Alcohol id: nil, alcohol_percentage: 0.0>, ...]>

irb(main):015:0> Alcohol.select("alcohol_percentage").distinct
  Alcohol Load (8.8ms)  SELECT DISTINCT `alcohols`.`alcohol_percentage` FROM `alcohols` /* loading for inspect */ LIMIT 11
=> #<ActiveRecord::Relation [#<Alcohol id: nil, alcohol_percentage: 0.0>, #<Alcohol id: nil, alcohol_percentage: 5.0>, #<Alcohol id: nil, alcohol_percentage: 12.0>, #<Alcohol id: nil, alcohol_percentage: 6.0>, #<Alcohol id: nil, alcohol_percentage: 8.0>, #<Alcohol id: nil, alcohol_percentage: 3.0>, #<Alcohol id: nil, alcohol_percentage: 7.0>, #<Alcohol id: nil, alcohol_percentage: 40.0>, #<Alcohol id: nil, alcohol_percentage: 10.0>, #<Alcohol id: nil, alcohol_percentage: 15.0>, ...]>

Active Record クエリインターフェイス - Railsガイド

N + 1問題を回避したい時

N + 1問題とは

親テーブルから孫テーブルのデータを複数取得する際に、まず子テーブルに一回SQL文を発行してから孫テーブルに複数SQL文を発行してしまうことを指す。

まとめると、子データクエリーを投げる数(1)に対して、欲しいデータ数(N) が無駄に発行されてしまう問題のことを指す。

順番から考えると、N+1問題というよりは1+N問題と呼んだ方がイメージしやすい・

irb(main):020:0> alcohols = Alcohol.limit(10)
  Alcohol Load (0.4ms)  SELECT `alcohols`.* FROM `alcohols` /* loading for inspect */ LIMIT 10

この問題を回避する方法はいくつかある。

試しに、allを使った場合とincludeを使った場合を比較してN+1問題を理解してみる。

allを使用した場合

<a href="https://gyazo.com/03381abc25abdc2b16e569af2d19bcee"><img src="https://i.gyazo.com/03381abc25abdc2b16e569af2d19bcee.png" alt="Image from Gyazo" width="861"/></a>

Completed 200 OK in 204ms (Views: 100.3ms | ActiveRecord: 22.8ms | Allocations: 29218)

includeを使用した場合

<a href="https://gyazo.com/5767a3f7c5ecd8f0f9a2cf92005ddafd"><img src="https://i.gyazo.com/5767a3f7c5ecd8f0f9a2cf92005ddafd.png" alt="Image from Gyazo" width="923"/></a>

Completed 200 OK in 158ms (Views: 69.6ms | ActiveRecord: 68.7ms | Allocations: 23876)

includeとallを比較してみると、includeの方がviewにレンダリングする時間が短いことがわかる。includeを使用すると事前にActiverecordからデータをひっぱってきて、その後にviewにレンダリングするため、viewsに表示させるときにかかる時間が短いみたいだ。

preloadとeager_load,includeの使い分け

  • includesはなるべく利用しない方が良い

    preloadとeager_loadの使い分けをよしなにやってくれてしまうため、予期せぬ挙動をする可能性がある。熟練者はしっかり使い分けるそうで。

    • 理由:意図しない挙動を防ぐため
    • 代わりに、preloadeager_loadを使う
  • preload

    指定したassociationをLEFT OUTER JOINで引いてキャッシュする。クエリの数が1個で済むので場合によってはpreloadより速い。しっかりjoinしているため、whereで参照元のデータを絞り込み検索することができる。

    三つの中で一番クエリ速度が早い

    irb(main):032:0> User.preload(:analyzes)
      User Load (2.7ms)  
    SELECT `users`.* 
    FROM `users` /* loading for inspect */ LIMIT 11
      Analyze Load (3.2ms) 
     SELECT `analyzes`.* 
    FROM `analyzes` 
    WHERE `analyzes`.`user_id` 
    IN (1, 2, 3, 4)
    
    • どんな場合に使うといいか : 多対多のアソシエーションの場合
    • できないこと : アソシエーション先のデータ参照(Whereによる絞り込みなど)
    • 注意 : データ量が大きいと、IN句が大きくなりがちで、メモリを圧迫する可能性がある
  • eager_loadはどんな場合に使うといいか

    指定したassociationを複数のクエリに分けて引いてキャッシュする。

    UserとAnalyzeを紐付けてUser情報を取得したい場合。二つとも別のクエリを発行しているため、whereは使えない。

    irb(main):031:0> User.eager_load(:analyzes)
      
    SQL (3.1ms)  
    SELECT DISTINCT 
    `users`.`id` FROM `users` 
    LEFT OUTER JOIN `analyzes` 
    ON `analyzes`.`user_id` = `users`.`id` /* loading for inspect */ 
    LIMIT 11
     
     SQL (10.5ms)  
    SELECT `users`.`id` AS t0_r0, 
    `users`.`nickname` AS t0_r1, 
    `users`.`email` AS t0_r2, `users`.`crypted_password` AS t0_r3, 
    `users`.`salt` AS t0_r4, 
    `users`.`role` AS t0_r5, 
    `users`.`created_at` AS t0_r6, 
    `users`.`updated_at` AS t0_r7, 
    `users`.`avatar` AS t0_r8, 
    `users`.`reset_password_token` AS t0_r9, 
    `users`.`reset_password_token_expires_at` AS t0_r10, 
    `users`.`reset_password_email_sent_at` AS t0_r11, 
    `analyzes`.`id` AS t1_r0, `analyzes`.`user_id` AS t1_r1, 
    `analyzes`.`total_points` AS t1_r2, 
    `analyzes`.`created_at` AS t1_r3, 
    `analyzes`.`updated_at` AS t1_r4, 
    `analyzes`.`description` AS t1_r5, 
    `analyzes`.`shuchedule` AS t1_r6, 
    `analyzes`.`next_motivation` AS t1_r7, 
    `analyzes`.`alcohol_strongness` AS t1_r8 
    FROM `users` 
    LEFT OUTER JOIN `analyzes` 
    ON `analyzes`.`user_id` = `users`.`id` 
    WHERE `users`.`id` 
    IN (3, 2, 1, 4) /* loading for inspect */
    
    • 1対1あるいはN対1のアソシエーションをJOINする場合(belongs_to, has_one アソシエーション)
    • JOINした先のテーブルの情報を参照したい場合(Whereによる絞り込みなど)
  • joinsはどんな場合に使うか

    アソシエーションを組んで結合元のデータを全て取得したいとき

    irb(main):030:0> User.joins(:analyzes)
      User Load (11.5ms)  SELECT `users`.* FROM `users` INNER JOIN `analyzes` ON `analyzes`.`user_id` = `users`.`id` /* loading for inspect */ LIMIT 11
    
    • メモリの使用量を必要最低限に抑えたい場合
    • JOINした先のデータを参照せず、絞り込み結果だけが必要な場合
      • 逆に言うと、引用先のデータを参照しない場合、使用しないほうがいいです

対Nのアソシエーションの場合はpreload

データ量が増えるほど、eager_loadよりも、preloadの方がSQLを分割して取得するため、レスポンスタイムは早くなる

対1のアソシエーションの場合はeager_load

データ量が増えても、1回のSQLでまとめて取得した方が効率的な場合が多い

https://qiita.com/k0kubun/items/80c5a5494f53bb88dc58

pluck

1つのモデルで使用されているテーブルからカラム (1つでも複数でも可) を取得するクエリを送信する

irb(main):034:0> Alcohol.pluck(:id,:name)
   (7.3ms)  
SELECT `alcohols`.`id`, `alcohols`.`name` 
FROM `alcohols`

Active Record クエリインターフェイス - Railsガイド

SQL文はわかるけど、、な時

find_by_sqlを使うことで、生SQLでデータベースからデータを取得することができる。

Alcohol.find_by_sql("SELECT `users`.* FROM `users` INNER JOIN `analyzes` ON `analyzes`.`user_id` = `users`.`id` /* loading for inspect */ LIMIT 11")
  Alcohol Load (1.8ms)  
SELECT `users`.* 
FROM `users` 
INNER JOIN `analyzes` 
ON `analyzes`.`user_id` = `users`.`id` /* loading for inspect */ 
LIMIT 11

Active Record クエリインターフェイス - Railsガイド

一言

なんか3ヶ月前よりもSQL文が理解できてるきがする、、!気のせいかな?

【個人開発】ZEROKENの進捗を✅

「こんな感じに切ってください〜」とクリロナの写真を見せてお願いしたところクリス松村みたいな髪型になった23期酒ケジュール作成中です。#気に入ってます。

今回は呟き始めてから100日目なので、開発中のアプリである「ZEROKEN」の進捗をチェックしていきたいと思います。

ZEROKENとは

「あなたにとっての0軒目」というコンセプトで個人開発を進めているアプリ

■アプリURL

まだ完成していませんが、減るものでもないのでURLを貼っておきます!

https://zeroken.herokuapp.com/

■概要 1軒目で飲むお酒の順番を提供してくれるアプリになります。

東大生の考案した「TAST」というお酒の強さを診断するテストと、

厚生労働省の用意した「酔いの状態グラフ」を組み合わせることによって

「独自のお酒の順番」(酒ケジュール)を提供いたします。

■開発経緯

忘年会、新年会、歓送迎会、月初会。

みなさん事あるごとに飲み会を開きますよね。

しかも飲み会は楽しいから、つい誘われたら行ってしまう。

僕も青学という飲み会の多い大学に通っていた時に痛感しました。

いや今月大会だし、、英語ディベートの資料も用意しないといけないし、、

でも飲み会誘われたら行ってまう!

そんな飲み会の誘惑に負けてしまう人に向けた「自己防衛手段」が本アプリ「ZEROKEN」になります。

進捗

残っているissueが4、着手中のissueが3、解決済みのissueが10になっています。

ほとんどがUIに関することなのですが、まだリクエストスペックが完成していないので安心できない状況です。

Image from Gyazo

issues

改善したいポイントを箇条書きでまとめていきます。そして、リリース後に改善?それとも正式リリース前に改善?を考えていきたいと思います。

【本番環境】携帯とパソコンでjavascriptの動きが違う

クリックしたら次の項目に遷移する挙動をjavascriptで書いているが、パソコンだとぬるっと動くのに対して携帯だとカクカク動いてしまう。

パソコンからアクセスした場合

Image from Gyazo

携帯からアクセスした場合

FullSizeRender.mov

携帯とパソコンでjavascriptが異なる挙動をするような欠陥アプリを世に出すことはできません。早急に治したいのですが、、

画面のY座標とクリックした座標を認識してsmoothに次のボタンに遷移するような式を書いているんですけどねぇ、、

clickScroll(e) {
      const targetArea = e.currentTarget.getBoundingClientRect().top;
      window.scrollTo({
        top: window.pageYOffset + targetArea,
        behavior: 'smooth',
      });
    },

診断結果で酒ケジュールが一目でわからない

診断結果で酒ケジュールが横並びで配置されているようにしたいのですが、横並びにすると文字幅が狭くなって見辛くなってしまいます。どうしたものか、、

Image from Gyazo

結果画面に算出方法を表示させたい

job-hunter-choiceさんのようにアルゴリズムの解説を診断結果画面に表示させたいです。アプリが「診断系」なので、やはり診断ロジックは知りたいですよね、、

https://github.com/subaru-hello/Zeroken/issues/89

用語集を実装させたい

ZEROKENでは、「酒ケジュール」や「酒テータス」といった造語がたくさん使われているので、ユーザーが困惑することを防ぐためにも用語集は実装したいですよね、、

https://github.com/subaru-hello/Zeroken/issues/28

javascriptの冗長な記述をリファクタリングしたい

DRYじゃない部分が何箇所かあるのでリファクタリングをしたいです。

Analyzeコンポーネントの記述が1000行を超えているんですよね、、冷静に長すぎますね、、

Zeroken/Analyze.vue at 85232274eac6c9dd4c40a3402c8a5d3cb363d94a · subaru-hello/Zeroken

今後の方針

  • 診断結果の見た目に関しては、書籍もしくは記事を読み進めてより使いやすいインターフェイスを模索する。
  • リクエストスペックを書いてAPI系のコントローラーの動作を確認する。
  • public/manifest.jsonが何かを調べて、別ファイルに切り出してみる。

一言

早く皆さんの酒ケジュールが見たいです。完成させてお疲れ自分飲みをしとうございまする。。

アプリ右下に問い合わせフォームがあるので、奇譚なき機能追加サジェスチョンをお待ちしております。。

ZEROKEN(ゼロケン) - 一軒目に飲むお酒の順番を診断します。あなたにあったお酒の順番を把握。アルハラを予防しましょう。 -

【Vue】SPAアプリにOGP設定をする方法を✅

就活面談を通して、つくづくRUNTEQのサポートは手厚いなぁとしみじみ感じている酒ケ酒ケジュール作成中のスバスバです。

さて、今回はSPA(SinglePageApplication)アプリにOGP設定をする方法を✅していきます。

 

OGPとは

どんなWebページに対してもアクセスされた時の見た目をリッチにしてくれるプロトコル。The Open Graph protocolの略。

https://ogp.me/#metadata

サイトを共有するときにTwitterCardに埋め込まれている画像や、サイトを検索したときにサイト名の下に書かれている説明文なんかが当てはまる。

なんで使う?

共有されたユーザーの訴求率が高まり、より多くの人に記事を見てもらうことが可能になるから。

適切に設定することで、どのようなページで何を伝えたいのかが明確になる。

Facebookではこれを設定することで、分析や解析に役立つ「ページインサイト」の機能を利用することができるようになるらしい。

アプリにOGP設定をしていく

app/views/layouts/application.html.erbのheadタグにmetaタグを埋め込んでいく。

<html>
  <head>
    <title>ZEROKEN</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta property="og:type" content="website"/>
    <meta property="og:title" content="ZEROKEN(ゼロケン) - 一軒目に飲むお酒の順番を診断します。あなたにあったお酒の順番を把握。アルハラを予防しましょう。 - " />
    <meta property="og:url" content="<https://zeroken.herokuapp.com/>" />
    <meta property="og:image" content="<https://firebasestorage.googleapis.com/v0/b/zeroken-42112.appspot.com/o/zeroken_ogp.png?alt=media&token=d9b88af8-c935-40b5-82a5-88117a6e16f7>" />
    <meta property="og:description" content="一軒目に飲むお酒の順番を診断します。あなたにあったお酒の順番を把握。アルハラを予防しましょう。" />
    <meta name="keywords" content="ZEROKEN,ゼロケン,飲み会,酒豪、下戸、お酒の強さ" />
    <meta name="description" content="ZEROKENは、あなたにあった一次会で飲むお酒の量を提供します。" />

    <meta property="og:site_name" content="ZEROKEN(ゼロケン)" />
    <meta name="twitter:card" content="summary_large_image" />
    <meta name="twitter:image" content="<https://japaclip.com/files/alcoholic-beverages.png>" />
    <meta name="twitter:site" content="ZEROKEN(ゼロケン)" />

この状態だと全て共通の内容になってしまう。動的に変更する方法もあるみたい。firebaseと連携させたり、vue-metaやhelperを使って実装できるみたいだが、今回は見送らせていただく。

twitter share

Twitterでシェアをする機能をつけていく。

  1. Twitterボタンを作成

こちらは、material uiが用意しているアイコンを使用している。

 <v-icon color="#1da1f2"> mdi-twitter </v-icon>

シェアする内容を作成

「material UIで作成したtwitterのiconをv-list-itemで囲み、クリックしたらTwitterシェア画面に遷移すること」を実現したい。順を追って実装を進めていく。

まずはテンプレートファイルから。

<template>
         <v-list-item>
              <v-list-item-action>
                <v-icon color="#1da1f2"> mdi-twitter </v-icon>
              </v-list-item-action>
              <v-btn :href="sns.twitter" target="_blank"> Twitter </v-btn>
            </v-list-item>
          <v-list-item>
</template>

Image from Gyazo

次がjavascript部分になる。

<script>
export default{
data(){
return:{
sns: {
        twitter: '',
        url: '',
        title: '',
        hashtags: 'ZEROKEN,アルハラ防止',
        line: '',
        lineDescription: 'で一次会に向けてお酒の強さを診断しましょう。',
      },
}
},
methods: {
snsUrl() {
      setTimeout(
        function () {
          // 現在のurlをエンコード
          this.sns.url = encodeURIComponent(`https://zeroken.herokuapp.com`);
          this.sns.title = encodeURIComponent(document.title);
          this.sns.twitter =
            '<https://twitter.com/intent/tweet?url=>' +
            this.sns.url +
            '&text=' +
            this.sns.title +
            '&hashtags=' +
            this.sns.hashtags;
          this.sns.line =
            '<https://timeline.line.me/social-plugin/share?url=>' +
            this.sns.url +
            '&text=' +
            this.sns.title +
            this.sns.lineDescription;
        }.bind(this),
        300
      );
    },
}
}
</script>

解説

encodeURIComponent()

引数をエンコードしている。例えばhttps://zeroken.herokuapp.comは下記のようエンコーディングされる。

encodeURIComponent(<https://zeroken.herokuapp.com>)

⇨https%3A%2F%[2Fzeroken.herokuapp.com](<http://2fzeroken.herokuapp.com/>)

**https://twitter.com/intent/tweet?url=**

Twitterシェアする時のurl。url=の後ろにアプリのURLを続ける。

&text=

⇨ツイートにデフォルトで表示させる文字を設定させる

&hashtags=

⇨ツイートにデフォルトで表示させるハッシュタグを設定させる

実際にシェアされる時のツイートを確認

twitter社が用意しているtwitter card validatorで実際にシェアされる時のツイートを確認することができる。

(https://cards-dev.twitter.com/validator)

Image from Gyazo

line share

lineシェアも先程とほとんど同じロジックで実装できてしまう。

実際にシェアボタンを押してみると下記のような画面に遷移する。

Image from Gyazo

送る人を自分で選択し、送信ボタンを押す。なんかLineでシェアすると気まずい空気が流れる。

Image from Gyazo

追記

アプリのイメージ画像が出来上がった!自分で作ったにしては上出来!!酒とゼロケンの融合!

Image from Gyazo

【Vue.js】ツイッターのツイートに画像がついてるツイッターカードのつけ方 - はなちるのマイノート

 

Github Projectsを✅

時間とお金に余裕が生まれると本気でSDGsのような壮大な問題解決を自分ごとのように考え出すと聞き、早くエンジニアになって世のため人のために働きたいと心で叫んでる23期酒ケジュール作成中です。

エンジニアは日常をハック(効率化?)するためにgithub projectsを活用するともくもく会で聞きました。

例えばバレンタインデーで妻に渡す花を選ぶ時。

忘れたらやばいですよね。でも、やることは単純、かつ毎年訪れるイベント。

そんな時はGithub Project

青山フラワーマーケットの注文フォームが届くようにタスクを設定しておけば、ワンクリックで事故を未然に防ぐことができる。(ノンフィクションだそうです。)

僕も日常に自動化を取り入れたいので、今回はGithub Projectを学習していきたいと思います。

Github Projectとは

Project boards on GitHub help you organize and prioritize your work. You can create project boards for specific feature work, comprehensive roadmaps, or even release checklists. With project boards, you have the flexibility to create customized workflows that suit your needs

仕事に優先順位をつけてくれるGithubの機能。細かいタスク、アプリ作成の概要(ロードマップ)からリリースにおけるチェックリストまで、自分の好きにカスタマイズしてプロジェクトを進めることができる。

About project boards - GitHub Docs

TwilloやJIRAで行っていたようなカンバン式のタスク管理ができるようです。

ざっくりprojectの進め方を解釈する。

  1. リポジトリを作成する
  2. プロジェクトボードを作成する。
  3. ボードへリポジトリをリンクさせる
  4. イシューを作成する
  5. カードを作成する
  6. タスクの状態を管理する

 

プロジェクトを作成する

Image from Gyazo

Image from Gyazo

Image from Gyazo

テンプレートを選ぶ

Image from Gyazo

カードを作る

Image from Gyazo

カードをタスク化する

Image from Gyazo

自動化することができる

pushもしくはpull requestを実行したときに自動でprojectが動くように編集することができます。

  1. 自動化したいプロジェクトボードに移動します。

  2. 自動化したい列で、をクリックします。

    https://docs.github.com/assets/cb-4685/images/help/projects/edit-column-button.png

  3. [Manage automation] をクリックします。

    https://docs.github.com/assets/cb-18259/images/help/projects/manage-automation-button.png

  4. [Preset] ドロップダウンメニューで、自動化のプリセットを 1 つ選びます。

    https://docs.github.com/assets/cb-26658/images/help/projects/select-automation.png

  5. 列に設定したいワークフロー自動化を選択します。

    https://docs.github.com/assets/cb-35176/images/help/projects/select-automation-options-existing-column.png

  6. [Update automation] をクリックします。

About automation for project boards - GitHub Docs

テンプレートも用意されている

自分でprojectを作成するのだるい〜効率化するために非効率なことしたくない〜と言う方。安心してください、テンプレートが搭載されてますよ!

  • Basic kanban: タスクを管理する。ベーシックなカンバン式のタスク管理テンプレート

Image from Gyazo

  • Automated kanban: カードの状態に応じて To do, In progress, and Done columnsを行き来する。

Image from Gyazo

  • Automated kanban with review: タスクに逐一レビューをつけることができる

Image from Gyazo

  • Bug triage: トリアージを使ってバグの緊急性を管理することができる。トリアージは医療業界でよく使われる言葉。緊急性を赤や黄といった色で表し、救急の優先順位をつけている。

Image from Gyazo

まとめ

  • Github Projectを使うとアプリの進捗をカンバン式に管理することができる
  • 日常にも応用することができる

意気込み

Github Projectを活用してハードルで日本選手権に出るぞ!!

https://qiita.com/Yamotty/items/95bcd4743ab10da89db5

https://qiita.com/gumimin/items/63dcb36d4730213bd63ahttps://zenn.dev/t4t5u0/articles/f3aeb3895fd1fb

【Vue】グローバルAPIを✅

ハイボール7%3杯で酩酊になりました。酒ケジュールの設計を見直そうと思っている23期中野昴です。

 

今回は、グローバルAPIについて学習していきます。

 

グローバルAPIとは

vueで使用できる、Vueの動作をグローバルに変更するAPI

複数用意されているみたいです。

よく見るグローバルAPIを列挙していきます。

Vue.nextTick( [callback, context] )

  • 引数:

    • {Function} [callback]
    • {Object} [context]
  • 使用方法:

    callback を延期し、DOM の更新サイクル後に実行される。

    基本的には、setTimeout( callback, 0)と同じ挙動をする。

    # データを変更する
    vm.msg = 'Hello'
    # DOM 更新前
    Vue.nextTick(function () {
    # DOM 更新後の処理
    })
    
    // promise での使用法 
    Vue.nextTick()
      .then(function () {
        # DOM 更新後の処理
      })
    

    effect( id, definition )

    特定のビューモデルに依存しないエフェクト(v-ifと組み合わせて対象値変更時に特定処理を行う)を生成できる。

    例)1秒おきに表示を切り替えてコンソールに出力するコード

    <div id="sample">
      <div v-effect="foo" v-if="bar">message</div>
    </div>
    <script src="js/vue.js"></script>
    <script>
      Vue.effect('foo', {
        enter: function(el,  insert,  timeout){
          insert();
          console.log('inserted');
        },
        leave: function(el,  remove,  timeout){
          remove();
          console.log('removed');
        },
      });
      var vm = new Vue({
        el: '#sample',
        data: {
          bar: false
        }
      });
      setInterval( function() {
        vm.bar = vm.bar ? false : true
      }, 1000);
    </script>
    

    コンポーネント(Vue.component())

    Vue.component() は、コンポーネントと呼ばれる Vue の独自要素を定義します。

    下記の例では、'<strong>Hello!</strong>' を表示する独自要素 <my-element-140> を定義している。

    <div id="app-140">
      <my-element-140></my-element-140>
    </div>
    <script>
    Vue.component('my-element-140', {
      template: '<strong>Hello</strong>'
    })
    var app140 = new Vue({
      el: '#app-140',
    })
    </script>
    
    

    表示

    Hello

    このテンプレートは他のコンポーネントで呼び出す事ができる。

    カスタムディレクティブの登録(Vue.directive())

    v-for や v-show などの ディレクティブ に加えて、グローバルに独自のカスタムディレクティブを追加することができます。

    v-の後に好きな文字をつけて、独自のディレクティブを作る事ができる。

    下記の例では、フォントサイズを 16pt にする v-font16 カスタムディレクティブを作成している。

    <div id="app-144">
      <div v-font16>Hello!</div>
    </div>
    <script>
    Vue.directive('font16', {
      inserted: function(el) {
        el.style.fontSize = '16pt'
      }
    })
    var app144 = new Vue({
      el: '#app-144'
    })
    </script>
    
    

    表示

    Hello!

【RSpec】リクエストスペックを✅

「ストリートナンパを始めた」という話を聞いたけど、そもそもストリート以外でナンパってすることあるのか?と気になって夜も眠れない23期酒ケジュール作成中です。

今回はrspecのリクエストスペックについて学習していきます。

 

リクエストスペックとは

Railsのコントローラーにおいて、API系処理をテストしたいときに書くRSpec

リクエストスペックでは、api処理を担当するコントローラーが正常に動くかどうかをテストしている。

フィーチャースペックのようにブラウザをシミュレートする必要はないため、Capybaraは使わない。代わりにHTTPメソッド(get,post,delete,patch等)を使う。

GET リクエス

GETリクエストでは正しくデータを取得した際にステータスコード200が返るか、正しく要求したデータが取得できたかをテストしている。

実際に使ってみる

今回は、alcohols_controllerのindexアクションが正常に動くか試してみる

$ rails g rspec:request alcohol

api/v1/alcohols_controller.rb

module Api
  module V1
    class AlcoholAnalyzeController < ApplicationController
      def index; end
    end
  end
end

spec/requests/alcohol_requests_spec.rb

require 'rails_helper'

RSpec.describe "Alcohols", type: :request do
  describe "get api/v1/alcohols" do
    before do
      get api_v1_alcohols_path ・・・1
      @json = JSON.parse(response.body)・・・2
    end

    it '200が返ってくる' do
      expect(response).to have_http_status(200) ・・・3
    end
  end
end

1 alcohol index pathでget通信をしている

2 1で帰ってきたJson形式のレスポンスをオベジェクト形式、もしくは値に変換している。

3 responseが200(正常)であるか確かめている

$ bundle exec rspec -fd ./spec/requests/alcohol_request_spec.rb

Image from Gyazo

テストを書いたら逐一bundle exec rspec -fd spec/requests/〇〇_spec.rbを実行するのも億劫なので、guardファイルを作成しておく。

guardファイルとは

Rubyで書かれたライブラリで、ファイルやディレクトリの変更を検知し、ユーザが定義した様々なタスクを実行する事ができる。

guardを使用するためにはGemfileにguard-rspecを追加する必要がある。

gem 'guard-rspec', require: false
gem 'terminal-notifier-guard

追加後はbundleを実行し、下記コマンドでguardファイルを生成する

$ bundle exec guard init

生成されたGuardfileは以下の通りになる。

Guardfile

# A sample Guardfile
# More info at <https://github.com/guard/guard#readme>

## Uncomment and set this to only include directories you want to watch
# directories %w(app lib config test spec features) \\
#  .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}

## Note: if you are using the `directories` clause above and you are not
## watching the project directory ('.'), then you will want to move
## the Guardfile to a watched dir and symlink it back, e.g.
#
#  $ mkdir config
#  $ mv Guardfile config/
#  $ ln -s config/Guardfile .
#
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"

# Note: The cmd option is now required due to the increasing number of ways
#       rspec may be run, below are examples of the most common uses.
#  * bundler: 'bundle exec rspec'
#  * bundler binstubs: 'bin/rspec'
#  * spring: 'bin/rspec' (This will use spring if running and you have
#                          installed the spring binstubs per the docs)
#  * zeus: 'zeus rspec' (requires the server to be started separately)
#  * 'just' rspec: 'rspec'

guard :rspec, cmd: "bundle exec rspec" do
  require "guard/rspec/dsl"
  dsl = Guard::RSpec::Dsl.new(self)

  # Feel free to open issues for suggestions and improvements

  # RSpec files
  rspec = dsl.rspec
  watch(rspec.spec_helper) { rspec.spec_dir }
  watch(rspec.spec_support) { rspec.spec_dir }
  watch(rspec.spec_files)

  # Ruby files
  ruby = dsl.ruby
  dsl.watch_spec_files_for(ruby.lib_files)

  # Rails files
  rails = dsl.rails(view_extensions: %w(erb haml slim))
  dsl.watch_spec_files_for(rails.app_files)
  dsl.watch_spec_files_for(rails.views)

  watch(rails.controllers) do |m|
    [
      rspec.spec.call("routing/#{m[1]}_routing"),
      rspec.spec.call("controllers/#{m[1]}_controller"),
      rspec.spec.call("acceptance/#{m[1]}")
    ]
  end

  # Rails config changes
  watch(rails.spec_helper)     { rspec.spec_dir }
  watch(rails.routes)          { "#{rspec.spec_dir}/routing" }
  watch(rails.app_controller)  { "#{rspec.spec_dir}/controllers" }

  # Capybara features specs
  watch(rails.view_dirs)     { |m| rspec.spec.call("features/#{m[1]}") }
  watch(rails.layouts)       { |m| rspec.spec.call("features/#{m[1]}") }

  # Turnip features and steps
  watch(%r{^spec/acceptance/(.+)\\.feature$})
  watch(%r{^spec/acceptance/steps/(.+)_steps\\.rb$}) do |m|
    Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
  end
end

Guardを起動する。

$ bundle exec guard

スペックファイルを編集すると逐一変更を検知、実行してくれる。下記の通り

Image from Gyazo

ざっくりスペックの種類をおさらい

システムスペック

アプリの統合テストを行うRSpec

個人開発において、アプリ開発の終盤に書くことが推奨される。なぜなら、UIの変化が激しいから。工数が多いため、モデルスペックとリクエストスペックだけで終わらせる場合もある。

モデルスペック

ActiveModelが正常に動くかをテストするRSpec

コントローラースペック

Controllerが正常に動くかをテストするRSpec

リクエストスペック

API系のコントローラーをテストするRspec

Request Specを使おう - Qiita

【動画付き】Railsチュートリアルの統合テスト(integration test)は、RSpecのリクエストスペックに置き換えるのがラクです - Qiita

【Rails】APIテストの書き方 - Qiita