20210314のRailsに関する記事は27件です。

Action Cableでできることと実装方法

Action Cableは、Railsのアプリケーションと同様の記述で、WebSocket通信という双方向の通信によるリアルタイム更新機能を実装できるフレームワークで、Rails5から実装されました。

Action Cableを利用することで、たとえばリアルタイムで更新されるチャット機能を実装することができます。

チャネルの作成

channel

リアルタイム更新機能を実現するサーバー側の仕組みでデータの経路を設定したり、送られてきたデータを画面上に表示させたりします。

チャネルを作成するコマンド

ターミナル

rails g channel message #チャネル名
# ↓実行結果↓
Running via Spring preloader in process *****
      invoke  test_unit
      create  test/channels/message_channel_test.rb
      create  app/channels/message_channel.rb
   identical  app/javascript/channels/index.js
   identical  app/javascript/channels/consumer.js
      create  app/javascript/channels/message_channel.js

生成されるファイルのうち以下の2つが重要です。
app/controller/message_controller.rb → クライアントとサーバーを結びつけるためのファイル
app/javascript/channels/message_channel.js → サーバーから送られてきたデータをクライアントに描画するためのファイル

クライアントとサーバーを結びつける

stream_fromメソッド

サーバーとクライアントを関連づけるメソッドでAction Cableにあらかじめ用意されています。

app/controller/messages_controller.rb
class MessageChannel < ApplicationCable::Channel
  def subscribed
    stream_from "message_channel" #記述箇所
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end
end

broadcast

stream_fromメソッドで関連付けられるデータの経路で、サーバーから送られるデータの経路です。broadcastを介してデータをクライアントに送信します。

broadcastを使ってコントローラーへ以下のように記述します。

app/controller/messages_controller.rb
class MessagesController < ApplicationController
  def new
    @messages = Message.all
    @message = Message.new
  end

  def create
    @message = Message.new(text: params[:message][:text])
    if @message.save
      ActionCable.server.broadcast 'message_channel', content: @message
    end
  end
end

broadcastという経路を通して、message_channelに向けて@messagecontentに入れてデータを送信しています。

JavaScriptへの記述

サーバーから送信されたデータはdataの中にcontentという名称で入っているので、data.content.textで使用できます。

app/javascript/channels/message_channel.js
import consumer from "./consumer"

consumer.subscriptions.create("MessageChannel", {
  received(data) {
    // 以下で使用
    const html = `<p>${data.content.text}</p>`;
    const messages = document.getElementById('messages');
    const newMessage = document.getElementById('message_text');
    messages.insertAdjacentHTML('afterbegin', html);
    newMessage.value='';
  }
});

ちなみにビューはこんな感じです。

app/views/messages/new.html.erb
<h3>mini-talk-app</h3>
<%= form_with model: @message do |f| %>
  <%= f.text_field :text %>
  <%= f.submit '送信' %>
<% end %>
<div id="messages">
  <% @messages.reverse_each.each do |message| %>
    <p><%= message.id %></p>
    <p><%= message.text %></p>
  <% end %>
</div>

content以外もクライアントに送信できる

app/controller/messages_controller.rbでは保存したメッセージの値をcontentに入れて送信していましたが、設定すれば他の値も送ることができます。

ぼくが今開発しているアプリでは、こんな感じで情報を追加してクライアント側に送信しています。

app/controllers/chats_controller.rb
class ChatsController < ApplicationController  def create
    @chat = Chat.new(chat_params)
    if @chat.save
      # userとtimeにそれぞれ@userと@timeを入れている
      @user = current_user
      @time = @chat.created_at.strftime("%Y-%m-%-d %-H:%-M")
      ActionCable.server.broadcast 'chat_channel', content: @chat, user: @user, time: @time
    end
  end

  private
  def chat_params
    params.require(:chat).permit(:text).merge(
      user_id: current_user.id,
      group_id: params[:group_id]
    )
  end
end

クライアント(JavaScript)側ではこのようにデータを利用しています。JavaScript側で投稿日時の表示形式を変更する方法が分からなかったので、事前にサーバー(Ruby)側で表示形式を変更してから送信しています。

app/javascript/channels/chat_channel.js
import consumer from "./consumer"

consumer.subscriptions.create("ChatChannel", {
  received(data) {
    // 送信されたデータを以下で使用
    const html = `<div class="each-chat">
                    <p class="chat-time">${data.time}</p>
                    <p class="chat-text">${data.content.text}</p>
                    <p class="chat-user">${data.user.name}</p>
                  </div>`;
    const chats = document.getElementById("chats");
    chats.insertAdjacentHTML("afterbegin", html);
    const newChat = document.getElementById("chat_text");
    newChat.value="";
  }
});

参考資料

【Rails6.0】ActionCableを使用したライブチャットアプリを実装する手順を解説

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

docker内でGem(特にbundler)のdefault設定を解除したい!

概要

ローカル環境でも厄介なGemのdefault設定。
「このバージョンのGemで運用していきたいのに!」といった気持ちに反して、bundle installすると、default設定されたバージョンでインストールされてしまう。。。

ローカル環境ではその解除はできるが、Dockerのコンテナ内では少しだけ訳が違ったので、まとめたいと思い本記事を制作しました。

不足点、誤りがあれば遠慮なくご指摘いただけますと幸いです。

経緯

これまでローカル環境にて開発を行い、いざ本番環境へデプロイ!となるとエラーが返され、苦い思いをしてきた私。
今回はdocker-compose upで立ち上げたコンテナ内でgem listを見ると、ローカルでのgem listとバージョンの違いが生じていました。
そこで、コンテナ内でも指定したバージョンのGemで運用したく奮闘したのが経緯です。

方法

①コンテナ内に入る

ローカルにて自身のアプリのディレクトリにて

% docker-compose exec web bash 
root@コンテナID:/アプリ名#        以降プロンプト以外は省略します
②Gemを確認(省略可能)
# gem list bundler ※ちなみに全てのGemをリスト化するには"bundler"を省略

*** LOCAL GEMS ***

bundler (2.1.4, default: 1.17.2)

**この忌々しいdefaultを解除していきます。

③default設定されているGemのありかを探る。
# gem environment

RubyGems Environment:

...略

  - INSTALLATION DIRECTORY: /usr/local/bundle
  - USER INSTALLATION DIRECTORY: /root/.gem/ruby/2.6.0

...略

  - GEM PATHS:
     - /usr/local/bundle
     - /root/.gem/ruby/2.6.0
     - /usr/local/lib/ruby/gems/2.6.0

...略

ここでちなみにローカルで同様にgem environmentコマンドを実施した時の結果を下記に記します。

% gem environment 

RubyGems Environment:

...略

  - INSTALLATION DIRECTORY: /Users/ユーザー/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0
  - USER INSTALLATION DIRECTORY: /Users/ユーザー/.gem/ruby/2.6.0

...略

  - GEM PATHS:
     - /Users/ユーザー/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0
     - /Users/ユーザー/.gem/ruby/2.6.0

...略

INSTALLATION DIRECTORYに注目してください。
指定されているディレクトリがわずかに違います。
⑤の項目にてさらに解説します。

④ホームに戻る
# cd

root@コンテナID:~#
⑤default設定のあるディレクトリまでたどり着く

ここで、ローカル環境でのたどり着き方の場合は、INSTALLATION DIRECTORYに記載されているパスまでcdコマンドで行けばいいのですが・・・

ローカルの場合!

% cd /Users/ユーザー/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0
ユーザー@PC 2.6.0 %

コンテナ内の場合!!

# cd /usr/local/lib/ruby/gems/2.6.0
root@コンテナID:/usr/local/lib/ruby/gems/2.6.0#

INSTALLATION DIRECTORYに記載されているパスでは「そんなディレクトリは無い!!」と怒られたので、別のディレクトリを探したところ,
GEM PATHSに記載のあったディレクトリが正解だったようです。

⑥specificationsの中のdefaultを削除する

lsコマンドを使ってみると、このディレクトリの中にspecificationsというディレクトリがあるはずです。
この中にdefault設定があります。
※気になる方はここで、cd specificationsして、lsコマンドをすると、defaultと記載のあるGemがいくつかあると思います。

# rm -rf specifications/default/

これでspecifications内のdefaultディレクトリが全て削除されます。

⑦念の為、確認。
# gem list bundler

*** LOCAL GEMS ***

bundler (2.1.4)

bundlerのdefaultが解除されています。
ここでbundle installし、Gemfile.lockを確認すると最後の行に・・・

BUNDLED WITH
   2.1.4

と、自分の好きなバージョンでバンドルされていることが確認できました!

考察

ここで学んだことが2つあります。

まず1つ
当然ではありますが、ローカル環境とコンテナ環境は別物。Gemのバージョンの相違に気づけたこと。

もう1つ
同じgem environmentに返される結果は、似てこそあれども微妙な違いがあること。
自分の求める情報はどこに記載があるのか注視しなければならない。

これまでデプロイの時に何かしらのエラーに叩きのめされていたので、docker使用時に未然にGemのバージョンを合わせれて良かったと思います。

誰かの参考になれば幸いです。
ありがとうございました。

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

rails&vueでherokuにデプロイする際に参考になったこと

はじめに

Rails 6.0.3.5
ruby 2.6.5
yarn 1.22.10
にてデプロイを実行した。

アプリ概要

投稿サイト
・jwtによるログインログアウト機能
・投稿機能(ログイン後投稿可、投稿者のみ削除編集可)
・プロフィール編集機能

エラー内容

デプロイは成功し、
ログイン機能は正常に動いたのだが、
POST機能、EDIT機能を行う際に、
検証ツールのconsoleにて、

401 Unauthorized

エラーが出現、

なぜ、ログインしているにも関わらず401が出現するのか、

protect_from_forgery with: :null_session

になっていることを確認し、

skip_before_action :verify_authenticity_token

を追加。

すると、今度は、 

500 (Internal Server Error)

が出現、

$ heroku logs

にてエラー内容を確認したところ、

undefined method `post' for nil:NilClass

というエラー内容、
また、プロフィール編集時のエラーは、

undefined method `update' for nil:NilClass

となりました、、、

原因検索1

ローカル環境で動いているため、
[post]や[update]が定義されていないということは考えにくい。

def create
    post = current_user.posts.create!(post_params) #ここのcurrent_user
    render json: post, serializer: postSerializer
  end

postする前のcurrent_userが渡っていなと推測。

なぜcurrent_userが取れていないのか、
local環境とproduction環境の違いをチェック。

config/enviroments/development.rbとproduction.rbの違いを確認した。
認証に関わりそうな部分はなく、
特にここが原因ではないみたい。

原因検索2

jwtが送信できているかを、ブラウザ検証ツールのnetworkタブで確認する.
Authorization: Bearer にて、トークンが到達していることを確認。

原因検索3

ローカル環境で、問題なく動いているため、
再度、herokuにて問題がないことを確認、
herokuのsettings/Reveal Config Varsにて、、、、、

master_keyの設定をしていませんでした。。。

secret_key_baseを設定し安心し切っていましたが、
思わぬところに原因がありました。

補足

rails & vueのデプロイ
(herokuにpushした後)につきましては、
ログアウト → ログインをしてから、挙動を確かめるようにしましょう。

最後に

初心者のうちは思いもよらないところにミスがある可能性が高いので、
深読みしすぎず、やるべきことをしっかりやってあるかの確認を、
怠らないように気をつけたいと思います。

ご視聴いただきありがとうございます。
誰かの役に立てば幸いです。

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

GitにおけるRails Tutorialになかった注意点!(in チーム開発の学び)

はじめに

これは実務と同じ方式で行うチーム開発体験学習で、
GitHubを利用する際、現役の先輩エンジニアにご指摘いただいた内容です。
それまで私は全く考えてこなかったことですが、セキュリティを考えると当然のことです。

目次

  1. gitに記録してはいけないもの
  2. もし実務でやったらセキュリティインシデント(アカンやつ)
  3. 次からは気をつけよう。ネットに放流した鍵はもう安全ではない。

gitに記録してはいけないもの

端的に言うと下記2つのファイル

・ config/credential.yml.enc
・ config/master.key

これらをgitに記録してしまい、
あまつさえGitHubにプッシュしてしまいました。
やっちまったなあ!

もし実務でやったらセキュリティインシデント

こちらをご覧ください

つまり、秘密情報を管理する仕組みとその鍵を自分は公衆の面前に晒してしまったわけですね。
これが仕事だったらもう大変なことです。

ネットに放流した鍵はもう安全ではない。対策方法は?

GitHubに一度上げてしまったことを完全に戻すのは以下の方法を取らないといけないみたいです。

しかし、これも結局は悪用する人が先にセンシティブファイルを発見したら終わりなわけで…
こんなことにならないよう、普段から大事なファイルはgitignoreに書き込んでおきましょう!

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

flutter webがrailsを乗る

内容

flutter webで開発したfrontをrails serverでアクセルできるようにする手順

バージョン

flutter 2.0.1
rails 6.1.3

手順

  1. flutterをbuildする

flutter projectのフォルダで以下のcommandでbuildすると

flutter build web

フォルダbuild/webが作成される

  1. rails applicationを作成
rails new flutter-on-rails
rails g controller Home index

config/routes.rbを以下のように設定

Rails.application.routes.draw do
  root 'home#index'
end
  1. flutterのbuildしたファイルをrailsにcopy
    • flutter projectのフォルダbuild/web/assetsをrailsのapp/assets/下にcopy
    • flutter projectのフォルダbuild/web/iconsをrailsのapp/assets/下にcopy
    • flutterのbuildしたbuild/web/main.dart.jsファイルの内容をrailsのapp/javascript/packs/flutter.jsにcopy
    • flutterのファイルbuild/web/index.htmlの内容でrailsのapp/views/home/index.html.erbファイルを上書きする
    • railsのapp/views/home/index.html.erbファイルにflutter.jsの参照するようにする
<!DOCTYPE html>
<html>
<head>
  .......省略
  <link rel="manifest" href="manifest.json">
  <%= javascript_pack_tag "flutter" %>
</head>
  .......省略
  1. rails sでserverを起動する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ActiveRecord を googleauth の TokenStore として使う方法

Google API を Ruby から操作するための Google 製 gem が googleauth で、これは当然 OAuth2 のトークンをファイルや DB に保存しておくように作られているが、中に含まれているトークン保存のためのクラス (TokenStore) は以下の2種類しかなくて、つまり ActiveRecord 用の TokenStore は用意されていない。

だけど、上記 2 クラスは Google::Auth::TokenStore https://github.com/googleapis/google-auth-library-ruby/blob/master/lib/googleauth/stores/redis_token_store.rb を継承しているようだったので、雑に継承して通せたのでメモ。

これから作るのは、

  • GoogleAuthToken ( token_key と client_id の組をもつ Model )
  • ActiveRecordTokenStore ( Google::Auth::TokenStore のサブクラスで、 ActiveRecord 用 )
$ rails generate model google_auth_token token_key:string client_id:string 
class GoogleAuthToken < ApplicationRecord
  belongs_to :user #これは任意だけど、ふつうこういう形になるんじゃないかな 
  validates :token_key, presence: true, uniqueness: true
  validates :client_id, presence: true
end

app/models/active_record_token_store.rb に、

class ActiveRecordTokenStore < Google::Auth::TokenStore
  def initialize user_id
    @user_id = user_id
  end

  def load id
    record = GoogleAuthTokenStore.find_by! user_id: @user_id, token_key: id
    record.to_json
  end

  def store id, token
    json = JSON.parse(token)
    client_id = json["client_id"]
    access_token = json["access_token"]
    refresh_token = json["refresh_token"]
    expiration_time_millis = json["expiration_time_millis"]
    record = GoogleAuthToken.find_or_initialize_by user_id: @user_id, token_key: id, client_id: client_id, access_token: access_token, refresh_token: refresh_token, expiration_time_millis: expiration_time_millis
    record.save
  end

  def delete id
    record = GoogleAuthToken.find_by! user_id: @user_id, token_key: id
    record.destroy
  end
end

こんな感じにしておけば、例えば

def authorize client_id, token_store
  Google::Auth::UserAuthorizer.new client_id, [
    Google::Apis::DriveV3::AUTH_DRIVE,
  ], token_store
end

とすると、 API アクセスしやすい。

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

【Rails】CSSにbackground-imageで設定した背景画像が表示されない

bgimage.jpg
現在作成している自作のアプリケーションで、
上のように背景に画像を設定したく、
CSSでbackground-image プロパティを使えば良いことが分かり、
以下のように記述しました。

.sample-contents {
  width: 100vw;
  height: 350px;
  background-image: image-url('sample-top.JPG');
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
}

これで画像が出るはず!だったんですが、画像が表示されなかったので、
解決するために試したこと等を記録します。

解決したかったこと

CSSでbackground-image プロパティを使ったが、画像が表示されない

今回の解決方法

記述していた.css ファイルの拡張子を.scss に変更したら表示できた。

というわけで本当にただのケアレスミスでした。
ただまだcssとscssがどう違うのかや、何故cssだと表示できなくてscssだとできたのかはちゃんと理解できていないのでこれから勉強します。

解決するまでにチェックしたポイント

background-color は表示できるか

クラス名のスペルミスが無いかの確認も兼ねて一旦background-color を指定すると、
きちんと表示できました。
じゃあ画像に何か問題があるのかと考えて次へ。

画像パスの指定は合っているか

app/assets/images にちゃんと保存してあるかどうかを確認。

VSCodeの場合、ファイルを右クリックで画像のようにパスがコピーできたので、パスは確かだろうと確認できました。
スクリーンショット 2021-03-14 19.01.01.png

以上です。

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

ActionController::UnknownFormat in ○○Controller#show

○○Controller#show is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot.

うわあああああ~!!!!!!
難しそうなエラーだけど、対応するViewが無い時に起こるエラー
Viewがどうなっているか確認すれば治ることが多い!

いかなる時も落ち着いて!私っ!!

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

サーバーの構築からソースコードのデプロイまで(Amazon Linux 2, Nginx + Puma, Rails 6.1, Ruby2.7.2)

この記事での注意

この作業はローカル環境からの作業とサーバーでの作業が複雑に入り交じっています。その為、ターミナルで実行するコマンドの説明に「ローカル」「サーバー」と記載しています。お間違えないように気をつけてください。

アプリケーション名を「sample611」 としています。ご自身のアプリケーション名に読み替えてご参考ください。

サーバーにインストールする環境

  • Amazon Linux 2
  • Ruby 2.7.2 (rbenv)
  • Rails 6.1
  • MySQL or PostgreSQL
  • Nginx + Puma 5
  • Node.js 12
  • Yarn

本番サーバーの構築

本番サーバーのEC2のOSがAmazon Linux2の場合の設定手順を紹介します。

1. システムライブラリアップデート

まずはAmazon Linux 2のシステムライブラリを更新します。

サーバー
$ sudo yum update -y

2. タイムゾーン、ロケール

日本での運用する場合はタイムゾーンと言語の設定を日本にした方がわかりやすいと思います。

サーバー
$ sudo timedatectl set-timezone Asia/Tokyo
$ sudo localectl set-locale LANG=ja_JP.UTF-8

3. NTP

サーバーの時刻のずれを自動で補正するタイムサーバーの設定を行います。

サーバー
$ yum info chrony
$ chronyc sources -v

4. 標準パッケージインストール

Rubyのインストールなどで必要になるライブラリをインストールします。

サーバー
$ sudo yum -y install gcc-c++ glibc-headers openssl-devel readline libyaml-devel readline-devel zlib zlib-devel libffi-devel libxml2 libxslt libxml2-devel libxslt-devel git

5. データベースモジュールのインストール

Railsからデータベースにアクセスするためのアダプタをインストールする必要がありますが、そのインストールに必要なライブラリをデータベース別にインストールします。

  • PostgreSQLの場合
サーバー
$ sudo yum -y install postgresql-devel
  • MySQLの場合
サーバー
$ sudo yum -y install mysql-devel

6. Rubyのインストール

Railsを動かすためにはRubyのインストールが必要です。いろんなインストール方法がありますが、Rubyのバージョンを切り替えることができるrbenvを使った方法を説明します。

6.1 rbenvのインストール

サーバー
$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile
$ rbenv -v

6.2 ruby-buildのインストール

サーバー
$ git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ cd ~/.rbenv/plugins/ruby-build
$ sudo ./install.sh
$ rbenv install -l

6.3 Rubyのインストール

rbenvとruby-buildをインストールが終わったので、Rubyのインストールを行います。

サーバー
$ rbenv install 2.7.2
$ rbenv global 2.7.2

rubyのインストールにはすこし時間がかかります。
rbenv globalでシステム全体で使用するRubyのバージョンを指定しています。

7. Rails 6で必要なモジュールのインストール

7.1 Node.jsのインストール

Rails 6からは Node.jsYarn が必要になりました。Node.jsはサーバーサイドでのJavaScript実行環境で、Yarnはfacebook製のJavaScriptのパッケージマネージャーです。

サーバー
$ curl -sL https://rpm.nodesource.com/setup_14.x | sudo bash -
$ sudo yum install -y nodejs
$ sudo npm install -g n
$ sudo n stable
$ sudo ln -snf /usr/local/bin/node /usr/bin/node

7.2 Yarnのインストール

サーバー
$ curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo
$ sudo yum install yarn -y

8. Railsのインストール

Rails 6のインストールを行います。gem install railsでもインストールできますが、ここではパッケージ管理ソフトbundlerからインストールする方法を紹介します。

サーバー
$ cd ~
$ mkdir rails
$ cd rails
$ bundle init
$ vim Gemfile
(gem "rails"をコメントアウト解除)

$ bundle install
$ bundle exec rails -v
(インストールされたことを確認)

$ cd ..
$ rm -rf rails

データベースの設定

9. 本番データベースの作成

ここではRDSの設定については詳しく説明しません。すでにRDSにデータベースが設定されていることを前提に進めていきますが、下記をご確認ください。

  1. RDSにて作成したデータベースのユーザー名、パスワード、データベースエンドポイントをご確認ください。
  2. RDSのセキュリティグループでウェブサーバーとなるEC2のセキュリティグループのアクセス許可を設定してください。

10. Railsアプリと本番データベースの接続設定

Railsとデータベースの接続設定を行います。データベースの設定にはデータベースにアクセスするためのパスワードが含まれますが、これを直接database.ymlに記載すると、github上でパスワードを知ることができてしまい危険です。

こういった機密情報は config/credentials.yml.enc にセットします。(Rails5.2以降で可能)

このファイルはエディタで表示すると暗号化された文字列になっていますが、 master.key を使うことで閲覧、編集することができます。この credentials.yml.encmaster.key の関係を理解しておいてください。

次のコマンドでcredentials.yml.encを編集します。ここではエディタをvimに指定しています。

ローカル
$ EDITOR=vim bin/rails credentials:edit

下記のようにdbの項目を追記し、RDS作成時に設定したユーザー名、パスワード、データベースエンドポイントを記述します。

credential.yml.enc(ローカル)
# aws:
#   access_key_id: 123
#   secret_access_key: 345

# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

db:
  username: (username)
  password: (password)
  host: (database endpoint)

終わったら、:wqでエディタを終えます。

次に本番環境において、データベースの設定が適用されるようconfig/database.ymlを編集します。database.ymlの最後の方にproductionの項があります。そこを下記のように修正します。

database.yml(ローカル)
production:
  <<: *default
  database: sample611_production
  username: <%= Rails.application.credentials.db[:username] %>
  password: <%= Rails.application.credentials.db[:password] %>
  host:     <%= Rails.application.credentials.db[:host] %>
  port:     5432

Rails.application.credentials.dbとすることでcredentials.yml.encのdbの項を読みにいきます。
portはデータベースで使用するポートを指定します。一般的にはPostgreSQLの場合は 5432、MySQLの場合は 3306 になります。

ここで、改修したコードをいったんコミットし、githubへプッシュしましょう。

ローカル
$ git add .
$ git commit -m "Setup database"
$ git push origin master

デプロイ設定

デプロイツールCapistranoを使ったデプロイの設定を行います。

11. Capistranoのインストール

Capistranoをインストールします。下記のようにGemfileのdevelopmentグループにcapistrano, capistrano-rails, capistrano-rbenvを追加し、bundle installします。

Gemfile(ローカル)
group :development do
  gem "capistrano", "~> 3.14", require: false
  gem "capistrano-rails", "~> 1.6", require: false
  gem 'capistrano-rbenv', '~> 2.2'
end
ローカル
$ bundle install
$ bundle exec cap install

12. Capfileの設定

下記のようにCapfileに追記します。

Capfile(ローカル)
  :
# require "capistrano/rails/migrations"
# require "capistrano/passenger"
require 'capistrano/rails'
require 'capistrano/rbenv'
  :

13. デプロイの設定

Capistranoのデプロイ設定を行います。全体的なデプロイの設定を「config/deploy.rb」、本番環境用の設定を「config/deploy/production.rb」に記述します。

config/deploy.rb(ローカル)
lock "~> 3.15.0"

set :application, "sample611"

# 自分のリポジトリを設定
set :repo_url, "git@github.com:yukeippi/sample611.git"

# rbenvの設定
set :rbenv_type, :user
set :rbenv_ruby, '2.7.2'

# Railsアプリの設置先
set :deploy_to, "/var/rails/sample611"

# 共有ファイルの設定
append :linked_files, "config/master.key"

# 共有ディレクトリの設定
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"

# リリース保存数
set :keep_releases, 5

linked_filesはデフォルトではconfig/database.ymlとなっていますが、config/master.keyに変更してください。
次にconfig/deploy/production.rbを編集します。

下記の (server ip) にはデプロイ先EC2のIPをセットしてください。
githubへのキー登録については下記をご参照ください。

config/deploy/production.rb(ローカル)
set :rails_env, 'production'
set :branch, 'master'

# githubへの設定
set :ssh_options, {
  auth_methods: [ 'publickey' ],
  keys:         [ '~/.ssh/aws.pem' ],
}

server '(server IP)', user: 'ec2-user', roles: %w{app db web}

区切りが良いので、ここでいったんコミット、プッシュしましょう。(プッシュは任意です)

ローカル
$ git add .
$ git commit -m "Setup capistrano"
$ git push oririn master

14. Githubへのキー登録

デプロイ時にデプロイ先サーバーがgithubからソースコードを取得するための設定を行います。
この設定は下記の記事をご参照ください。

15. デプロイ先ディレクトリの作成

Railsアプリのプログラムの設置(デプロイ)を行います。アプリケーションの設置場所を「/var/rails」、実行者を「ec2-user」とした場合、下記のコマンドを実行し、ディレクトリを作ります。

サーバー
$ cd /var/
$ sudo mkdir rails
$ sudo chown ec2-user:ec2-user rails

ここで「cap deploy:check」を実行しますが、下記のように失敗します。この処理の目的は、Capistranoにデプロイに必要なディレクトリを作らせることです。

ローカル
$ bundle exec cap production deploy:check

00:02 deploy:check:make_linked_dirs
      01 mkdir -p /var/rails/sample611/shared/config
    ✔ 01 ec2-user@(server IP) 0.078s
00:02 deploy:check:linked_files
      ERROR linked file /var/rails/sample611/shared/config/master.key does not exist on (server IP)

エラーメッセージにあるように「master.key」がないことが原因です。サーバーに入って下記のようにmaster.keyを作成してください。

サーバー
$ cd /var/rails/sample611/shared/config/
$ vim master.key
(ローカルにあるmaster.keyを貼り付け)

これで問題は解決しましたので、再度、デプロイチェックを行います。

ローカル
$ bundle exec cap production deploy:check

成功するはずです。これでデプロイ先ディレクトリができました。

16. Databaseのcreate

しかし、まだこの時点でもデプロイ途中で失敗します。それはデータベースがcreateされていないからです。deployでデータベースのmigrateは実行しますが、createしていないため、migrateできずに失敗してしまいます。

そこで、失敗前提でデプロイを実行し、そのときにデプロイされたソースコードを使ってcreateします。

ローカル
$ bundle exec cap production deploy

最初はgemをインストールするため、時間がかかります。
しばらく待つとデプロイは終わり、途中で失敗していることが確認できます。その後、本番サーバーに入り、下記のようにデプロイされたソースコードのルートに移動し、データベースを作成します。

サーバー
$ cd /var/rails/sample611/releases/(最新リリース)/
$ bundle exec rails db:create RAILS_ENV=production

(最新リリース)は「20210216104333」といったディレクトリ作成日時を意味するディレクトリを指します。ソースコードがデプロイされると、Capistranoがこのようなディレクトリを作成し、そこにプログラムを保存します。

これでデータベースが作成されたら、デプロイが可能になります。
再度デプロイします。

ローカル
$ bundle exec cap production deploy

これで成功するはずです。

WebサーバーとRailsアプリの連携

17. Pumaの設定

本番環境のPumaの設定を行います。

config/puma/production.rb(ローカル)
# Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum; this matches the default thread size of Active Record.
#
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
port        ENV.fetch("PORT") { 3000 }

# Specifies the `environment` that Puma will run in.
#
environment ENV.fetch("RAILS_ENV") { "production" }

# Specifies the `pidfile` that Puma will use.
# pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }

# Specifies the number of `workers` to boot in clustered mode.
# Workers are forked web server processes. If using threads and workers together
# the concurrency of the application would be max `threads` * `workers`.
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }

# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory.
#
# preload_app!

# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart

bind "unix:///var/rails/sample611/shared/tmp/sockets/puma.sock"

RAILS_ENVをproductionに指定しているところと、最下行でsocketを指定しているのがポイントです。

ここでいったんコミット、プッシュしてください。

ローカル
$ git add .
$ git commit -m "Setup puma for production"
$ git push origin master

18. Nginxのインストール

ここからはサーバーでの作業になります。
サーバーにNginxをインストールします。

サーバー
$ sudo amazon-linux-extras install nginx1 -y

19. NginxとPumaの紐付け

次にNginxとPumaを紐付けるための設定ファイルを作成します。ポイントはuptreamの設定、rootの指定、proxy_passの指定です。

サーバー
$ sudo vim /etc/nginx/conf.d/sample611.conf
/etc/nginx/conf.d/sample611.conf(サーバー)
upstream puma {
    server unix:///var/rails/sample611/shared/tmp/sockets/puma.sock fail_timeout=0;
}

server {
    listen 80;
    server_name example.com;

    root /var/rails/sample611/current/public;

    try_files $uri/index.html $uri @railsapp;

    location @railsapp {
        proxy_pass http://puma;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
    }

    error_page 500 502 503 504 /500.html;
    client_max_body_size 4G;
    keepalive_timeout 10;
}

upstream puma内にあるpuma.sockのパスに気をつけてください。これは15のbindで設定したパスにあわせなければなりません。

20. Pumaのサービス設定

次にpumaをsystemctlで起動や再起動ができるように設定を行います。

サーバー
$ sudo vim /etc/systemd/system/puma.service
/etc/systemd/system/puma.service(サーバー)
[Unit]
Description=Puma HTTP Server
After=network.target

[Service]
Type=simple
Environment="RAILS_ENV=production"
WorkingDirectory=/var/rails/sample611/current
ExecStart=/home/ec2-user/.rbenv/shims/bundle exec /var/rails/sample611/shared/bundle/ruby/2.7.0/bin/puma -C /var/rails/sample611/current/config/puma/production.rb
Restart=always

[Install]
WantedBy=multi-user.target

ここでpumaの実行パスや実行時の設定ファイルを指定しています。
あと、このままでは実行権限がないため、下記のコマンドでpuma.serviceに実行権限を付与してください。

サーバー
$ sudo chmod +x /etc/systemd/system/puma.service

これでサーバー側の設定は終わりです。

21. Puma, Nginxの起動

いよいよウェブサーバー、アプリケーションサーバーの起動です。まず、Pumaを起動します。

サーバー
$ sudo systemctl daemon-reload
$ sudo systemctl start puma

下記のコマンドで起動を確認します。

サーバー
$ sudo systemctl status puma

● puma.service - Puma HTTP Server for sample611 (production_mysql)
   Loaded: loaded (/etc/systemd/system/puma.service; enabled; vendor preset: disabled)
   Active: active (running) since 土 2021-02-20 15:47:30 JST; 27min ago
 Main PID: 2876 (bundle)
   CGroup: /system.slice/puma.service
           └─2876 puma 5.1.1 (tcp://0.0.0.0:3000,unix:///var/rails/sample611/shared/tmp/sockets/puma.sock) [20210216072308]

上記のようになければ「Active: active」になっていれば、起動成功です。
もし「Active: inactive」になっていれば、何かしらの設定に間違いがある可能性があります。
journalctl -xe等を使って原因を調べてください。

次にnginxの起動します。

サーバー
$ sudo systemctl start nginx.service

これでブラウザを使って下記のようにサイトにアクセスすれば画面に表示されるはずです。

http://(サイトのIP)/

21. デプロイ時のリスタート設定

プログラムをデプロイしたときはアプリケーションサーバーをリスタートする必要があります。capistranoのrestartタスクを追加しておくと、それも自動で実行してくれます。

config/deploy.rb(ローカル)
namespace :deploy do
  task :restart do
    on roles(:web), in: :sequence, wait: 5 do
      within release_path do
        execute "sudo systemctl daemon-reload"
        execute "sudo systemctl restart puma"
      end
    end
  end

  after :finishing, :restart
end

これでローカル側のコード改修が終わりです。
最後にここまでをコミットしましょう。

ローカル
$ git add .
$ git commit -m "Setup restart puma"

再起動することを確認するため、デプロイします。

ローカル
$ bundle exec cap production deploy

 :
00:34 deploy:cleanup
      Keeping 5 of 6 deployed releases on (server IP)
      01 rm -rf /var/rails/sample611/releases/20210314010936
    ✔ 01 ec2-user@(server IP) 0.797s
00:35 deploy:restart
      01 sudo systemctl daemon-reload
    ✔ 01 ec2-user@(server IP) 0.199s
      02 sudo systemctl restart puma
    ✔ 02 ec2-user@(server IP) 0.094s
00:35 deploy:log_revision
      01 echo "Branch master (at 0e3b185044e3a56f20a3bf37d7c6ce31f5a92b54) deployed as release 20210314015609 b…
    ✔ 01 ec2-user@(server IP) 0.119s

デプロイログからrestartが実行されていることがわかると思います。
これでデプロイは成功です!お疲れ様でした。

puma, nginxの自動起動設定

最後にpuma,nginxの自動起動設定を行っておくとよいでしょう。これを行うことでサーバーを再起動しても自動でWebアプリケーションを起動してくれます。

サーバー
$ sudo systemctl enable puma.service
$ sudo systemctl enable nginx.service

以上です。

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

Rails 6アプリをAWSへのデプロイする手順

この記事での注意

この作業はローカル環境からの作業とサーバーでの作業が複雑に入り交じっています。その為、ターミナルで実行するコマンドの説明に「ローカル」「サーバー」と記載しています。お間違えないように気をつけてください。

アプリケーション名を「sample611」 としています。ご自身のアプリケーション名に読み替えてご参考ください。

サーバーにインストールする環境

  • Amazon Linux 2
  • Ruby 2.7.2 (rbenv)
  • Rails 6.1
  • MySQL or PostgreSQL
  • Nginx + Puma 5
  • Node.js 12
  • Yarn

本番サーバーの構築

本番サーバーのEC2のOSがAmazon Linux2の場合の設定手順を紹介します。

1. システムライブラリアップデート

まずはAmazon Linux 2のシステムライブラリを更新します。

サーバー
$ sudo yum update -y

2. タイムゾーン、ロケール

日本での運用する場合はタイムゾーンと言語の設定を日本にした方がわかりやすいと思います。

サーバー
$ sudo timedatectl set-timezone Asia/Tokyo
$ sudo localectl set-locale LANG=ja_JP.UTF-8

3. NTP

サーバーの時刻のずれを自動で補正するタイムサーバーの設定を行います。

サーバー
$ yum info chrony
$ chronyc sources -v

4. 標準パッケージインストール

Rubyのインストールなどで必要になるライブラリをインストールします。

サーバー
$ sudo yum -y install gcc-c++ glibc-headers openssl-devel readline libyaml-devel readline-devel zlib zlib-devel libffi-devel libxml2 libxslt libxml2-devel libxslt-devel git

5. データベースモジュールのインストール

Railsからデータベースにアクセスするためのアダプタをインストールする必要がありますが、そのインストールに必要なライブラリをデータベース別にインストールします。

  • PostgreSQLの場合
サーバー
$ sudo yum -y install postgresql-devel
  • MySQLの場合
サーバー
$ sudo yum -y install mysql-devel

6. Rubyのインストール

Railsを動かすためにはRubyのインストールが必要です。いろんなインストール方法がありますが、Rubyのバージョンを切り替えることができるrbenvを使った方法を説明します。

6.1 rbenvのインストール

サーバー
$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile
$ rbenv -v

6.2 ruby-buildのインストール

サーバー
$ git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ cd ~/.rbenv/plugins/ruby-build
$ sudo ./install.sh
$ rbenv install -l

6.3 Rubyのインストール

rbenvとruby-buildをインストールが終わったので、Rubyのインストールを行います。

サーバー
$ rbenv install 2.7.2
$ rbenv global 2.7.2

rubyのインストールにはすこし時間がかかります。
rbenv globalでシステム全体で使用するRubyのバージョンを指定しています。

7. Rails 6で必要なモジュールのインストール

7.1 Node.jsのインストール

Rails 6からは Node.jsYarn が必要になりました。Node.jsはサーバーサイドでのJavaScript実行環境で、Yarnはfacebook製のJavaScriptのパッケージマネージャーです。

サーバー
$ curl -sL https://rpm.nodesource.com/setup_14.x | sudo bash -
$ sudo yum install -y nodejs
$ sudo npm install -g n
$ sudo n stable
$ sudo ln -snf /usr/local/bin/node /usr/bin/node

7.2 Yarnのインストール

サーバー
$ curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo
$ sudo yum install yarn -y

8. Railsのインストール

Rails 6のインストールを行います。gem install railsでもインストールできますが、ここではパッケージ管理ソフトbundlerからインストールする方法を紹介します。

サーバー
$ cd ~
$ mkdir rails
$ cd rails
$ bundle init
$ vim Gemfile
(gem "rails"をコメントアウト解除)

$ bundle install
$ bundle exec rails -v
(インストールされたことを確認)

$ cd ..
$ rm -rf rails

データベースの設定

9. 本番データベースの作成

ここではRDSの設定については詳しく説明しません。すでにRDSにデータベースが設定されていることを前提に進めていきますが、下記をご確認ください。

  1. RDSにて作成したデータベースのユーザー名、パスワード、データベースエンドポイントをご確認ください。
  2. RDSのセキュリティグループでウェブサーバーとなるEC2のセキュリティグループのアクセス許可を設定してください。

10. Railsアプリと本番データベースの接続設定

Railsとデータベースの接続設定を行います。データベースの設定にはデータベースにアクセスするためのパスワードが含まれますが、これを直接database.ymlに記載すると、github上でパスワードを知ることができてしまい危険です。

こういった機密情報は config/credentials.yml.enc にセットします。(Rails5.2以降で可能)

このファイルはエディタで表示すると暗号化された文字列になっていますが、 master.key を使うことで閲覧、編集することができます。この credentials.yml.encmaster.key の関係を理解しておいてください。

次のコマンドでcredentials.yml.encを編集します。ここではエディタをvimに指定しています。

ローカル
$ EDITOR=vim bin/rails credentials:edit

下記のようにdbの項目を追記し、RDS作成時に設定したユーザー名、パスワード、データベースエンドポイントを記述します。

credential.yml.enc(ローカル)
# aws:
#   access_key_id: 123
#   secret_access_key: 345

# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

db:
  username: (username)
  password: (password)
  host: (database endpoint)

終わったら、:wqでエディタを終えます。

次に本番環境において、データベースの設定が適用されるようconfig/database.ymlを編集します。database.ymlの最後の方にproductionの項があります。そこを下記のように修正します。

database.yml(ローカル)
production:
  <<: *default
  database: sample611_production
  username: <%= Rails.application.credentials.db[:username] %>
  password: <%= Rails.application.credentials.db[:password] %>
  host:     <%= Rails.application.credentials.db[:host] %>
  port:     5432

Rails.application.credentials.dbとすることでcredentials.yml.encのdbの項を読みにいきます。
portはデータベースで使用するポートを指定します。一般的にはPostgreSQLの場合は 5432、MySQLの場合は 3306 になります。

ここで、改修したコードをいったんコミットし、githubへプッシュしましょう。

ローカル
$ git add .
$ git commit -m "Setup database"
$ git push origin master

デプロイ設定

デプロイツールCapistranoを使ったデプロイの設定を行います。

11. Capistranoのインストール

Capistranoをインストールします。下記のようにGemfileのdevelopmentグループにcapistrano, capistrano-rails, capistrano-rbenvを追加し、bundle installします。

Gemfile(ローカル)
group :development do
  gem "capistrano", "~> 3.14", require: false
  gem "capistrano-rails", "~> 1.6", require: false
  gem 'capistrano-rbenv', '~> 2.2'
end
ローカル
$ bundle install
$ bundle exec cap install

12. Capfileの設定

下記のようにCapfileに追記します。

Capfile(ローカル)
  :
# require "capistrano/rails/migrations"
# require "capistrano/passenger"
require 'capistrano/rails'
require 'capistrano/rbenv'
  :

13. デプロイの設定

Capistranoのデプロイ設定を行います。全体的なデプロイの設定を「config/deploy.rb」、本番環境用の設定を「config/deploy/production.rb」に記述します。

config/deploy.rb(ローカル)
lock "~> 3.15.0"

set :application, "sample611"

# 自分のリポジトリを設定
set :repo_url, "git@github.com:yukeippi/sample611.git"

# rbenvの設定
set :rbenv_type, :user
set :rbenv_ruby, '2.7.2'

# Railsアプリの設置先
set :deploy_to, "/var/rails/sample611"

# 共有ファイルの設定
append :linked_files, "config/master.key"

# 共有ディレクトリの設定
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"

# リリース保存数
set :keep_releases, 5

linked_filesはデフォルトではconfig/database.ymlとなっていますが、config/master.keyに変更してください。
次にconfig/deploy/production.rbを編集します。

下記の (server ip) にはデプロイ先EC2のIPをセットしてください。
githubへのキー登録については下記をご参照ください。

config/deploy/production.rb(ローカル)
set :rails_env, 'production'
set :branch, 'master'

# githubへの設定
set :ssh_options, {
  auth_methods: [ 'publickey' ],
  keys:         [ '~/.ssh/aws.pem' ],
}

server '(server IP)', user: 'ec2-user', roles: %w{app db web}

区切りが良いので、ここでいったんコミット、プッシュしましょう。(プッシュは任意です)

ローカル
$ git add .
$ git commit -m "Setup capistrano"
$ git push oririn master

14. Githubへのキー登録

デプロイ時にデプロイ先サーバーがgithubからソースコードを取得するための設定を行います。
この設定は下記の記事をご参照ください。

15. デプロイ先ディレクトリの作成

Railsアプリのプログラムの設置(デプロイ)を行います。アプリケーションの設置場所を「/var/rails」、実行者を「ec2-user」とした場合、下記のコマンドを実行し、ディレクトリを作ります。

サーバー
$ cd /var/
$ sudo mkdir rails
$ sudo chown ec2-user:ec2-user rails

ここで「cap deploy:check」を実行しますが、下記のように失敗します。この処理の目的は、Capistranoにデプロイに必要なディレクトリを作らせることです。

ローカル
$ bundle exec cap production deploy:check

00:02 deploy:check:make_linked_dirs
      01 mkdir -p /var/rails/sample611/shared/config
    ✔ 01 ec2-user@(server IP) 0.078s
00:02 deploy:check:linked_files
      ERROR linked file /var/rails/sample611/shared/config/master.key does not exist on (server IP)

エラーメッセージにあるように「master.key」がないことが原因です。サーバーに入って下記のようにmaster.keyを作成してください。

サーバー
$ cd /var/rails/sample611/shared/config/
$ vim master.key
(ローカルにあるmaster.keyを貼り付け)

これで問題は解決しましたので、再度、デプロイチェックを行います。

ローカル
$ bundle exec cap production deploy:check

成功するはずです。これでデプロイ先ディレクトリができました。

16. Databaseのcreate

しかし、まだこの時点でもデプロイ途中で失敗します。それはデータベースがcreateされていないからです。deployでデータベースのmigrateは実行しますが、createしていないため、migrateできずに失敗してしまいます。

そこで、失敗前提でデプロイを実行し、そのときにデプロイされたソースコードを使ってcreateします。

ローカル
$ bundle exec cap production deploy

最初はgemをインストールするため、時間がかかります。
しばらく待つとデプロイは終わり、途中で失敗していることが確認できます。その後、本番サーバーに入り、下記のようにデプロイされたソースコードのルートに移動し、データベースを作成します。

サーバー
$ cd /var/rails/sample611/releases/(最新リリース)/
$ bundle exec rails db:create RAILS_ENV=production

(最新リリース)は「20210216104333」といったディレクトリ作成日時を意味するディレクトリを指します。ソースコードがデプロイされると、Capistranoがこのようなディレクトリを作成し、そこにプログラムを保存します。

これでデータベースが作成されたら、デプロイが可能になります。
再度デプロイします。

ローカル
$ bundle exec cap production deploy

これで成功するはずです。

WebサーバーとRailsアプリの連携

17. Pumaの設定

本番環境のPumaの設定を行います。

config/puma/production.rb(ローカル)
# Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum; this matches the default thread size of Active Record.
#
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count

# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
port        ENV.fetch("PORT") { 3000 }

# Specifies the `environment` that Puma will run in.
#
environment ENV.fetch("RAILS_ENV") { "production" }

# Specifies the `pidfile` that Puma will use.
# pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }

# Specifies the number of `workers` to boot in clustered mode.
# Workers are forked web server processes. If using threads and workers together
# the concurrency of the application would be max `threads` * `workers`.
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }

# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory.
#
# preload_app!

# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart

bind "unix:///var/rails/sample611/shared/tmp/sockets/puma.sock"

RAILS_ENVをproductionに指定しているところと、最下行でsocketを指定しているのがポイントです。

ここでいったんコミット、プッシュしてください。

ローカル
$ git add .
$ git commit -m "Setup puma for production"
$ git push origin master

18. Nginxのインストール

ここからはサーバーでの作業になります。
サーバーにNginxをインストールします。

サーバー
$ sudo amazon-linux-extras install nginx1 -y

19. NginxとPumaの紐付け

次にNginxとPumaを紐付けるための設定ファイルを作成します。ポイントはuptreamの設定、rootの指定、proxy_passの指定です。

サーバー
$ sudo vim /etc/nginx/conf.d/sample611.conf
/etc/nginx/conf.d/sample611.conf(サーバー)
upstream puma {
    server unix:///var/rails/sample611/shared/tmp/sockets/puma.sock fail_timeout=0;
}

server {
    listen 80;
    server_name example.com;

    root /var/rails/sample611/current/public;

    try_files $uri/index.html $uri @railsapp;

    location @railsapp {
        proxy_pass http://puma;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
    }

    error_page 500 502 503 504 /500.html;
    client_max_body_size 4G;
    keepalive_timeout 10;
}

upstream puma内にあるpuma.sockのパスに気をつけてください。これは15のbindで設定したパスにあわせなければなりません。

20. Pumaのサービス設定

次にpumaをsystemctlで起動や再起動ができるように設定を行います。

サーバー
$ sudo vim /etc/systemd/system/puma.service
/etc/systemd/system/puma.service(サーバー)
[Unit]
Description=Puma HTTP Server
After=network.target

[Service]
Type=simple
Environment="RAILS_ENV=production"
WorkingDirectory=/var/rails/sample611/current
ExecStart=/home/ec2-user/.rbenv/shims/bundle exec /var/rails/sample611/shared/bundle/ruby/2.7.0/bin/puma -C /var/rails/sample611/current/config/puma/production.rb
Restart=always

[Install]
WantedBy=multi-user.target

ここでpumaの実行パスや実行時の設定ファイルを指定しています。
あと、このままでは実行権限がないため、下記のコマンドでpuma.serviceに実行権限を付与してください。

サーバー
$ sudo chmod +x /etc/systemd/system/puma.service

これでサーバー側の設定は終わりです。

21. Puma, Nginxの起動

いよいよウェブサーバー、アプリケーションサーバーの起動です。まず、Pumaを起動します。

サーバー
$ sudo systemctl daemon-reload
$ sudo systemctl start puma

下記のコマンドで起動を確認します。

サーバー
$ sudo systemctl status puma

● puma.service - Puma HTTP Server for sample611 (production_mysql)
   Loaded: loaded (/etc/systemd/system/puma.service; enabled; vendor preset: disabled)
   Active: active (running) since 土 2021-02-20 15:47:30 JST; 27min ago
 Main PID: 2876 (bundle)
   CGroup: /system.slice/puma.service
           └─2876 puma 5.1.1 (tcp://0.0.0.0:3000,unix:///var/rails/sample611/shared/tmp/sockets/puma.sock) [20210216072308]

上記のようになければ「Active: active」になっていれば、起動成功です。
もし「Active: inactive」になっていれば、何かしらの設定に間違いがある可能性があります。
journalctl -xe等を使って原因を調べてください。

次にnginxの起動します。

サーバー
$ sudo systemctl start nginx.service

これでブラウザを使って下記のようにサイトにアクセスすれば画面に表示されるはずです。

http://(サイトのIP)/

21. デプロイ時のリスタート設定

プログラムをデプロイしたときはアプリケーションサーバーをリスタートする必要があります。capistranoのrestartタスクを追加しておくと、それも自動で実行してくれます。

config/deploy.rb(ローカル)
namespace :deploy do
  task :restart do
    on roles(:web), in: :sequence, wait: 5 do
      within release_path do
        execute "sudo systemctl daemon-reload"
        execute "sudo systemctl restart puma"
      end
    end
  end

  after :finishing, :restart
end

これでローカル側のコード改修が終わりです。
最後にここまでをコミットしましょう。

ローカル
$ git add .
$ git commit -m "Setup restart puma"

再起動することを確認するため、デプロイします。

ローカル
$ bundle exec cap production deploy

 :
00:34 deploy:cleanup
      Keeping 5 of 6 deployed releases on (server IP)
      01 rm -rf /var/rails/sample611/releases/20210314010936
    ✔ 01 ec2-user@(server IP) 0.797s
00:35 deploy:restart
      01 sudo systemctl daemon-reload
    ✔ 01 ec2-user@(server IP) 0.199s
      02 sudo systemctl restart puma
    ✔ 02 ec2-user@(server IP) 0.094s
00:35 deploy:log_revision
      01 echo "Branch master (at 0e3b185044e3a56f20a3bf37d7c6ce31f5a92b54) deployed as release 20210314015609 b…
    ✔ 01 ec2-user@(server IP) 0.119s

デプロイログからrestartが実行されていることがわかると思います。
これでデプロイは成功です!お疲れ様でした。

puma, nginxの自動起動設定

最後にpuma,nginxの自動起動設定を行っておくとよいでしょう。これを行うことでサーバーを再起動しても自動でWebアプリケーションを起動してくれます。

サーバー
$ sudo systemctl enable puma.service
$ sudo systemctl enable nginx.service

以上です。

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

プログラミング初学者がLINE-botを作ってみた

概要

私事ですが、全く天気予報を見ないです。
そのため朝に雨が降っていなければ傘を持っていかず、毎回コンビニで傘を買ったり、雨に打たれながら帰宅することが多々あります。
そこで!傘代の節約、これ以上家に傘を増やさないためにも、自分用に朝7時に傘が必要か通知してくれて、こちらが「明日・明後日・今日・などなど」と入力すればその日に雨が降るかを返してくれるLINE Botを実装してみました。

IMB_wjbsdm.gif

手順

・事前にRailsの環境を整える
新規Railsアプリケーションの作成「rails new」コマンドを実行

$ rails new アプリケーション名

作成されたディレクトリ(フォルダ)に移動

$ cd 作成されたディレクトリ名

Gemfileの一番下に以下のコードを追加

gem ' dotenv-rails ' 
gem 'line-bot-api' #dotenv-railsは環境変数を扱うために使用するgem

以下のコマンドでGemをインストール

$bundle install

Userモデルを作成

$ rails generate model モデル名 カラム名:データ型

上記のコマンドで新しくマイグレーションファイルも作成される。このマイグレーションファイルを以下のように編集

class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :line_id, null: false # この行を修正

      t.timestamps
    end
  end
end

以下のコマンドでデータベース・テーブルを作成

$ bundle exec rails db:create
$ bundle exec rails db:migrate

新規作成されたファイルを編集

lib/tasks/scheduler.rake
task :update_feed => :environment do
  require 'line/bot'  # gem 'line-bot-api'
  require 'open-uri'
  require 'kconv'
  require 'rexml/document'
#line-bot側の設定
  client ||= Line::Bot::Client.new { |config|
    config.channel_secret = ENV["LINE_CHANNEL_SECRET"]
    config.channel_token = ENV["LINE_CHANNEL_TOKEN"]
  }
  # 使用したいxmlデータURLを入力
  # xmlデータをパース
  # パスの共通部分を変数化
  # 6時〜12時の降水確率
  # メッセージを発信する降水確率の下限値の設定
  min_per = 20
  if per06to12.to_i >= min_per || per12to18.to_i >= min_per || per18to24.to_i >= min_per
    word1 =
      ["テキスト入力",].sample
    word2 =
      ["テキスト入力"].sample
    # 降水確率によってメッセージを変更する閾値の設定。
#ifの条件式の中で降水確率によって送信されるメッセージを変更
    mid_per = 50
    if per06to12.to_i >= mid_per || per12to18.to_i >= mid_per || per18to24.to_i >= mid_per
      word3 = "テキスト入力"
    else
      word3 = "テキスト入力!"
    end
    # 発信するメッセージの設定
    push =
      "#{word1}\n#{word3}\n降水確率\n   6〜12時 #{per06to12}\n 12〜18時  #{per12to18}\n 18〜24時 #{per18to24}\n#{word2}"
    # メッセージの発信先idを配列で渡す必要があるため、userテーブルよりpluck関数を使ってidを配列で取得。
#multicastsメソッドは、今回利用しているgem「line-bot-api」で定義されており、このメソッドを呼び出している
    user_ids = User.all.pluck(:line_id)
    message = {
      type: 'text',
      text: push
    }
    response = client.multicast(user_ids, message)
  end
  "OK"
end

以下のコマンドでコントローラの作成

$ bundle exec rails g controller linebot

作成されたコントローラーの編集

app/controller/linebot_controller.rb
class LinebotController < ApplicationController
  require 'line/bot'  # gem 'line-bot-api'
  require 'open-uri'
  require 'kconv'
  require 'rexml/document'

  def callback
    body = request.body.read
    signature = request.env['HTTP_X_LINE_SIGNATURE']
    unless client.validate_signature(body, signature)
      return head :bad_request
    end
    events = client.parse_events_from(body)
    events.each { |event|
      case event
        # メッセージが送信された場合の対応を入力

        # ユーザーからテキスト形式のメッセージが送られて来た場合の対応を入力

          when /.*(明日|あした).*/
            # info[2]:明日の天気
            per06to12 = doc.elements[xpath + 'info[2]/rainfallchance/period[2]'].text
            per12to18 = doc.elements[xpath + 'info[2]/rainfallchance/period[3]'].text
            per18to24 = doc.elements[xpath + 'info[2]/rainfallchance/period[4]'].text
            if per06to12.to_i >= min_per || per12to18.to_i >= min_per || per18to24.to_i >= min_per
              push =
                "テキスト\nテキスト\n降水確率\n   6〜12時 #{per06to12}\n 12〜18時  #{per12to18}\n 18〜24時 #{per18to24}\nテキスト"
            else
              push =
                "テキスト"            end
          # テキスト以外(画像等)のメッセージが送られた場合
        else
          push = "テキスト"
        end
        message = {
          type: 'text',
          text: push
        }
        client.reply_message(event['replyToken'], message)
        # LINEお友達追された場合
      when Line::Bot::Event::Follow
        # 登録したユーザーのidをユーザーテーブルに格納
        line_id = event['source']['userId']
        User.create(line_id: line_id)
        # LINEお友達解除された場合
      when Line::Bot::Event::Unfollow
        # お友達解除したユーザーのデータをユーザーテーブルから削除
        line_id = event['source']['userId']
        User.find_by(line_id: line_id).destroy
      end
    }
    head :ok
  end

  private

  def client
    @client ||= Line::Bot::Client.new { |config|
      config.channel_secret = ENV["LINE_CHANNEL_SECRET"]
      config.channel_token = ENV["LINE_CHANNEL_TOKEN"]
    }
  end
end



記号の意味
・「.」:何か1文字(「あ」や「a」など1文字なら何でもヒットします)
・「*」:直前の文字が0回以上繰り返す場合にマッチ


ルーティングの設定
メッセージが来た時、友達追加された時、解除がされた時に、linebotコントローラのcallbackアクションが呼ばれるようにしています。

config/routes.rb
Rails.application.routes.draw do
  post '/callback' => 'linebot#callback'
end

この後、お好きなレンタルサーバー、共有サーバーでデプロイ
(私はHerokuでデプロイしました)


こちらの
LINE Botアカウント作成・設定
をして完成!!

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

テストコード

テストコードについて

テストコードとはアプリの挙動を確認するためのコードである。
テストコードを行うことで以下のような利点がある。

1.アプリの内容が理解できる

これはコードを書くときは必ず確認したい事項があるのでそれを記述できるということは、アプリの内容を理解しているということになる。

2.クオリティが担保できる

ブラウザでひとつひとつ確認していると抜け漏れが発生し、全て確認できない可能性がある。また仕様が変更されるたびにブラウザで確認をしなければならなくなる。これらを防ぐことができる

テストコードの種類

単体テストコード

モデルやコントローラーなどのひとつひとつの機能を確認する。例えばバリデーションが働くかなど。

結合テストコード

ユーザーが行う挙動の流れを再現して問題ないか確認する。

テストコードのパターン

テストコードにはうまくいく時とうまくいかない時を記述する。
前者を正常系、後者を異常系という。

テストコードを書くための準備

Rspecの導入
Gemfileのgroup developement, ;test do~end の間に gem 'rspec-rails,' '~> 4.0.0'と記述する。
記述できたらbundle install
アプリでRspecを使えるようにする
rails g rspec:install
いくつかファイルが形成される。
そのなかに.rspecというファイルがあるのでそこに -format documentationと記述する。
そうすることでターミナルで実行結果が見れるようになる。
テストコードを書くためのファイル
Userモデルだったら

ターミナル
rails g rspec:model user

最後にファイルを開いてrequire 'rails_helper'という記述が一番上にあることを確認する。
これはRspecでrailsの機能のテストを行うときは、共通の設定やメソッドを適用するためにspec/rails_helper.rbというファイルを読みこむ必要がある。
これは生成時にはすでに記述されている。

以上です。

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

Ruby on Rails ディレクトリ構造

【Ruby on Rails ディレクトリ構造】

Ruby on RailsでのWebアプリケーション開発をする時に
ディレクトリ構造の理解が必要なため備忘録としてまとめました。

Webアプリケーションを開発する時は、app、config、dbをよく使います。

それぞれのディレクトリ構造の理解をしよう。

app:MVCのアプリケーションに関するものを管理
bin:Webアプリケーション起動時に使用するスクリプトファイルを管理
config:ルーティングやDBの設定ファイルを管理
db:DB関連のマイグレーションスクリプトやシートデータを管理
lib:全体で共有する必要のあるライブラリを管理
log:Webアプリケーション実行時のログファイルを管理
public:静的なファイルやページなどのリソースを管理
test:テスト用のコードファイルやデータなどを管理
tmp:一時的な情報を管理
vendor:第三者によって開発されたコードを配置する

Webアプリケーション直下に生成されるファイル

.gitignore:gitのバージョン管理対象から外すファイル
.ruby-version:Rubyのバージョンを管理するファイル
Gemfile:Railsで使用するGemパッケージの設定ファイル
Gemfile.lock:Gemパッケージの依存関係を管理するファイル
README.md:起動実行手順の説明用のファイル
Rakefile:Rakeコマンドの実行を管理するファイル
config.ru:Rackがrailsサーバー起動に使用する設定ファイル
package.json:npmを使用する場合に必要なファイル

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

ポートフォリオのAWSネットワーク構成を整理した【Rails + Nginx + Unicorn + MySQL】

Railsアプリポートフォリオのネットワーク構成を頭の整理と備忘を兼ねて整理してみました。
俯瞰的に整理するものなので各リソースやネットワークの細かい設定等には触れません。
初学者なりに自分の言葉で整理したものになっていますので、おかいし点などありましたらご指摘いただけると幸いです。

はじめに:参考書籍

AWSのネットワーク構築において以下の本を参考にさせていただきました。
初学者でも大変分かりやすく、かつ内容に従って操作することで本格的でセキュアなネットワークを構築できるハンズオン形式にもなっており、これ無くしてこのネットワーク環境は作れなかったと思います。
まだ発刊されて日も浅い(2021年3月時点)ため、ネット上の記事や古い書籍でありがちな、解説のUIと現在のUIが違って迷う、ということもほぼ無かったのも良かったです。おすすめです!

ネットワーク構成図

ネットワーク構成図_公開用.png

開発環境・デプロイ

開発・テストはDockerリモートコンテナ上で実施
ソースコードはGitHubで管理、デプロイはCapistranoで行う

IAM

IAM(Identity and Access Management)はAWSへのアクセスを安全に管理するための仕組み
AWSアカウントのデフォルトユーザであるルートユーザは権限が強く、通常の開発等で使うにはリスクが大きい
そのため別途権限を抑えたユーザを作成し、こちらでネットワークの構築を行う
また仮想デバイスを用いたMFA(多要素認証)を導入し、更にセキュアな接続を実現する

VPC

ここは私の領域ですよ、と宣言する領域。Virtual Private Cloud
この中にAWSのネットワークを構築していく

アベイラビリティゾーン

耐障害性を上げるために、同じ東京リージョンの中でも異なるアベイラビリティゾーンにそれぞれサブネットを構成する

サブネット

VPCのIPアドレスの範囲を分割する範囲。分割する理由は主に以下2点

  • 役割の分離:外部に公開するリソースかどうかを区別するため
  • 機器の分離:AWS内での物理的な冗長化を行うため

今回は、2つのアベイラビリティゾーンにそれぞれパブリック・プライベートサブネットを用意する

インターネットゲートウェイ

VPCで作成したネットワークとインターネット間の通信を可能にするためのもの

NATゲートウェイ

インターネットゲートウェイを使った通信ではVPCのリソースは外部のネットワークと直接通信を行うため、VPCリソースはパブリックIPを持っている必要がある
しかしパブリックIPを持つ=インターネットに直接公開されている状態になり、サブネットをパブリックとプライベートに分けた意味が無くなる
プライベートサブネットのリソースは、インターネットに出ていく必要はあってもインターネットから直接アクセスはされたくない
→このような要求を実現するための仕組みがNAT(ネットワークアドレス変換)

AWSではこの役割を果たすのがNATゲートウェイになる

ルートテーブル

サブネット同士、あるいはサブネットと各ゲートとの間には、通信をするための経路がまだ無い
そのため、サブネットが別のサブネット内のリソースを見たり、サブネット外のリソースにアクセスができない
サブネット間の通信経路を設定するために、AWSにはルートテーブルという機能がある
今回はサブネットが4つある すべてのサブネットにルートテーブルを作成する必要がある(ただし複数のサブネットで同じルートテーブルを共有することは可能

これを踏まえ今回作成するのは以下3つ

  • パブリックルートテーブル:パブリックサブネット1,2共有
    • 外部とはIGW経由、プライベートサブネットとはLocalターゲットとしてアクセス
  • プライベートルートテーブル1:プライベートサブネット1用
  • プライベートルートテーブル2:プライベートサブネット2用
    • 外部とはNATGW経由、パブリックサブネットとはLocalターゲットとしてアクセス

セキュリティグループ

今のままだとインターネットを通じてどんなアクセスもできてしまう
VPC内のリソースを守るため、外部からのアクセス制限をつける必要がある
→セキュリティグループという機能を用いて実現する

セキュリティグループでは、外部からのアクセスを次の2つの概念で制御できる

  • ポート番号
    • サービスのアクセスで使われる80番(HTTP)、443番(HTTPS)、
    • 管理者としてサーバ接続時に使われる22番(SSHなど)
  • IPアドレス

今回は以下2つのセキュリティグループが必要

  • 全てのリソースに接続するための入り口となる「踏み台サーバ」
    • インバウンドルール:SSH接続(22番)のみ
  • リクエストや処理を分散する「ロードバランサー」
    • インバウンドルール:HTTP(80番)とHTTPS(443番)接続のみ

EC2(踏み台サーバ)

今回は機器の冗長化、負荷分散のために2つのアベイラビリティゾーンにサブネットを構築した
しかしリソースを増やすということはそれだけ侵入の経路が増えセキュリティ上のリスクが高まることになる
そこで全てのリソースに接続するための入り口となる踏み台(bastion)サーバを用意し、
このサーバ経由でないと各リソースに接続できないようにする

踏み台サーバは目的のリソースへの通り道となる以外の用途がないため、低スペックでもOK
EC2インスタンス上に構築する
踏み台サーバはパブリックサブネット1に設置し、先程作成した踏み台サーバ用のセキュリティグループを適用する(=SSH接続のみ許可する。秘密鍵はローカルマシンで保持)

EC2(Webサーバ・アプリケーションサーバ)

実際のアプリケーションを稼働させるEC2インスタンス
この中にWebサーバとしての役割を果たすNginx、Rackアプリケーション用サーバとしての役割を果たすUnicorn、アプリ本体であるRailsをインストールする
プライベートサブネット1,2にそれぞれ設置

こちらもssh認証の秘密鍵はローカルで持ち、ssh-agentを利用してセキュアに多段接続する(踏み台サーバには秘密鍵をアップしない)

ここで踏み台サーバとWebサーバの違いを整理する

項目 踏み台 Webサーバ
設置サブネット パブリック プライベート
パブリックIP 必要 不要
セキュリティグループ デフォルト+SSH接続 デフォルト+HTTP, HTTPS接続
誰がいつ接続する? 管理者が必要に応じて Webサービスのユーザが常時
接続形態 直接接続 間接接続(ロードバランサ経由)

ロードバランサー

ユーザが増えてくると1台のWebサーバではリクエストを捌き切れなくなる可能性がある
スケールアウトの方法として用いられるのがロードバランサーで、リクエストの分散を実現できる

設定項目は以下

  • アベイラビリティゾーン:インターネットゲートウェイへの経路があるサブネット(今回はパブリックサブネット1,2)があるゾーンを指定する必要がある
  • セキュリティグループ:デフォルト+HTTP, HTTPS接続(内部向けと外部向け)
  • ターゲットグループ:Webサーバ1, 2、プロトコルはHTTP

データベース

AWSのサービスの一つであるRDSを用いて実現
RDSは以下の4点で構成される

  • データベースエンジン
    → 今回はMySQLを使用)

  • パラメータグループ
    → 主にデータベースエンジン固有の設定を行うためのもの

  • オプショングループ
    → 主にRDS固有の設定を行うためのもの

  • サブネットグループ
    → データベースサーバーを複数のアベイラビリティーゾーンに分散させて配置させる時に使われる設定
     RDSはサブネットグループを指定することで自動的に複数のアベイラビリティーゾーンにデータベースを作成し、
     耐障害性を上げてくれる(マルチAZ)

S3

今回のアプリではユーザのアイコンや投稿機能で画像をアップロードすることが可能
その画像を保管する場所として、AWSのストレージサービスであるS3を利用する
S3はVPCの外に配置するものである
そのためIAMでS3にアクセスするためのロールを作成し、EC2に適用することでアプリからS3へのアクセスを実現する

Amazon Route 53

Webアプリを公開するためには分かりやすくて覚えやすいドメインが必要
AWSではドメインを取得することができるRoute 53というサービスがある
こちらで希望のドメインを取得することで公開用のURLを発行することができる
(費用は$10~15/年 前後)

また、Route 53では他に以下の設定を行う

  • SSLサーバー証明書の発行(Certificate Manager)
    → ドメインの正しさを保証する証明書の発行を行う。
     今回はドメイン検証済み(DV)証明書を使う
     発行した証明書を使い、HTTPSのリスナーをロードバランサーに追加する

  • パブリックDNSの設定
    → 踏み台サーバとロードバランサーに対し設定する

  • プライベートDNSの設定
    → 踏み台サーバ、Webサーバ1,2、DBサーバに対し設定する

Amazon SES

SES(Simple Email Service)はメールの送受信行う機能を提供するAWSのサービス
今回のアプリではパスワードを忘れた際に再発行するためにメールの送信を行うため用意する
受信に関する設定は不要のため行わない

通常のメール送受信においては、以下プロトコルが利用される

  • SMTP(Simple Mail Transfer Protocol)
    → 送信用のプロトコル

  • POP3(Post Office Protocol Version 3)
    → 受信用のプロトコル。メールは受信者のローカルに取り込まれる

  • IMAP4(Internet Message Access Protocol Version 4)
    → 受信用のプロトコル。メールはサーバ上に保管されるためインターネットアクセスが必要

アプリからメールを送るには、メール送信用のIAMユーザを作成し、Amazon SES API または Amazon SES SMTPインターフェイスで認証する必要がある
今回は通常のメールサーバと同じ方法で実装ができるAmazon SES SMTPインターフェイスを利用している

CloudWatch

AWSで提供されている、各種サーバ等の運用状況を監視するツール
今回はWebサーバ・アプリサーバを包含するEC2インスタンスの死活監視、CPU使用率監視を行っている

おわりに

CircleCI/CDを使った自動テスト・自動デプロイも導入したいと考えています。
導入できたらこちらも更新しようと思います。

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

【Ruby on Rails6】gem "devise" のviewsのpasswordsだけが編集できない。

【Rails6】gem "devise" のviewsのpasswordsだけが編集できない。

↑記事で解決できました。
しかし、私の場合passwordsのviewsだけhtml.erb編集が反映されなかったので事象を記録します。

環境 Ruby 2.6.6p146/Rails 6.0.3

通常通りdeviseを導入後、viewを編集。なぜかpasswordsだけ編集できずググって解決。

結論

config/initializers/devise.rb

devise.rb
  # ==> Scopes configuration
  # Turn scoped views on. Before rendering "sessions/new", it will first check for
  # "users/sessions/new". It's turned off by default because it's slower if you
  # are using only default views.
  # config.scoped_views = false

のコメントアウト箇所…

devise.rb
 # ==> Scopes configuration
  # Turn scoped views on. Before rendering "sessions/new", it will first check for
  # "users/sessions/new". It's turned off by default because it's slower if you
  # are using only default views.
  config.scoped_views = true 

config.scoped_views = true にしてください。
そのままのコメントアウトだとconfig.scoped_views = falseなので注意です。

その後ターミナルで

rails s でサーバー再起動も忘れずに。
※僕は再起動しないと反映されませんでした。

これにて解決。

ここからが??な事象です。もし、謎を知っている方がいたら教えていただきたいです。

反映されない該当箇所をブラウザリロードをすると、ターミナル表示が

 Started GET "/users/password/new" for ::1 at 2021-03-14 11:49:26 +0900
Processing by Devise::PasswordsController#new as HTML
  Rendering /Users/sxkx/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/devise-i18n-1.9.2/app/views/devise/passwords/new.html.erb within layouts/application
  Rendered /Users/sxkx/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/devise-i18n-1.9.2/app/views/devise/shared/_error_messages.html.erb (Duration: 0.1ms | Allocations: 15)
  Rendered /Users/sxkx/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/devise-i18n-1.9.2/app/views/devise/shared/_links.html.erb (Duration: 0.4ms | Allocations: 246)
  Rendered /Users/sxkx/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/devise-i18n-1.9.2/app/views/devise/passwords/new.html.erb within layouts/application (Duration: 2.5ms | Allocations: 863)
[Webpacker] Everything's up-to-date. Nothing to do
  Rendered layouts/_header.html.erb (Duration: 0.3ms | Allocations: 119)
  Rendered layouts/_sidemenu.html.erb (Duration: 0.2ms | Allocations: 135)
[Webpacker] Everything's up-to-date. Nothing to do
Completed 200 OK in 28ms (Views: 26.4ms | ActiveRecord: 0.0ms | Allocations: 11089)

Rendering /Users/sxkx/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/devise-i18n-1.9.2/app/views/devise/passwords/new.html.erb within layouts/application
??なんだか長い…。 devise-i18n…. 日本語化のために入れたgemが悪さをしている??

devise.rb
  config.scoped_views = true 

をすると

Started GET "/users/password/new" for ::1 at 2021-03-14 11:54:00 +0900
Processing by Devise::PasswordsController#new as HTML
  Rendering users/passwords/new.html.erb within layouts/application
  Rendered users/shared/_error_messages.html.erb (Duration: 0.1ms | Allocations: 15)
  Rendered users/shared/_links.html.erb (Duration: 0.9ms | Allocations: 225)
  Rendered users/passwords/new.html.erb within layouts/application (Duration: 4.7ms | Allocations: 1058)
[Webpacker] Everything's up-to-date. Nothing to do
  Rendered layouts/_header.html.erb (Duration: 0.5ms | Allocations: 119)
  Rendered layouts/_sidemenu.html.erb (Duration: 0.3ms | Allocations: 135)
[Webpacker] Everything's up-to-date. Nothing to do
Completed 200 OK in 41ms (Views: 37.7ms | ActiveRecord: 0.0ms | Allocations: 12092)

Rendering users/passwords/new.html.erb within layouts/application
うん。なんかちゃんと狙いのファイルを拾ってくれていそう。

スコープを指定したから、しっかりできた感じなんですね。
ただなぜpasswordsだけがそうなったのかは謎です。

以上。

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

複数タグ投稿機能の実装(formオブジェクト使用)

投稿に複数のタグを紐付ける

現在作成中のポートフォリオの中で複数のタグをつけることができる機能をつけたかったのでそれを実践した際のコードの記述についてまとめておこうと思います。またポートフォリオ完成した際には別記事で解説したいと思います。
それでは実際の実装を書いていきます。。

conntrollerの記述

  def new
    @room = RoomsTag.new
  end

  def create
    @room = RoomsTag.new(room_params)
    tag_list = params[:room][:name].split(",")
    if @room.valid?
      @room.save(tag_list)
      redirect_to root_path
    else
      render "new"
    end
  end

  def edit
    @form = RoomsTag.new(room: @room)
  end

  def update
    @form = RoomsTag.new(room_params, room: @room)
    tag_list = params[:room][:name].split(",")
    if @form.valid?
      @form.save(tag_list)
      redirect_to room_path(@room)
    else
      render :edit
    end
  end

  private
  def room_params
    params.require(:room).permit(:image, :title, :content, :place_id, :floor_id, :style_id, :name).merge(user_id: current_user.id)
  end

formオブジェクトの記述

class RoomsTag
  include ActiveModel::Model
  attr_accessor :image, :title, :content, :place_id, :floor_id, :style_id, :name, :user_id

# バリデーションをカットしています。

  delegate :persisted?, to: :room

  def initialize(attributes = nil, room: Room.new)
    @room = room
    attributes ||= default_attributes
    super(attributes)
  end

  def save(tag_list)

    ActiveRecord::Base.transaction do
      @room.update(image: image, title: title, content: content, place_id: place_id, floor_id: floor_id, style_id: style_id, user_id: user_id)
      @room.room_tag_relations.each do |tag|
        tag.delete
      end

      tag_list.each do |tag_name|
        tag = Tag.where(name: tag_name).first_or_initialize
        tag.save

        room_tag = RoomTagRelation.where(room_id: @room.id, tag_id: tag.id).first_or_initialize
        room_tag.update(room_id: @room.id, tag_id: tag.id)
      end
    end

  end

  def to_model
    room
  end

  private
  attr_reader :room

  def default_attributes
    {
      image: room.image,
      title: room.title,
      content: room.content,
      place_id: room.place_id,
      floor_id: room.floor_id,
      style_id: room.style_id,
      name: room.tags.pluck(:name).join(',')
    }
  end

end

まずデータベースに保存できるところまでを記述していきます。

controller内の記述について

tag_list = params[:room][:name].split(",")

上記の記述はsplitメソッドを用いて , で区切って、配列に変換しています。
その後formオブジェクトへ送ります。

formオブジェクト内の記述について

delegate :persisted?, to: :room

こちらの記述から新規作成か更新かを判別して、formのメソッドをPUTとPATCHで分けている。

def initialize(attributes = nil, room: Room.new)
    @room = room
    attributes ||= default_attributes
    super(attributes)
end

formオブジェクトパターンで編集機能を実装する際は、編集画面でそれぞれのデータを表示させておくには、今回私の実装で行くところのRoomsTagのインスタンスを生成して、それをformに持っていかなくてはいけません。上記のinitializeメソッドの定義によってnewアクションの時は中身は空で、editアクションの時は中身がある状態でformに持っていくことができます。
super(attributes)の記述がないとinitializeメソッドが機能しないようです。

続いてtag_listを引数にとっているsaveメソッド内について。

ActiveRecord::Base.transaction do
end

この間に囲まれた部分で何か処理の失敗が起きた時、do~end間の処理を全てなかったことにするものです。

@room.update(image: image, title: title, content: content, place_id: place_id, floor_id: floor_id, style_id: style_id, user_id: user_id)
@room.room_tag_relations.each do |tag|
  tag.delete
end

@room.updateにてデータの更新をします。
その下の記述で、このroomに紐付く全てのタグを一旦削除しています。

tag_list.each do |tags|
    tag = Tag.where(name: tags).first_or_initialize
    tag.save

    room_tag = RoomTagRelation.where(room_id: @room.id, tag_id: tag.id).first_or_initialize
    room_tag.update(room_id: @room.id, tag_id: tag.id)
end

配列で運ばれてきたtag_listをeachメソッドを使って、一つずつ取り出し、whereメソッドとfirst_or_initializeメソッドで今までに保存したことがないタグだけ保存しています。その後中間テーブルのものを更新しています。
ここまででsave(tag_list)内の記述は終了です。

def to_model
    room
end

createかupdateか適切なコントローラーへのパスをしてあげる記述みたいです、、
最後、privateメソッド内の記述でこれで読み取り専用のメソッドが定義されるようです。

以上で複数タグの投稿機能の実装を行いました。かなり手探りで色々な記事を参考にしながらのものだったので拙い表現となっているかと思います。ご了承ください。。

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

【クソサイト】世界一情報量が多いサイト【個人開発】

はじめに

情報量が多い画像、情報量が多い動画などはたくさんあるのに「情報量が多いサイト」というのは見つからなかったので作ってみました。

概要

為替
ニュース
名言
じゃんけん
投稿
情報量の多い画像
日付け
イカれた鳥
などの情報が載ってます。

あと隠しページがありますので暇な人は探してみてください。そこそこむずいです。

工夫した点

APIを大量に使う

大量と言っても3つですが
NewsAPI...ニュース
exchangeratesapi...為替
名言教えるよ...名言

を使いました。

色使い

原色、反対色、相性の悪い色
をあえて大量に使いまとまりをなくしました。

画像

あえてリサイズをせずサイズをバラバラにしてみました。

動き

スライドショーやイカれた鳥を使い動きを持たせさらに混乱するようにしました。

まとめ

深夜テンションで決めたネタを続投しないよう法が良い

ツイッターやってます
https://twitter.com/yamada1531

追記

皆さん、隠しページ見つけるの早いですね...。公開して3日くらいは絶対見つからないと思ってたのにもう1、2人...。
もうちょっと難しくしようかなーなんて思ってます。

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

【Heroku】Failed to install gems via Bundler.の解決方法

仮想環境構築中、いっそのこと新しいバージョンにできるものは
バージョンアップした結果、bundlerがHerokuに対応していないバージョンになったためだったと思います。

解決策

bundlerを2.1.4に戻したら、デプロイできました。

# バージョン確認
$ bundler -v
Bundler version 2.2.11

$ gem list bundler
gem list bundler
bundler (2.2.11)
capistrano-bundler (2.0.1)

capistranoは自動デプロイツールのようです。

下記、参考サイトを見ながらbundlerの入れ直しを行いました。

# bundler削除
$ gem uninstall bundler
Successfully uninstalled bundler-2.2.11

# 2.1.4インストール
$ install bundler -v 2.1.4
Successfully installed bundler-2.1.4

# バージョン確認
$ bundler -v
Bundler version 2.1.4

# 再度bundle
$ bundle install

これでgit pushしてから、
git push herokuで正常にデプロイできました。

新しいバージョンだからいいってもんでは、
やっぱりないのですね(o'∀')ノ

参考サイト

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

【Rails6】親モデルと一緒に複数の子レコードを一括で登録/編集するフォームの作り方

Ruby on Railsで複数のモデル、複数のレコードを1つのフォームで一括登録/編集したい、Javascriptはあまり書かずに...という機会があったので、その実装について今回まとめてみました。

完成イメージ

  • レシピのレコードと材料のレコードを同時に作成/編集できること
  • 材料レコードは一括で複数作成/編集できること

sample_complete_recipe.gif

この記事を終えると、こんなアプリが出来るようになります。

前提

バージョン

  • ruby 2.7.2
  • rails 6.1

扱うモデル

  • Recipe(レシピ) Model
  • Ingredient(材料) Model

レシピは複数の材料を持っている

スクリーンショット 2021-03-13 22.05.15.png

このような構成はすでに出来ている状態から始めます。具体的なmodelは以下を参考。

app/models/recipe.rb

# == Schema Information
#
# Table name: recipes
#
#  id         :integer          not null, primary key
#  name       :string           not null
#  created_at :datetime         not null
#  updated_at :datetime         not null
#
class Recipe < ApplicationRecord
  has_many :ingredients, dependent: :destroy
end

app/models/ingredient.rb

# == Schema Information
#
# Table name: ingredients
#
#  id         :integer          not null, primary key
#  recipe_id  :integer          not null
#  name       :string           not null
#  created_at :datetime         not null
#  updated_at :datetime         not null
#
# Indexes
#
#  index_ingredients_on_recipe_id  (recipe_id)
#
# Foreign Keys
#
#  recipe_id  (recipe_id => recipes.id)
#
class Ingredient < ApplicationRecord
  belongs_to :recipe
end

手順

さてここから、完成を目指して一つずつ手順を記していきます

1. accepts_nested_attributes_forを追加

app/models/recipe.rbにaccepts_nested_attributes_forを追加します。

class Recipe < ApplicationRecord
  has_many :ingredients, dependent: :destroy
  # ↓追加
  accepts_nested_attributes_for :ingredients, allow_destroy: true, reject_if: :all_blank
end

参考:ActiveRecord::NestedAttributes::ClassMethods

2. cocoonを追加

cocoonとは、複数レコードをインタラクティブに追加、編集、削除を簡単に行うことが出来るようにするGem, Packageです

  1. Gemのcocoonをインストール

    ↓のようにGemfileにcocoonを追加して bundle installします

    gem "cocoon"
    
  2. cocoonのpackage追加

    yarn add @nathanvda/cocoon 
    
  3. app/javascripts/packs/application.js の編集

    import Rails from "@rails/ujs"
    import Turbolinks from "turbolinks"
    import * as ActiveStorage from "@rails/activestorage"
    import "channels"
    // これを追加
    import "@nathanvda/cocoon"
    Rails.start()
    Turbolinks.start()
    ActiveStorage.start()
    

3. jqueryを使えるようにする

  1. config/webpack/plugins/jquery.jsを作成

    $ bundle exec rails webpack:install
    $ mkdir config/webpack/plugins/
    $ touch config/webpack/plugins/jquery.js
    
  2. config/webpack/plugins/jquery.jsに以下を記述

    const { environment } = require('@rails/webpacker')
    const jquery = require('./plugins/jquery')
    environment.plugins.prepend('jquery', jquery)
    module.exports = environment
    
  3. config/webpack/environment.js でjqueryを読み込む

    const { environment } = require('@rails/webpacker')
    const jquery = require('./plugins/jquery')
    environment.plugins.prepend('jquery', jquery)
    module.exports = environment
    

4. RecipeController作成とRoutesを設定

  1. bundle exec rails g controller recipesでController作成

    $ bundle exec rails g controller recipes
    Running via Spring preloader in process 38240
      create  app/controllers/recipes_controller.rb
      invoke  erb
      create    app/views/recipes
      invoke  test_unit
      create    test/controllers/recipes_controller_test.rb
      invoke  helper
      create    app/helpers/recipes_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    scss
      create      app/assets/stylesheets/recipes.scss
    
  2. config/routes.rbにrecipeのpathを追加

    Rails.application.routes.draw do
      root "recipes#index"
      resources :recipes, only: [:index, :new, :create]
    end
    

5. Recipeの一覧ページを作成

  1. app/controller/recipes_controller.rbにindexアクション追加

    class RecipesController < ApplicationController
    def index
    @recipes = Recipe.all
    end
    end
    
  2. Recipeの一覧ページを作成

$ touch app/views/recipes/index.html.erb
<h1>レシピ一覧</h1>
<ul>
  <% @recipes.each do |recipe| %>
    <li><%= link_to recipe.name, edit_recipe_path(recipe) %></li>
    <ul>
      <% recipe.ingredients.each do |ingredient| %>
        <li><%= ingredient.name %></li>
      <% end %>
    </ul>
  <% end %>
</ul>
<%= link_to "レシピの新規作成", new_recipe_path %>

するとこんなページが出来上がります

スクリーンショット 2021-03-14 2.19.11.png

6. Recipeの新規作成ページを作成

  1. app/controller/recipes_controller.rbにnewアクション追加

    class RecipesController < ApplicationController
    ~~ 省略 ~~~
    def new
    @recipe = Recipe.new
    end
    end
    
  2. app/views/recipes/new.html.erb を作成

    $ touch app/views/recipes/new.html.erb
    
    <h1>レシピの新規作成</h1>
    <%= render "recipes/form", model: @recipe %>
    
  3. app/views/recipes/_form.html.erbを作成

    $ touch app/views/recipes/_form.html.erb
    
    <%= form_with model: model do |form| %>
      <div>
        <%= form.label :name, "レシピ名" %>
        <%= form.text_field :name %>
     </div>
     <div id="ingredients">
       <%= link_to_add_association '材料の追加', form, :ingredients,
         data: { association_insertion_method: 'append' } %>
       <%= form.fields_for :ingredients do |ingredient| %>]
         <%# ファイル名はingredient_fieldsにしてください %>
         <%= render "recipes/ingredient_fields", f: ingredient %>
       <% end %>
      </div>
      <%= form.submit "作成する" %>
    <% end %>
    
  4. app/views/recipes/_ingredient_fields.html.erbを作成

    $ touch app/views/recipes/_ingredient_fields.html.erb
    
<div class="nested-fields">
  <%= f.label :name, "材料名" %>
  <%= f.text_field :name %>
  <%= link_to_remove_association "削除", f %>
</div>

ここまで行うと、以下のような画面になります

sample_recipe.gif

7. レシピ、材料の作成(Create)アクションを作成

class RecipesController < ApplicationController

  def new
    @recipe = Recipe.new
  end
  # ここから
  def create
    @recipe = Recipe.new
    if @recipe.update(recipe_params)
      redirect_to root_path
    else
      render :new
    end
  end

  private

  def recipe_params
    # :idがないと毎回新しくレコードが作られてしまいます
    # :_destroyがないと削除ができないです
    params.require(:recipe).permit(:name, ingredients_attributes: [:id, :name, :_destroy])
  end
  # ここまで追加
end

createアクションの追加が完了すると、レシピと材料を一括で作成してDBに保存する処理まで出来るようになります!

8. レシピの編集ページを作成

  1. app/views/recipes/edit.html.erbの作成

    $ touch app/views/recipes/edit.html.erb
    
  2. app/views/recipes/edit.html.erbの編集

    <h1><%= @recipe.name %>の編集</h1>
    <%= render "recipes/form", model: @recipe %>
    
  3. app/controllers/recipes_controller.rbの編集

class RecipesController < ApplicationController
~~ 省略 ~~
  def edit
    @recipe = Recipe.find(params[:id])
  end
~~ 省略 ~~
end 

こんなページが出来上がります
スクリーンショット 2021-03-14 2.28.55.png

9. レシピのupdateアクションを作成

app/controllers/recipes_controller.rbにupdateアクションを追加

class RecipesController < ApplicationController
~~ 省略 ~~
  def update
    @recipe = Recipe.find(params[:id])
    if @recipe.update(recipe_params)
      redirect_to root_path
    else
      render :edit
    end
  end
~~ 省略 ~~
end 

これで一括の更新もできるようになりました!

終わりに

長かったですが、これで以上になります。
あまりJavaScriptを書かずに動的にフォームの追加や削除をしたいということがあったので、今回のような実装について書きました。

同じようなことがあればぜひご参考ください!

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

[WIP]Rails6.1ハンズオン(4)~6.0の機能を触る

はじめに

※この記事は執筆途中です。5-1までで力尽きたので5-2以降はまた今度書きます...

Rails6.1ハンズオン(3)の続きです。

6.1ハンズオンと言いながら、実は6.0をまともに触っていない筆者。。。
ですので今回は6.0で追加された機能を触ってみます。
コードはGithubにあげています。章ごとにコミットしてますので、参考にしていただければ幸いです。

やること

個人的に気になった(面白そうor仕事で使いそう)機能をピックアップして、現在のRails_6.1_hands_onプロジェクトに追加してみます。

  • Action Text
  • Action Mailbox
  • ActiveRecord::Relation#pick

参考:Ruby on Rails 6の主要な新機能・機能追加・変更点 - Qiita

5-1. Action Text

5-1-1. Action Text, Active Storageの導入

参考:Action Text の概要 - Railsガイド

まずはActiveStorageが必要なので、

rails active_storage:install

をしたが、

rails aborted!
Don't know how to build task 'active_storage:install' (See the list of available tasks with `rails --tasks`)

と出た。ActiveStorageのREADMEをみると、

rails/rails

Run bin/rails active_storage:install to copy over active_storage migrations.
NOTE: If the task cannot be found, verify that require "active_storage/engine" is present in config/application.rb.

らしいので、config/application.rbをみると、

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_mailbox/engine"
# require "action_text/engine"
require "action_view/railtie"
require "action_cable/engine"
require "sprockets/railtie"

となっていた。rails new で色々無効にしていたのでコメントアウトされていたらしい。

action_text/engineも無効化されているのでこちらも有効にしておく。

そしてやっと、

rails active_storage:install
rails action_text:install

migrationファイルができているので、忘れずにrails db:migrateする。

config/storage.ymlを作る(最初からあるイメージだが、今回は無かった)

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

config/environments/development.rb(test.rbも任意で、このハンズオンでテスト書くのかな...)に追加:

config.active_storage.service = :local

5-1-2. ActionTextをモデルに適用する

_2021-03-13_16.52.16.png

Commentのcontentがtextになっていたのを、Action Textに置き換える。

rails g migration RemoveContentInComment
class RemoveContentInComment < ActiveRecord::Migration[6.1]
  def change
    remove_column :comments, :content, :text
  end
end

rails db:migrateして、

app/models/comment.rb

class Comment < ApplicationRecord
  belongs_to :community
  has_rich_text :content
end

has_rich_textでcontentカラムを作ったかのように振る舞ってくれるようになる。

app/views/comments/new.html.haml(一部)

= form.label :content, class: 'form-label'
= form.rich_text_area :content, class: 'form-control mb-4'

app/views/communities/show.html.haml

= comment.content

simple_formatを外した。

すると以下のようになる。

_2021-03-14_9.56.53.png

思ってたのと違う。cssが読み込まれていない。action_text:installしたときに新しく生成されたcssは、app/assets/stylesheets/actiontext.scss。内容がこちら

//
// Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and
// the trix-editor content (whether displayed or under editing). Feel free to incorporate this
// inclusion directly in any other asset bundle and remove this file.
//
//= require trix/dist/trix

// We need to override trix.css’s image gallery styles to accommodate the
// <action-text-attachment> element we wrap around attachments. Otherwise,
// images in galleries will be squished by the max-width: 33%; rule.
.trix-content {
  .attachment-gallery {
    > action-text-attachment,
    > .attachment {
      flex: 1 0 33%;
      padding: 0 0.5em;
      max-width: 33%;
    }
// (以下省略)

trix/dist/trixをrequireしていて、更に添付ファイルの表示に関するスタイルを上書きしているらしい。

app/views/layouts/application.html.hamlでは、

= stylesheet_link_tag 'application'を消して、= stylesheet_pack_tag 'application'に変えているので、上記ファイルは読み込まれない。

= stylesheet_link_tag 'application'を復活させれば動く(動作確認済み)。

だが、せっかくなので、webpackerに寄せる。

app/javascript/stylesheets/application.scss

@import "~@fortawesome/fontawesome-free/scss/fontawesome";

$primary: #9400d9;
@import "bootstrap";
@import "trix/dist/trix";
@import "actiontext.scss";

app/javascript/stylesheets/actiontext.scssではsprocket用のコードを外して記述。

// We need to override trix.css’s image gallery styles to accommodate the
// <action-text-attachment> element we wrap around attachments. Otherwise,
// images in galleries will be squished by the max-width: 33%; rule.
.trix-content {
  .attachment-gallery {
    > action-text-attachment,
    > .attachment {
      flex: 1 0 33%;
      padding: 0 0.5em;
      max-width: 33%;
    }

    &.attachment-gallery--2,
    &.attachment-gallery--4 {
      > action-text-attachment,
      > .attachment {
        flex-basis: 50%;
        max-width: 50%;
      }
    }
  }

  action-text-attachment {
    .attachment {
      padding: 0 !important;
      max-width: 100% !important;
    }
  }
}

すると、

_2021-03-14_10.25.12.png

できた。

_2021-03-14_10.27.29.png

こんな感じで入力できる。

それで投稿すると

_2021-03-14_10.27.47.png

画像が表示されない。

chromeのconsoleを見ると、

GET http://localhost:3000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBCZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--dce7eada98f7015981b311d8495658640b895ad9/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJY0c1bkJqb0dSVlE2RkhKbGMybDZaVjkwYjE5c2FXMXBkRnNIYVFJQUJHa0NBQU09IiwiZXhwIjpudWxsLCJwdXIiOiJ2YXJpYXRpb24ifX0=--9a6414b9afa153c394bf23eb32d0e368370e8b60/Screenshot_20210304-220735.png 500 (Internal Server Error)

読み込めてない。500なので内部的なエラー。

したがってrailsのコンソールを見ると、

LoadError (Generating image variants require the image_processing gem. Please add `gem 'image_processing', '~> 1.2'` to your Gemfile.)

ちゃんと書いてあった。Gemfileにgem 'image_processing', '~> 1.2'を追加。bundle install。

_2021-03-14_10.37.36.png

表示されました。

5-2. Action Mailbox

(執筆中です)

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

JST 時刻文字列から UTC 時刻型に変換する

Time.parse(jst_start_date_string).in_time_zone.utc
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

マイグレーションファイルを変更する方法(Rails)

マイグレーションファイル変更する方法

参考にしたサイト Railsチュートリアル(5.1)

$ rails db:rollback
$ rails db:migrate VERSION=0
$ rails db:rollback STEP=○
  • 1つ前の状態に戻します。
  • 最初の状態に戻したいときはVERSION=0つける
  • 最後のマイグレーションファイルから数えて戻したい数だけ戻せる
$ rails db:rollback
$ rails db:migrate:status

Status   Migration ID    Migration Name
--------------------------------------------------
   up     20200830060821  Devise create users
  down    20200830062141  Create books

$ rails db:migrate:statusを使うことで変更できるファイルの一覧を確認できます。
downが変更可能でupが変更できないファイルです。
$ rails db:rollbackを使うとdownに変更されていると思います。

$ rails g migration add_index_to_users_email

usersテーブルの中にemailカラムをを追加するコマンド

おまけ

$ rails db:migrate 
$ bundle exec rake db:migrate 
  • Rails 5以降のコマンド
  • Rails 4以前のコマンド
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HerokuでMySQLサーバーを使う方法

Heroku CLIをインストールしてから、ClearDBの導入から、Heroku apps:infoまでの手順を紹介します!(学習記録)

Heorkuのインストールからアプリ作成:shinto_shrine:

# heroku cliのインストール
% brew tap heroku/brew && brew install heroku 

# herokuのversionを確認
 % heroku --version 

# herokuにログインする
% heroku login --interactive

#パスワードとメールアドレスを入力してログイン

# heroku createで本番環境用のアプリを作成
cd ~/(アプリまでの相対パス)
heroku create (アプリの名前)

# アプリが正常に作られていることを確認
% git config --list | grep heroku

HerokuにMySQLを導入

# ClearDBを導入
% heroku addons:add cleardb

# Railsに対応させる
heroku_cleardb=`heroku config:get CLEARDB_DATABASE_URL`

# データベースのURLの再設定
 heroku config:set DATABASE_URL=mysql2${heroku_cleardb:5}

Heroku上で非公開の値を管理

# masterkeyを確認
% cd ~/(アプリまでの相対パス)
% EDITOR="code --wait" bin/rails credentials:edit

# heroku上に環境変数を設定
% heroku config:set RAILS_MASTER_KEY=`cat config/master.key`

# 正しく反映されているか確認
% heroku config

Herokuにpush

# herokuに自分のアプリを追加
% git push heroku master

# Rubyのバージョンによっては必要なコマンド
heroku stack:set heroku-18 -a (アプリの名前)

# migrateを実行
heroku run rails db:migrate

# heroku apps:infoで確認
heroku apps:info

以上がHerokuでMySQLサーバーを使う流れになります。参考までにどうぞ!!

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

HerokuでMySQLサーバーを使う方法(Rails)

Heroku CLIをインストールしてから、ClearDBの導入、Heroku apps:infoまでの手順を紹介します!(学習記録)

Heorkuのインストールからアプリ作成:shinto_shrine:

# heroku cliのインストール
% brew tap heroku/brew && brew install heroku 

# herokuのversionを確認
 % heroku --version 

# herokuにログインする
% heroku login --interactive

#パスワードとメールアドレスを入力してログイン

# heroku createで本番環境用のアプリを作成
cd ~/(アプリまでの相対パス)
heroku create (アプリの名前)

# アプリが正常に作られていることを確認
% git config --list | grep heroku

HerokuにMySQLを導入

# ClearDBを導入
% heroku addons:add cleardb

# Railsに対応させる
heroku_cleardb=`heroku config:get CLEARDB_DATABASE_URL`

# データベースのURLの再設定
 heroku config:set DATABASE_URL=mysql2${heroku_cleardb:5}

Heroku上で非公開の値を管理

# masterkeyを確認
% cd ~/(アプリまでの相対パス)
% EDITOR="code --wait" bin/rails credentials:edit

# heroku上に環境変数を設定
% heroku config:set RAILS_MASTER_KEY=`cat config/master.key`

# 正しく反映されているか確認
% heroku config

Herokuにpush

# herokuに自分のアプリを追加
% git push heroku master

# Rubyのバージョンによっては必要なコマンド
heroku stack:set heroku-18 -a (アプリの名前)

# migrateを実行
heroku run rails db:migrate

# heroku apps:infoで確認
heroku apps:info

#最後にHerokuのMySQLサーバーをリセットするコマンド
% heroku run DISABLE_DATABASE_ENVIRONMENT_CHECK=1 rails db:drop db:create db:migrate

以上がHerokuでMySQLサーバーを使う流れになります。参考までにどうぞ!!

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

【Ruby】クラス、メソッドの定義とインスタンス変数について

はじめに

この記事は、Ruby/Railsの学習期間約1ヶ月の著者が書いたものです。
初投稿となりますので、誤った解釈があるかもしれません。その場合は、コメント等頂けますと幸いですm(_ _)m

今回は、Ruby学習中によく分からなくなりがちな、クラス、メソッドの定義、インスタンス変数について説明します。

独自のクラスを定義する

まず、Rubyではクラスを定義します。
クラスとは、設計図のようなもので、classから始まり、endで終わります。

クラス名は、ClassNameというように、単語の先頭を大文字で定義します。
ラクダのコブのようであることから、CamelCaseと表現します。

class.rb
#クラスの定義
class ClassName
end

クラスにメソッドを定義する

クラスの設計図の中にメソッドを定義することで、そのクラス独自が持っているメソッドとして認識されます。

メソッドとは、何らかの処理を示しており、処理内容をdefendで囲みます。
メソッド名は、method_nameのように2単語目以降は、アンダーバーで示しますので注意しましょう。
(このことを蛇に例えて、snake_caseと呼びます。)

method.rb
#メソッドの定義
class ClassName
  def method_name
  # メソッドの処理を書く
  end
end

#クラスのインスタンスを作成し、メソッドで呼び出す
instance = ClassName.new
instance.method_name

Rubyのオブジェクトは何らかのメソッドを持っていて、そのメソッドを定義することで、処理を返してくれるようになります。

ローカル変数とインスタンス変数について

次に、ローカル変数とインスタンス変数の違いを見てみましょう。

ローカル変数

ローカル変数は、使い回しできる範囲が他の変数よりも限られています。
例えば、メソッド内で定義した場合は、そのメソッド内でしか使えず、同じクラス内であっても使い回しができません。

試しに、以下のローカル変数messageを、say_morningのメソッドの処理として定義し、
say_good_morningのメソッド内で使い回してみましょう。

local_variable.rb
class Greeting
  def say_morning
    message = "morning!"
    puts message
  end

  def say_good_morning
    puts "Good #{message}"
  end
end

instance = Greeting.new
instance.say_morning
instance.say_good_morning

以下のようなエラーが出てしまいました。

`say_good_morning': undefined local variable or method `message' for #<Greeting:0x00007fe5b1984038> (NameError)

つまり、say_good_morningのメソッドの中で、messageというローカル変数やメソッドは定義されてませんよ、と言っているわけです。
このように、ローカル変数は、あるメソッド内で定義した変数は、他のメソッド内で使い回すことができません。

インスタンス変数

一方、インスタンス変数とは、同じオブジェクト内(同クラス内)で使い回せる変数のことをいいます。
変数の先頭に@をつけて定義します。

先程のローカル変数messageを、インスタンス変数@messageに置き換えて見てみましょう。

instance_variable.rb
class Greeting
  def say_morning
    @message = "morning!"
    puts @message
  end

  def say_good_morning
    puts "Good #{@message}"
  end
end

instance = Greeting.new
instance.say_morning
instance.say_good_morning

結果は以下の通り。

morning!
Good morning!

欲しい結果が出ましたね!
つまり、インスタンス変数とは、同じクラス内であれば、異なるメソッド内であっても何度でも使い回せる変数である、ということが証明されました。

おわりに

今回はRubyの基本であるクラス、メソッドの定義とローカル変数とインスタンス変数の違いについて、説明しました。
今後も、Ruby/Railsを学習する上で、皆様に有益な情報を発信できればと思いますので、良ければLGTMお願いします。

参考記事

ローカル変数・インスタンス変数・クラス変数の違い(Ruby)

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

【Rails】Cloud9で送信メールのプレビュー時の自分のホスト名と指定のURLの指定方法【Railsチュートリアル11章】

自分がつまづいた時の状況

Railsチュートリアル第11章アカウントの有効化でHTMLメールとテキストメールのプレビューをする際に、アプリケーションのdevelopment環境の設定ファイルの設定と、Railsサーバー起動時のURLが分からず、送信メールのプレビューが表示できませんでした。

・クラウドIDE:AWS Cloud9
・Rails:v6.0.3
・OS:Win

config/environments/development.rb
Rails.application.configure do
  .
  .
  .
  config.action_mailer.raise_delivery_errors = false

  host = 'example.com' # ここの指定方法が分からなかった(自分のホスト名を指定する)
  config.action_mailer.default_url_options = { host: host, protocol: 'https' }
  .
  .
  .
end

自分のホスト名の調べ方

まず、自分のホスト名を調べる方法ですが、
Cloud9のPreview PreviewRunning Applicationを押下し、アドレスバーがついているpreviewタブを表示させます。
その後、そのアドレスバーに表示されているlocalhost以下の文字列が自分のアドレスになります。

その自分のホスト名の中でもhttpsから.vsfの間の文字列を先ほどのdevelop環境の設定ファイルに反映します。

image.png

config/environments/development.rb
config.action_mailer.raise_delivery_errors = false

  host = 'hoge.vfs.cloud9.us-east-2.amazonaws.com'     # クラウドIDE
  config.action_mailer.default_url_options = { host: host, protocol: 'https' }

設定ファイルを変更したら、Railsサーバーは再起動してください。

送信メールのプレビューのURL

URLですが、下記でプレビューが表示されました。
ホストの後ろに「/rails/mailers/user_mailer」とつけるだけでした。

https://hogehoge.vfs.cloud9.ap-northeast-1.amazonaws.com/rails/mailers/user_mailer

image.png

参考

Railsチュートリアル第11章アカウントの有効化
rails tutorial にて 送信メールのプレビューができない
rails チュートリアルの11章の2・2にてクラウドIDEのホスト名がわからない

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

PG::ConnectionBad (FATAL: role "postgres" does not exist )の対処法 | PostgreSQLでRoleを作成する

はじめに

railsで環境構築していたところbin/rails db:createで下記のエラーが発生した。解決に少し手間取ったのでやり方をまとめておく。

PG::ConnectionBad (FATAL:  role "postgres" does not exist );

原因

まずは原因から。このエラーが発生しているのは、postgresというロールが存在しないから。

postgresというロールはPostgreSQLを最初に起動すると自動で作られるらしいが、自分の場合は作られていなかったらしい。

ロールというのはユーザやグループに対してアクセス制限をかけることができるものらしい。AWSのIAMロールと同じだと思う。
例)Role:AはSUPERUSERでなんでもOK
  Role:Bはデータベースの作成のみOK

解決策

postgresというロールを作成して権限を与えてあげればOK

ロールを作成する手順

DBに接続する。

$ psql postgres

psqlのバージョンとヘルプが返ってきたら接続成功。

psql (13.1)
Type "help" for help.

\duで存在するロールの一覧が取得できる。

postgres=# \du

Role nameにpostgresが存在していないと思うので作成する。
CREATE ROLE ロール名 属性;またはCREATE ROLE ロール名;で作成できる。

postgres=# CREATE ROLE postgres SUPERUSER;

ロールを確認する。

postgres=# \du

Role nameにpostgresが作成されているはず。

  Role name   |                         Attributes 
 postgres     | Superuser, Cannot login 

属性にCannot loginがついてしまっているのでloginの権限を与える。
ALTER ROLE ロール名 属性;

postgres=# ALTER ROLE postgres LOGIN;

もう一度ロールを確認し、下記のようになっていたらOK。

postgres=# \du

  Role name   |                         Attributes 
 postgres     | Superuser

dbから抜ける。

postgres=# \q

自分の環境でDBを作成する。(自分の場合はrails)

$ bin/rails db:create

$ bin/rails db:migarte

補足

psql postgresでDBに接続して作業にpostgres=#ではなく、postgres-#と表示されたとき
postgres=# ALTER ROLE postgres LOGIN
postgres-# 

postgres-#は分割入力をわかりやすく区別するためのもの。意図して分割していない場合、SQL文が完了していないと思う。

postgres=# ALTER ROLE postgres LOGIN
postgres-# ;
最後の';'を忘れるのはよくある
・postgresにSuperuserを適用している件について

今回postgresに適用したSuperuserは一番強い権限でなんでもできるやつ。しかし、基本的にアクセス権限は無闇に強い権限を与えず、必要最小限の権限を与えることが推奨されている。

だからSuperuserを与えるのは良くないんじゃないのと思うかもしれないが、そもそもpostgresが自動で作られた場合、Superuserの権限を持っているらしい。postgresから他のロールを作成してアクセス権限を与えてロールを管理するのが通常らしい。そのため今回はpostgresにSuperuserの権限を与えている。

余談だけど"PG::ConnectionBad (FATAL: role "postgres" does not exist )"で検索しても記事が全然ヒットしなかったけど、"PostgreSQL Role 作成"で検索したら記事がたくさんヒットしたから検索の仕方は大事だなと思った。

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