技術ブログ

(技術系中心)基本自分用備忘録なので、あくまで参考程度でお願いします。

TablePlusの書き方(sshトンネルで接続したい時)

TablePlusの書き方(sshトンネルで接続したい時)

■ ダウンロードする

TablePlus | Modern, Native Tool for Database Management

■ こちらの記事を参考にsshトンネルで接続してみる

TablePlusの書き方(sshトンネルで接続したい時) - Qiita

Docker環境のRails7をherokuにデプロイした時にハマった現象まとめ

Docker環境のRails7をherokuにデプロイした時にハマった現象まとめ

1. Heroku Stackの設定

Docker コンテナを Heroku で使用する為にheroku stack:set containerコマンドを叩く必要があります。

このコマンドを叩くことで、Heroku が Dockerfile を使用してアプリケーションのビルドとデプロイを行うよう指示するようになります。

2. heroku.ymlの設定

HerokuでDockerコンテナを使用する場合、heroku.ymlファイルを作成します。

例)

build:
  docker:
    web: Dockerfile
run:
  web: bundle exec puma -C config/puma.rb

3. 環境変数の設定

3.1 DATABASE_URL

Herokuでは、アプリケーションにアタッチされたデータベースアドオンの情報は自動的にDATABASE_URLに保存されます。

Ruby on Railsの場合、database.ymlファイルでデータベースの設定を行いますが、production環境の設定では以下のようにDATABASE_URL環境変数を設定する必要があります。

production:
  <<: *default
  database: aaaaa_production
  username: aaaaa
  password: <%= ENV["MYAPP_DATABASE_PASSWORD"] %>
  url: <%= ENV['DATABASE_URL'] %

3.2 PORT

Heroku環境では、アプリケーションがリッスンすべきポート番号が PORT 環境変数によって提供されます。そのため、RailsアプリケーションがHerokuで正しく動作するためには、この PORT 環境変数を適切に使用する必要があります。

例えば、Pumaサーバーを使用している場合、config/puma.rb ファイルで以下のように設定する必要があります。

// ローカル環境など、PORT 環境変数が設定されていない環境でもアプリケーションが動作するようにするため、|| 3000 の部分が必要
port ENV['PORT'] || 3000

3.3 SECRET_KEY_BASE

SECRET_KEY_BASEは、RailsアプリケーションがセッションやCookieを暗号化するために使用する秘密鍵です。

config/credentials.yml.enc ファイルにある secret_key_base の設定を確認し、herokuの'production'環境用のシークレットキーを追加する必要があります。

4. ローカルでassets:precompileしてからデプロイする

herokuにCSSが反映しないので、 ローカルでassets:precompileしてからデプロイした。

ただし、この記事をみる限り、Herokuではデプロイ時に自動的にassets:precompileが実行されるようになっているのでこの作業が必要かどうかは怪しい。

Docker環境でGemfileを変更してもインストールされない

発生手順

  1. gemfileにgem追加
  2. docker-compose run --rm web bundle install
  3. dockerコマンド叩く(例: docker-compose run --rm web bundle exec rubocop)
➜  git:(master) ✗ docker-compose run --rm web bundle exec rubocop
Creating compass-backend_web_run ... done
ERROR: ld.so: object '/usr/lib/x86_64-linux-gnu/libjemalloc.so.2' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.
bundler: command not found: rubocop
Install missing gem executables with `bundle install`

bundle installできてないとエラーになる

原因

Dockerのイメージの再ビルドをしていない。 Gemfileを変更したときは、イメージの再ビルドを行わないと、gemはインストールされない

解決

  1. docker-compose down -v
  2. docker-compose build --no-cache
  3. docker-compose up

1で既存のvolume削除して、2で再度コンテナ立ち上げる

参考記事

Docker環境でGemfileを変更してもインストールされない | junblog

Vue3を開発する時のVsCodeのおすすめプラグイン設定

Vue3を開発する時のVsCodeのおすすめプラグイン設定

おすすめ拡張機能紹介

1. Volar

Volar は Vue.js の開発をサポートする VS Code拡張機能です。.vue ファイルのシンタックスハイライトやインテリセンスによる補完ができるようになります。詳細は、Volar の GitHub リポジトリ (opens new window)を参照してください。

GitHub - johnsoncodehk/volar: ⚡ Explore high-performance tooling for Vue

2. Vue3 snippest

スニペットを使用する機能を提供してくれます

Vue 3 Snippets - Visual Studio Marketplace

3. Vue Peek

選択したコンポーネントがどこで定義しているか表示してくれる

Vue.jsを使う人が最低限導入しておきたいVisual Studio Codeの拡張機能 – Webrandum

4. Prettier

バラバラに書かれたコードを、自動的に綺麗にしてくれます。

【自動整形】VSCodeでPrettierを使う方法【設定必要です】 | RalaCode

5.Auto Rename Tag

開始タグを修正すると自動で閉じタグも修正してくれる拡張機能

Visual Studio Codeで開始タグを修正すると自動で閉じタグも修正してくれる拡張機能「Auto Rename Tag」 – Webrandum

7. JavaScript (ES6) code snippets

ES6用のスニペットを提供してくれる拡張機能

JavaScript (ES6) code snippets - Visual Studio Marketplace

8. Material Icon Theme

Material Icon Themeを設定するとフォルダにもアイコンが表示されるようになります。

Material Icon Theme - Visual Studio Marketplace

マネタイズ方法まとめ

マネタイズ方法まとめ

WEBサービスでマネタイズするパターンをそれぞれ記載します

1. 機能絞り込み

機能を絞り込み、人気機能を利用するには有料プランに以降を促す

例) - Pairs - openwork

■ メリット

  • ユーザーが気軽にサービス利用できる

■ デメリット

  • 魅力的な有料プランでなければ課金されない

2.フリーミアムモデル

最初無料でインストールできるが、特定のところまで行くと課金が必須になる(初月無料的な)

■ メリット

  • ユーザーが気軽にお試し利用できる

■ デメリット

  • 無料期間に魅力を感じなければ課金されない

3. 広告表示

広告を表示させて、その表示回数に合わせて広告料金をもらう

■ メリット

  • 導入が容易

■ デメリット

  • かなりのアクセス数がないと、稼げなく難易度が高い。
  • Googleアルゴリズムの影響を受けやすい

4.サブスクリションモデル

月額制で利用料金を支払うサブスクリションモデル

例) - netflex

■ メリット

■ デメリット

  • サブスクビジネスモデルの競合が多い(ライバルが多い)

5.手数料サービス

プラットフォームサービスでユーザーと企業がマッチングした時に発生する金額の一部を成果報酬として得る。

例) - Menta

■ メリット

  • toBであれば取引金額が高い

■ デメリット

  • ユーザー同士の揉め事があった時の対応がしんどい

Nuxt3でvalidationする方法

Nuxt3でvalidationする方法

インストール

npm i vee-validate @vee-validate/i18n @vee-validate/rules

vee-validate -> veeValidate

@vee-validate/i18n -> バリデーションエラーメッセージに日本語化

@vee-validate/rules -> バリデーションルールを利用する

pluginの設定

■ plugins/vee-validate.client.ts

import { localize, setLocale } from "@vee-validate/i18n";
// VeeValidateで用意されている英語語版入力チェックエラーメッセージを使う
import en from "@vee-validate/i18n/dist/locale/en.json";
// VeeValidateで用意されている日本語版入力チェックエラーメッセージを使う
import ja from "@vee-validate/i18n/dist/locale/ja.json";
import AllRules from "@vee-validate/rules";
import { defineRule, configure } from "vee-validate";
import { defineNuxtPlugin } from "#app";

export default defineNuxtPlugin((_nuxtApp) => {
  configure({
    generateMessage: localize({
      en,
      ja
    }),
  });

 
  configure({
    generateMessage: localize('ja', {
      names: {
        password: 'パスワード',
        email: 'Email'
      },
    }),
  });


  Object.keys(AllRules).forEach((rule) => {
    defineRule(rule, AllRules[rule]); // 全ルールを使えるようにする
  });

  setLocale("ja");
});

Fieldのバリデーション

<template>
  <div class="min-h-screen flex flex-col">
    <div
      class="container max-w-md mx-auto flex-1 flex flex-col items-center justify-center px-2"
    >
      <div class="bg-white px-6 py-8 text-black w-full">
        <h1 class="mb-8 text-3xl text-center">
          新規登録 🎉
        </h1>
        <input
          v-model="email"
          type="text"
          class="block border border-grey-light w-full p-3 rounded mb-4"
          name="email"
          placeholder="Email"
        >

        <input
          v-model="password"
          type="password"
          class="block border border-grey-light w-full p-3 rounded mb-4"
          name="password"
          placeholder="Password"
        >

        <input
          v-model="passwordConfirm"
          type="password"
          class="block border border-grey-light w-full p-3 rounded mb-4"
          name="passwordConfirm"
          placeholder="Confirm Password"
        >

        <button
          type="submit"
          :class="validInput ? 'bg-indigo-500 hover:bg-indigo-700' : 'bg-gray-500'"
          class="block border border-grey-light w-full p-3 rounded mb-4 text-white"
          :disabled="!validInput"
          @click="register"
        >
          登録
        </button>
      </div>
    </div>
  </div>
</template>

<script>
import { useField } from 'vee-validate';

export default {
  setup() {
    const { value: email, errorMessage: emailError } = useField(
      "email",
      "required|email",
    );

    const { value: password, errorMessage: passwordError } = useField(
      "password",
      "required|min:5",
    );

    const { value: passwordConfirm, errorMessage: passwordConfirmError } = useField('passwordConfirm', (passwordConfirm) => {
      if ( passwordConfirm === password.value) {
        return true;
      }
      return 'パスワードと一致してません';
    });


    return {
      email,
      emailError,
      password,
      passwordError,
      passwordConfirm,
      passwordConfirmError
    }
  },
}
</script>

参考リンク

Nuxt3でvee-validate4(Composable API)を使う | デバッグライフ

Vue3でフォームバリデーションのVee-Validate4の基礎を理解 | アールエフェクト

Nuxt3とfirebase9で認証機能設定する方法

Nuxt3とfirebase9で認証機能設定する方法

各バージョン

Nuxt: 3.0.0-rc.11
Firebase: 9.13.0

インストール

npm install firebasepackage.jsonfirebaseが追加されればOK

Firebaseの初期設定

■ plugins/firebase.client.ts

import { initializeApp } from 'firebase/app'
import { defineNuxtPlugin } from '#app'

export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig()
  const firebaseConfig = {
    apiKey: config.FIREBASE_API_KEY,
    authDomain: config.FIREBASE_AUTH_DOMAIN,
    projectId: config.FIREBASE_PROJECT_ID,
    storage_bucket: config.FIREBASE_STORAGE_BUCKET,
    messaging_sender_id: config.FIREBASE_MESSAGING_SENDER_ID,
    api_id: config.FIREBASE_API_ID,
    measurement_id: config.FIREBASE_MEASUREMENT_ID,
  }
  initializeApp(firebaseConfig)
})

(Authenticationのみで利用するので、初期化時の引数はapiKeyとauthDomainとprojectIdの3つを最低限指定しておけば問題なく動く。)

  • plugins ディレクトリについて
  • pluginsディレクトリ配下のjsファイルは自動的に読まれるので、nuxt.config.jsへの設定は不要。
  • defineNuxtPluginというplugin読み込み用の関数が用意されているので、これを使う。
  • プラグイン内でランタイムの設定を使用したい場合は、defineNuxtPlugin 関数内でuseRuntimeConfig() を使用する

Nuxt3はdotenvが内包されているので.envでお手軽に環境変数を設定する

■ .env

FIREBASE_API_KEY=****************
FIREBASE_AUTH_DOMAIN=****************
FIREBASE_PROJECT_ID=****************
FIREBASE_STORAGE_BUCKET=****************
FIREBASE_MESSAGING_SENDER_ID=****************
FIREBASE_API_ID=****************
FIREBASE_MEASUREMENT_ID=G-****************

nuxt3には、実行時に設定値をロードするための仕組みとしてruntimeConfigというものがある。コンポーネント内からはuseRuntimeConfigや$configで値にアクセスすることができる。

■ nuxt.config.ts

  runtimeConfig: {
    public: {
      FIREBASE_API_KEY: process.env.FIREBASE_API_KEY || '',
      FIREBASE_AUTH_DOMAIN: process.env.FIREBASE_AUTH_DOMAIN || '',
      FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID || '',
      FIREBASE_STORAGE_BUCKET: process.env.FIREBASE_STORAGE_BUCKET || '',
      FIREBASE_MESSAGING_SENDER_ID: process.env.FIREBASE_MESSAGING_SENDER_ID || '',
      FIREBASE_API_ID: process.env.FIREBASE_API_ID || '',
      FIREBASE_MEASUREMENT_ID: process.env.FIREBASE_MEASUREMENT_ID || '',
    },
  },

例)config.FIREBASE_API_KEYと記載すれば.envで設定されたFIREBASE_API_KEYの値が取得できる

各認証メソッド

認証の処理はcomposableにまとめる。

Composables directoryではこちらのドキュメントの通り、各Componentでimportを書かなくても呼び出しができるものになります。

■ composables/useAuth.ts

import {
  getAuth,
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  signOut as firebaseSignOut,
  onAuthStateChanged,
  GoogleAuthProvider,
  signInWithPopup,
  TwitterAuthProvider
} from 'firebase/auth'

export const useAuth = () => {
  const token = useState<string>('token', () => null)

  // メールアドレス新規登録関数
  async function signUp(email:string, password:string){
    return await new Promise((resolve)=>{
      // getAuth()でAuthを取得
      const auth = getAuth()
      // メールアドレスとパスワードでアカウントを作成する
      createUserWithEmailAndPassword(auth, email, password)
      .then((userCredential) => {
        console.log(userCredential)
        // サインアップできたらログインする
        resolve("success")
      })
      .catch((error) => {
        console.log(error)
        const errorMessage = error.message;
        resolve(errorMessage)
      })
    })
  }

  // メールアドレスとパスワードでログインする関数
  async function signIn(email: string, password: string) {
    return await new Promise<void>((resolve, reject) => {
      const auth = getAuth()
      // メールアドレスとパスワードでログインする
      return signInWithEmailAndPassword(auth, email, password)
        .then((userCredential) => {
          userCredential.user
            .getIdToken()
              .then((idToken) => {
                token.value = idToken
                resolve()
              })
            .catch(reject)
        })
        .catch(reject)
    })
  }

  // ログアウトする関数
  async function signOut() {
    return await new Promise<void>((resolve, reject) => {
      const auth = getAuth()
      // ログアウトする
      firebaseSignOut(auth)
        .then(() => {
          token.value = null
          resolve()
        })
        .catch((error) => {
          reject(error)
        })
    })
  }

  // ログイン状態確認関数
  async function checkAuthState() {
    return await new Promise<void>((resolve, reject) => {
      // client only
      if (process.server) return resolve()
      const auth = getAuth()
      onAuthStateChanged(
        auth,
        (user) => {
          if (user) {
            user
              .getIdToken()
              .then((idtoken) => {
                token.value = idtoken
                resolve()
              })
              .catch(reject)
          } else {
            token.value = null
            resolve()
          }
        },
        (error) => {
          reject(error)
        }
      )
    })
  }

  // google認証関数
  async function loginWithGoogle() {
    const auth = getAuth();
    const provider = new GoogleAuthProvider();
    signInWithPopup(auth, provider)
      .then((result) => {
        const credential = GoogleAuthProvider.credentialFromResult(result);
        const token = credential?.accessToken;
        const user = result.user;
        console.log({ credential, token, user });
    })
    .catch((error) => {
        const errorCode = error.code;
        const errorMessage = error.message;
        const email = error.email;
        const credential = GoogleAuthProvider.credentialFromError(error);
        console.log({ errorCode, errorMessage, email, credential });
    });
  };

  // twitter認証関数
  async function loginWithTwitter() {
    const auth = getAuth();
    const provider = new TwitterAuthProvider();
    signInWithPopup(auth, provider)
      .then((result) => {
        const credential = TwitterAuthProvider.credentialFromResult(result);
        const token = credential?.accessToken;
        const user = result.user;
        console.log({ credential, token, user });
    })
    .catch((error) => {
        const errorCode = error.code;
        const errorMessage = error.message;
        const email = error.email;
        const credential = TwitterAuthProvider.credentialFromError(error);
        console.log({ errorCode, errorMessage, email, credential });
    });
  };



  return {
    signUp,
    signIn,
    signOut,
    token,
    checkAuthState,
    loginWithGoogle,
    loginWithTwitter
  }
}

ログイン状態の永続性

■ middleware/auth.ts

export default defineNuxtRouteMiddleware(async () => {
  if (!process.server) {
    const { checkAuthState, token } = useAuth()
    await checkAuthState()
    if (!token.value) {
      return navigateTo('/login', { replace: true })
    }
  }
})

Page コンポーネントや Layout コンポーネントにて、使用する middleware を指定すると、コンポーネントが作成 (setup) される前に(ページ遷移に先立ち) middleware 内の指定のファイルが実行されます。

<script setup>
definePageMeta({
  middleware: ['auth']
})
</script>

参考

Nuxt3 & Firebase9でFirebase Authentication決定版

Nuxt3のruntimeConfigで環境変数を設定する | デバッグライフ

【Vue.js】Nuxt3でComposables directoryを使ってグローバルstateを管理する

Nuxt 3 の Route Middleware で簡単な認証フローを構築する