motivation My name is yoshixj. Recently I started writing Golang. I'm going to flirt with Rails. December 2020 If you develop a new API with personal Rails, I will roughly write the flow of doing it like this. The author's experience is rough, Rails experience is about 3-4 years, he has a lot of experience in new development at startup, and he has little operational experience.
The content is better if people who started to use Rails for the time being should do it around here. This article is like a summary of what I'm glad to have implemented by googled, as I haven't had much experience in the last few years. I think there are some long and redundant parts, but it's soft.
agenda --Create git repository --Install Gemfile --docker settings
This time, we will start with a repository already on github. When creating rails, git is automatically set by rails new, but git is set before rails new.
$ git pull repository_name
$ cd repository_name
$ ls -al
drwxr-xr-x 14 yoshikimasubuchi staff 448 5 2 11:08 .git
I will set up Docker. I will do it with docker-compose from rails new. I refer to this area. Rails new senior recommended procedure
The directory structure is up to the team.
In my experience, When you turn off docker-compose with rails alone, it looks like this.
--repository_name
├── Gemfile
├── docker-compose.yml
├── app
├── docker
├── api
├── Dockerfile
The contents of the Dockefile look like this
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:
Set up a Gemfile to install Rails.
$ docker-compose build
$ docker-compose run api bundle init
Then, Gemfile will be installed.
Add gem'rails'
to the installed Gemfile.
frozen_string_literal: true
source 'https://rubygems.org'
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
gem 'rails'
After adding, bundle.
$ docker-compose run api bundle install --path vendor/bundle
rails has been installed!
It may not be relevant in the docker environment, but I will add the bundle settings as well.
---
BUNDLE_PATH: vendor/bundle
BUNDLE_JOBS: 4
BUNDLE_DISABLE_SHARED_GEMS: '1'
rails new
Finally rails new.
This time we will do rails new
in API mode.
This time we're using rspec for our testing framework, so we'll do --skip-test
.
You can skip active-strage
etc. other than the following, but I will not skip it because I may use it.
$ docker-compose run api bundle exec rails new . --api -d=postgresql --skip-git --skip-test --api --skip-bundle
Once you have an app with rails new
, write a Gemfile.
For the time being, I put it in development first.
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
I will also write the DB settings.
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'] %>
The setting of the default part matches the description of docker-compose.yml
.
$ docker-compose run api bundle exec rails db:create
Now you can create an API with rails!
It is important to properly configure rubocop in team development. If there is no ruby project in your company and there is no rule on ruby cop settings, set it like this.
.rubocop.yml
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
Install rspec. If you forget to install it first, the model specs and controller specs will not be created automatically, which will be troublesome.
$ docker-compose run api bundle exec rails generate rspec:install
If you forget to install the factory bot as well, the file will not be created at the same time as the model is created, so it will be troublesome.
RSpec.configure do |config|
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.include FactoryBot::Syntax::Methods # add
--ruby process timezone
Set. Rails has a complicated Timezone, so I set each setting first. Reference: Organize Rails and surrounding TimeZone settings (active_record.default_timezone trap)
This time, we are assuming a product that will be used only in Japan. If you have overseas expansion, I think it's a good idea to arrange all in UTC.
ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
config/application.rb
config.time_zone = 'Tokyo'
config.active_record.default_timezone = :local
set time zone 'Asia/Tokyo';
-- postgresql.timezone in conf='Japan'Also listed
Try to use TimeWithZone
when dealing with time in your coding.
Set CROS. If you don't do this, you won't be able to hit the api from the front application with a cross origin error.
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
#Add the following
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
At this point, I think you've set up a working Rails app. From the following, we will add the settings in consideration of operation.
Make settings to unify the error response.
I used to rescue the response in ApplicationController, but apparently it's better to use Rails'exception_app
.
I haven't actually used this, but I think it's probably set as follows.
Reference: I summarized the exception handling and error page display of Rails application
# 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
When developing an API, first decide on guidelines such as how and where to write the logic. The basic idea is resource thinking. Logic is packed in resource = Model. I will list how I can think of it and put a reference URL on it.
The constraints should be tight.
The constraints are tight.
# 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
If you write the logic in Model, it is easy to write unit test, so you can safely touch it later.
If you define it with scope, you can also change from Instance from Class, but if you define the search logic with instance method or class method, you can only use either one.
Be as careful as possible to avoid mysterious actions when others see them.
Advantages of DHH-style routing and points to incorporate --KitchHike Tech Blog How DHH writes Rails controllers/| POSTD
Microservices Oriented Rails API Development Guide/building rails api on microservices --Speaker Deck The story of laying RESTful API rails on Rails apps and greatly increasing productivity \ | Wantedly Engineer Blog Laying RESTful API rails on Rails-Qiita
As I wanted to increase the argument, I will add a hash type payload at the end for the time being.
Sidekiq Anti-Patterns: Introduction
-I want to put in committee × OpenAPI and test the response with request spec.
Ruby is flexible to write, so if you decide how to write it, I will refer to cookpad's style guide for the time being. Cookpad's style guide
Since I often developed by myself, I just digged on the web to improve my experience. This article is like a summary of the articles that have been influenced by it. If something is wrong with this, please comment. Rails in 2020!
rubocop-rails Organize Rails and surrounding TimeZone settings (active_record.default_timezone trap) (https://qiita.com/joker1007/items/2c277cca5bd50e4cce5e) Rails Time Zone Summary I summarized the exception handling and error page display of Rails application Advantages of DHH-style routing and points to incorporate --KitchHike Tech Blog How DHH writes Rails controllers/| POSTD Microservices Oriented Rails API Development Guide/building rails api on microservices --Speaker Deck The story of laying RESTful API rails on Rails apps and greatly increasing productivity \ | Wantedly Engineer Blog Laying RESTful API rails on Rails-Qiita Cookpad's style guide Sidekiq Anti-Patterns: Introduction How to create a web application with Rails that can withstand operation How to defeat Fat Model serodriguez68/rails-antipatterns rspec style guide
Recommended Posts