motivation
yoshixjと申します。
最近、Golangを書き始めてしまいました。
Railsを浮気しそうです。
2020年12月個人的Railsで新規のAPIを開発をするとしたら、こんな感じでやっていくという流れをざっくり書いていきます。
筆者の経験はざっくり、Railsの経験は3-4年くらい、スタートアップでの新規開発を経験多め、運用経験すくなめです。
内容はRailsをとりあえずかけるようになった人がこの辺やっておくとよいとbetterかもてきな感じです。
この記事は、この数年間経験が浅い私が、ひたすらググり、実装してよかったものをまとめたような記事です。
長く冗長的な部分もあるかなと思いますが、おてやわらかに。
agenda
- git repository 作成
- Gemfileを設置
- dockerの設定
- rails new
- setting rubocop
- install rspec
- install factry bot
- timezoneの設定
- CORSの設定
- エラーレスポンスの統一
- ガイドラインの統一
- やったことないけど、今後新規やるとしたらやるかなぁと思うこと
git repository 作成
今回は、githubですでにレポジトリがある状態からスタートします。
railsを作成する際、rails new で自動的にgitが設定されますが、rails newをする前にgitを設定が設定されているという状態です。
$ git pull repository_name
$ cd repository_name
$ ls -al
drwxr-xr-x 14 yoshikimasubuchi staff 448 5 2 11:08 .git
dockerの設定
Dockerの設定していきます。
rails new から docker-compose でやっていきます。 この辺を参考にしています。
rails newの先輩オススメ手順
どういうディレクトリ構成にするかはチームに合わせます。
僕が経験したなかだと、
rails単体でdocker-composeを切る場合はこんな感じ。
--repository_name
├── Gemfile
├── docker-compose.yml
├── app
├── docker
├── api
├── Dockerfile
Dockefileの中身はこんなかんじ
FROM ruby:2.7.1
ENV LANG C.UTF-8
ENV TZ Asia/Tokyo
RUN apt-get update -qq && apt-get install -y \
build-essential \
nodejs \
&& rm -rf /var/lib/apt/lists/*
ENV ENTRYKIT_VERSION 0.4.0
RUN wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
&& tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
&& rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
&& mv entrykit /bin/entrykit \
&& chmod +x /bin/entrykit \
&& entrykit --symlink
WORKDIR /usr/src/app
COPY . /usr/src/app
RUN gem install bundler
ENTRYPOINT [ \
"prehook", "ruby -v", "--", \
"prehook", "bundle install -j3 --quiet", "--"]
version: '3'
services:
db:
image: postgres:11.3
ports:
- "5432:5432"
volumes:
- db:/var/lib/postgresql/data:z
api:
build:
context: .
dockerfile: ./docker/api
volumes:
- .:/usr/src/app
command: /bin/sh -c "rm -f /usr/src/app/tmp/pids/server.pid && bundle exec rails s -p 8080 -b '0.0.0.0'"
ports:
- "8080:8080"
depends_on:
- db
env_file:
- rails.env
environment:
- DB_USER=postgres
- DB_PASS=
- DB_HOST=db
- DB_PORT=5432
# for ruby 2.6 alart https://k-koh.hatenablog.com/entry/2020/02/07/145957
# - RUBYOPT='-W:no-deprecated -W:no-experimental'
tty: true
stdin_open: true
volumes:
db:
Gemfileを設置 railsをinstall
RailsをinstallするためのGemfileを設定します。
$ docker-compose build
$ docker-compose run api bundle init
をすると、Gemfileが設置されます。
設置されたGemfileに gem 'rails'
を追記します。
# frozen_string_literal: true
source 'https://rubygems.org'
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
gem 'rails'
追記したら、bundle します。
$ docker-compose run api bundle install --path vendor/bundle
railsがinstallされました!
docker環境だとあまり関係ないかもしれませんが、bundleの設定も付け加えておきます。
---
BUNDLE_PATH: vendor/bundle
BUNDLE_JOBS: 4
BUNDLE_DISABLE_SHARED_GEMS: '1'
rails new
ようやくrails newをします。
今回はAPIモードでrails new
を行います。
今回はテストフレームワークにrspecを使用するので、 --skip-test
します。
以下以外でも active-strage
などもskipできるますが、もしかしたら使うかもしれないのでskipしません。
$ docker-compose run api bundle exec rails new . --api -d=postgresql --skip-git --skip-test --api --skip-bundle
rails new
でアプリができたら、Gemfileを記述していきます。
developmentにはとりあえず最初に入れておこうってやつをいれています。
# frozen_string_literal: true
source 'https://rubygems.org'
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
ruby '2.7.1'
gem 'rails'
gem 'puma', '~> 3.11'
gem 'bootsnap', '>= 1.4.2', require: false
gem 'rack-cors'
gem 'pg'
group :development, :test do
gem 'brakeman' # for security
gem 'dotenv-rails'
gem 'guard-rspec', require: false
gem 'rspec-benchmark'
gem 'rspec-rails'
gem 'rspec_junit_formatter'
gem 'rubocop'
gem 'factory_bot_rails'
gem 'faker'
gem 'faker-japanese'
gem 'pry-rails'
gem 'pry-byebug'
end
group :development do
gem 'listen', '>= 3.0.5', '< 3.2'
gem 'hirb'
end
$ docker-compose run api bundle
DBの設定も書いていきます。
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: <%= ENV['DB_USER'] %>
password: <%= ENV['DB_PASS'] %>
host: <%= ENV.fetch('DB_HOST') { 'localhost' } %>
port: <%= ENV.fetch('DB_PORT') { 5432 } %>
development:
<<: *default
database: earlybird-api_development
test:
<<: *default
database: earlybird-api_test
production:
<<: *default
database: earlybird-api_production
username: earlybird-api
password: <%= ENV['earlybird-api_DATABASE_PASSWORD'] %>
defaultの部分の設定は docker-compose.yml
の記述にあわせます。
$ docker-compose run api bundle exec rails db:create
これで、railsでAPIを作れる状態になりました!
rubocopの設定
チーム開発でrubocopをちゃんと設定することは重要です。
会社でrubyのプロジェクトがなかく、rubocopの設定に決まりがない場合は、こんな感じで設定しておきます。
require: rubocop-rails
Rails:
Enabled: true
AllCops:
TargetRubyVersion: 2.7.3
TargetRailsVersion: 6.0.2
Exclude:
- bin/*
- db/**/*
- vendor/**/*
- test/**/*
- config/**/*
- lib/**/*
- Gemfile
- db/schema.rb
rspecのinstall
rspecをinstallします。
最初にinstallをし忘れると、modelのspecやら、controllerのspecやら、自動でつくられないのであとあと面倒になります。
$ docker-compose run api bundle exec rails generate rspec:install
factry botのinstall
factory botもinstallし忘れると、modelを作成と同時にfileが作られないので、あとあと面倒になります。
RSpec.configure do |config|
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.include FactoryBot::Syntax::Methods # add
timezoneの設定
- ruby プロセスのタイムゾーン
- config.time_zone
- config.active_record.default_timezone
- DBサーバ(postgres等)のタイムゾーン
の設定をします。RailsはTimezoneがややっこしいので最初にそれぞれの設定をしてしまいます。
参考: Railsと周辺のTimeZone設定を整理する (active_record.default_timezoneの罠)
今回は日本でのみつかうプロダクトを想定しています。
海外展開もある場合は、一旦全部 UTCで揃えるようにするとよいと思います。
ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
config.time_zone = 'Tokyo'
config.active_record.default_timezone = :local
set time zone 'Asia/Tokyo';
-- postgresql.conf に timezone='Japan' も記載
コーディングで時間を扱う際には、TimeWithZone
を使用するようにします。
CORSの設定
CROSの設定をします。
これをしないと、cross origin error でapiをフロントアプリケーションから たたけ無いと思います。
require_relative 'boot'
require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "active_storage/engine"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "action_cable/engine"
# require "sprockets/railtie"
# require "rails/test_unit/railtie"
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module App
class Application < Rails::Application
config.time_zone = 'Tokyo'
config.active_record.default_timezone = :local
config.active_record.time_zone_aware_types = [:datetime, :time]
config.i18n.default_locale = :ja
config.api_only = true
# 以下を追加
config.middleware.use Rack::Cors do
allow do
origins ENV['CORS_ALLOWED_ORIGINS']
resource "*",
headers: :any,
methods: [:get, :post, :put, :patch, :delete],
expose: []
end
end
end
end
このあたりで、動くRails アプリの設定ができたと思います。
以下からは、運用を考えて設定を追記していきます。
エラーレスポンスの統一
エラーのレスポンスを統一するための設定をします。
私は以前まで、ApplicationControllerでレスポンスはresuceしていましたが、どうやらRailsのexception_app
というのを使うのが良さそうです。
これは、実際に運用したことは無いのですが、たぶん以下のように設定すると思います。
参考: Railsアプリの例外ハンドリングとエラーページの表示についてまとめてみた
# app/controllers/errors_controller.rb
class ErrorsController < ActionController::Base
rescue_from StandardError, with: :render_500
rescue_from ActiveRecord::RecordNotFound, with: :render_404
rescue_from ActiveRecord::RecordInvalid, with: :render_400
def show
raise env['action_dispatch.exception']
end
def render_400(err)
render_error(err, 400)
end
def render_404(err)
render_error(err, 404)
end
def render_500(err)
render_error(err, 404)
end
def render_error(err, status)
render json: ErrorSerializer.new(errr), status: status
end
end
end
# config/initializers/exceptions_app.rb
Rails.application.configure do
config.exceptions_app = ErrorsController.action(:show)
end
ガイドラインの統一
APIを開発するにあたって、ロジックをどのように、どこに書いていくかなどのガイドラインを最初に決めます。
基本的な思想は、リソース思考です。リソース=Model にロジックは詰めていきます。
思い当たるのをいかにリストアップし、参考のURLをつらつらとのせていきます。
Validationは原則いれる。きつく→ゆるくはできるが、ゆるく→きつくはできない
制約はきつくしておいたほうがよいです。
同じ理由で、Migrationの外部キー制約は基本いれる。
制約はきつくします。
# frozen_string_literal: true
class CreateShopPhotos < ActiveRecord::Migration[5.2]
def change
create_table :shop_photos do |t|
t.references :shop, index: true, foreign_key: true, null: false
t.string :image_url, null: false
t.timestamps
end
end
end
こまったら、FatModelにとりあえずしておく
Modelにロジックを書いておけば、unit testが書きやすいので、あとから手を入れるときも安全に手をいれられます。
whereなどを使う検索ロジックは、scopeで定義する。インスタンスメソッド, クラスメソッドでは定義しない。
scopeで定義すれば、ClassからもInstanceからもつかえますが、 インスタンスメソッド、クラスメソッドで検索ロジックを定義していまうとどちらかでしかつかえません。
controller には基本 defaultの index, show, create, update, delete しか書かない
他の人が見た時に、謎のアクションがないようにできるだけ注意します。
DHH流のルーティングで得られるメリットと、取り入れる上でのポイント - KitchHike Tech Blog
DHHはどのようにRailsのコントローラを書くのか /| POSTD
APIのレスポンスには原則Serialzierを使う
マイクロサービス指向 Rails API 開発ガイド/building rails api on microservices - Speaker Deck
Rails アプリに RESTful API のレールを敷いて生産性が大きく上がった話 | Wantedly Engineer Blog
Rails に RESTful API のレールを敷く - Qiita
Sidekiqの引数に注意する
引数は増やしたくなったように、最後にhash型のpayloadをとりあえずいれておきます。
やったことないけど、今後新規やるとしたらやるかなぁと思うこと
- committee×OpenAPI をいれて、request specでレスポンスのテストもしたいです
その他
Ruby は書き方が柔軟なので、書き方にまよったら、とりあえずcookpad さんのスタイルガイドを参考にします。
cookpadさんのスタイルガイド
終わりに
一人で開発することが多かったので、webでひたすらdigって自分の経験を伸ばしてきました。
今回の記事はそのなかで影響を受けている記事の総まとめのような記事です。
もし、ここがおかしいなどあればコメントくださると幸いです。
2020年もおつかRails!
参考記事一覧
rubocop-rails
Railsと周辺のTimeZone設定を整理する (active_record.default_timezoneの罠)
Railsタイムゾーンまとめ
Railsアプリの例外ハンドリングとエラーページの表示についてまとめてみた
DHH流のルーティングで得られるメリットと、取り入れる上でのポイント - KitchHike Tech Blog
DHHはどのようにRailsのコントローラを書くのか /| POSTD
マイクロサービス指向 Rails API 開発ガイド/building rails api on microservices - Speaker Deck
Rails アプリに RESTful API のレールを敷いて生産性が大きく上がった話 | Wantedly Engineer Blog
Rails に RESTful API のレールを敷く - Qiita
cookpadさんのスタイルガイド
Sidekiq アンチパターン: 序
運用に耐えるRailsによるWebアプリケーションの作り方
Fat Modelの倒し方
serodriguez68/rails-antipatterns
rspec style guide