rails環境新規構築(jsbundling + cssbundling + tailwind)

# はじめに
前回書いた[記事](https://qiita.com/youfuku/items/782b0a7f974e2a53d89e#%E3%81%95%E3%81%84%E3%81%94%E3%81%AB)の構成違い版です。
そのため、そんなに楽しい記事ではありませんが、備忘録として記します。

# 前回の記事との変更点
- css：bootstrap　→　tailwind
- importmap　→　jsbundling + cssbundling
- docker関係のディレクトリ構成


# 準備

### 初期ファイルの生成
```shell:コマンド
touch compose.yml
touch Dockerfile.dev
touch Gemfile
touch Gemfile.lock
touch template.rb
```

```yaml:compose.yml
services:
  app:
    container_name: app
    build:
      context: .
      dockerfile: Dockerfile.dev
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    ports:
      - 3000:3000
    depends_on:
      - mysql
    volumes:
      - .:/workspaces
      - bundle_data:/usr/local/bundle
    environment:
      RAILS_ENV: development
      DB_HOST: mysql
      DB_NAME: root
      DB_PASSWORD: password
    tty: true

  mysql:
    container_name: mysql
    image: public.ecr.aws/docker/library/mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: password
      TZ: "Asia/Tokyo"
    volumes:
      - mysql_data:/var/lib/mysql

volumes:
  mysql_data:
  bundle_data:

```

```dockerfile:Dockerfile.dev
FROM public.ecr.aws/docker/library/node:21.7-slim AS node
FROM public.ecr.aws/docker/library/ruby:3.3-slim

RUN apt-get update -y -qq \
    && apt-get install -y build-essential libpq-dev default-libmysqlclient-dev git \
    && rm -rf /usr/local/bundle/cache/* /usr/local/share/.cache/* /var/cache/* /tmp/*

# install node
COPY --from=node /opt/yarn-* /opt/yarn
COPY --from=node /usr/local/bin/node /usr/local/bin/
COPY --from=node /usr/local/lib/node_modules/ /usr/local/lib/node_modules/
RUN ln -fs /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
  && ln -fs /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npx \
  && ln -fs /opt/yarn/bin/yarn /usr/local/bin/yarn \
  && ln -fs /opt/yarn/bin/yarnpkg /usr/local/bin/yarnpkg

ENV TZ Asia/Tokyo

WORKDIR /workspaces

# gem
COPY Gemfile* /workspaces/
RUN gem update --system && \
    bundle update --bundler && \
    bundle install

COPY . /workspaces

```


```ruby:Gemfile
source 'https://rubygems.org'
gem 'rails'
```


```ruby:template.rb
def add_rspec_gem
  gem 'rspec'
  gem 'rspec-rails'
  gem 'factory_bot_rails'
  gem 'capybara'
  gem 'faker'
  gem 'spring-commands-rspec'
end

def rspec_setup
  run 'rm -rf test'
  run 'bundle install'
  run 'rails g rspec:install'
  file '.rspec', <<~CODE
    --require spec_helper
    --format documentation
    --color
  CODE
end

def add_rubocop_gem
  gem 'rubocop', require: false
  gem 'rubocop-performance', require: false
  gem 'rubocop-rails', require: false
end

def rubocop_setup
  run 'bundle install'
  run 'bundle exec rubocop --auto-gen-config > /dev/null'

  file '.rubocop.yml', <<~CODE
    inherit_from: .rubocop_todo.yml

    require:
      - rubocop-performance
      - rubocop-rails

    AllCops:
      SuggestExtensions: false
      # 最新のルールを適用する
      NewCops: enable
      # 何のルールに引っかかったか表示する
      DisplayCopNames: true
      # rubocop対象外(リポジトリ毎で調節)
      Exclude:
        - "Gemfile"
        - "bin/**/*"
        - "db/**/*"
        - "log/**/*"
        - "tmp/**/*"
        - "vendor/**/*"
        - "lib/tasks/auto_annotate_models.rake"
        - "config/environments/*"
        - "config/puma.rb"

    # 日本語にコメントを許可
    Style/AsciiComments:
      Enabled: false

  CODE
end

def add_dev_gem
  gem 'annotate'
  gem 'solargraph'
end

def dev_setup
  run 'bundle install'
  run 'rails g annotate:install'
end

def add_gems
  gem_group :development, :test do
    add_rspec_gem
    add_dev_gem
  end
  gem_group :development do
    add_rubocop_gem
  end
end

###############################
# Main setup
###############################
add_gems

after_bundle do
  rspec_setup
  rubocop_setup
  dev_setup
end
```


# 初期構築（rails new）

rails new
```コマンド
docker compose build
```
```コマンド
docker compose run --rm app rails new .  \
--database=mysql  \
--skip-action-mailer \
--skip-action-mailbox  \
--skip-test   \
--javascript=esbuild  \
--css=tailwind  \
--force   \
-m template.rb
```

### DBの接続先を登録
```app/config/database.yml

default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV.fetch("DB_NAME") %>       # ←この行を修正
  password: <%= ENV.fetch("DB_PASSWORD") %>   # ←この行を修正
  host: <%= ENV.fetch("DB_HOST") %>           # ←この行を修正
```

DB生成(一部過剰ですが)
```
docker compose run --rm app bundle exec rails db:drop db:create db:migrate db:seed
```


## 起動時にtailwindのビルドが走るように修正

```diff:Procfile.dev
- web: env RUBY_DEBUG_OPEN=true bin/rails server
+ web: env RUBY_DEBUG_OPEN=true bin/rails server -b 0.0.0.0
js: yarn build --watch
css: yarn build:css --watch
```

composeを少し修正
```diff:compose.yml
services:
  app:
    container_name: app
    build:
      context: .
      dockerfile: Dockerfile.dev
-    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
+    command: bash -c "rm -f tmp/pids/server.pid && bin/dev"
    ports:
      - 3000:3000
    depends_on:
```

デフォルトページを表示
```
docker compose up
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632357/23620c9d-37d0-2380-97b6-5b60902ac000.png)




## トップページを生成
```コマンド
docker compose run --rm app rails g controller top index
```

```app/config/routes.rb

Rails.application.routes.draw do
  get 'top/index'
  root 'top#index'   # ←この行を修正
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
  # Can be used by load balancers and uptime monitors to verify that the app is live.
  get "up" => "rails/health#show", as: :rails_health_check

  # Defines the root path route ("/")
  # root "posts#index"
end
```

```html:app/app/views/top/index.html.erb
<button type="button" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">Default</button>
<button type="button" class="py-2.5 px-5 me-2 mb-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700">Alternative</button>
<button type="button" class="text-white bg-gray-800 hover:bg-gray-900 focus:outline-none focus:ring-4 focus:ring-gray-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-gray-800 dark:hover:bg-gray-700 dark:focus:ring-gray-700 dark:border-gray-700">Dark</button>
<button type="button" class="text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700">Light</button>
<button type="button" class="focus:outline-none text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800">Green</button>
<button type="button" class="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900">Red</button>
<button type="button" class="focus:outline-none text-white bg-yellow-400 hover:bg-yellow-500 focus:ring-4 focus:ring-yellow-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:focus:ring-yellow-900">Yellow</button>
<button type="button" class="focus:outline-none text-white bg-purple-700 hover:bg-purple-800 focus:ring-4 focus:ring-purple-300 font-medium rounded-lg text-sm px-5 py-2.5 mb-2 dark:bg-purple-600 dark:hover:bg-purple-700 dark:focus:ring-purple-900">Purple</button>
```

コンテナを立て直すと、以下のようになってるはず！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632357/8258a87d-71da-5028-41c3-352a142f951f.png)

# さいごに
殆ど前回のおさらいのような記事でした。
ただ、rails newのオプションの組み合わせで色々やった際に嵌ったので、個人的には前回の記事か今回の記事の二択になるのでは？とは思っています。