RailsEngineを作る手順メモ
RailsEngineを作る手順を雑にメモする。
Docker/Rspecを使う前提。
docker設定
Dockerfile
FROM ruby:2.6.6 ENV NODE_VERSION 10.12.0 ENV BUNDLER_VERSION 1.17.3 ENV LANG C.UTF-8 # https://stackoverflow.com/questions/55361762/apt-get-update-fails-with-404-in-a-previously-working-build RUN sed -i '/jessie-updates/d' /etc/apt/sources.list RUN apt-get update -qq && apt-get install -y build-essential git mariadb-client # Node.js RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - RUN apt-get -y -qq install nodejs RUN npm install -g phantomjs-prebuilt --unsafe-perm RUN gem install bundler -v $BUNDLER_VERSION RUN mkdir /app WORKDIR /app
docker-compose.yml
version: '3' services: mysql: image: mysql:5.7 # volumes: # - "./mysql-data:/var/lib/mysql" environment: MYSQL_ROOT_PASSWORD: root app: build: . volumes: - "./:/app" ports: - "3000:3000" tty: true working_dir: "/app"
docker起動/ログイン
# docker起動 docker-compose up # docker内に入る docker exec -it my_engine_app_1 /bin/bash
(DOCKER内)プロジェクト作成
# railsインストール gem install rails # プロジェクトフォルダ作成 # ハイフン区切りだと階層化されるので注意 bundle exec rails plugin new my_engine --mountable -T --dummy-path=spec/dummy_app cd my_engine
(ホスト側)gemspecを修正する
my_engine/my_engine.gemspec
# 略 # TODO欄を埋める s.homepage = "https://example.com/" s.summary = "概要を書く" s.description = "説明を書く" # 略 # 必要なgemを追加する s.add_development_dependency "mysql2" s.add_development_dependency "rspec-rails" s.add_development_dependency "database_cleaner" s.add_development_dependency "pry-rails" # 略
(ホスト側)engineを修正する
lib/my_engine/engine.rb
module MyEngine class Engine < ::Rails::Engine isolate_namespace MyEngine # rspec使えるように追加 config.generators do |g| g.test_framework :rspec end end end
(ホスト側)rspecの設定ファイルを修正する
spec/rails_helper.rb
# 略 # 向き先をdummy_appに変更する # テストはEngineをバンドルしたdummy_app内で行われる require File.expand_path('../dummy/config/environment', __FILE__) # 略
spec/dummy_app/config/database.yml
# dummyアプリのdbの向き先をmysqlに変更する default: &default adapter: mysql2 pool: 5 timeout: 5000 host: mysql port: 3306 username: root password: root development: <<: *default database: app_development test: <<: *default database: app_test production: <<: *default database: app_production
(DOCKER内)でコードジェネレートする
# モデル生成 bundle install bundle exec rails g model xxx_logs # ここでマイグレーションファイル修正する # DB反映 bundle exec rake db:create bundle exec rake db:migrate
(DOCKER内)テスト実行
# テストを実装しておく # テスト実行 bundle exec rspec spec/
メインアプリに組み込む
Gemfileに参照先を設定して `bundle install` する
migrationファイルのコピー
bundle exec rake my_engine:install:migrations bundle exec rake db:migrate
(補足)gem登録する
gem signin # ~/.gem/credentialsができる rake build # pkgができる rake release # gemが登録される
Rspecでcontrollerのテスト
controllerのテストをまともに書いたことなかったので。
describe TestController, type: :controller do let(:account) { create(:account) } describe 'auth' do context '有効なトークンを渡したとき' do before do token = TokenService.build_token(account) # sessionに値をセットできる session[:use_auth] = true # 指定のメソッドをparamsつきで呼ぶ get :auth, token: token end it '200を返す/ログインユーザが取得できる' do # ステータスコードをチェックできる expect(response).to have_http_status(200) # privateメソッドの値はcontroller.sendで取得できる current_user = controller.send :current_user expect(current_user.id).to eq account.id end end context '無効なトークンを渡したとき' do before do get :auth, token: 'xxxxxxx' end it '401を返す' do expect(response).to have_http_status(401) end end end end
RSpecで例外チェック
これまでbegin-rescueで愚直にやってたので、もう少しシュッとした書き方を。
require 'spec_helper' describe TokenService do describe :validate! do context '改ざんしたトークンを渡す' do # 処理を定義 subject { token = TokenService.build_token TokenService.validate!(token + 'a') } it '例外発生' do # 処理結果としてエラーがraiseされたかをチェック expect { subject }.to raise_error(TokenService::InvalidTokenError) end end end end
RSpecのmockの使い方
APIクライアントを外から注入できるようにして、
モックを渡してローカル環境単体でテストできようにした例。
require 'spec_helper' class DummyService # # APIクライアントを外から指定できるようにして # 単体でテストできるようにする # def self.get_info(info_id, options = {}) api_client = options[:api_client] # || DummyApiClient res = api_client.get_info(info_id) res.body end end describe DummyService do describe :get_info do let(:api_client) { # モックの作成 _api_client = double("MockApiClient") # モックにメソッドを生やす allow(_api_client).to receive(:get_info).and_return( Struct.new(:body).new("TEST_DATA") ) _api_client } it 'モックで指定した値を返す' do info = DummyService.get_info('test', api_client: api_client) expect(info).to eq 'TEST_DATA' end end end
RSpecのletの使い方 - before&インスタンス変数使うやり方との比較
ずっとbefore&インスタンス変数でやってたので、let使うやり方をメモしておく。
require 'spec_helper' describe 'beforeとletの違いについて' do context 'インスタンス変数を使う場合' do before do @account = create(:account) end it 'アカウントが存在する' do db_account = Account.find_by(account_id: @account.id) expect(db_account.present?).to eq true end end context 'letを使う場合' do # beforeと同じタイミングで、accountメソッドが作られる # 呼ばれたときに初期化されて、以降は同じインスタンスを返す let(:account) { create(:account) } context '一度も参照しない場合' do it 'アカウントは存在しない' do expect(Account.count).to eq 0 end end context '一度でも参照した場合' do it 'アカウントは存在する' do db_account = Account.find_by(account_id: account.id) expect(db_account.present?).to eq true expect(Account.count).to eq 1 end end end context 'let!を使う場合' do # 定義と同時に初期化される let!(:account) { create(:account) } context '一度も参照しない場合' do it 'アカウントは存在する' do expect(Account.count).to eq 1 end end end end
※参考にさせていただきました
RSpecのletを使うのはどんなときか?(翻訳) - Qiita
ポートフォワードメモ
ローカルフォワードするとき
書き方をよく忘れているのでメモ。
ssh xx-user@app-stg-batch -L 8080:stg.internal-api.local:80 -N
.ssh/configの設定メモ
よくやる書き方をコメント付きでメモ。
# # ssh接続をタイムアウトしないための設定 # - 15秒ごとに応答確認 # - 10回応答がなかったら切断する # ServerAliveInterval 15 ServerAliveCountMax 10 # # Hostにはワイルドカードを指定できる # 設定を共有化したいときに便利 # ForwardAgentでssh-agent有効化 # StrictHostKeyCheckingでfingerPrintのチェックをスキップできる # Host app-prod-* User ec2-user IdentityFile ~/.ssh/app-prod.pem ForwardAgent yes StrictHostKeyChecking no # # fumidaiへの接続設定 # Host app-prod-fumidai Hostname xx.xx.xx.xx # # fumidai経由による # アプリケーションサーバへの接続設定 # -W で Hostnameで指定したホストに入出力を送ることができる # %h:%pは ホスト名とポート番号に置換されて実行される # Host app-prod-batch Hostname xx.xx.xx.xx ProxyCommand ssh app-prod-fumidai -W %h:%p # # ローカルフォワードして接続する # ローカルから内部API叩いたりしたいときなど # Host app-prod-forward Hostname xx.xx.xx.xx ProxyCommand ssh app-prod-fumidai -W %h:%p LocalForward 8080 prod.app.local:80