20200926のRailsに関する記事は17件です。

二つのテーブルを繋ぐ存在、中間テーブル

中間テーブルとは

 二つのアソシエーションしたいテーブル同士が「多対多」の関係のときに、テーブルの間を受け持つ、便利なやつ。

具体例

 LINEを思い浮かべると、一人のユーザーはいくつものグループに所属している。また、一つのグループには、何人ものユーザーが招待されている。このとき、ユーザーとグループは「多対多」の関係と言える。

問題点

 「多対多」のテーブルをそのままアソシエーションすることが困難である。

解決策

 先の例で言えば、ユーザーとグループの間に組み合わせを記録するテーブルをかませる。それが、中間テーブル。ユーザーとグループの組み合わせを記録するというのがポイントで、カラムにはユーザーidとグループidが必要となる。

記述

 アソシエーションをするため、「has_manyメソッド」「belongs_toメソッド」を使う。
 そのモデル(テーブル)ファイルの目線になって、繋がりを持ちたいモデル(テーブル)にはが複数か単数か考える。
ex) user(モデル) has_many groups
ここは、もはや英語の話。

group.rb
has_many :user_groups
has_many :users, through: :user_groups
user.rb
has_many :user_groups
has_many :groups, through: :user_groups
user_group.rb
belongs_to :user
belongs_to :group

注意

 直接的に繋がっていないモデルには、「throughオプション」を使う。

ポイント

  • 中間テーブルは必ず「belongs_toメソッド」で、後に続くモデル名は単数形。
  • アソシエーションしたいテーブル同士は間接的に多対多の関係になる。
  • 中間テーブルではないテーブルは必ず「has_manyメソッド」で、後に続くモデル名は複数形。
  • 「throughオプション」を使って、経由しているモデル名を複数形で記述。

最後に

 人生で初めてQiitaに投稿します。自分のアウトプットを目的としていますが、お気づきの点があれば、ご指摘ください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】devise関連のルーティングまとめ

deviseも便利な機能が故、それぞれ独自のメソッドが自動で生成してくれるルーティングについて、こんがらがってしまいます。
そんな同じお悩みの方はぜひ。

devise_for

routes.rb
  devise_for :users
$ rails routes

Prefix                   Verb   URI Pattern                    Controller#Action

new_user_session         GET    /users/sign_in(.:format)       devise/sessions#new
user_session             POST   /users/sign_in(.:format)       devise/sessions#create
destroy_user_session     DELETE /users/sign_out(.:format)      devise/sessions#destroy
user_password            POST   /users/password(.:format)      devise/passwords#create
new_user_password        GET    /users/password/new(.:format)  devise/passwords#new
edit_user_password       GET    /users/password/edit(.:format) devise/passwords#edit
                         PATCH  /users/password(.:format)      devise/passwords#update
                         PUT    /users/password(.:format)      devise/passwords#update
cancel_user_registration GET    /users/cancel(.:format)        devise/registrations#cancel
       user_registration POST   /users(.:format)               devise/registrations#create
   new_user_registration GET    /users/sign_up(.:format)       devise/registrations#new
  edit_user_registration GET    /users/edit(.:format)          devise/registrations#edit
                         PATCH  /users(.:format)               devise/registrations#update
                         PUT    /users(.:format)               devise/registrations#update
                         DELETE /users(.:format)               devise/registrations#destroy

resources :usersとの比較

アクション名 リクエスト resources devise_for
new GET /users/new /users/sign_up(.:format)
edit GET /users/:id/edit /users/edit(.:format)
show GET /users/:id なし
index GET /users なし
create POST /users /users(.:format)
update PATCH / PUT /users /users(.:format)
destroy DELETE /users /users(.:format)

devise_scope

routes.rb
  devise_scope :user do
    get 'signin' => 'devise_token_auth/sessions#new'
    post 'signin' => 'devise_token_auth/sessions#create'
    post 'signup' => 'users#create'
    put 'update' => 'users#update'
  end
$ rails routes

Prefix Verb   URI Pattern                  Controller#Action

signin GET    /signin(.:format)            devise_token_auth/sessions#new
       POST   /signin(.:format)            devise_token_auth/sessions#create
signup POST   /signup(.:format)            users#create
update PUT    /update(.:format)            users#update

mount_devise_token_auth_for

routes.rb
  mount_devise_token_auth_for 'User', controllers: {
    registrations: 'users'
  }
$ rails routes

Prefix Verb   URI Pattern                  Controller#Action

signin GET    /signin(.:format)            devise_token_auth/sessions#new
       POST   /signin(.:format)            devise_token_auth/sessions#create
signup POST   /signup(.:format)            users#create
update PUT    /update(.:format)            users#update
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

undefined method `**_path'のエラー

記事の目的

初学者の為、間違っているところはご指摘頂けると幸いです。
備忘録・アウトプット目的で投稿です。

エラー内容

new.html.slim
= form_with model: @review, local: true do |f|
  .form-group
    = f.label :purpose
    = f.text_field :purpose, class:'form-control', id: 'review_purpose'

において、以下の「undefined method `reviews_path'」というエラーが発生。
そもそもreviews_path定義していないけどな。

ActionView::Template::Error (undefined method `reviews_path' for #<#<Class:0x00007fb7553f5138>:0x00007fb7553fc9b0>):

調べると、以上の「= form_with model: @review, local: true do |f|」のコードにおいては、form_with model: @review の箇所が内部的にpolymorphic_path(@review)というメソッドを実行しているそうで、 その実行結果がreviews_pathとなるのでそこでエラーが起きているらしい。

対処法

記事をみてみると、以下のように「form_forでしっかりどこに飛ばすのか指定してあげれば解決するらしい。解決。

new.html.slim
= form_for @review,:url => {:action => :create} do |f|

こちらに関しては、urlのオプションから実行しているコントローラーのnewアクションに該当するpathを作り出しており、このパスは存在するのでエラーとならないようです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker-composeを用いたRailsのAPIサーバー環境構築

筆者について

ProgateのRuby, Railsコースを一通りやった。
その後Dockerで環境作ってRailsでAPIサーバー作った。

本記事について

Progateではviewを含んだrailsの書き方を学んだが、APIサーバーとしてのみRailsを使いたかった。
また、dockerで開発したかったため公式のクイックスタートを参考にRailsの開発環境を作った。
その過程で得られたノウハウをまとめる。

APIサーバーの開発で使うGemについては別記事で書く。

前提

Docker, docker-composeの使い方がわかること

環境

Ruby 2.7
MySQL 5.7
VSCode
Mac 10.14.5
Sequel Pro データベース見る用

Rails new するまで

まずはプロジェクトフォルダを作成。ここではrails-sampleとした。
そして、この直下に Dockerfileを作成

FROM ruby:2.7

RUN apt-get update -qq && apt-get install -y
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

次にrailsをインストールするためのGemfileを用意

source 'https://rubygems.org'
gem 'rails', '~>5'

空のGemfile.lockを作る

touch Gemfile.lock

Dockerfileに記載されているentrypoint.shを作る

entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

なぜこれらのファイルを作るのかはDocker 公式クイックスタートの方を見ていただければ。

docker-compose.ymlを作る

docker-compose.yml
version: '3'
services:
  server:
    build: .
    tty: true
    volumes:
      - .:/myapp
    working_dir: /myapp
    ports:
      - "3000:3000"
    depends_on:
      - mysql
    command: bash
    # command: > 
    #   bash -c "rm -f tmp/pids/server.pid &&
    #       bundle exec rails s -p 3000 -b '0.0.0.0'"
  mysql:
    image: mysql:5.7
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: mysql
    volumes:
      - "./mysql/db-data/:/var/lib/mysql" # データ永続化
      - "./mysql/my.cnf:/etc/mysql/conf.d/my.cnf" # 日本語をデータとして使うために必要

MySQLのデータの永続化と日本語対応のために

rails-sample > mysql > db-data フォルダを作成。
mysqlフォルダ以下にmy.cnfを作成

my.cnf
[mysqld]
character-set-server=utf8

ここまででこのような構成になる。
スクリーンショット 2020-09-26 20.12.16.png

それができたらrails newする。

docker-compose run server rails new . -MC --force --api --database=mysql --skip-active-storage

railsコマンドやコマンドのヘルプでオプションを調べ、不要そうなものを省いている。
重要なオプションは以下

--force // rails new 時にファイルを上書き
--api // api開発に不要なviewとかを生成しない
--database=myql // デフォルトではsqliteだがmysqlを指定する

これでいろいろ作られる。

スクリーンショット 2020-09-26 20.23.52.png

で、ここでGemfileGemfile.lockが上書きされて新しくなっている。
これをイメージのほうに反映させるために、

docker-compose build

する必要がある。

事前に必要なgemがわかっている場合にはここでGemfileに書いておく。

これでrails new完了

mysqlに繋がるように設定ファイルを書く。

config > database.yml

database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: mysql

development:
  <<: *default
  host: mysql
  database: myapp_development

...
test:
  <<: *default
  host: mysql
  database: myapp_test

productionは不要なのでまるっとコメントアウトした
host名はdocker-compose.ymlのサービス名
passwordもdocker-compose.ymlで指定したもの

できたらdocker-compose up -dでコンテナを起動し、VSCodeのAttach Shellで中に入る

rails db:create

でDBを作る

root@dc075120af8a:/myapp# rails db:create
...
Created database 'myapp_development'
Created database 'myapp_test'

これでデータベースが作れた。
Sequel Proで接続すればデータベースができていることが確認できる

あとはProgateで学んだ通りモデル作ってマイグレーションすればテーブルができる。

参考

Docker 公式クイックスタート 英語
日本語の方は内容が古いので注意。

Railsガイド RailsによるAPI専用アプリケーション

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

配列内の任意の文字を探す判定するメソッドの作り方

【概要】

1.結論

2.includeメソッドとは何か

3.どのようにプログラムしたか

1.結論

include?メソッドを使う!


2.include?メソッドとは何か

include?は配列の中に指定した要素が、あるかを判定するメソッドです!


3.どのようにプログラムしたか

def array_hello(strs)
  if strs.include?(hello)
    puts "True"
  else
    puts "False"
  end
end

"strs"の引数から"hello"という文字を探したいので、
strs.include?(hello)という書き方にしています!
引数.include?(探したい配列内の文字)で適用できます!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(ECサイト)注文情報入力時におけるバリデーション

前提条件

ECサイトでECサイトで注文情報入力 ==> 注文確認画面の間に注文情報入力でバリデーションをかけたい

1.よく使うバリデーション

app/models/book.rb
validates :title, presence: true

などの普段よく使うバリデーションは
booksテーブルにtitleというカラムがあり
データベースに保存される時、つまり @book.save(book_params)の時にtitleカラムが空白だとバリデーションがかかる。
しかし、ECサイトで注文情報入力 ==> 注文確認画面
 ==> 注文確定(ここで.saveが行われる)というフローで実装する際、注文情報入力 ==> 注文確認画面の間に.saveが発生しない為、モデルにかけるバリデーションをrenderで呼び出すことができない為if文で条件分岐を書いてflash[:notice]を使う必要がある。

2.if文の実装

app/controllers/order_controllers.rb
when 3
        if    params[:order][:new_add][:postal_code] == "" && params[:order][:new_add][:address] == "" && params[:order][:new_add][:name] == ""
              flash[:notice] = "新しいお届け先が全て入力されていません"
              redirect_to new_order_path
        elsif params[:order][:new_add][:postal_code] == ""
              flash[:notice] = "郵便番号が入力されていません"
              redirect_to new_order_path

        elsif params[:order][:new_add][:address] == ""
              flash[:notice] = "住所が入力されていません"
              redirect_to new_order_path
        elsif params[:order][:new_add][:name] == ""
              flash[:notice] = "宛名が入力されていません"
              redirect_to new_order_path
        else
            @order.postal_code = params[:order][:new_add][:postal_code]
            @order.address = params[:order][:new_add][:address]
            @order.name = params[:order][:new_add][:name]
        end
 end

3.コードの説明

まず、case文を使い、ラジオボタンを3個order/new.html.erbに書きwhenで3パターン分岐させている。
そして、3個目のラジオボタンを選ぶと注文情報入力時に新しいお届け先を入力できるように実装している。
[:new_add] は注文情報入力時に新しいお届け先を入力する際のパラメータ。

app/controllers/order_controllers.rb
params[:フォームで送られてくる値] == ""
flash[:notice] = ”ホニャホニャ"

と書くことで空欄の時にフラッシュメッセージを出せる。

4.今回の実装を終えて

当初 == nil にしており値が返ってきていなかったがターミナルを見ると""になっていたので条件文を変えるとうまくいった。
&&で条件文を繋ぐ時に最後に一個だけイコールを入れるのではなく毎回イコールが必要である。

5.参考サイト

https://qiita.com/GreenFingers_tk/items/ed5219e1e0cdd5e5d1b1#new

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsにフォントを導入するまで

はじめに

ネット上には素晴らしいフリーフォントがたくさんあります。
デザインの表現の幅を広げるために是非、活用していきたいところ。
そこで、フリーフォントをRailsに導入していきます。

フォントファイルの導入

ここでは導入したいフォントファイルをsmog.otfとします。
publicフォルダにfontsフォルダを作り、そこにフォントファイルを配置。

- public
    - fonts
        - smog.otf

使用するcssファイル(ここではsample.css)に以下のコードを追加します。
font-familyの名前は自由に設定できます、今回は'Smog'としましょう。
またformatはファイルのフォーマットに合わせます。

フォーマット 拡張子
format('woff') .woff
format('truetype') .ttf
format('opentype') .otf または .ttf
format('embedded-opentype') .eot
format('svg') .svg または .svgz
sample.css
@font-face {
  font-family: "Smog";
  src: asset-url('/fonts/smog.otf') format('opentype');
  font-weight: normal;
  font-style: normal;
}

あとはいつも通りcssでフォントを指定すれば使えます。
以下は例です。

sample.css
.foo h1 {
  font-family: 'Smog';
}
index.html.erb
<div class="foo">
  <h1>hello!</h1>
</div>

以上です、お疲れ様でした!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】【bootstrap】レスポンシブに文字の大きさを変更したい

前提

gem bootstrapを使用

やりたいこと

Railsを使ってレスポンシブな画面を作る。
その際に文字の大きさをいい感じに大きくしたり小さくしたりしたい。

結論

app/assets/stylesheets/application.scss
body, html { font-size: 30px; }
@media screen and (min-width: 576px) and (max-width: 768px) { body, html { font-size: 30px; } }
@media screen and (min-width: 769px) and (max-width: 992px) { body, html { font-size: 30px; } }
@media screen and (min-width: 993px) and (max-width: 1200px) { body, html { font-size: 16px; } }
@media screen and (min-width: 1201px) { body, html { font-size: 20px; } }

上記のサイズを変更すればOK
色々見ているとpxで設定しない方がよいというのもあるので、他の方法も検討中
今回はRailsというよりはscssの書き方になる

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

sidekiqのリトライ上限を設定して死んだキューをslackで通知する方法

はじめに

非同期でjobを処理してくれるsidekiqを導入する中で、利便性を生かしつつも自分のアプリ専用にカスタマイズしたい、そんな時に役に立った設定をまとめています。

sidekiqとは

sidekiqとは、非同期処理を可能にしてくれるライブラリです。複数のジョブを同時に実行させる時などに、各ジョブのqueueの名称を分けることで、処理の優先順位を指定することができます。似たようなライブラリにresqueやdelayed_jobなどがあるかと思います。
簡単に導入できるのですが、つまづいてしまったキューをデフォルトで25回だか26回だかリトライするとか、その後はDEADとなって実行していたキューのログが見られないなどといった面倒な点もあります。
そこでこの記事では、例外を投げた時などにいつまでもリトライせず上限を設定してDEADとし、その際にslackにキューの内容とエラーメッセージを投げるという設定をする方法についてお話しします。

環境

Ruby 2.6.6
Rails 6.0.2
sidekiqにはredisが必要になります。

# On OSX
brew update
brew install redis
brew services start redis

失敗したキューをダッシュボードで確認する

gemにsidekiq-failuresを追加することでダッシュボードで確認することができ、失敗したキュー全てをリトライさせることもできます。

gem 'sidekiq-failures'

image.png

failuresの一覧でもエラーの詳細が確認できます。
image.png

他にも投げられたキューを知る&分析するなどでたくさんgemがあるようですので参考までに載せておきます

リトライの上限を設定する

sidekiqは確かデフォルトで25,6回程度リトライをしてからキューを殺すといった仕様になっていたかと思います。
そこで、たくさんのジョブを一気に処理させる時など、そこまでリトライしなくていいからダメになった何度かチャレンジしてダメになったら教えて・・・という人のために、リトライの上限をjobファイルに記載します。

app/jobs/your_job.rb
class YourJob < ActiveJob::Base
  ...
  queue_as :default
  sidekiq_options retry: 5
  ...
end

これで5回トライしてダメだったキューはDEADキューとなります。

キューの死亡をslackで通知

Railsアプリにおいてslack通知をとても簡単にしてくれるgemがslack-incoming-webhooksです。

gem 'slack-incoming-webhooks'

通知させるチャネルのURL取得はこのページを確認してください。

initializerでキューが死亡した場合の処理を設定(チャネルのURLは.envに保存します)

app/config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
  config.death_handlers << ->(job, ex) do
    slack = Slack::Incoming::Webhooks.new(ENV['SLACK_WEBHOOK_URL'])
    attachments = [{
      title: "Sidekiq failure",
      text: "ONE DEAD JOB IS FOUND:\n (#{job['args']}) \n msg(#{job['error_message']})",
      color: "#fb2489"
    }]
    slack.post "", attachments: attachments
  end
end

これで自然とDEADになってしまったキューはslack通知されるようになります。
image.png

注意点

  • sidekiqのダッシュボード上でキューを殺しても通知は来ない
    config.death_handlersに通知の設定をする場合、ダッシュボード上でマニュアルにキューを死亡させた場合には通知が来ません。なのでリトライ上限の5回を超えた場合にのみ通知が来るようになります。

  • rescure_from Exceptionで通知設定するとリトライしてくれない
    例外処理なので一度ジョブを処理してエラーとなると死亡と看做され、slack通知はしてくれますがリトライはしてくれません。なんらかの事情で処理されずそれでもリトライして欲しい場合には、上記の通りconfig.death_handlersでslack通知の設定をおすすめします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

sidekiqのリトライ上限を設定して死んだjobをslackで通知する方法

はじめに

非同期でjobを処理してくれるsidekiqを導入する中で、利便性を生かしつつも自分のアプリ専用にカスタマイズしたい、そんな時に役に立った設定をまとめています。

sidekiqとは

sidekiqとは、非同期処理を可能にしてくれるライブラリです。複数のジョブを同時に実行させる時などに、各ジョブのqueueの名称を分けることで、処理の優先順位を指定することができます。似たようなライブラリにresqueやdelayed_jobなどがあるかと思います。
簡単に導入できるのですが、つまづいてしまったキューをデフォルトで25回だか26回だかリトライするとか、その後はDEADとなって実行していたキューのログが見られないなどといった面倒な点もあります。
そこでこの記事では、例外を投げた時などにいつまでもリトライせず上限を設定してDEADとし、その際にslackにキューの内容とエラーメッセージを投げるという設定をする方法についてお話しします。

環境

Ruby 2.6.6
Rails 6.0.2
sidekiqにはredisが必要になります。

# On OSX
brew update
brew install redis
brew services start redis

失敗したキューをダッシュボードで確認する

gemにsidekiq-failuresを追加することでダッシュボードで確認することができ、失敗したキュー全てをリトライさせることもできます。

gem 'sidekiq-failures'

image.png

failuresの一覧でもエラーの詳細が確認できます。
image.png

他にも投げられたキューを知る&分析するなどでたくさんgemがあるようですので参考までに載せておきます

リトライの上限を設定する

sidekiqは確かデフォルトで25,6回程度リトライをしてからキューを殺すといった仕様になっていたかと思います。
そこで、たくさんのジョブを一気に処理させる時など、そこまでリトライしなくていいからダメになった何度かチャレンジしてダメになったら教えて・・・という人のために、リトライの上限をjobファイルに記載します。

app/jobs/your_job.rb
class YourJob < ActiveJob::Base
  ...
  queue_as :default
  sidekiq_options retry: 5
  ...
end

これで5回トライしてダメだったキューはDEADキューとなります。

キューの死亡をslackで通知

Railsアプリにおいてslack通知をとても簡単にしてくれるgemがslack-incoming-webhooksです。

gem 'slack-incoming-webhooks'

通知させるチャネルのURL取得はこのページを確認してください。

initializerでキューが死亡した場合の処理を設定(チャネルのURLは.envに保存します)

app/config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
  config.death_handlers << ->(job, ex) do
    slack = Slack::Incoming::Webhooks.new(ENV['SLACK_WEBHOOK_URL'])
    attachments = [{
      title: "Sidekiq failure",
      text: "ONE DEAD JOB IS FOUND:\n (#{job['args']}) \n msg(#{job['error_message']})",
      color: "#fb2489"
    }]
    slack.post "", attachments: attachments
  end
end

これで自然とDEADになってしまったキューはslack通知されるようになります。
image.png

注意点

  • sidekiqのダッシュボード上でキューを殺しても通知は来ない
    config.death_handlersに通知の設定をする場合、ダッシュボード上でマニュアルにキューを死亡させた場合には通知が来ません。なのでリトライ上限の5回を超えた場合にのみ通知が来るようになります。

  • rescure_from Exceptionで通知設定するとリトライしてくれない
    例外処理なので一度ジョブを処理してエラーとなると死亡と看做され、slack通知はしてくれますがリトライはしてくれません。なんらかの事情で処理されずそれでもリトライして欲しい場合には、上記の通りconfig.death_handlersでslack通知の設定をおすすめします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2) のエラー対処法

記事の目的

初学者の為、間違っているところはご指摘頂けると幸いです。
備忘録・アウトプット目的で投稿です。

Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)

初学者の私は、こちらのエラーに何度も苦しみました。

このエラー以外にもCan't connect to local MySQL server through socket '/tmp/mysql.sock'(38)だとか、ERROR! The server quit without updating PID fileというエラーにも悩まされました。記事をみてみると、対処法として①PIDファイルの作成②権限の変更③再インストール対応が挙げられていましたが、色んな記事を試してもダメでした。(初学者なので、記事の理解・原因の理解ができていないからだと思います。)
同じように悩まれている初学者の方が私の解決策が参考となればと思い、残したいと思います。
(原因の理解はまだできていないので、解説抜きです。)
なので、試す場合は自己責任でお願いします。

対処法

私の場合、mysql8.0をインストールしており、それで不都合が出るようでした。
なのでアンインストール対応します。

brew uninstall mysql

こちらのコマンドを打ちます。

sudo rm -rf /usr/local/Cellar/mysql*
sudo rm -rf /usr/local/bin/mysql*
sudo rm -rf /usr/local/var/mysql*
sudo rm -rf /usr/local/etc/my.cnf
sudo rm -rf /usr/local/share/mysql*
sudo rm -rf /usr/local/opt/mysql*
sudo rm -rf /etc/my.cnf

次にmysql@5.7をインストールして下さい。

brew install mysql@5.7

その後、パスを通すので、以下のようにexportかけます。

export PATH="/usr/local/opt/apr-util/bin:$PATH" >> ~/.bash_profile
export PATH="/usr/local/opt/mysql@5.7/bin:$PATH" >> ~/.bash_profile
source ~/.bash_profile

その後、mysql.server startを打ちます。

mysql.server start

Starting MySQL..SUCCESS!となるでしょうか?
パソコンを再起動すると、毎回このエラーが出ますが、私の場合は上記の対処法でエラーを解決することができます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails6でActiveStorage導入でNameError: uninitialized constant ::Analyzableエラーが発生した対処

エラー内容

NameError: uninitialized constant #<Class:0x0000000000000000>::Analyzable
from /bundle/ruby/2.7.0/gems/activestorage-6.0.3.3/app/models/active_storage/blob.rb:26:in `<class:Blob>'

発生した環境

  • rails 6.0.3.3
  • ruby 2.7

対処

https://railsguides.jp/upgrading_ruby_on_rails.html#rails-6でclassicモードのオートローダーを使う方法

# config/environments/development.rb

config.autoloader = :classic

関連 Issue

https://github.com/rails/rails/issues/38681

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

バリデーションメッセージを Decorator で加工する

のっぴきならない事情があり、フォームの入力パターンに応じて、バリデーションメッセージを生成後に加工したかったのでメモ。
例えば、下記のようなフォームがあったとする。

new.slim
= form_for post_form, url: confirm_post_path do |f|
  - if post_form.errors.present?
    ul
      - post_form.errors.full_messages.each do |msg| 
        li = msg

しかし、フォーム内の特定の選択肢を選んだ場合に、バリデーションメッセージを変えるという要件が加わったとする。
この場合、errors.full_messages は Array なので、Decorator で中身を1件ずつチェックして力技で置換した。

new.slim
= form_for post_form, url: confirm_post_path do |f|
  - post_form_decorator = ::PostFormDecorator.new(f.object)
  - if post_form.errors.present?
    ul
      - post_form_decorator.post_error_display(post_form.errors.full_messages).each do |msg|
        li = msg
post_form_decorator.rb
class PostFormDecorator
  delegate_missing_to :@post_form

  def initialize(post_form)
    @post_form = post_form
  end

  def post_error_display(error_full_messages)
    error_full_messages.each do |msg|
      if @post_form.category == 'music'
        msg.gsub!(/#{@post_form.model.class.human_attribute_name(:author)}/, '歌手')
      end
    end
  end
end

破壊的メソッドで中身を入れ替えているので、「!」を付けなければgsubで置換されないので注意。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(Ruby on Rails6) 投稿された内容を"消去" する

まえがき

ここでは、RubyonRails6を使用して、投稿内容を 編集(内容の消去) を行う忘却録を記録します。
以前の投稿で 編集機能の実装 を行いました。編集機能の実装はそちらで記録しているので確認されたい方は、そちらでご確認ください。

投稿された内容を"消去" する

この投稿を実装するためには、アクションデータの消去(クリック)・投稿一覧への転送 を行います。

routes の設定

routes に deleteアクション を作成します。
form/:id/delete で、消去したいidのURLを特定させています。
また、データベースを変更する場合はroutes上で "get" ではなく "post" で設定します。

config/routes



post "form/:id/delete" => "form#delete"

View へのリンク設定

app/views/任意.html.erb
・
・
・
<%= link_to("削除", "/form/#{@post.id}/delete", {method: "post"}) %>

link_to を用いて、先ほど作成した deleteアクション へリンクしています。

注意: get と post の違い

{method: "post"}) をつけることで、routesのpostと紐づけることができます。
ややこしい!!

app/views/任意.html.erb
postでは↓エラー
<%= link_to("削除", "/form/#{@forms.id}/delete") %>

↓が正しい
<%= link_to("削除", "/form/#{@forms.id}/delete", {method: "post"}) %>

データを取り出して削除する

app/controllers/任意.rb
  def delete
    @forms = Form.find_by(id: params[:id])
    @forms.destroy

    redirect_to("/")
  end

↑の詳細

app/controllers/任意.rb
  def delete
    # find_byメソッドで、データを取得する
    @forms = Form.find_by(id: params[:id])
  end
app/controllers/任意.rb
  def delete
    # @テーブル名.destroy で削除する
    @forms.destroy
  end
app/controllers/任意.rb
  def delete
    # redirect_toメソッド でリダイレクトを設定する
    redirect_to("/")
  end

以上が、完了しエラーが怒らなければ、削除機能が出来上がっているはずです。
いかがでしたか?

あとがき

ここまで読んでいただき、ありがとうございました。
データベースの設定から表示・修正の機能を実装していて、投稿に削除機能をつけると本格的なサイトみたいですね。
でも、投稿機能が無事に削除された時は嬉しいけど切ない気持ちになります(Why?)

データーベースの作成・編集・デリート と基本機能を今までで実装しました。
セキュリティーや詳細機能を今後は学びたいと思います。

参考リンク

Ruby on Rails6 実践ガイド

Myリンク

また、Twitter・Portfolio のリンクがありますので、気になった方は
ぜひ繋がってください。プログラミング学習を共有できるフレンドが出来るととても嬉しいです。

Twitter
Portfolio
Github

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ConoHa VPS(CentOS 8.2)に Rails 6 + PostgreSQL + Nginx + Unicorn + Capistrano でデプロイする

*この記事は自分のブログに投稿していた内容からの転載です。日頃、Qiitaの記事には大変お世話になっているため、私のブログ記事の中でもよくアクセスされる(=つまり参考にされていると思われる)ページをQiitaに投稿してわずかばかりでも恩返しとなればと思っています。では以下、早速スタートです。

前置き

初デプロイ時に相当苦労し、調べに調べてなんとかデプロイ完了まで漕ぎ着けました(デプロイするのに数日間…)。作業中は手順を逐一記録しており、それらをデプロイ完了後に改めて整理したのが本記事の手順です。本手順を作成した後で一度VPSを削除し、改めてゼロから本手順に通りに作業したところ、無事にデプロイができました。そのため、ある程度信頼できる手順になっているのではないかと思います。なおローカル環境についてはMacでターミナルを使用して作業を進めています。

また、パッケージのインストール部分について補足しておきます。

パッケージ(PostgreSQLなど)をインストールする部分では、CentOS (今回は8.2)の標準リポジトリからインストールできるバージョンを利用しています。つまり、パッケージのバージョンは特に指定せず、その時にCentOSが標準でインストールするものを導入する形です。ただしこの場合、必ずしも最新版のパッケージがインストールされるわけではありません。例えば、本記事作成時点のPostgreSQL最新版は12ですが、CentOS 8.2の標準だと10がインストールされます。

パッケージ開発元のリポジトリから最新版を指定してインストールすることもできるのですが、その場合はバージョンを明示する必要があったりと、今後さらにバージョンが変わっていった時に、手順の内容が使えない(古い)ものになったために不用意なエラーが起こる可能性があります。今回は手順の汎用性を考慮して全てCentOS標準のバージョンをインストールしています。
CentOS標準のバージョンで困ることは無いと考えていますが、もしも最新版を使いたい場合は、一度本手順に沿ってデプロイまで済ませた後、個別にバージョンを変更するのが安定しているかと思います。

いずれにせよ、初学者にとってはデプロイするというハードルを超えるのが一苦労なので、そこまでたどり着くことが本手順の第一目的です。

以上、前置きでした。それでは以下が手順です。

1.ConoHaのVPSを契約する

メモリ等のプランは任意。私は以下を選択。

  • リージョン:東京、メモリ:1GB、CPU:2Core、SSD:100GB、880円/月
  • イメージタイプ:CentOS 8.2(64bit)
  • rootパスワード:任意のパスワード
  • ネームタグ:任意のネーム
  • 追加オプション:全てデフォルトのまま(例:SSH Key 使用しない)

以降、VPSのことをサーバと記載。

2.サーバにログインする

1で作成したサーバの詳細ページを開き、IPアドレスを確認。その後、ターミナルから以下のコマンドでサーバに接続。

ssh root@IPアドレス

その後、

The authenticity of host IPアドレス can't be established.
ECDSA key fingerprint is SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

上記のように、known hostsに追加するかと聞かれるので、yesと入力し回答。

これでローカルの「~/.ssh/known_hosts」にあるknown hostsファイルに追記される。

その後パスワードを求められるので、1で設定したrootパスワードを入力してログイン。

3.作業用ユーザを作成する

今後のために、ここで作業用ユーザを作成。

以下コマンドで新規ユーザ作成。

adduser ユーザ名

ここで作成したユーザのことを、以降は作業用ユーザと記載する。

その後、パスワードを設定。

passwd 作業用ユーザ名

root権限(sudo権限)を付与するために、設定ファイルを開く。

visudo

以下の部分を探す。

## Allow root to run any commands anywhere 
root    ALL=(ALL)   ALL

キーボードのiを押下して挿入モードに移行し、上記を下記の通り更新。

## Allow root to run any commands anywhere
root        ALL=(ALL)       ALL
作業用ユーザ名   ALL=(ALL)       ALL

ESCキーを押下してコマンドモードに戻り、その後、:wqを入力しエンターで、ファイルを保存。

sudo実行時に、パスワード入力を求められないように設定を変えるため、再び設定ファイルを開く。

visudo

以下の部分を探す。

## Same thing without a password
# %wheel        ALL=(ALL)       NOPASSWD: ALL

先ほどと同様に操作し、上記を下記の通り更新して保存。

## Same thing without a password
# %wheel        ALL=(ALL)       NOPASSWD: ALL
%作業用ユーザ名   ALL=(ALL)       NOPASSWD: ALL

ここまでの設定ができているかを確認する。作成したユーザにスイッチしてから、sudoコマンドを使用してみる。

su 作成したユーザ名
sudo echo test

上記実行後、パスワード入力を求められずに、testとだけ表示されればOK.

ここまで完了したら、exitで一度サーバから抜けて、作成したユーザでログインできることを確認する。

exit
exit
ssh 作成したユーザ名@IPアドレス

ユーザ作成後に設定したパスワードを入力し、ログインする。

その後、rootによるログインを不許可にしておくため、下記で設定ファイルを開く。

sudo vi /etc/ssh/sshd_config

下記の記載を探す。

/etc/ssh/sshd_config
# Authentication:

#LoginGraceTime 2m
PermitRootLogin yes
#StrictModes yes
#MaxAuthTries 6
#MaxSessions 10

上記のうち、PermitRootLoginの部分をnoに変えて、下記の通りとして保存する。

/etc/ssh/sshd_config
# Authentication:

#LoginGraceTime 2m
PermitRootLogin no
#StrictModes yes
#MaxAuthTries 6
#MaxSessions 10

設定を反映させるためにsshdのデーモンを再起動し、設定を読み込ませる。以下のコマンドを実行。

sudo systemctl restart sshd

一度サーバから抜けて、rootでログインを試してみると、正しいパスワードを入力しても、Permission deniedとなってログインできなくなっている。

4.Gitをインストールする

3で作業用ユーザをせっかく作成したわけだが、一回一回sudoするのが手間のため、以降はrootユーザで作業をしていく。

作業用ユーザでサーバにログイン後、rootユーザへスイッチ。

su root

その後、サーバで下記を実行し、Gitをインストール。

*メモ:CentOS 8では、パッケージ管理システムとしてYumではなくDNFが使われている(DNFはYumの事実上の後継)。現状、引き続きyumコマンドを使用することもできるが、yumコマンドを入力しても内部的には dnf が呼び出されており、将来的にyumは廃止されるため、以降使用するのはdnfコマンドに統一しておく。(といっても、yumと打つところをdnfと打つだけの違い。)

dnf -y install git

ちゃんとインストールできたか確認する。下記のコマンドを実行。

git --version

git version 2.18.4などとバージョンが表示されればOK.

5.rbenvをインストールする

下記を実行。リモートのrbenvリポジトリを/usr/local/rbenvというディレクトリにクローンしている。

git clone https://github.com/rbenv/rbenv.git /usr/local/rbenv

次にパスを通す(環境変数を設定する)ため、ファイルを作成する。

vi /etc/profile.d/rbenv.sh

ファイルが作られ、開かれるので下記を記述する。

/etc/profile.d/rbenv.sh
export RBENV_ROOT=/usr/local/rbenv
export PATH="$RBENV_ROOT/bin:$PATH"
eval "$(rbenv init -)"

そして、設定ファイルを読み込ませるために以下を実行。

source /etc/profile.d/rbenv.sh

その後、ちゃんとインストールできたか確認する。

rbenv --version

rbenv 1.1.2などとバージョンが表示されればOK.

6.ruby-buildをインストールする

rootユーザで下記を実行。ruby-buildは、rbenvでインストールを実行するときに必要なプラグイン。

git clone https://github.com/rbenv/ruby-build.git /usr/local/rbenv/plugins/ruby-build

7.開発パッケージをインストールする

Rubyをインストールするために必要なパッケージをインストールする。

dnf -y groupinstall "Development Tools"
dnf -y install gcc gcc-c++ glibc-headers openssl-devel readline readline-devel zlib zlib-devel libffi-devel libxml2 libxml2-devel libxslt libxslt-devel mysql-devel bzip2

2つ目のコマンドの中で、1つ目のコマンドでインストールしたものと重複するものが含まれている場合があるが、その場合は未インストールのものだけを自動的にインストールしてくれるので、そこまで気にしなくても大丈夫。

8.Rubyをインストールする

以下を入力して、rbenvでインストールできるRubyのバージョン一覧を表示する。

rbenv install -list

表示される結果例は下記の通り。

2.5.8
2.6.6
2.7.1
jruby-9.2.13.0
maglev-1.0.0
mruby-2.1.2
rbx-5.0
truffleruby-20.2.0
truffleruby+graalvm-20.2.0

Rubyをインストールする。バージョンはローカルで使っているものを指定。私の場合は2.7.1をインストールする。下記を実行。

rbenv install 2.7.1

インストールにはそれなりに時間がかかるので、気長に。

ちゃんとインストールされたかを確認する。

rbenv versions

2.7.1などと表示されればOK.

次にサーバで利用するRubyの使用バージョンを設定する。

rbenv global 2.7.1
rbenv rehash

設定できているか確認する。

ruby -v

2.7.1と表示されていればOK.

次に、作業用ユーザでも同様のことを行う。作業用ユーザに戻す。

exit

さらに以下を入力し、ディレクトリを移動。

cd /usr/local/rbenv/

事前確認のため、以下を実行。

ls -la

ファイルやフォルダの所有者が全てrootになっていることを確認。続いて、以下を実行。

sudo chown 作業用ユーザ名 version
sudo chown 作業用ユーザ名 shims

その後、再度以下を実行。

ls -la

versionとshimsの所有ユーザが作業用ユーザ名に変更されていることを確認する。

一度サーバからログアウトし、改めて作業用ユーザでログインする。その後、さらに以下を実行。

rbenv global 2.7.1
rbenv rehash

最後に、以下を入力し、設定を確認。

ruby -v

2.7.1と表示されていればOK.

9.Bundlerをインストールする

rootユーザにスイッチする。

su root

そして以下のコマンドを実行。

rbenv exec gem install bundler
rbenv rehash

インストールできているか確認する。

which bundler

上記入力後、/usr/local/rbenv/shims/bundler と返ってくればOK.

10.Node.jsをインストールする

以降、引き続きrootユーザのまま。インストールを実行。

dnf install -y nodejs

インストールできていることを確認する。

node -v

v10.21.0などとバージョンが表示されればOK.

11.Yarnをインストールする

以下を実行。

npm install -g yarn

インストールできていることを確認する。

yarn -v

1.22.5 などとバージョンが表示されればOK.

12.PostgreSQLをインストールする

以下を実行。

dnf install -y postgresql postgresql-server postgresql-devel postgresql-contrib postgresql-docs
postgresql-setup initdb

OS起動時にPostgreSQLも自動起動するように設定。

systemctl enable postgresql

自動起動設定できたことを確認。

systemctl is-enabled postgresql

enabledであればOK.

現時点ではPostgreSQLは起動していないため、起動させる。

systemctl start postgresql

インストールできたことを確認。

psql --version

psql (PostgreSQL) 10.6などと表示されればOK.

さらに、PostgreSQLが起動していることを確認する。

systemctl status postgresql

active (running)になっていればOK.

次にPostgreSQLの設定を変える。下記を実行してファイルを開く。

vi /var/lib/pgsql/data/postgresql.conf

CONNECTIONS AND AUTHENTICATIONの項目にある以下

/var/lib/pgsql/data/postgresql.conf
#listen_addresses = 'localhost'

のコメントアウトを解除して下記の通り変更する。

/var/lib/pgsql/data/postgresql.conf
listen_addresses = '*'

変更後、さらにもう1つファイルを開く。

vi /var/lib/pgsql/data/pg_hba.conf

ファイル下部にある以下を、

/var/lib/pgsql/data/pg_hba.conf
# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             all                                     peer
# IPv4 local connections:
host    all             all             127.0.0.1/32            ident
# IPv6 local connections:
host    all             all             ::1/128                 ident
# Allow replication connections from localhost, by a user with the
# replication privilege.
local   replication     all                                     peer
host    replication     all             127.0.0.1/32            ident
host    replication     all             ::1/128                 ident

次のように変更する。(途中3行をコメントアウト、最終行に2行の記載追加)

/var/lib/pgsql/data/pg_hba.conf
# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
# local   all             all                                     peer
# IPv4 local connections:
# host    all             all             127.0.0.1/32            ident
# IPv6 local connections:
# host    all             all             ::1/128                 ident
# Allow replication connections from localhost, by a user with the
# replication privilege.
local   replication     all                                     peer
host    replication     all             127.0.0.1/32            ident
host    replication     all             ::1/128                 ident
local   all             all                                     trust
host    all             all             0.0.0.0/0               md5

設定を反映させるため、PostgreSQLを再起動する。

systemctl restart postgresql

13.Railsアプリで使用するDBを作成する

一度ターミナルから目を離し、デプロイしたいRailsアプリの config/database.yml を開いてファイル内の以下の記述を確認する。

/config/database.yml
production:
  <<: *default
  database: test_production
  username: test
  password: <%= ENV['TEST_DATABASE_PASSWORD'] %>

“test”の部分はrails newした時のプロジェクト名が設定されている。

その後、ターミナルに戻って以下の作業。

PostgreSQLにデフォルトで作られている、postgresというユーザを使ってPostgreSQLに接続する。

psql -U postgres

と入力し、プロンプトが postgres=# となったことを確認する。

その後、以下コマンドによりロールを作成したいのだが、

CREATE ROLE "ロール名" WITH SUPERUSER LOGIN;

“ロール名”の部分には、config/database.ymlのusernameに書いてある名称を記述する。今回の例ではtestなので、

CREATE ROLE "test" WITH SUPERUSER LOGIN;

となる。これを入力して実行する。

次に、以下コマンドによりデータベースを作成したいのだが、

CREATE DATABASE "データベース名"

“データベース名”の部分には、config/database.ymlのdatabaseに書いてある名称を記述する。今回の例ではtest_productionなので、

CREATE DATABASE "test_production";

となる。これを入力して実行する。

データベースの操作はこれで終わりなので、

\q

と入力して、PostgreSQLから抜ける。

最後に、PostgreSQLを再起動しておく。

systemctl restart postgresql

14.Nginxをインストールする

以下のコマンドを実行。

dnf -y install nginx

インストールされていることを確認。

nginx -v

nginx version: nginx/1.14.1などと表示されればOK.

次に以下を実行して、OS起動時にNginxも自動起動するよう設定しておく。

systemctl enable nginx

自動起動設定できたことを確認。

systemctl is-enabled nginx

enabledであればOK.

次に以下を実行して、Nginxを起動する。

systemctl start nginx

Nginxが起動していることを確認する。

systemctl status nginx

active (running)になっていればOK.

次に、Nginxにhttpで接続できるように、firewalldの80番ポートを開放する。

firewall-cmd --add-service=http --permanent

firewalldをリロードする。

firewall-cmd --reload

その後、ウェブブラウザを開いてサーバのIPアドレスを入力して接続し、「Welcome to nginx」が出ればOK.

15.Capistranoでデプロイできるようにする

15−1.サーバとGitHubでSSH接続できるようにする

Capistranoを使うために、サーバとGitHub間でSSH接続できるようにする。

ここで作業用ユーザに戻る。

exit

ホームディレクトリに移動。

cd

その後、以下で公開鍵と秘密鍵を作成。

ssh-keygen -t rsa

この時、

Enter file in which to save the key (/home/devsup/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:

と聞かれるが、全てそのままEngerを押下すればOK.

次に、以下を入力し、公開鍵の中身を表示する。

cat ~/.ssh/id_rsa.pub

すると、

ssh-rsa XXXX...= ユーザ名@IPアドレス

といったものが表示されるので、ssh-rsaから最後のIPアドレスまで全てをコピー。

その後、GitHubにログインし、SSHキーの登録ページ(https://github.com/settings/keys)からNew SSH Keyを押下して、コピーした内容をペーストし登録。

これで、サーバとGitHub間でSSH接続できるようになった。

次に、サーバにアプリ公開用のディレクトリを作成する。

まずディレクトリを移動。

cd /var

wwwディレクトリを作成。

sudo mkdir www

wwwディレクトリの所有ユーザとグループをrootから変更する。

sudo chown -R 作業用ユーザ名:作業用ユーザ名 www

所有が変わったことを確認する。

ls -l

rootではなく、作業用ユーザの所有に変わっていればOK.

15−2.Capistrano、Unicornをインストールする

ローカルでRailsアプリを開き、Gemfileに以下を追記。

/Gemfile
gem 'dotenv-rails'
gem 'unicorn'
gem 'mini_racer', platforms: :ruby

group :development, :test do
  gem 'capistrano'
  gem 'capistrano-bundler'
  gem 'capistrano-rails'
  gem 'capistrano-rbenv'
  gem 'capistrano3-unicorn'
end

Gemfile更新後、ローカルでbundle install実行。

bundle install

次に、Capistranoの設定ファイルを作成する。ローカルで実行。

bundle exec cap install

/Capfileが作成されるので、そのファイルに以下を追記。

/Capfile
require 'capistrano/setup'
require 'capistrano/deploy'
require 'capistrano/rbenv'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
require 'capistrano3/unicorn'

デフォルトで記載されているものもあるため、不足しているものについて、上記となるように追記する。

次に、/config/deploy/production.rbを開き、以下を追記。

/config/deploy/production.rb
server 'サーバのIPアドレス', user: 'サーバの作業用ユーザ名', roles: %w{app db web}
set :ssh_options, keys: 'ローカルの秘密鍵のパス'

ローカルの秘密鍵のパスは、’/Users/ユーザ名/.ssh/id_rsa’ といった感じになる。

次に、Gemfile.lockを開き、インストールされたCapistranoのバージョンを確認。

/Gemfile.lock
capistrano (3.14.1)
  airbrussh (>= 1.0.0)
  i18n
  rake (>= 10.0.0)
  sshkit (>= 1.9.0)

capistranoの右の3.14.1がバージョンを示している。

次に、/config/deploy.rbを開き、既存の記述を全て削除した後、下記を記載。

/config/deploy.rb
# config valid only for current version of Capistrano

lock "~> 3.14.1" # Capistranoのバージョン

set :application, 'アプリケーション名' # アプリケーション名
set :repo_url, 'https://github.com/xxxx/yyyy' # クローンするGitHubリポジトリ(xxxxはユーザ名、yyyyはアプリ名)
set :deploy_to, '/var/www/アプリケーション名' # デプロイ先のディレクトリ
set :linked_files, %w{.env config/secrets.yml} # シンボリックリンクを貼るファイル
set :linked_dirs, %w{log tmp/pids tmp/cache tmp/sockets public/uploads} # シンボリックリンクを貼るディレクトリ
set :keep_releases, 3 # 保持するバージョンの数
set :rbenv_ruby, '2.7.1' # Rubyのバージョン
set :rbenv_type, :system
set :log_level, :debug # 出力するログのレベル 概要レベルにしたければ :info とする

namespace :deploy do
  desc 'Restart application'
  task :restart do
    invoke 'unicorn:restart'
  end
  desc 'Create database'
  task :db_create do
    on roles(:db) do |host|
      with rails_env: fetch(:rails_env) do
        within current_path do
          execute :bundle, :exec, :rails, 'db:create'
        end
      end
    end
  end
  desc 'Run seed'
  task :seed do
    on roles(:app) do
      with rails_env: fetch(:rails_env) do
        within current_path do
          execute :bundle, :exec, :rails, 'db:seed'
        end
      end
    end
  end
  after :publishing, :restart
  after :restart, :clear_cache do
    on roles(:web), in: :groups, limit: 3, wait: 10 do
    end
  end
end

アプリケーション名、CapistranoやRubyのバージョン、GitHubのリポジトリは環境(人)によって異なるので、適宜変更。アプリケーション名は、Rails newした時のプロジェクト名に合わせておけば適当かと。

次に、config直下にunicornディレクトリを作成し、その直下にproduction.rbを作成する(/config/unicorn/production.rb)。作成したproduction.rbに以下を記載。

/config/unicorn/production.rb
$worker  = 2
$timeout = 30
$app_dir = "/var/www/アプリケーション名/current"
$listen  = File.expand_path 'tmp/sockets/unicorn.sock', $app_dir
$pid     = File.expand_path 'tmp/pids/unicorn.pid', $app_dir
$std_log = File.expand_path 'log/unicorn.log', $app_dir
worker_processes  $worker
working_directory $app_dir
stderr_path $std_log
stdout_path $std_log
timeout $timeout
listen  $listen
pid $pid
preload_app true
before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.pid
    begin
      Process.kill "QUIT", File.read(old_pid).to_i
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end
after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

アプリケーション名の部分は、/config/deploy.rb内で記述したアプリケーション名を記載する。

ここまで終わったら、更新内容をコミットして、GitHubにプッシュする。

15−3.Unicornの自動起動設定をする

サーバに戻り、以下を実行。

sudo vi /usr/lib/systemd/system/unicorn_アプリケーション名.service

作成されたファイルに以下を記載。

/usr/lib/systemd/system/unicorn_アプリケーション名.service
[Unit]
Description=unicorn アプリケーション名 service
After=network.target

[Service]
User=作業用ユーザ名
Environment=RAILS_ENV=production
WorkingDirectory=/var/www/アプリケーション名/current
PIDFile=/var/www/アプリケーション名/current/tmp/pids/unicorn.pid
ExecStart=/usr/local/rbenv/shims/bundle exec unicorn -c /var/www/アプリケーション名/current/config/unicorn/production.rb -D
Restart=always

[Install]
WantedBy=multi-user.target

記述の書式が正しいことをチェック。

sudo systemd-analyze verify /usr/lib/systemd/system/unicorn_アプリケーション名.service

何も表示されなければ問題ないということなのでOK.

OS起動時にUnicornも自動起動するように設定。

sudo systemctl enable unicorn_アプリケーション名.service

自動起動設定できているか確認。

sudo systemctl is-enabled unicorn_アプリケーション名.service

enabledであればOK.

なおUnicornはこの後デプロイする中で起動されるので、今は起動しない。

15−4.Nginxの設定ファイルを作成

以下を実行。

sudo vi /etc/nginx/conf.d/アプリケーション名.conf

作成されたファイルに以下を記載。

/etc/nginx/conf.d/アプリケーション名.conf
upstream unicorn_アプリケーション名 {
  server unix:/var/www/アプリケーション名/current/tmp/sockets/unicorn.sock;
}
server {
  listen 80;
  server_name サーバのIPアドレス;
  root /var/www/アプリケーション名/current/public;
  access_log /var/log/nginx/アプリケーション名_access.log;
  error_log /var/log/nginx/アプリケーション名_error.log;
  location / {
    try_files $uri @unicorn;
  }
  location @unicorn {
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_pass http://unicorn_アプリケーション名;
  }
}

設定ファイル作成後、以下のコマンドで設定ファイルに問題ないかをテストする。

sudo nginx -t

下記のメッセージが表示され、テストに成功していることが確認できればOK.

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

その後、設定を反映させるためにNginxを再起動する。

sudo systemctl restart nginx

15−5.デプロイできる状態か確認する

ローカル(のアプリのプロジェクトディレクトリ)で以下を実行する。

bundle exec cap production deploy:check

なお、デプロイチェックにあたり、GitHubについて、以下の点に留意。

  • ローカルとGitHubでSSH接続できる(GitHubに鍵が登録されている)状態となっていること。
  • GitHubのリポジトリがパブリックとなっていること。

GitHubのプライベートリポジトリでデプロイしたい場合は、本記事下部の「80−2.GitHubのプライベートリポジトリからデプロイする」を参照して設定しておく。

デプロイチェック実行後、

ERROR linked file /var/www/アプリケーション名/shared/.env does not exist on IPアドレス

というエラーが出るが想定内なので大丈夫。

上記処理内ではデプロイできるかのチェックだけでなく、サーバで先ほど作成したwwwディレクトリ配下にアプリ用のディレクトリも作っている。

15−6.環境変数を設定する

ローカルで以下を実行し、乱数を生成する。

bundle exec rake secret

出力された乱数をコピーしておく。

次にサーバで、secrets.ymlファイルを作成する。

sudo vi /var/www/アプリケーション名/shared/config/secrets.yml

ファイルが開いたら、下記の通り、ローカルで生成した乱数をペーストして保存する。

/var/www/アプリケーション名/shared/config/secrets.yml
production:
  secret_key_base: 生成した乱数

次に、.envファイルを作成する。

sudo vi /var/www/アプリケーション名/shared/.env

ファイルが開いたら、Railsアプリで.envに記述していた環境変数をコピーして、上記の.envファイルにペーストする。

15−7.改めてデプロイできる状態か確認する

改めてローカル(のアプリのプロジェクトディレクトリ)で以下を実行する。

bundle exec cap production deploy:check

今度はエラーが発生しないことを確認する。これでデプロイできる状態となった。

16.デプロイを実行する

ローカルで以下を実行する。

bundle exec cap production deploy

bundler:installのところで時間がかかるため、固まったのか…?という不安と戦いながらじっと耐えるべし。

もし本当に固まったように思われた場合、Ctrl + Cでデプロイを中止し、もう一度デプロイコマンドを実行すると、次はうまくいくことが多い。

これでようやくデプロイ完了。アクセスしてみる。

http://IPアドレス(トップページを設定していないのであれば、http://IPアドレス/blogsなど存在するページを指定)

Railsアプリのページが表示されることを確認する。

80.その他設定編

これまででデプロイはできたわけだが、以降、追加の設定について。

80−1.SSHのポート番号を変更する

セキュリティの観点から実施しておく。

rootユーザで作業する。

su root

まずは、SELinux(Linuxカーネルのセキュリティ機能のひとつ)の状態を確認する。

getenforce

CentOS 8.2では、デフォルトがDisabledとなっていることを確認。そのままにしておく。

ファイルを開き、

vi /etc/ssh/sshd_config

以下の部分を見つけ、

/etc/ssh/sshd_config
# If you want to change the port on a SELinux system, you have to tell
# SELinux about this change.
# semanage port -a -t ssh_port_t -p tcp #PORTNUMBER
#
#Port 22
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::

#Port 22の部分のコメントアウトを外し、適当な番号を設定する。下記は例。

/etc/ssh/sshd_config
# If you want to change the port on a SELinux system, you have to tell
# SELinux about this change.
# semanage port -a -t ssh_port_t -p tcp #PORTNUMBER
#
Port 50022
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::

ポート番号は自由に決めることができるが、自分のサーバ内で他のサービスが使っているポート番号と被ると問題になるので注意。とりあえず49152〜65535の間で適当に決めるのが良い。

どんなサービスがどんなポート番号を使っているかは、参考までに下記を参照。

TCPやUDPにおけるポート番号の一覧 – Wikipedia

また、設定ファイル内に、「# If you want to change the port on a SELinux system, you have to tell …」と記載がある通り、先に確認したSELinuxがDisabledではなく有効になっていると、SELinuxの設定も変える必要がある。

次に、Firewalldのデフォルト設定の格納場所である「/usr/lib/firewalld/services/」直下にある「ssh.xml」を、カスタム設定の格納場所である「/etc/firewalld/services/」直下へコピーする。

cp /usr/lib/firewalld/services/ssh.xml /etc/firewalld/services/ssh.xml

コピーして作成したファイルを開いて編集する。

vi /etc/firewalld/services/ssh.xml

以下部分のポート番号を、

/etc/firewalld/services/ssh.xml
<port protocol="tcp" port="22"/>

先ほど設定したポート番号に変える。

/etc/firewalld/services/ssh.xml
<port protocol="tcp" port="50022"/>

設定を反映させるため、Firewalldをリロード、SSHDを再起動する。

firewall-cmd --reload
systemctl restart sshd

ここまで終わったら、ターミナルをもう一つ立ち上げる。この時、上記作業に使ったターミナルはサーバに接続したままにしておき、決して閉じないように。万が一設定がうまくいっていないままターミナルを閉じると、二度と繋げなくなってしまう。

新たに立ち上げたターミナルで以下を実行。

ssh 作業用ユーザ名@IPアドレス

結果、

ssh: connect to host IPアドレス port 22: Operation timed out

になっていればOK. 22番ポートは閉じられている。

次に以下で接続。以下はSSHのポート番号を50022にしている場合。

ssh 作業用ユーザ名@IPアドレス -p 50022

接続できることを確認。

ここまでの作業でSSHのポート番号が変わったわけだが、それと同時に、Capistranoでデプロイする時にもそのポート番号を使うように設定しておかないといけない。

ローカルでRailsアプリの/config/deploy/production.rbを開き、

/config/deploy/production.rb
server 'サーバのIPアドレス', user: 'サーバの作業用ユーザ名', roles: %w{app db web}
set :ssh_options, keys: 'ローカルの秘密鍵のパス'

上記に、ポート番号の設定を追加する。下記はポート番号が5022の場合。

/config/deploy/production.rb
server 'サーバのIPアドレス', user: 'サーバの作業用ユーザ名', port: '50022', roles: %w{app db web}
set :ssh_options, keys: 'ローカルの秘密鍵のパス'

変更後は、コミットしてGitHubへプッシュ。

最後にローカルで、

bundle exec cap production deploy:check

を実行して、ちゃんとデプロイできるようになっているか確認しておく。

80−2.GitHubのプライベートリポジトリからデプロイする

GitHubのリポジトリをプライベートにする場合は、デプロイの設定にも手を入れる必要がある。

まず、GitHubの以下ページから、

https://github.com/settings/tokens

Generate new tokenを押下し、repo項目のFull control of private repositoriesをチェックして生成。トークンが表示されたらコピーしておく。

その後、Railsアプリの/config/deploy.rbを開き、

/config/deploy.rb
set :repo_url, 'https://github.com/xxxx/yyyy' # クローンするGitHubリポジトリ(xxxxはユーザ名、yyyyはアプリ名)

上記となっているset :repo_urlを、以下のように編集。

/config/deploy.rb
set :repo_url, 'https://トークン:@github.com/xxxx/yyyy' # クローンするGitHubリポジトリ(xxxxはユーザ名、yyyyはアプリ名)

https://github.com/の部分を、https://トークン:@github.com/に変える。

最後にローカルで下記を実行し、

bundle exec cap production deploy:check

デプロイできる状態となっていることを確認しておく。

80−3.タイムゾーンを日本時間で統一する

必要であれば実施。

アプリ内で時刻を使用する場合、OS(CentOS)、DB(PostgreSQL)、Railsの時刻基準を合わせておくと良さそう。

まず、CentOSのタイムゾーンを変更する。サーバ上で、rootユーザにて以下を実行。

timedatectl set-timezone Asia/Tokyo

上記後、以下で確認。

timedatectl status

Time zone: Asia/Tokyo (JST, +0900) となっていればOK.

次にPostgreSQLのタイムゾーンを変更。DBにアクセスし、

psql -U postgres

プロンプトが、postgres=# となったのを確認して、DB一覧を確認。

\l

タイムテーブルを変更したいDBの名前をコピーしておき、一度DBから抜ける。

\q

そしてタイムゾーンを変更したいDBに接続する。

psql -U postgres -d DB名

プロンプトが、DB名=# となったのを確認して、以下でタイムゾーンを変更。

ALTER DATABASE "DB名" SET timezone TO 'Asia/Tokyo';

その後以下を実行し、

SELECT CURRENT_TIMESTAMP;

日本時間で現在時刻が表示されていればOK、DBから抜ける。

\q

なお、上記ではDB単位でタイムゾーンを設定しているため、他のDBのタイムゾーンは変わっていないことに注意。

最後にRailsのタイムゾーン設定を変える。

ローカルでRailsアプリの、config/application.rbを開く。

そして、class Application < Rails::Applicationブロックの中に以下2行を追記。

/config/application.rb
module XXXX
  class Application < Rails::Application
    .........
    ......
    ...
    config.time_zone = 'Tokyo' #追記
    config.active_record.default_timezone = :local #追記
  end
end

最後に忘れずコミット&プッシュ&デプロイしておくこと。

80−4.ログローテーションの設定

放っておくと際限なくログファイルが膨れ上がってしまうため、自動的に削除するように設定しておく。

まず、Nginxのログについてはデフォルトでログローテーションされるようになっているため対応不要。

次に、Unicornのログについて設定。/etc/logrotate.d/の下に設定ファイルを作る。サーバ上でrootユーザにて以下を実行。

vim /etc/logrotate.d/アプリケーション名_unicorn

開いたファイルに以下を記載。

/etc/logrotate.d/アプリケーション名_unicorn
/var/www/アプリケーション名/current/log/*unicorn.log {
  create 0664 作業用ユーザ名 作業用ユーザ名
  daily
  rotate 10
  missingok
  notifempty
  compress
  postrotate
    pid=/var/www/アプリケーション名/current/tmp/pids/unicorn.pid
    test -s $pid && kill -USR1 "$(cat $pid)"
  endscript
}

上記では、1日1ファイルを作成、10世代保存という設定にしている。

最後に、Railsのログについて設定。ローカルでRailsアプリのconfig/environments/production.rbを開き、下記を追加する。設定内容の意味はコメントに記載している通り。

/config/environments/production.rb
# production.0, production.1, production.2 の3世代保持 10Mを超えるとローテーション
config.logger = Logger.new('log/production.log', 3, 10 * 1024 * 1024)

その後、コミット&プッシュ&デプロイを忘れずに。

90.その他知っておくこと編

90−1.再度デプロイする場合

資産を更新しデプロイする場合は、GitHubにプッシュした後、ローカルで再度、

bundle exec cap production deploy

を実行すればOK.

90−2.サーバで本番環境のRails cを使う方法

サーバ内の、

var/www/アプリケーション名/current

にて、下記を実行する。

bundle exec rails console -e production

-e productionを付けずに行うと、開発環境(development)のコンソールが開かれるので注意。

コンソール上で以下を入力するとその時の環境が表示されるので、確認することも可能。

Rails.env

本番環境で開けていれば、”production”と表示される。

90−3.サーバで本番環境のRails dbコマンドを使う方法

サーバ内の以下で実施。

var/www/アプリケーション名/current

コマンドの後ろに、RAILS_ENV=productionを付ける。例えば、rails db:seedなら以下。

bundle exec rails db:seed RAILS_ENV=production

ただし、本番環境のRails db:resetしたいときは以下ではダメ。

bundle exec rails db:reset RAILS_ENV=production

上記を実行すると、「ActiveRecord::ProtectedEnvironmentError: You are attempting to run a destructive action against your ‘production’ database.」というエラーが出る。

本番環境でRails db:resetする場合は下記を入力する。

bundle exec rails db:reset RAILS_ENV=production DISABLE_DATABASE_ENVIRONMENT_CHECK=1

他にも、本番環境のDBに破壊的なコマンドを発行するときには、DISABLE_DATABASE_ENVIRONMENT_CHECK=1が必要。

90−4.ログの場所

本手順で構築した場合のログの格納場所。

Nginxのログはサーバ内の以下。

/var/log/nginx/
  • アクセスログ
    • /var/log/nginx/アプリケーション名_access.log
  • エラーログ
    • /var/log/nginx/アプリケーション名_error.log

UnicornおよびRailsのログはサーバ内の以下。

/var/www/アプリケーション名/shared/log/
  • Unicornのログ
    • /var/www/アプリケーション名/shared/log/unicorn.log
  • Railsのログ
    • /var/www/アプリケーション名/shared/log/production.log

以上

長くなったがようやく終わり。めでたしめでたし。

*追記:本記事の続きにあたるものとして、独自ドメイン適用およびSSL(https)化の手順に関する記事を投稿しました。ご参考ください。

「VPSのRailsに独自ドメインを適用し、SSL(https)化する(CentOS 8.2 / Nginx)」

補足

記事をお読みいただきありがとうございました。少しでも参考になりましたら幸いです。

一応、元のブログ記事へのリンクを以下に貼っておきますが、記載内容は本記事と同じため、特に参照いただく必要はないです(…むしろ、Qiitaのシンタックスハイライト機能があるため、Qiitaで記事を読んだ方がわかりやすいはずです)。

knmts.com | ConoHa VPS(CentOS 8.2)に Rails 6 + PostgreSQL + Nginx + Unicorn + Capistrano でデプロイする

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails6.0ルーティングまとめ

はじめに

Rails6.0のルーティングのパス名 resouces resourceなどを忘備録を兼ねてまとめます。

明示的なルーティング指定

routes.rb
# ルートへアクセスするルーティング
root 'users#show'
root to 'users#show'
root '/', to: 'users#show'
routes.rb
# '/users/:id'でusersコントローラのshowアクションにルーティング
get '/users/:id', to: 'users#show'
routes.rb
# 'hoge'でusersコントローラのshowにルーティング
# as:オプションを使うとルーティングに名前を指定可能
get '/users/:id', to: 'users#show', as: 'hoge'

<%= form_with %><%= link_to %>などでurlを指定する際に、直接/posts/:idなどのurlのように指定可能ですが、変更の際に修正箇所が多くなり得策ではありません。
as:で名前を付けることで、修正しやすくなり、コードも読みやすくなります。

resourcesとresouceの違い

resources(複数)...7つのアクションがid付きで生成されます。
resource(単数) ...indexアクションを除いた6つのアクションがidなしで生成されます。
「写真」「ユーザー」「商品」のようにアプリケーションに複数存在する場合はresources(複数)
「自身のプロフィール」のように一つしか存在せず、idやindexが必要ない場合はresource(単数)

resources(複数)

routes.rb
resources :photos

のような記述で、以下の7つのルーティングが生成されます。この場合、いずれもPhotosコントローラに対応します。

動詞 パス コントローラ#アクション 目的
GET /photos photos#index すべての写真の一覧を表示
GET /photos/new photos#new 写真を1つ作成するためのHTMLフォームを返す
POST /photos photos#create 写真を1つ作成する
GET /photos/:id photos#show 特定の写真を表示する
GET /photos/:id/edit photos#edit 写真編集用のHTMLフォームを1つ返す
PATCH/PUT /photos/:id photos#update 特定の写真を更新する
DELETE /photos/:id photos#destroy 特定の写真を削除する

resource(単数)

routes.rb
resource :geocoder

以上の記述で以下の6つのルーティングが生成されます。この場合、いずれもgeocoderに対応します。

動詞 パス コントローラ#アクション 目的
GET /geocoder/new geocoders#new geocoder作成用のHTMLフォームを返す
POST /geocoder geocoders#create geocoderを作成する
GET /geocoder geocoders#show 1つしかないgeocoderリソースを表示する
GET /geocoder/edit geocoders#edit geocoder編集用のHTMLフォームを返す
PATCH/PUT /geocoder geocoders#update 1つしかないgeocoderリソースを更新する
DELETE /geocoder geocoders#destroy geocoderリソースを削除する

注意点

Railsのルーティングはルーティングファイルの上から順に実行します。そのため、同じ条件のルーティングが複数存在する場合、上のルーティングのみ有効になります。なお、無関係なURLの場合は順番は関係ありません。

参考

Railsのルーティング - Railsガイド v6.0
https://railsguides.jp/routing.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Safari で disable_with オプションを付けても変わらない場合

Railsでフォームを作る際、送信ボタンに disable_with オプションを付けることで、二重送信を防ぐことができる。

= f.button '送信する', data: { disable_with: '送信中…' }

しかし、Safariに限って、なぜかボタンの文言が変化しなかった。GitHub上でも、多々報告されている。
disable_with doesn't work with link in Safari #306

解決策

結論から書くと、JSで送信処理をあえて遅らせることで、とりいそぎは回避できた。
Issue内ではバニラJSで書かれた例が出てくるので、jQueryを使っていなけば、そちらを参考にした方が早いかもしれない。

= f.button '送信する', data: { disable_with: '送信中…' }, class: 'disable_with_safari'
$('.disable-with-safari').click(function (event) {
  if ($(this).data('disableWith')) {
    $(this).prop('disabled', true);
    $(this).text($(this).data('disableWith'));
    var form = $(this).closest('form');
    if (form.length) {
      event.preventDefault();
      setTimeout(() => form.submit(), 300);
    }
  }
});

備考

冒頭のIssueでも議論されていたが、Safariの独特な仕様で、一度submitが走った後はDOMの更新が行われなくなるようだ。(あくまでもIssue上でのコメントなので、正確な仕様は一次情報を参照してください)
そのため、disable_withをつけても、ボタンの文言は変わらなかった。

ちなみに、今回の問題について検索すると以下の解決策が出てきていたが、わたしの場合は効果がなかった。

  • cursor: pointer; をつける
  • 空のclickイベントを設定する
  • 空のtouchstartイベントを設定する
  • バインドを$(document)に変更する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む