mind.

学んだことの記録

Rails APIモードのテンプレートリポジトリを作った【解説編】

概要

前回の記事の解説です。
learning-mind.hatenablog.com

テンプレートリポジトリ
github.com

Gemの選定について

Rails Gem おすすめ」で検索してヒットした記事をいくつか読みつつ、普段仕事で使っているGemと合わせて選びました。
どんなプロダクトでも必要になると思われるものを選んだつもりです。

Gemの中にはコードの保守性を高めたり脆弱性を検知するものをいくつか含めています。

  • rubycritic
  • rubocop
  • bundler-audit
  • bullet
  • rails_best_practices
  • brakeman

これらは後から導入すると既存のコードの大部分を変更しなければならないケースが多いです。
初めから入れておいて型にはまったコーディングができれば、長期的に見たときに開発効率が高まると考えて選びました。
もしチーム開発を前提とするならやはりこういうGemは先に入れてメンバー間の認識を早期に固めておいた方が気持ちよく開発作業が進むと思います。

Docker

前回の記事で

rails newで作成したファイルに極力変更を加えない

と書きましたがDockerfileだけはそのままでは動かせず、シンプルなものをゼロベースで作成しました。
参考としてRailsが自動で作成するものを以下に記載します。

# syntax = docker/dockerfile:1

# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=3.2.2
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base

# Rails app lives here
WORKDIR /rails

# Set production environment
ENV RAILS_ENV="production" \
    BUNDLE_DEPLOYMENT="1" \
    BUNDLE_PATH="/usr/local/bundle" \
    BUNDLE_WITHOUT="development"


# Throw-away build stage to reduce size of final image
FROM base as build

# Install packages needed to build gems
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y build-essential git libpq-dev libvips pkg-config

# Install application gems
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
    rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
    bundle exec bootsnap precompile --gemfile

# Copy application code
COPY . .

# Precompile bootsnap code for faster boot times
RUN bundle exec bootsnap precompile app/ lib/


# Final stage for app image
FROM base

# Install packages needed for deployment
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y curl libvips postgresql-client && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Copy built artifacts: gems, application
COPY --from=build /usr/local/bundle /usr/local/bundle
COPY --from=build /rails /rails

# Run and own only the runtime files as a non-root user for security
RUN useradd rails --create-home --shell /bin/bash && \
    chown -R rails:rails db log storage tmp
USER rails:rails

# Entrypoint prepares the database.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]

# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD ["./bin/rails", "server"]

参考記事

zenn.dev

作業手順

テンプレートリポジトリを作成したときに使ったコマンドを雑に列挙します。

概略

  1. Rubyのアップデート
  2. Railsのアップデート
  3. rails new
  4. 各種Gemの初期設定

コマンド

brew update & brew upgrade
rbenv install 3.2.2
rbenv rehash
rbenv global 3.2.2
rbenv versions
ruby -v

gem update --system
gem search -l rails
gem search rails | grep "^rails ("
gem install rails -N
rails -v
brew install postgresql
gem install pg -v 0.18.4

rails new rails-api-template --api -d postgresql

# 以下Gem初期設定
docker compose run web rails generate rspec:install

docker compose run web bundle exec rails g rswag:api:install
docker compose run web bundle exec rails g rswag:ui:install
docker compose run web bundle exec rails g rswag:specs:install

docker compose run web bundle exec rails g bullet:install

docker compose run web bundle exec rubocop --auto-gen-config

今回はローカルPCでrails newを実行しましたが、dockerで実行してプロジェクトを作成することもできます。

zenn.dev

感想

テンプレートができたので今後新しいプロダクトを作り始めるときの心理的ハードルが下がりました。
今回触ったGemに対して解像度が高まったように思います。
Dockerに関して、可能な限り自動で作成されたものを使用したかったのでかなり時間を使ってマルチステージビルド等の設定を調べましたがもう少し早めに見限って今の形にした方が良かったかもしれません。
どの道Dockerfileはプロダクトの環境に応じて書き直すことになりますしね。

この記事はkb advent calendar24日目の記事です。

adventar.org

Rails APIモードのテンプレートリポジトリを作った(2023年12月版)

完成したリポジトリ

github.com

なぜやるのか

新規プロジェクトを立ち上げるときに初動で躓きたくなかったからです。
早く作って試せる状態にしておいた方がRailsメリットを活かせると考えています。

方針

  • Rails APIモード
  • cloneしてから以下の手順でRailsのデフォルトページが表示されるリポジトリ
    1. コンテナをビルド
    2. マイグレーション
    3. コンテナ起動
  • 開発環境のみで本番環境は想定しない
  • rails newで作成したファイルに極力変更を加えない
  • credentialsを.gitignoreに記載
    • 作業するときにうっかり上げないようにしたかったからです
    • READMEに再発行が必要な旨を記載しました
  • どんなプロダクトでも必要になるGemをインストールし初期設定を行う
  • Ridgepoleは賛否両論あるようなので未インストール

    techracho.bpsinc.jp

    qiita.com

  • 作成手順や詰まったところは別記事で

あとがき

この記事はkb advent calendar17日目の記事です。

adventar.org

Flutter APIキー等を記載した秘匿ファイルを読み込むflutter_dotenv

やりたいこと

Flutterアプリで使うAPIキーをソースファイルに書かずに使用したい。

やり方

  • .envファイルを作成する
  • .envを.gitignoreに追記する
  • flutter_dotenvをインストールする
  • main().envファイルを読み込む
  • 必要な箇所で環境変数を読む

.envファイルを作成する

プロジェクトのルートディレクトリに作成します。
.env

HOGEHOGE=hogehoge

.envを.gitignoreに追記する

.gitignore

.env

flutter_dotenvをインストールする

下記を記載してUpgradeを実行。
pubspec.yaml

dependencies:
  flutter_dotenv: ^3.1.0

ソースファイルでimport。
main.dart

import 'package:flutter_dotenv/flutter_dotenv.dart' as DotEnv;

main().envファイルを読み込む

main.dart

Future main() async {
  await DotEnv.load();
  runApp(MyApp());
}

ライブラリは非同期で動作するようなのでasync awaitが必要。

必要な箇所で環境変数を読む

main.dart

DotEnv.env['HOGEHOGE']

HOGEHOGE.envに記載した環境変数名です。

備考

flutter_dotenvの詳細は下記ページを参照。
pub.dev

flutter_dotenvのバージョン3系と2系では書き方が異なるようです。

blog.mbaas.nifcloud.com

Flutterアプリの動作確認用テンプレート

備忘録として。

import 'package:flutter/material.dart';

void main() => runApp(RootWidget());

class RootWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: TestWidget(),
    );
  }
}

class TestWidget extends StatefulWidget {
  @override
  TestWidgetState createState() => new TestWidgetState();
}

class TestWidgetState extends State<TestWidget> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Startup Name Generator'),
      ),
      body: Center(
        child: Text('Text'),
      ),
    );
  }
}

実行結果

f:id:cm_kenji:20210331103611p:plain

Javascript cookie内の特定の値のみを取り出す正規表現

hogehoge_cookieの値だけを取り出す

document.cookie.replace(/(?:(?:^|.*;\s*)hogehoge_cookie\s*\=\s*([^;]*).*$)|^.*$/, "$1")

こういうノウハウって運良く知る機会があったので助かりましたが、知らないままだと自力で正規表現作ってたんだろうなって思うと九死に一生を得た気分になりますね。

techacademy.jp

この記事はkb Advent Calendar 2020 15日目の記事です。

adventar.org

追記(2020/12/16)

もっといいやり方ありました。
developer.mozilla.org

備忘録としてサンプルアプリを書いておきます。

<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<script>
document.cookie = "test1=Hello";
document.cookie = "test2=World";

const cookieValue = document.cookie
  .split('; ')
  .find(row => row.startsWith('test2'))
  .split('=')[1];

function alertCookieValue() {
  alert(cookieValue);
}

</script>
</head>
<body>
<button onclick="alertCookieValue()">Show cookie value</button>
</body>
</html>

Rails APIモード + Vue.jsでアプリを作成する手順

やること

フロントエンドアプリケーションはVue.jsで実装し、バックエンドアプリケーションはAPIサーバーとしてのみ機能するシステムを作ること。
ルーティングはVue Routerで行い、Railsのテンプレートエンジンは使用しない。

環境構築

OS

Virtualbox + Vagrantを使います。
Downloads – Oracle VM VirtualBox
Downloads | Vagrant by HashiCorp

アプリのファイルを変更しやすくしたいので、ホストマシンで見られるように共有ファイルの設定もしました。
qiita.com

Rails

Rails6でやります。
インストールは以前やったので省略。
learning-mind.hatenablog.com

※バージョンが更新されたせいか、node.jsとかyarnのインストールでエラーになることがありましたのでご注意。
qiita.com

Rails APIモードでアプリを作成する

qiita.com

rails newのオプションに--apiを加えます。

rails new sample --api

Vue.js

Vue.js 2.6.12でやります。

Vue CLIのインストール

nodeバージョン確認

$ node --version
v15.3.0

node.jsがインストールされていない場合は下記ページを参照してインストールします。
prog-8.com

インストール

npm install -g @vue/cli

Macで実行したところ失敗したので下記記事の手順で解決しました。
qiita.com

バージョン確認

$ vue -V
@vue/cli 4.5.9

$ npm list -g vue
/.nvm/versions/node/v15.3.0/lib
└─┬ @vue/cli@4.5.9
  ├─┬ vue-codemod@0.0.4
  │ ├─┬ @vue/compiler-sfc@3.0.4
  │ │ └── vue@3.0.4 deduped
  │ └── vue@3.0.4
  └── vue@2.6.12

プロジェクトを作成

vue create projectname

利用するプリセット(Vue.jsのバージョン)とパッケージマネージャを訊かれます。

  • プリセットはdefault vue2を選択
  • パッケージマネージャはyarnを選択

Macで実行したときのエラーは下記記事の手順で解決しました。
blog.kimizuka.org

アプリを起動

cd projectname
npm run serve

コンソールに表示されるURLをブラウザで表示

プロジェクトをビルド

npm run build

プロジェクトルートディレクトリの配下にdistというディレクトリが作成されます。
このディレクトリにあるファイルをHTTPサーバーに置くとかなんとか。(今回は使いません)

Vue Routerをインストール

vue add router

コーディング

Rails

note.com

JSONを返すAPIなら

render json: {[JSONオブジェクト]}

これをやればいいだけです。

モデルの作成

下記記事を参照しました。
qiita.com

rails g model post title:string
rails g controller posts
rails db:create
rails db:migrate

コントローラにJSONを返す処理を追加

app/controllers/posts_controller.rb

class PostsController < ApplicationController
  def index
    posts = Post.all
    render json: posts
  end
end

ルートを追加

config/routes.rb

Rails.application.routes.draw do
  resources :posts
end

テスト用のレコードを用意

rails cを実行して

irb(main):003:0> Post.create(title: "テスト用のタイトル")

APIの動作確認

XはサーバーのIPアドレス3000Railsのデフォルトのポート番号。

$ curl http://XXX.XXX.XXX.XXX:3000/posts
[{"id":1,"title":"テスト用のタイトル","created_at":"2020-12-13T22:20:41.884Z","updated_at":"2020-12-13T22:20:41.884Z"}]

クロスドメイン制約対策

APIサーバー側で設定が必要です! qiita.com
config/application.rb

config.action_dispatch.default_headers = {
  'Access-Control-Allow-Credentials' => 'true',
  'Access-Control-Allow-Origin' => 'http://[フロントエンドアプリケーションのIPアドレスとポート番号]',
  'Access-Control-Request-Method' => '*'
}

(これに気付かず、フロント側でどうにかしようとしててめちゃくちゃ時間を使いました…)

Rails側はこれでOKです。

Vue.js

ルーティング

生成されたVue Routerのファイル
src/router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
  // ★遷移先が増える場合はここに追加していく
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

ルーター経由でページを遷移させる場合は、<router-link>要素を使う。
src/App.vue

<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </div>
    <router-view/>
  </div>
</template>

遷移先のパスはv-bind:to=属性でも指定できる

<router-link v-bind:to="'/about'">About</router-link>

nameプロパティでも指定でき、これを名前付きルートという。

<router-link v-bind:to="'{ name: 'about' }">About</router-link>

ルーター経由で呼び出されたコンポーネントは、<router-view/>で確保された領域に反映される。

プログラムでルーター経由のページ移動

$router.push([パス名])

ルーター経由では以下のコンポーネントにパラメータを渡す

  1. ルートを追加する
  2. ルートパラメータを受け取る
  3. リンク文字列を生成する

  4. ルートを追加する 前述の★の部分に新規のルートの情報を追加します。
    src/router/index.js

import Article from '../views/Article.vue'
..
{
  path: '/article/:aid',
  name: 'Article',
  component: Article
}

:aidプレースホルダです。

  1. ルートパラメータを受け取る
$route.params.aid

aidプレースホルダです。
今回は受け取り先として新規ビューファイルを作成します。
src/views/Article.vue

<template>
  <span>
    記事コード:{{ $route.params.aid }}
  </span>
</template>
  1. リンク文字列を生成する src/App.vue
      <router-link to="/article/108">記事:No.108</router-link>

axiosのインストール

jp.vuejs.org

npm install axios

CDN版を使おうとしたのですが、単一ファイルコンポーネントで使用する方法がわかりませんでした……。

axiosでAPIリクエス

Aboutページに表示させてみます。
src/views/About.vue

<template>
  <div class="about">
    <h1>This is an about page</h1>
    {{ info }}
  </div>
</template>

<script>
import axios from 'axios'

export default {
  data () {
    return {
      info: null,
    }
  },
  created () {
    axios
      .get('http://[APIサーバーのIPアドレス:3000]/posts')
      .then(response => (this.info = response))
  }
}
</script>

Vue.jsもこれでOK。

動作確認

Rails

rails s -b 0.0.0.0

Vue.js

npm run serve

Vue.jsアプリのIPアドレスとポート番号をブラウザに打ち込んで、「About」のリンクをクリックします。
ページ下部に先程確認したJSONのデータが表示されていれば成功です。
この後、JSONを解釈して表示すべき状態に開発を進めることができます。

参考書籍

www.amazon.co.jp

この記事はkb Advent Calendar 2020 6日目の記事です。

adventar.org