20210112のRailsに関する記事は30件です。

RailsでAjaxで「いいね!」機能を実装する。

何をしたか

Railsの課題を実施しています。その中で「いいね!」機能をAjaxで作成しましょうというタスクがありました。
実は以前、非同期通信ではない「いいね!」機能は作成したことがありました。

▼その時の記録はこちら
Railsで「いいね!」機能を作る - ①アソシエーションに別名をつける
Railsで「いいね!」機能を作る - ②「いいね!」のcreateアクション
Railsで「いいね!」機能を作る - ③「いいね!」を解除できるようにする

また、別の記事で「いつかAjaxで”いいね!”機能を作ってみたい」と言いつつ、作っていなかったので、作成の手順をノートにまとめたいと思います。

なお、ここではメモ程度に実装手順を紹介しています。実際に詳しい実装の手順は↑上記の記事をご覧ください。

また、実装環境は以下の通りです。

  • Rails 5.2.3
  • Ruby 2.6.0

仕様確認・定義する

「いいね!」機能はアソシエーションの定義に若干ひねりが必要です。今回もまずはDB構造と仕様を確認します。その結果、DB構造と仕様は下記の通りでした。

Image from Gyazo

imagesテーブルは画面外にあります。
※他にもあるテーブルの中の一部を表示しています。

このうち、「自分の投稿にいいね!できない」というのは、下記のように、自分の投稿の時にはビューに「いいね!」ボタンを表示しない、という形で実現しています。

▼自分の投稿の時(編集・削除ボタンを表示)
Image from Gyazo

▼自分以外の投稿の時(いいね!ボタンを表示)
Image from Gyazo

また、like_postsと赤文字で書いてあるのは、この後記載するアソシエーションの別名です。
「アソシエーションの別名って???」という方は、こちらの記事に詳しく説明してあります。

Railsで「いいね!」機能を作る - ①アソシエーションに別名をつける

マイグレーションの作成

では、ここから実装していきます。まずは、マイグレーションファイルを作成します。
usersテーブルとpostsテーブルはそれぞれ作成済という前提です。
likesテーブルは下記のように作成します。

db/migrate/XXXXXXXXXX_create_likes.rb
class CreateLikes < ActiveRecord::Migration[5.2]
  def change
    create_table :likes do |t|
      t.references :user
      t.references :post

      t.timestamps
      t.index [:user_id, :post_id], unique: true
    end
  end
end

「いいね!」はlikesテーブルにデータをきちんと入力できれば実現されます。
(どうしてそうなるのかは、こちらの記事をご覧くださいませ^^)
Railsで「いいね!」機能を作る - ②「いいね!」のcreateアクション

また、

t.index [:user_id, :post_id], unique: true

の部分で、同じユーザー・同じ投稿への「いいね!」が投稿できないように、DB側で制御をしています。

アソシエーションの定義

postsuserslikesのモデルファイルに、それぞれ下記のようにアソシエーションを記載しました。

class User < ApplicationRecord
  # ★1
  has_many :posts, dependent: :destroy

  # ★2
  has_many :likes, dependent: :destroy
  has_many :like_posts, through: :likes, source: :post
end

class Post < ApplicationRecord
  # ★1
  belongs_to :user

  # ★2
  has_many :likes, dependent: :destroy
  has_many :users, through: :likes
end

class Like < ApplicationRecord
  belongs_to :post
  belongs_to :user
end

UserPostの★1と★2の下の2行は、それぞれ

  • ★1 ... ユーザーが投稿したpostに関する定義
  • ★2 ... ユーザーが「いいね!」したpostに関する定義

です。User => Like => Post の流れに、:like_postsとアソシエーションの別名をつけているのがポイントです。

has_many :like_posts, through: :likes, source: :post

これで、

user = User.first
user.like_posts

↑この形で、ユーザーが「いいね!」した投稿一覧が取得できます。

コントローラーの定義

コントローラーの内容は以下の通りです。「いいね!」(create)と「いいね!解除」(destroy)をそれぞれ以下のように定義します。

likes_controller.rb
class LikesController < ApplicationController
  def create
    @post = Post.find(params[:post])
    current_user.like(@post)
  end

  def destroy
    @post = Like.find(params[:id]).post
    current_user.unlike(@post)
  end
end

コード中にあるlikeunlikeはそれぞれ、「いいね!」と「いいね解除」を行う、Userのモデルメソッドです。

Userのモデルメソッドの定義(likeunlike

User.rbには、以下のモデルメソッドを定義します。

models/user.rb
class User < ApplicationRecord
  has_many :posts, dependent: :destroy

  has_many :likes, dependent: :destroy
  has_many :like_posts, through: :likes, source: :post
  def own?(object)
    id == object.user_id
  end

  def like(post)
    likes.find_or_create_by(post: post)
  end

  def like?(post)
    like_posts.include?(post)
  end

  def unlike(post)
    like_posts.delete(post)
  end
end

like?ユーザーがすでにその投稿に「いいね!」しているかを判別するメソッドです。この後ビューで使うので載せています。

また、owm?も今回の実装には直接関係がないのですが、この後ビューで使用しているので載せています。対象のオブジェクトの作成者を判別するメソッドです。

like_posts.delete(post)

何気に、like_postsでユーザーが「いいね!」したポスト一覧を取得してdestroyしているのも、ミソかなあと思っています。

ビューの定義(非同期「ではない」実装の場合)

その後、ビューを書いていきます。まずは非同期ではない実装でビューを作成していきます。なお、読みやすさのため、装飾のための要素や機能は省いています。

- @posts.each do |post|
  - if logged_in?  # ログイン確認
    - if current_user.own?(post)  # 所有を確認
      = link_to post_path(post), method: :delete do
        = icon 'far', 'trash-alt' # ゴミ箱アイコン
      = link_to edit_post_path(post) do
        = icon 'far', 'edit' # 編集アイコン
    - else
      - if current_user&.like?(post) # すでにいいね!してるか確認
        = link_to like_path(current_user.likes.find_by(post: post)), method: :delete do
        = icon 'fa', 'heart' # ハート(黒)
      - else
        = link_to likes_path(post: post), method: :post do
          = icon 'far', 'heart' # ハート(白)

logged_in?はGemで生成されている、ログインしているかどうかを判別するメソッドです。

実は、ここまでの実装で、「いいね!」機能自体はできています。「いいね!」ボタンを押して、画面をリロードすると「いいね!」と「いいね解除」がそれぞれ切り替えられているのがわかります。

▼「いいね!」を押してから、リロード(画面外のボタンを押下)すると、アイコンが切り替わっている
Image from Gyazo

「いいね!」を非同期で実現する

さて、ここからが本題です。これら「いいね!」機能を非同期で実現していきます。

リンクをremote: trueにする

まずはリンクをremote: trueにして、通信を非同期通信にします。

- @posts.each do |post|
  - if logged_in?
    - # 省略
    - else # それぞれ、link_toの後ろにremote: trueを追記
      - if current_user&.like?(post)
        = link_to like_path(current_user.likes.find_by(post: post)), method: :delete, remote: ture do
        = icon 'fa', 'heart'
      - else
        = link_to likes_path(post: post), method: :post, remote: ture do
          = icon 'far', 'heart'

Ajax用のビューファイルを作る - (1)ボタンの移動

上記のビューファイルのうち、さらにlikeunlikeのボタンについては、それぞれ後に続くAjaxの処理用に、別のパーシャルに分ます。また、Ajaxの処理の目印となるように、id属性も付与しています。

- @posts.each do |post|
  - if logged_in?
    - # 省略
    - else id="like-button-#{post.id}" # id属性を追記
      - if current_user&.like?(post)
        = render 'likes/unlike_button', post: post # パーシャルへ移動
      - else
        = render 'likes/like_button', post: post # 同上

パーシャルの中身はこちらです。

views/likes/_unlike_button.html.slim
= link_to like_path(current_user.likes.find_by(post: post)), method: :delete, remote: true do
  span.c-icon-button= icon 'fa', 'heart', class: 'fa-lg'
views/likes/_like_button.html.slim
= link_to likes_path(post: post), method: :post, remote: true do
  span.c-icon-button= icon 'far', 'heart', class: 'fa-lg'

Ajax用のビューファイルを作る - (2).js.erbファイルを作る

likescreatedestroyアクションに対応した.js.erbファイルをそれぞれ作成します。

/views/likes/create.js.erb
$("#like-button-<%= @post.id %>").html("<%= j(render 'unlike_button', post: @post) %>")
/views/likes/destroy.js.erb
$("#like-button-<%= @post.id %>").html("<%= j(render 'like_button', post: @post) %>")

.js.erbファイルについては、こちらの記事で解説していますので、よろしければご覧ください。

remote: trueでajaxの投稿をPOSTをするよ。

完成!

非常に簡単なステップでしたが、上記の実装で、いいね!機能が非同期で実装できています:relaxed:
意外にあっさり、簡単ですね!

Image from Gyazo

感想...「いいね!」機能もRailsの機能でAjaxを作るのも実は3回目だったので、どちらもスルスルできてよかったです:relaxed:

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

ターミナルのコンソール内においてテストコードのエラーメッセージが表示されない時の対処法

きっかけ

items_controllers.rb
it "郵便番号が空では登録できないこと" do
        @user_item.postal_code = nil
        binding.pry
        @user_item.valid?
        expect(@user_item.errors.full_messages).to include("Postal code can't be blank")
      end

コントローラー内において、上記のような記述をして、ターミナルにてエラーメッセージを取得しようと思ったら、下のような結果が出た。

コンソール
[1] pry(#<RSpec::ExampleGroups::UserItem::Nested::Nested_2>)> @user_item.errors.full_messages
=> []

なぜ??
エラーメッセージが出ないということはバリデーションに問題があるのかと思ったが、原因はそこではなかった。

結論

binding.pryの記述する場所が間違っていた。
or
binding.pryをかけてからエラーメッセージにたどり着くまでの順番を間違えていた。

binding.pryをかけるとその箇所でPCの動きが止まるが、そこからコンソールで動かす場合は、順番が大切なのだった。むやみやたらにエラーメッセージをくれぇぇぇ!と言っても、パソコン様は動いてはくれない。
まずは、上記の場所にbinding.pryをかけた場合は、

コンソール
@item.valid?

という記述をコンソール上でしなくてはいけなかった。
そして、返り値がfalseだった場合に、初めて

コンソール
@item.errors.full_messages

を記述することでエラーメッセージが返ってくるようになる。

まとめ

binding.pryをかけた場合は、むやみやたらにパラムスの値やエラーメッセージを求めるのではなく、通常のコントーラーでの動きと同じように順番を確認しながら進めるとコンソールは望みを叶えてくれる。

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

ActiveStorageを使う!

アウトプットに時間を使わなきゃと思いつつ中々やらずにいたので、重い腰をあげようかと…
今回は、毎度使っていてあれ?と振り返っていることなので自分が使っていることこちらに書き出してみようと思います。

概要

  • ActiveStorageとは
  • 導入方法
  • 使用方法
  • 保存した画像を表示
  • 画像加工のツールについて
  • 感想

ActiveStorageとは

Active StorageとはAmazon S3、Google Cloud Storage、Microsoft Azure Storageなどの クラウドストレージサービスへのファイルのアップロードや、ファイルをActive Recordオブジェクトにアタッチする機能を提供します。development環境とtest環境向けのローカルディスクベースのサービスを利用できるようになっており、ファイルを下位のサービスにミラーリングしてバックアップや移行に用いることもできます。

アプリケーションでActive Storageを用いることで、ImageMagickで画像のアップロードを変換したり、 PDFやビデオなどの非画像アップロードの画像表現を生成したり、任意のファイルからメタデータを抽出したりできます。

Railsガイド引用

画像アップロード機能が簡単に実装できるGemです。
ActiveStorageはRails5.2から標準で搭載されるようになりました。

導入方法

  • Active Record モデルを用意する まず、導入したいアプリケーションのディレクトリで
rails active_storage:install

rails active_storage:installコマンドを実行すると、Active Storageに関連したマイグレーションが作成されます。
続けてマイグレートします。

rails db:migrate

生成されるテーブルは以下の二つになります。
画像用のカラムを用意する必要がない点もActive Storageの特徴の一つです。

active_storage_blobs

カラム名 保存内容
id ID
key ファイルを一意に識別するkey
filename アップロードしたファイルの名前
content_type ファイルの種類
metadata メタデータ(画像なら縦横の大きさなどが格納される)
byte_size ファイルサイズ(byte単位)
checksum チェックサム
created_at 作成日時

active_storage_attachments

カラム名 保存内容
id ID
name モデルの属性名(Userモデルのavatarとか)
record_type モデル名(Userとか)
record_id record_typeのモデルのID
blob_id active_storage_blogsのID
created_at 作成日時

使用方法

例としてpostテーブルをつかいます。

やることは

  • Active Storageのテーブルとpostテーブルのアソシエーションを定義
  • post_controller.rbにて、imageカラムの保存を許可

モデルに1つの画像を添付するには、has_one_attachedを使います。

class モデル < ApplicationRecord
  has_one_attached :ファイル名
end

has_one_attachedメソッド

各レコードとファイルを1対1の関係で紐づけるメソッドです。
has_one_attachedメソッドを記述したモデルの各レコードは、それぞれ1つのファイルを添付できます。
has_one_attached:ファイル名は、
:photo、:avatar、:hogeなど、ファイルの用途に合わせて好きなものを指定してください。

画像の保存を許可するストロングパラメーター

  private

  def post_params
    params.require(:post).permit(:image).merge(user_id: current_user.id)
  end

ストロングパラメーターについて割愛
これでイメージ画像の保存を許可できました。

保存した画像を表示

  • image_tagメソッド

img要素を生成するRailsのヘルパーメソッドです。

image_tagメソッドでは、複雑なRailsのディレクトリパスを指定しなくても、モデルから画像ファイルを呼び出して引数に記述するだけで、画像を表示するimg要素を生成します。つまり簡単。

# ファイルをモデルから指定する場合
<%= image_tag モデル.画像ファイル %>
<%= image_tag user.avatar %>

# app/assets/ディレクトリ下の画像ファイルパスでも指定できる
<%= image_tag 画像ファイルのパス %>
<%= image_tag "avatar.png" %>

自談(自分体験談そして余談)
image_tagの存在忘れて一生懸命画像表示しようとしていたw
この場合のpostモデルから引っ張ってもimage自体は他テーブルにあるため、下記のような表示にしかならなかった。

#<ActiveStorage::Attached::One:0x00007f8ba7b765f8>

どうようにしてオプションもある。

 <%= image_tag post.image, class: 'post-image' if post.image.attached? %>

attached?メソッド

レコードにファイルが添付されているかどうかで、trueかfalseを返すメソッドです。

モデル.ファイル名.attached?

用途としては必ず画像を表示する記述が読み込まれるため、画像が存在しない場合にはエラーが起きてしまうので、こちらのメソッドを使い
if文を使って工夫することで画像が存在する場合にのみ画像を読み込む記述を読み込まれるようにすることができる。
**クラス属性もつけられます。

画像加工のツールについて

  • ImageMagick
  • MiniMagick
  • ImageProcessing

ImageMagick

コマンドラインから画像に処理を加えることができるツールです。
処理としては、画像の作成やサイズ変更、保存形式の変更などがあります。

ImageMagickはGemではなく、ソフトウェアです。
そのため、Homebrewからインストールします。

GemではないImageMagickをRubyやRailsで扱うには、MiniMagickというGemが必要となります。

brew install imagemagick

こちらは一度入れれば問題ないのですでに入っている場合は、とばしてOK。

MiniMagick

ImageMagickの機能をRubyで扱えるようにしてくれるGemです。
RailsでImageMagickを扱うために必要となります。

ImageProcessing

MiniMagickでは提供できない、画像サイズを調整する機能を提供するGemです。

# Gemfileの一番下に記述する

gem 'mini_magick'
gem 'image_processing', '~> 1.2'
 bundle install

gemの更新をしたので、サーバーの立ち上げを忘れずに。

こちらを実装するとvariantメソッドを使用することで、ファイルの表示サイズを指定できます。

モデル.ファイル名.variant(resize: '幅x高さ')

感想

記録として残すつもりで書きました。
最後グダッてしまいましたが、文章力や伝え方について改善点があればコメントお願いします。
他、書いてあることに間違いがあれば申し訳ないです。ご指摘お願いします。
このほかにもS3を使う方法なんかをよく見かけますがこちらは実際に試したあと、追記で残そうかなと思っています。
以上、ありがとうございました。

 参考URL

https://qiita.com/hmmrjn/items/7cc5e5348755c517458a
https://qiita.com/kimuray/items/3335a87d3488be340374
https://qiita.com/sibakenY/items/e550166970e84d96e16e
https://railsguides.jp/active_storage_overview.html#active-storage%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6

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

【Rails】created_atを使って「〇〇分前」と表示したい!

概要

本記事ではRailsのcreated_atで作成日時を表示する際に、従来の時間表記ではなく「〇〇分前」のように表示する方法を記載しています。
完成イメージは以下の通りです。
スクリーンショット 2021-01-12 22.01.28.png

開発環境

  • Ruby 2.6.5p114
  • Rails 6.0.3.4

手順

① 日本語設定をする

まずは、日本語設定をします。
configのapplication.rbファイルに以下を記述してください

config/application.rb
config.i18n.default_locale = :ja

② 日本語のyamlファイルを作成する

次に、どのような日本語表記にするかyamlファイルに設定を書いていきます。
日本語に関するja.ymlはRailsにデフォルトで入っていないため、以下を参考に作成してください。
ja.ymlファイルはconfig/locales配下に作成します。

config/locales/ja.yml
ja:
  datetime:
    distance_in_words:
      about_x_hours:
        one: 1時間
        other: %{count}時間
      about_x_months:
        one: 1ヶ月
        other: %{count}ヶ月
      about_x_years:
        one: 1
        other: %{count}
      almost_x_years:
        one: 1年弱
        other: "%{count}年弱"
      half_a_minute: 30秒前後
      less_than_x_seconds:
        one: 1秒以内
        other: "%{count}秒未満"
      less_than_x_minutes:
        one: 1分以内
        other: "%{count}分未満"
      over_x_years:
        one: 1年以上
        other: "%{count}年以上"
      x_seconds:
        one: 1
        other: "%{count}秒"
      x_minutes:
        one: 1
        other: "%{count}分"
      x_days:
        one: 1
        other: "%{count}日"
      x_months:
        one: 1ヶ月
        other: "%{count}ヶ月"
      x_years:
        one: 1
        other: "%{count}年"

③ ビューで表示する

最後にビューで表示します。
表示する際は、Railsが用意してくれているtime_ago_in_wordsメソッドを使用します。
引数には作成日時を渡します。

index.html.erb
<p>#{time_ago_in_words(post.created_at)}前にアウトプット</p>

※postの部分は適宜書きかえてください



以上で実装が完了しました!
表示方法を自分好みに修正したい時はja.ymlにて修正してください。

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

【Rails】created_atを使って「〇〇分前」という表記にしたい!

概要

本記事ではRailsのcreated_atで作成日時を表示する際に、従来の時間表記ではなく「〇〇分前」のように表示する方法を記載しています。
完成イメージは以下の通りです。
スクリーンショット 2021-01-12 22.01.28.png

開発環境

  • Ruby 2.6.5p114
  • Rails 6.0.3.4

方法

① 日本語設定をする

まずは、日本語設定をします。
configのapplication.rbファイルに以下を記述してください

config/application.rb
config.i18n.default_locale = :ja

② 日本語のyamlファイルを作成する

次に、どのような日本語表記にするかyamlファイルに設定を書いていきます。
日本語に関するja.ymlはRailsにデフォルトで入っていないため、以下を参考に作成してください。
ja.ymlファイルはconfig/locales配下に作成します。

config/locales/ja.yml
ja:
  datetime:
    distance_in_words:
      about_x_hours:
        one: 1時間
        other: %{count}時間
      about_x_months:
        one: 1ヶ月
        other: %{count}ヶ月
      about_x_years:
        one: 1
        other: %{count}
      almost_x_years:
        one: 1年弱
        other: "%{count}年弱"
      half_a_minute: 30秒前後
      less_than_x_seconds:
        one: 1秒以内
        other: "%{count}秒未満"
      less_than_x_minutes:
        one: 1分以内
        other: "%{count}分未満"
      over_x_years:
        one: 1年以上
        other: "%{count}年以上"
      x_seconds:
        one: 1
        other: "%{count}秒"
      x_minutes:
        one: 1
        other: "%{count}分"
      x_days:
        one: 1
        other: "%{count}日"
      x_months:
        one: 1ヶ月
        other: "%{count}ヶ月"
      x_years:
        one: 1
        other: "%{count}年"

③ ビューで表示する

最後にビューで表示します。
表示する際は、Railsが用意してくれているtime_ago_in_wordsを使用します。
引数には作成日時を渡します。

index.html.erb
<p>#{time_ago_in_words(post.created_at)}前にアウトプット</p>

※postの部分は適宜書きかえてください



以上で実装が完了しました!
表示方法を自分好みに修正したい時はja.ymlにて修正してください。

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

【Rails】created_atを使って「〇〇分前」と表示させたい!

概要

本記事ではRailsのcreated_atで作成日時を表示する際に、従来の時間表記ではなく「〇〇分前」のように表示する方法を記載しています。
完成イメージは以下の通りです。
スクリーンショット 2021-01-12 22.01.28.png

開発環境

  • Ruby 2.6.5p114
  • Rails 6.0.3.4

手順

① 日本語設定をする

まずは、日本語設定をします。
configのapplication.rbファイルに以下を記述してください

config/application.rb
config.i18n.default_locale = :ja

② 日本語のyamlファイルを作成する

次に、どのような日本語表記にするかyamlファイルに設定を書いていきます。
日本語に関するja.ymlはRailsにデフォルトで入っていないため、以下を参考に作成してください。
ja.ymlファイルはconfig/locales配下に作成します。

config/locales/ja.yml
ja:
  datetime:
    distance_in_words:
      about_x_hours:
        one: 1時間
        other: %{count}時間
      about_x_months:
        one: 1ヶ月
        other: %{count}ヶ月
      about_x_years:
        one: 1
        other: %{count}
      almost_x_years:
        one: 1年弱
        other: "%{count}年弱"
      half_a_minute: 30秒前後
      less_than_x_seconds:
        one: 1秒以内
        other: "%{count}秒未満"
      less_than_x_minutes:
        one: 1分以内
        other: "%{count}分未満"
      over_x_years:
        one: 1年以上
        other: "%{count}年以上"
      x_seconds:
        one: 1
        other: "%{count}秒"
      x_minutes:
        one: 1
        other: "%{count}分"
      x_days:
        one: 1
        other: "%{count}日"
      x_months:
        one: 1ヶ月
        other: "%{count}ヶ月"
      x_years:
        one: 1
        other: "%{count}年"

③ ビューで表示する

最後にビューで表示します。
表示する際は、Railsが用意してくれているtime_ago_in_wordsメソッドを使用します。
引数には作成日時を渡します。

index.html.erb
<p>#{time_ago_in_words(post.created_at)}前にアウトプット</p>

※postの部分は適宜書きかえてください



以上で実装が完了しました!
表示方法を自分好みに修正したい時はja.ymlにて修正してください。

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

[備忘録]本番環境への更新(AWS EC2)

前提

  • Railsでアプリケーションを作成している
  • AWSの初期設定が完了しており、EC2のインスタンスなども起動し、既にアプリケーションをデプロイしている
  • Capistranoによる自動デプロイ設定が完了している

本記事の目的

  • 作成したアプリケーションにローカルで変更を加えたので、AWSで本番環境にも変更を反映させたい
  • herokuでのプッシュ方法をいつも忘れてしまうので、備忘録代わりに投稿しておきたい

手順

1.ローカル環境での変更をgithub上のmasterブランチへプッシュ

2.EC2へログイン

terminal
~ % cd .ssh
.ssh % ssh -i example.pem ec2-user@更新したいアプリケーションのElastic IP

3.EC2内のアプリケーションのリポジトリへ移動

terminal
[ec2-user@ip-○○○-○○-○○-○○ ~]$ cd /var/www/アプリケーション名

4.現在動いているサーバーを落とす

terminal
[ec2-user@ip-○○○-○○-○○-○○ リポジトリ名]$ ps aux | grep unicorn
↓
「unicorn master -c」の文字があるプロセスIDを探す
↓
kill プロセスID
↓
exit を実行し、EC2インスタンスからログアウト

5.ローカルの更新したいアプリケーションのディレクトリにて自動デプロイを実行

terminal
アプリケーション名 % bundle exec cap production deploy

6.Elastic IPよりアクセスし。変更が反映されているか確認

以上。

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

Rails APIモード + devise_token_auth + Vue.js 3 で認証機能付きのSPAを作る(Rails編)

はじめに

本記事はAPIをRailsのAPIモードで開発し、フロント側をVue.js 3で開発して、認証基盤にdevise_token_authを用いてトークンベースの認証機能付きのSPAを作るチュートリアルになります。

RailsやVue.jsの環境構築には触れませんのであしからず。

Rails newでAPI作成

今回はsample-apiというプロジェクト名とします。

$ rails new sample-api -d postgresql -T --api

$ cd sample-api

$ rails db:create

DBはPostgreSQLを使用し、testディレクトリの生成をスキップし、APIモードでRailsアプリの雛形を作成するように実行します。

--api を指定することで、以下の設定がなされます。

・利用するミドルウェアを通常よりも絞り込んでアプリケーションを起動するよう設定します。特に、ブラウザ向けアプリケーションで有用なミドルウェア(cookiesのサポートなど)を一切利用しなくなります。

・ApplicationControllerを、通常のActionController::Baseの代わりにActionController::APIから継承します。ミドルウェアと同様、Action Controllerモジュールのうち、ブラウザ向けアプリケーションでしか使われないモジュールをすべて除外します。

・ビュー、ヘルパー、アセットを生成しないようジェネレーターを設定します。

出典: Rails による API 専用アプリケーション

deviseおよびdevise_token_authのインストール

Gemfileを編集します。

gem 'jbuilder'
gem 'rack-cors'
gem 'devise'
gem 'devise_token_auth'

$ bundle

jbuilderおよびrack-corsはデフォルトでコメントアウトされているので、それを外せばOKです。

簡単に使用意図を説明すると、jbuilderはRailsでJSONをシンプルに扱うため、rack-corsはCORS(Cross Origin Resource Sharing)の設定を簡単に行うために導入しています。

CORSとはなんぞや?については徳丸浩さんのYou Tubeが詳しかったです。
CORSの原理を知って正しく使おう

bundle installが終了したら、以下のコマンドを実行します。

sample-api $ rails g devise:install

sample-api $ rails g devise_token_auth:install User auth

上記のコマンドを実行すると、deviseのinitializerファイルやlocaleファイルが作成されます。
deviseの詳しい使い方については以下の記事が非常に詳しいです。

Devise入門64のレシピ その1

次に作成されたmigrationファイルを修正します。

## Rememberable
t.datetime :remember_created_at

# 追加
## Trackable
t.integer  :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string   :current_sign_in_ip
t.string   :last_sign_in_ip

## Confirmable
t.string   :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string   :unconfirmed_email # Only if using reconfirmable

修正したら以下のコマンドを実行します。

$ rails db:migrate

rack-corsの設定

config/initializers/cors.rbを以下のように編集します。

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'
    resource '*',
    headers: :any,
    expose: ['access-token', 'expiry', 'token-type', 'uid', 'client'],
    methods: [:get, :post, :options, :delete, :put, :patch, :head]
  end
end

上記の設定を行うことで、異なるオリジン(localhost:4200 から localhost:3000への通信等)間での通信を行うことができるようになります。

APIの疎通確認を行う

デフォルトの設定だと、リクエスト毎にtokenが更新されてしまうので、configをいじります。

# config/initializers/devise_token_auth.rb

  # By default the authorization headers will change after each request. The
  # client is responsible for keeping track of the changing tokens. Change
  # this to false to prevent the Authorization header from changing after
  # each request.
  config.change_headers_on_each_request = false # <= コメントアウトを外して、trueからfalseに修正する

  # By default, users will need to re-authenticate after 2 weeks. This setting
  # determines how long tokens will remain valid after they are issued.
  config.token_lifespan = 2.weeks # <= コメントアウトを外す

ここまでできたら、認証機能を試してみましょう。

まずは以下のコマンドを実行してください。

$ rails c

> User.create!(name: 'テストユーザー', email: 'test-user+1@example.com', password: 'password')

次に、rails s コマンドでローカルサーバーを起動し以下のcurlコマンドを実行します。

$ curl -D - localhost:3000/auth/sign_in -X POST -d '{"email":"test-user+1@example.com", "password":"password"}' -H "content-type:application/json"

ログインに成功すると、以下のような値が返ると思います。

HTTP/1.1 200 OKn"
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: application/json; charset=utf-8
access-token: q1wL0eAS3IwKGcs5-8vEyA
token-type: Bearer
client: sd74van0pd3Sxs4O-fowvQ
expiry: 1641540499
uid: book-clip-test1@gmail.com
ETag: W/"12ac3053b26f91ca234280ac13a0790c"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 707fe01b-d25a-4167-b0f2-95e009c9271a
X-Runtime: 0.403161
Vary: Origin
Transfer-Encoding: chunked

{"data":{"id":1,"email":"test-user+1@example.com","provider":"email","uid":"test-user+1@example.com","allow_password_change":false,"name":"テストユーザー"}}

access-tokenやclientの値はログインしているかどうか、の判定に用いることになります。

curlで指定しているオプションについて説明しておくと、

-D - # レスポンスヘッダーを表示する。
-X   # HTTPメソッドの指定に用いる
-d   # POSTリクエストでRequest bodyにdataを入れて送信
-H   # ヘッダ情報の付与

を行っています。

tokenの検証を行う

devise_token_authは、token検証のための機能も提供しています。

先ほど取得したtokenを活用して、以下のcurlリクエストを送ってみます。

$ curl -D - -H "access-token:取得したtoken" -H "client:取得したclient" -H "expiry:取得したexpiry" -H "uid:取得したuid" -H "content-type:application/json" localhost:3000/auth/validate_token

成功すると以下のレスポンスが返る

HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: application/json; charset=utf-8
access-token: q1wL0eAS3IwKGcs5-8vEyA
token-type: Bearer
client: sd74van0pd3Sxs4O-fowvQ
expiry: 1641540499
uid: book-clip-test1@gmail.com
ETag: W/"f3e45c8f2942619bd67981aead0bc740"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 0b9e57df-1f3b-4597-9c0f-01a6b3f904be
X-Runtime: 0.086486
Vary: Origin
Transfer-Encoding: chunked

{"success":true,"data":{"id":1,"provider":"email","uid":"test-user+1@example.com","allow_password_change":false,"name":"テストユーザー"}}

scaffoldで簡単なCRUDを行う

deviseを用いているので、authenticate_user!メソッドが使えるようになります。
なので、scaffoldを用いて作成したcontrollerにauthenticate_user!メソッドを記述して、
きちんと認証判定が行われるかをテストしてみましょう。

$ rails g scaffold Post title:string body:text user:references
Running via Spring preloader in process 21
      invoke  active_record
      create    db/migrate/20210112131218_create_posts.rb
      create    app/models/post.rb
       error    rspec [not found] # Rspecを導入していないため
      invoke  resource_route
       route    resources :posts
      invoke  scaffold_controller
      create    app/controllers/posts_controller.rb
       error    rspec [not found] # Rspecを導入していないため
      invoke    jbuilder
      create      app/views/posts
      create      app/views/posts/index.json.jbuilder
      create      app/views/posts/show.json.jbuilder
      create      app/views/posts/_post.json.jbuilder

いくつか編集を加えます。

# app/controllers/posts_controller.rb

class PostsController < ApplicationController
  before_action :set_post, only: [:show, :update, :destroy]
  before_action :authenticate_user! # authenticate_user!を各アクション実行前に呼ばれるようにする

  def index
    @posts = Post.all
  end

  def show
  end

  def create
    @post = Post.new(post_params)

    if @post.save
      render :show, status: :created # locationを削除
    else
      render json: @post.errors, status: :unprocessable_entity
    end
  end

  def update
    if @post.update(post_params)
      render :show, status: :ok # locationを削除
    else
      render json: @post.errors, status: :unprocessable_entity
    end
  end

  def destroy
    @post.destroy
  end

  private
    def set_post
      @post = Post.find(params[:id])
    end

    def post_params
      params.permit(:title, :body).merge(user: current_user) # require を削除し、current_userをmerge
    end
end
# config/routes.rb

  scope format: 'json' do # json形式のリクエストに対応
    resources :users do
      resources :posts
    end
  end
# app/models/user.rb

has_many :posts, dependent: :destroy
# app/views/_post.json.jbuilder

json.extract! post, :id, :title, :body, :user_id, :created_at, :updated_at
# json.url post_url(post, format: :json)を削除する

この状態でマイグレーションを実行してください。

$ rails db:migrate

先ほどと同様、curlコマンドで動作確認をしてみます。

# createアクション
$ curl localhost:3000/users/1/posts -X POST -d '{"title":"レビュー", "body":"面白い"}' \
-H "content-type:application/json" \
-H "access-token:取得したaccess-token" \
-H "client:取得したclient" \
-H "expiry:取得したexpiry" \
-H "uid:取得したuid"

{"id":1,"title":"レビュー","body":"面白い","user_id":1,"created_at":"2021-01-12T22:27:28.290+09:00","updated_at":"2021-01-12T22:27:28.290+09:00"}

# indexアクション

$ curl localhost:3000/users/1/posts -H "content-type:application/json" \
-H "content-type:application/json" \
-H "access-token:取得したaccess-token" \
-H "client:取得したclient" \
-H "expiry:取得したexpiry" \
-H "uid:取得したuid"

[{"id":1,"title":"レビュー","body":"面白い","user_id":1,"created_at":"2021-01-12T22:26:53.244+09:00","updated_at":"2021-01-12T22:26:53.244+09:00"}]

きちんと認証をパスしていて、正常にjsonを取得することができました。

次回はVue.js側を実装していきます。

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

RailsアプリをHerokuにデプロイする

個人の忘備録になります。基本操作はすべて、macOSのターミナルで行います。

1.Heroku CLIをインストール

ターミナルを開き、下記のコマンドを入力します。

% brew tap heroku/brew && brew install heroku

インストールが終了したら、完了を確認するために下記のコマンドを入力します。
ここでは、heroku/x.y.z出力に表示されるはずです。

% heroku --version
heroku/7.0.0 (darwin-x64) node-v8.0.0

2.ログイン

CLIをインストールした後、heroku loginコマンドを実行します。メールアドレスやパスワードをもとめられるので、入力していきます。

% heroku login
=> Enter your Heroku credentials.
# メールアドレスを入力し、エンターキーを押す
  => Email:
# パスワードを入力して、エンターキーを押す
  => Password:

Logged in as メールアドレス と出力されるとログイン成功です。

3.Heroku上にアプリケーションを作成

% cd デプロイしたいアプリの場所

アプリ作成の際にアプリ名は一意でなければいけないので、他の人がすでに使っている名前だとエラーが表示されます。
またアプリ名がドメインの一部となるので、ドメインに使えない文字列は利用できません。
以上2点に気をつけて、以下のコマンドを入力します。

% heroku create アプリケーション名

正しく設定できたことを確認する場合は、以下を入力します。

% git config --list | grep heroku

入力後、 fatal: not in a git directory 以外が表示されていれば成功です。

4.MySQLを使用する場合

Herokuでは、使用するデータベースの設定が、デフォルトでPostgreSQLになっています。MySQLを使うためには、ClearDBというデータベースサービスが提供しているアドオンを追加することによって、HerokuでMySQLを使用できるようになります。

下記のコマンドを入力して、ClearDBアドオンを追加します。

% heroku addons:add cleardb

Ruby on Railsを使う場合は、MySQLに対応するGemについて考慮する必要があり、そちらの設定を変更します。
まず、下記のコマンドを入力してください。

% heroku_cleardb=`heroku config:get CLEARDB_DATABASE_URL`

これでClearDBデータベースのURLを変数heroku_cleardbに格納できました。
続いて、下記のコマンドを入力します。

% heroku config:set DATABASE_URL=mysql2${heroku_cleardb:5}

5.Heroku上にmaster.keyを設置

Heroku上には、環境変数(OSが提供するデータ共有機能の1つで、「どのディレクトリ・ファイルからでも参照できる変数」)としてmaster.keyの値を設置します。
まず、下記のコマンドを入力してください。

% heroku config:set RAILS_MASTER_KEY=`cat config/master.key`

設定が正しくできているか、Herokuの環境変数一覧を表示するには、以下のコマンドを入力します。

% heroku config

入力後、RAILS_MASTER_KEY: という項目がある場合、成功です。

6.アプリケーションをHerokuへ追加

Herokuへコミットをプッシュすることで、Herokuにアプリケーションの情報が追加できます。以下のコマンドを入力します。

% git push heroku master

7.Heroku上でマイグレーションファイルを実行

データベースにはマイグレーションの情報が反映されていませんので、heroku runを頭につけて、マイグレーションを実行してください。

% heroku run rails db:migrate

8.アプリケーションの情報を確認

これまでの作業で、Herokuに反映したアプリケーションの情報を確認するには、以下のコマンドを入力します。

% heroku apps:info

9.アプリケーションを開く

% heroku open

問題なく、アプリケーションが開けましたらデプロイ完了です。

おまけ1:ログのチェック

アプリが適切に機能しないような問題が発生した場合は、ログをチェックします。--tailをつけることでログの最終10行のみ表示にすることができます。

% heroku logs --tail --app アプリケーション名

おまけ2:環境変数が更新されない場合の対処法

ファイルの変更履歴が存在する場合

Herokuにプッシュしたときから少しでもファイルに差分が存在する場合は、通常通りコミットして、そのコミットをHerokuにプッシュすれば問題ありません。以下のコードを上から順に実行していきます。

% git add .
% git commit -m "後から見てわかりやすいコミット名"
% git push heroku master

ファイルの変更履歴が存在しない場合

特に変更するファイルもないがHerokuに設定した環境変数だけ本番環境に反映させたい場合は、上記の方法ですと、「Everything up-to-date(すでに最新の状態に更新されています)」と表示されます。したがって、意図的に空のコミットを作成してHerokuにプッシュする方法を行います。以下のコードを上から順に実行していきます。

% git commit --allow-empty -m "空のcommit" 
% git push heroku master
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails6】Rubocop警告一覧

はじめに

転職活動用ポートフォリオ作成中です。
今回、CircleCiを導入したことにより、push時にRubocopでコードのスタイルチェックが行われるようになり、そこで指摘されるエラーをまとめていきたいと思いました。

環境

Ruby on Rails '6.0.0'
Ruby '2.6.5'

①Layout/ExtraSpacing: Unnecessary spacing detected.

不必要な、スペースが確認されたためです

app/models/company.rb
validates_format_of :password, on: :create,  with: PASSWORD_REGEX, message: 'には英字と数字の両方を含めて設定してください'

今回は少しわかりにくいですが、「on: :create」と「with: ~」の間に1文字分余計なスペースがあったため、警告が出た模様です。

②Layout/EmptyLineBetweenDefs: Use empty lines between method definitions.

メソッドの定義文が1つの空の行で区切られているかどうかをチェックしています。

def a
end
def b
end

「def a」と「def b」の間に空白を開けていなかったため発生した模様です。

③Layout/TrailingWhitespace: Trailing whitespace detected.

該当部分の末尾に余計な空白が存在しているため、警告が出ました。

終わりに

bundle exec rubocop -a

上記コマンドを使用すれば、勝手に修正してくれるみたいですが、
なぜか意味を調べたくなったので、投稿しました。気が向けば新たな警告文が出た場合は、投稿していきます。。。。

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

[備忘録]本番環境への更新(heroku)

前提

  • Railsでアプリケーションを作成している
  • heroku CLIのインストールが済んでいる

本記事の目的

  • 作成したアプリケーションにローカルで変更を加えたので、herokuで本番環境にも変更を反映させたい
  • herokuでのプッシュ方法をいつも忘れてしまうので、備忘録代わりに投稿しておきたい

手順

1.ローカル環境での変更をgithub上のmasterブランチへプッシュ

2.ターミナル上からherokuへログイン

terminal
% heroku login --interactive
↓
heroku: Enter your login credentials
Email [example@example.com]: example@example.com
Password: ********
Logged in as example@example.com


どのディレクトリで実行しても問題ないが、更新したいアプリのディレクトリ上で実行するのが無難だと思う。



3.アプリケーションをherokuにプッシュする

terminal
% git push heroku master
更新したいアプリのディレクトリ上で実行



4.もしデータベース関連でも変更がある場合は以下も実行する

terminal
% heroku run rails db:migrate
更新したいアプリのディレクトリ上で実行

以上。

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

DBのロールバックが出来ない時の対処方法〜UnknownMigrationVersionError〜

はじめに

rails db:rollbackでマイグレーションファイルをロールバックしようとしたところ、下記のようにUnknownMigrationVersionErrorというエラーに悩まされたので、この解決方法について書こうと思います。

ターミナル
rails aborted!
ActiveRecord::UnknownMigrationVersionError: 

No migration with version number 20201230115014.

UnknownMigrationVersionErrorの詳細

rails db:migrateでマイグレーションファイルの状態を確認したところ、NO FILEと書かれた身に覚えのないデータが残っていました。これは、statusがupのままVSコード上でマイグレーションファイルを削除してしまったため、DB上には対象ファイルが残っている状態でした。(おそらく、ActiveHashでモデルを作成した時に、skip-migrationを忘れて謝って作成したファイルだと思います...)
つまり、対応するファイルが存在しないマイグレーションデータが残ったままになっていたのが、UnknownMigrationVersionErrorの原因でした。

D5E70B3A-1D63-479C-94FD-900F409069C4_4_5005_c.jpeg

解決方法

1. VSコード上にマイグレーションファイルを作成

まず、NO FILEとなっていたIDが20201230115014のデータに対応するファイルを、VSコード上で作成します。(今回は、「20201230115014.dummy.rb」というファイル名で作成しています。)

99E65E07-FF88-42CC-9BC3-3C9C084D0334_4_5005_c.jpeg

2. ステータスをdownにする

マイグレーションファイルを作成しただけの状態で、ロールバックしようとすると下記のようにエラーが出ます。これは、先ほど作成したマイグレーションファイルの中身が何も記述されていないことが原因です。

ターミナル
rails aborted!
NameError: uninitialized constant Dummy

これを解消するために、マイグレーションファイルの中身を書いてあげます。その後、一度マイグレートしてあげましょう。

20201230115014.dummy.rb
class Dummy < ActiveRecord::Migration[6.0]
  def change
  end
end
ターミナル
rails db:migrate

この状態で、ロールバックしてあげると、無事ステータスがダウンになりました。
A5D9F5BE-30C3-4305-87D0-8AE73B397D3D_4_5005_c.jpeg

3. 対象ファイルの削除

ここまで来れば、後は対象ファイルを削除するだけです。VSコードで「20201230115014.dummy.rb」のファイルを削除した後、rails db:migrate:statusで確認すると、無事対象のデータが削除できてることが確認できます。
5C22EAFC-90C1-4B95-8F5A-AB7B82F34315_4_5005_c.jpeg

終わりに

何気なく、ステータスを確認せずにマイグレーションファイルを削除すると後々面倒だなと思いました。何か見てくださる方のヒントになれば幸いです。

参考

https://qiita.com/ISSO33/items/33a935cb3255c269bef2

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

マイグレーションのロールバックが出来ない時の対処方法  〜UnknownMigrationVersionError〜

はじめに

rails db:rollbackでマイグレーションファイルをロールバックしようとしたところ、下記のようにUnknownMigrationVersionErrorというエラーに悩まされたので、この解決方法について書こうと思います。

ターミナル
rails aborted!
ActiveRecord::UnknownMigrationVersionError: 

No migration with version number 20201230115014.

UnknownMigrationVersionErrorの詳細

rails db:migrateでマイグレーションファイルの状態を確認したところ、NO FILEと書かれた身に覚えのないデータが残っていました。これは、statusがupのままVSコード上でマイグレーションファイルを削除してしまったため、DB上には対象ファイルが残っている状態でした。(おそらく、ActiveHashでモデルを作成した時に、skip-migrationを忘れて謝って作成したファイルだと思います...)
つまり、対応するファイルが存在しないマイグレーションデータが残ったままになっていたのが、UnknownMigrationVersionErrorの原因でした。

D5E70B3A-1D63-479C-94FD-900F409069C4_4_5005_c.jpeg

解決方法

1. VSコード上にマイグレーションファイルを作成

まず、NO FILEとなっていたIDが20201230115014のデータに対応するファイルを、VSコード上で作成します。(今回は、「20201230115014.dummy.rb」というファイル名で作成しています。)

99E65E07-FF88-42CC-9BC3-3C9C084D0334_4_5005_c.jpeg

2. ステータスをdownにする

マイグレーションファイルを作成しただけの状態で、ロールバックしようとすると下記のようにエラーが出ます。これは、先ほど作成したマイグレーションファイルの中身が何も記述されていないことが原因です。

ターミナル
rails aborted!
NameError: uninitialized constant Dummy

これを解消するために、マイグレーションファイルの中身を書いてあげます。その後、一度マイグレートしてあげましょう。

20201230115014.dummy.rb
class Dummy < ActiveRecord::Migration[6.0]
  def change
  end
end
ターミナル
rails db:migrate

この状態で、ロールバックしてあげると、無事ステータスがダウンになりました。
A5D9F5BE-30C3-4305-87D0-8AE73B397D3D_4_5005_c.jpeg

3. 対象ファイルの削除

ここまで来れば、後は対象ファイルを削除するだけです。VSコードで「20201230115014.dummy.rb」のファイルを削除した後、rails db:migrate:statusで確認すると、無事対象のデータが削除できてることが確認できます。
5C22EAFC-90C1-4B95-8F5A-AB7B82F34315_4_5005_c.jpeg

終わりに

何気なく、ステータスを確認せずにマイグレーションファイルを削除すると後々面倒だなと思いました。何か見てくださる方のヒントになれば幸いです。

参考

https://qiita.com/ISSO33/items/33a935cb3255c269bef2

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

マイグレーションのロールバックが出来ない時の対処方法 UnknownMigrationVersionError

はじめに

rails db:rollbackでマイグレーションファイルをロールバックしようとしたところ、下記のようにUnknownMigrationVersionErrorというエラーに悩まされたので、この解決方法について書こうと思います。

ターミナル
rails aborted!
ActiveRecord::UnknownMigrationVersionError: 

No migration with version number 20201230115014.

UnknownMigrationVersionErrorの詳細

rails db:migrateでマイグレーションファイルの状態を確認したところ、NO FILEと書かれた身に覚えのないデータが残っていました。これは、statusがupのままVSコード上でマイグレーションファイルを削除してしまったため、DB上には対象ファイルが残っている状態でした。(おそらく、ActiveHashでモデルを作成した時に、skip-migrationを忘れて謝って作成したファイルだと思います...)
つまり、対応するファイルが存在しないマイグレーションデータが残ったままになっていたのが、UnknownMigrationVersionErrorの原因でした。

D5E70B3A-1D63-479C-94FD-900F409069C4_4_5005_c.jpeg

解決方法

1. VSコード上にマイグレーションファイルを作成

まず、NO FILEとなっていたIDが20201230115014のデータに対応するファイルを、VSコード上で作成します。(今回は、「20201230115014.dummy.rb」というファイル名で作成しています。)

99E65E07-FF88-42CC-9BC3-3C9C084D0334_4_5005_c.jpeg

2. ステータスをdownにする

マイグレーションファイルを作成しただけの状態で、ロールバックしようとすると下記のようにエラーが出ます。これは、先ほど作成したマイグレーションファイルの中身が何も記述されていないことが原因です。

ターミナル
rails aborted!
NameError: uninitialized constant Dummy

これを解消するために、マイグレーションファイルの中身を書いてあげます。その後、一度マイグレートしてあげましょう。

20201230115014.dummy.rb
class Dummy < ActiveRecord::Migration[6.0]
  def change
  end
end
ターミナル
rails db:migrate

この状態で、ロールバックしてあげると、無事ステータスがダウンになりました。
A5D9F5BE-30C3-4305-87D0-8AE73B397D3D_4_5005_c.jpeg

3. 対象ファイルの削除

ここまで来れば、後は対象ファイルを削除するだけです。VSコードで「20201230115014.dummy.rb」のファイルを削除した後、rails db:migrate:statusで確認すると、無事対象のデータが削除できてることが確認できます。
5C22EAFC-90C1-4B95-8F5A-AB7B82F34315_4_5005_c.jpeg

終わりに

何気なく、ステータスを確認せずにマイグレーションファイルを削除すると後々面倒だなと思いました。何か見てくださる方のヒントになれば幸いです。

参考

https://qiita.com/ISSO33/items/33a935cb3255c269bef2

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

マイグレーションのロールバックが出来ない時の対処方法(UnknownMigrationVersionError)

はじめに

rails db:rollbackでマイグレーションファイルをロールバックしようとしたところ、下記のようにUnknownMigrationVersionErrorというエラーに悩まされたので、この解決方法について書こうと思います。

ターミナル
rails aborted!
ActiveRecord::UnknownMigrationVersionError: 

No migration with version number 20201230115014.

UnknownMigrationVersionErrorの詳細

rails db:migrateでマイグレーションファイルの状態を確認したところ、NO FILEと書かれた身に覚えのないデータが残っていました。
これは、ステータスがupのままVSコード上でマイグレーションファイルを削除してしまったため、DB上にはデータが残っている状態でした。(おそらく、ActiveHashでモデルを作成した時に、skip-migrationを忘れて謝って作成したファイルだと思います...)
まとめると、対応するファイルが存在しないマイグレーションデータが残ったままになっていたのが、UnknownMigrationVersionErrorの原因でした。

D5E70B3A-1D63-479C-94FD-900F409069C4_4_5005_c.jpeg

解決方法

1. VSコード上にマイグレーションファイルを作成

まず、NO FILEとなっていたIDが20201230115014のデータに対応するファイルを、VSコード上で作成します。(今回は、「20201230115014.dummy.rb」というファイル名で作成しています。)

99E65E07-FF88-42CC-9BC3-3C9C084D0334_4_5005_c.jpeg

2. ステータスをdownにする

マイグレーションファイルを作成しただけの状態で、ロールバックしようとすると下記のようにエラーが出ます。これは、先ほど作成したマイグレーションファイルの中身が何も記述されていないことが原因です。

ターミナル
rails aborted!
NameError: uninitialized constant Dummy

これを解消するために、マイグレーションファイル内にクラスとchangeアクションを記述します。その後、一度マイグレートを行います。

20201230115014.dummy.rb
class Dummy < ActiveRecord::Migration[6.0]
  def change
  end
end
ターミナル
rails db:migrate

この状態で、ロールバックしてあげると、無事ステータスがダウンになりました。
A5D9F5BE-30C3-4305-87D0-8AE73B397D3D_4_5005_c.jpeg

3. 対象ファイルの削除

ここまで来れば、後は対象ファイルを削除するだけです。VSコードで「20201230115014.dummy.rb」のファイルを削除した後、rails db:migrate:statusで確認すると、無事対象のデータが削除できてることが確認できます。
5C22EAFC-90C1-4B95-8F5A-AB7B82F34315_4_5005_c.jpeg

終わりに

何気なく、ステータスを確認せずにマイグレーションファイルを削除すると後々面倒だなと思いました。何か見てくださる方のヒントになれば幸いです。

参考

https://qiita.com/ISSO33/items/33a935cb3255c269bef2

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

Ruby on Rails <2021年> かんたんログイン機能の実装(form_with)

前提条件

  • gem 'devise' でログイン機能を実装していること
  • 新規登録で mail: guest@guest, password: 111111で登録していること
 <%= form_with(model: User.new, url:new_user_session_path) do |f| %>
   <%= f.hidden_field :email, value: "guest@guest" %>
   <%= f.hidden_field :password, value: "111111" %>
   <%= f.submit "ゲストログイン", class:"btn btn-success btn-sm mb-3 btn-block guest"%>
<% end %>

ゲストログインがクリックされるとログイン画面へ行き、
valueの中のemailとpasswordが入り
ログインした画面へリダイレクトされます。

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

【Rails6】SNS認証による新規登録機能実装(Facebook, Google)

はじめに

現在Raisを使用しアプリケーションの作成を行なっています。今回は既に実装しているログイン機能にSNSアカウントでログインできるよう追加実装していきます。備忘録及び復習のため記述していきます。

環境

Ruby on Rails '6.0.0'
Ruby '2.6.5'

前提

①Gemの導入と環境変数の設定

omniauth
・Google,Facebook,Twitter等のSNSアカウントを用いてユーザー登録やログインなどを実装できるgemです。今回はFacebookとGoogleのomniauthをインストールします。

Gemfile
gem 'omniauth-google-oauth2'
gem 'omniauth-facebook'
ターミナル
% bundle install

また、外部API設定時に取得したIDやシークレットキーを環境変数に設定します。環境変数の設定方法はいくつかあると思いますが、私の場合は、「gem 'dotenv-rails'」により「.envファイル」を設定済みのため、そこに記述しました。

.env
FACEBOOK_CLIENT_ID='アプリID'
FACEBOOK_CLIENT_SECRET='app secret'
GOOGLE_CLIENT_ID='クライアントID'
GOOGLE_CLIENT_SECRET='クライアントシークレット'

次にアプリケーション側で環境変数を読み込む記述をします。

config/initializers/devise.rb
(省略)
  config.omniauth :facebook,ENV['FACEBOOK_CLIENT_ID'],ENV['FACEBOOK_CLIENT_SECRET']
  config.omniauth :google_oauth2,ENV['GOOGLE_CLIENT_ID'],ENV['GOOGLE_CLIENT_SECRET']

②モデルの設定

SNSのWebAPIにリクエストを送ると、API上で認証を行います。その後、SNS上に登録されている情報がアプリケーション側に返される仕組みです。
APIからのレスポンスの中の、「uid」と「provider」をアプリケーションのデータベースに、ユーザー情報(名前など)と共に保存します。
しかし、「SNS認証とユーザー登録のタイミングが異なる」仕様であるため、SNS認証時にはusersテーブルのレコードを作成することはできません。そこでSNS認証時の情報を保存する別テーブル(SnsCredentialモデル)を作成しました。

ターミナル
% rails g model sns_credential
db/migrate/XXXXXXXXXXXXXXXX_create_sns_credentials.rb
class CreateSnsCredentials < ActiveRecord::Migration[6.0]
 def change
   create_table :sns_credentials do |t|
     t.string :provider
     t.string :uid
     t.references :user,  foreign_key: true

     t.timestamps
   end
 end
end

userモデルとのアソシエーションのため、外部キーとしてuser_idを持たせています。編集がおわり次第、「rails db:migrate」にてデータベースに反映します。

アソシエーションの設定をします。

app/models/user.rb
(省略)
has_many :sns_credentials, dependent: :destroy

  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: [:facebook, :google_oauth2]

omniauth_providers: [:facebook, :google_oauth2]と記述することで、FacebookとGoogleのOumniAuthを使用できます。

app/models/sns_credential.rb
class SnsCredential < ApplicationRecord
  belongs_to :user
end

③deviseの再設定

deviseのコントローラーを再設定するため、コントローラーを作成しています。

ターミナル
% rails g devise:controllers users

生成したコントローラーを使用させるために、deviseのルーティングを変更します。

config/routes
Rails.application.routes.draw do
  devise_for :users, controllers: {
    omniauth_callbacks: 'users/omniauth_callbacks',
    registrations: 'users/registrations',
    sessions: 'users/sessions',
  }
・・・(省略)・・・
end

④コントローラーの設定

app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    authorization
  end

  def google_oauth2
    authorization
  end

  private

  def authorization
    @user = User.from_omniauth(request.env["omniauth.auth"]
  end
end

プライベートメソッド内に記述している「authorization」を呼び出す「facebook」と「google_oauth2」というアクションを定義します。

⑤Viewファイルの編集

app/views/users/registrations/new.html.erb
(省略)
<div class="d-flex justify-content-between mt-4">
  <%= link_to user_facebook_omniauth_authorize_path, class:"btn btn-outline-primary sns-btn", method: :post do %>
    <i class="fab fa-facebook fa-2x"></i>
  <% end %>
  <%= link_to user_google_oauth2_omniauth_authorize_path, class:"btn btn-outline-danger sns-btn", method: :post do %>
    <i class="fab fa-google fa-2x"></i>
  <% end %>
</div>

⑥modelの編集

Userモデルに入るデータのため、Userモデルにクラスメソッドを作成しました。
クラスメソッドとは、クラスで共通の情報を使った処理に使用するようです。記述方法は、メソッド名の前にselfを.(ドット)で繋いで定義します。

app/models/user.rb
(省略)
 def self.from_omniauth(auth)
   sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
 end

上記でomniauth_callbacks_controller.rbに記述した、User.from_omniauthも呼び出せるようになりました。
また、取得した情報内の「provider」と「uid」をsnsに代入して、first_or_createメソッドを用いています。
(first_or_createメソッド:保存するレコードがデータベースに存在するか検索を行い、検索した条件のレコードがあればそのレコードのインスタンスを返し、なければ新しくインスタンスを保存するメソッドです。)

次に、SNS認証を行なったかどうか確認した後のコードを記述します。

app/models/user.rb
(省略)
 def self.from_omniauth(auth)
   sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
   user = User.where(email: auth.info.email).first_or_initialize(
      first_name: auth.info.last_name,
      last_name: auth.info.first_name,
      email: auth.info.email
    )
 end

上記で追加したコードは、SNS認証を行なっていなかった場合、メールアドレスで検索をします。first_or_initializeを用いて、名前とメールアドレスを返します。
(first_or_initialize:whereメソッドとともに使うことで、whereで検索した条件のレコードがあればそのレコードのインスタンスを返し、なければ新しくインスタンスを作るメソッドです。)

first_or_create:新規レコードをデータベースに保存する
first_or_initialize:新規レコードをデータベースに保存しない

⑦controllerの編集

Userモデルから返ってきた後の処理を記述します。

app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    authorization
  end

  def google_oauth2
    authorization
  end

  private

  def authorization
    @user = User.from_omniauth(request.env['omniauth.auth'])

    if @user.persisted? #ユーザー情報が登録済みのため、新規登録ではなくログイン処理を行う
      sign_in_and_redirect @user, event: :authentication
    else #ユーザー情報が未登録のため、新規登録画面へ遷移する
      render template: 'devise/registrations/new'
    end
  end
end

Userモデルから返ってきた値を@userに代入します。これは、viewで取得した「名前」と「メールアドレス」を表示させるためです。

次に、ログイン時の記述を行います。

⑧Userモデルの編集

app/models/user.rb
(省略)
 def self.from_omniauth(auth)
   sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
   user = User.where(email: auth.info.email).first_or_initialize(
      first_name: auth.info.last_name,
      last_name: auth.info.first_name,
      email: auth.info.email
    )

  # userが登録済みであるか判断
   if user.persisted?
     sns.user = user
     sns.save
   end
   user
 end

persisted?で登録済みのユーザーと判断を行い、「if文」の中が呼ばれます。新規登録時は、SnsCredentialモデルが保存されるタイミングで、user_idが確定していなかったので、SnsCredentialモデルとUserモデルは紐づいていません。ログインの際に、sns.userを更新して紐付けを行います。

最後にログイン画面のviewファイルも編集して完了です!!

終わりに

もしこの記事を参考に、実装してみて、うまくできない場合は教えてください。。初めての実装のためお許しください。。

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

GitHub Actions/Cache : Dockerコンテナ上でbundle installしたgemをキャッシュする

GitHub AcitonsでRailsプロジェクトのrspec/rubocopを実行するような場合に、bundle install したgemのキャッシュを行いたいのだが、Permission deniedエラーが出てうまくキャッシュが作れず、キャッシュキーが汚染1されることがあった。

error例
Post job cleanup.
/bin/tar --posix --use-compress-program zstd -T0 -cf cache.tzst -P -C /home/runner/work/project/project --files-from manifest.txt
/bin/tar: ../../../../../tmp/cache/bundle/gems/puma-5.1.1/ext/puma_http11/.gem.20201227-1-wtkvl5: Cannot open: Permission denied
/bin/tar: ../../../../../tmp/cache/bundle/gems/mini_racer-0.3.1/ext/mini_racer_extension/.gem.
[中略]
/bin/tar: ../../../../../tmp/cache/bundle/gems/sassc-2.4.0/ext/.gem.20201227-1-1ompol5: Cannot open: Permission denied
/bin/tar: Exiting with failure status due to previous errors
Warning: Tar failed with error: The process '/bin/tar' failed with exit code 2

上記のエラーはローカルの /var/cache/bundle ディレクトリを、コンテナの /bundle にマウントした状態で BUNDLE_PATH=/bundle bundle install を実行し、/var/cache/bundleActions/Cache でキャッシュさせる場合に発生した

docker-compose と workflow のyamlは下記のような感じ

docker-compose.yaml
version: '3.7'

services:
  app:
    ...

    volumes:
      - ./:/app:cached
      - /var/cache/bundle:/bundle
.github/workflows/ci.yml
jobs:
  ...
  rspec:
    ...
    env:
      GEMS_CACHE_DIR: /tmp/cache/bundle

    steps:
    ...

    - name: Cache bundle gems
      id: cache-bundle-gems
      uses: actions/cache@v2
      with:
        path: ${{env.GEMS_CACHE_DIR}}
        key: ${{ runner.os }}-${{ matrix.ruby }}-${{ env.GEM_CACHE_TAG }}-${{ hashFiles('Gemfile.lock') }}
        restore-keys: |
          ${{ runner.os }}-${{ matrix.ruby }}-${{ env.GEM_CACHE_TAG }}-

解決策 1 アクセス権限を書き換える

アクセス権限の問題なので、雑にアクセス権限を書き換えてしまう方法。

cache作成時のtarコマンドでsudoがつけられたらいいのだが、そういうオプションはないので、下記のようなアクションstepsの最後に差し込む

.github/workflows/ci.yml
  - name: Fix permissions of bundle
    id: fix-permissions-of-bundle
    run: sudo chmod -R a+rwx ${{env.GEMS_CACHE_DIR}}

この方法で多分大丈夫。

ですが、アクセス権限書き換えることになるので、その辺りの動作確認は取れません(そこが問題になることなんてほぼないと思いますが…

解決策 2 コンテナ上でtarを作成してcacheさせる

アクセス権限の問題が発生するのは、コンテナ上で bundler install しているのにローカルでtarを作成しているため。

コンテナ上で /bundle のtarを作成し、そのtarを Actions/Cache でキャッシュして貰えば問題ありません。

下記の設定では必要最低限の軽量コンテナのみを立ち上げて(今回は standalone という名前のコンテナを使用している)、tarを作っています。

.github/workflows/ci.yml
  env:
    GEMS_ARCHIVE_DIR: /tmp/cache/bundle-gem-archive
  ...
  # $GEMS_ARCHIVE_DIR ディレクトリをキャッシュ
  - name: Cache bundle gems
    id: cache-bundle-gems
    uses: actions/cache@v2
    with:
      path: ${{ env.GEMS_ARCHIVE_DIR }}
      key: ${{ runner.os }}-${{ matrix.ruby }}-${{ env.GEM_CACHE_TAG }}-${{ hashFiles('Gemfile.lock') }}
      restore-keys: |
        ${{ runner.os }}-${{ matrix.ruby }}-${{ env.GEM_CACHE_TAG }}-

  # $GEMS_ARCHIVE_DIR をマウントした軽量コンテナ上で /archive/bundle.tar からファイルを抽出
  - name: Unarchive bundle gems cache
    id: unarchive-bundle-gems-cache
    run: test -f ${{env.GEMS_ARCHIVE_DIR}}/bundle.tar
      && docker-compose run --rm -v ${{ env.GEMS_ARCHIVE_DIR }}:/archive:cached standalone
        tar -xf /archive/bundle.tar -C /
      || echo "bundle gem cache does not exist."

  ...

  # 軽量コンテナ上で $GEM_HOME の内容をtarで固め、/archive/bundle.tar の名前で保存
  - name: Create bundle gems archive
    id: create-bundle-gems-archive
    if: steps.cache-bundle-gems.outputs.cache-hit != 'true'
    run: docker-compose run --rm -v ${{ env.GEMS_ARCHIVE_DIR }}:/archive:cached
        standalone tar -cf /archive/bundle.tar -C / bundle

この方法の場合、コンテナ上でtarを実行するので多少遅くなるようだが自分の環境の場合はほぼ誤差(数秒程度)しか変わらなかった(gemの量などにもよると思う)。

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

[Rails]date_selectにBootstrapのform-controlが適用されない

課題

Rails + Boostrap でdate_selectにform-controlが適用されない

_form.html.erb
<%= form.date_select :date, class: "form-control" %>

スクリーンショット 2021-01-12 8.53.42.png

結論

以下のような書き方で適用されます。

_form.html.erb
<%= form.date_select :date, {}, {class: 'form-control', style: 'display: inline-block;width: auto;'} %>

スクリーンショット 2021-01-12 8.56.37.png

ちなみにこの style: 'display: inline-block;width: auto; をサボると想定外にダッさい表示になります。

スクリーンショット 2021-01-12 8.58.41.png

地味に初学者が躓くので再度メモしました。
技術背景も含めて以下記事が完全回答版です。
過去の先人たちに感謝いたします。

参考情報

Rails date_selectをBootstrapを使っていい感じにする
https://qiita.com/t_oginogin/items/519fb52e1708e26a8b73

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

Rails + apipieでDartのクライアントコードを生成する

背景

こちらの記事の続きです。

Docker+Rails6+apipieを使ってAPI定義をSwagger UIに表示 - Qiita

Dart/Flutterでアプリ開発をするメンバーに上記の定義ファイルを送ったところ、「Dartのクライアントコードを自動生成できない」と言われてしまいました。

今回はDartのクライアントコードを自動生成することをゴールに、apipieの設定を書き換えます。

環境

Ruby: 2.7.1
Rails: 6.0.3

Flutter/Dartとは?

Flutterについてざっくりまとめるとこんな感じでしょうか。

  • モバイルアプリフレームワークの1つ
  • Googleが開発
  • Dartという言語を使う

ReactNativeなどはWeb系経験者がアプリに手を出しやすくするものだが、DartはWeb系の経験がなくとも手を出しやすい、ようです。

また、DartはTypeScriptと似たaltJSです。
基本的にFlutter用の言語で、altJSとして使うんだったらTypeScriptを使うほうが良さげ、なようです。

cf. Flutterとは何か? 使うメリットや特徴を理解する (1/4):CodeZine(コードジン)

apipieの調整

さて実装面。
アプリ担当からは以下のエラーを受け取りました。

E/flutter (17797): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: ApiException 500: Exception during deserialization. (Inner exception: ApiException 500: Could not find a suitable class for deserialization)
E/flutter (17797): 
E/flutter (17797): #0      ApiClient._deserialize (package:openapi/api_client.dart:57:5)
E/flutter (17797): #1      ApiClient._deserialize.<anonymous closure> (package:openapi/api_client.dart:50:43)
E/flutter (17797): #2      MappedIterator.moveNext (dart:_internal/iterable.dart:392:20)
E/flutter (17797): #3      MapBase._fillMapWithIterables (dart:collection/maps.dart:83:39)
E/flutter (17797): #4      new LinkedHashMap.fromIterables (dart:collection/linked_hash_map.dart:127:13)
E/flutter (17797): #5      ApiClient._deserialize (package:openapi/api_client.dart:49:26)
E/flutter (17797): #6      ApiClient.deserialize (package:openapi/api_client.dart:67:12)
E/flutter (17797): #7      UsersApi.postApiV1UsersToken (package:openapi/api/users_api.dart:77:49)

当方Dartは未経験でして、エラーを見てもなんのこっちゃだったので、アプリ担当に定義ファイルをいじってもらい動くようにしてもらいました。

その結果、以下3つが主な原因とわかりました。

  • basePathの指定がない
  • "additionalProperties": falseに指定している
  • レスポンスにtitleがない

basePathを指定する

api_base_urlが空欄だと警告がでるそうなので追加します。

apipie.rb
-  config.api_base_url            = ""
+  config.api_base_url            = "/api"

前回の記事 にて、わざわざapi_base_urlを消していたようです。。

"additionalProperties": falseに指定している

additionalPropertiesとは、「propertiesで指定したプロパティ以外も受け付けるかどうか」を指定するオプションのようです。

trueならpropertiesに記載がないプロパティも受け付けるが、falseだとダメなようです。

cf. OpenAPIのadditional_propertyオプションってなに?

クライアントコード生成時にエラーが起きた原因は、これがfalseになっていたから、のようです。

apipieでは初期値がfalseのため、trueに設定しましょう。

apipie.rb
+ config.swagger_allow_additional_properties_in_response = true

cf. Swagger-Specific Configuration Parameters

なお、レスポンスする全てのプロパティ(status, messageなど意図して返すもの)を含んでいる状態で生成を試みてもエラーは起きるようです。
(ここは未解決...)

レスポンスにtitleがない

API定義(swagger.json)にtitleがない場合、クラス名がInlineResponse200となってしまうようです。

swagger.json
            "schema": {
               "type": "object",
+              "title": "Token",
               "properties": {

apipieのリファレンスを見ても該当箇所にtitleを追加する記述は見つかりませんでした...。

が、 Response Description に似た実装があり、試したところうまくいきました!

api/v1/users_controller.rb
+  def_param_group :user_login_token do
+    property :status, Integer, desc: "status code"
+    property :message, String
+    property :detail, String, desc: "user token"
+  end

 api :POST, "/v1/users/token", "get access token"
  description "ログイン認証をしてトークンを返す"
  formats ["json"]
  param :email, String, desc: "メールアドレス", required: true
  param :password, String, desc: "パスワード", required: true
-  returns code: 200, desc: "return user token"
+  returns :user_login_token, desc: "return user token"
  error code: 401, desc: "Unauthorized."

以上の修正をすることで、Dartのクライアントコードを生成できるようになりました。

余談:ブラウザからDartのクライアントコードを作る

  1. Swagger Editor にAPI定義ファイルをペーストする
  2. ヘッダーのGenerate Clientからdartを選択する

という方法で、クライアントコードを生成できます。
今回程度の動作確認であれば十分できますね。

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

ローカルに保存されている画像データをZIPダウンロードしたいとき

解決したいこと

ローカルに保存されている、main.jpg、top_map_off.jpgをzipダウンロードしたい。
image.png

解決策

画像はバイナリモードでしか書き出せないので、openするときにバイナリモードを指定する。

Zip::OutputStream.open('example.zip') do |zip|
  default_images = ["main", "top_map_off"]
  default_images.each do |default_image|
    img_path = Rails.root.join(
      "app", "lib",  "output", "download_tpl", @kikaku_date.kikaku_cd, "#{target}", "images", "#{default_image}.jpg 
    ) # ローカルに保存されている画像ファイルのフルパス
    zip.put_next_entry "#{@zip_file_basename}/#{target}/images/#{default_image}.jpg"
    zip.print open(img_path, "rb").read  # 画像をバイナリモードで開く
  end
end

Rubyでバイナリデータを標準出力に出力する

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

No such file or directory @ rb_sysopen - public/user_images/1.jpgのようなエラー

画像を保存できない

Railsでアプリケーションを作っている時に、遭遇しました。

解決策は、publicディレクトリの中にuser_imagesというフォルダがない、ということなので、その名前のフォルダを作ることでエラーはなくなりました。

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

Webpacker::Manifest::MissingEntryError

rails generate controller home top

などのコマンドで適当に作成したページを開くと、以下のエラーが発生。
スクリーンショット 2021-01-04 21.58.22.png

ターミナル上は以下の表示。

Webpacker can't find application in アプリ名/public/packs/manifest.json. Possible causes:
1. You want to set webpacker.yml value of compile to true for your environment
   unless you are using the `webpack -w` or the webpack-dev-server.
2. webpack has not yet re-run to reflect updates.
3. You have misconfigured Webpacker's config/webpacker.yml file.
4. Your webpack configuration is not creating a manifest.

・manifest.jsonが見つからない。
・views/layouts内のapplication.html.erbの9行目がおかしい。

この2点を解決するために色々調べて試したが、改善せず。
調べた記事の中に、nodeのverが関係しているんじゃないかな〜的な物があったので、nodeのverを変更するついでに、webpacker installのために導入したnode,yarnやらを再installしてみた。
node verは最新版から、stableでinstallしたv14.15.4に変更。
下記の手順で実行すると、解決できました。

1.nodeのver変更するため、homebrewで管理しているnodeをuninstall。
  (homebrewでnodeのver変更する方法がわからなかったので。)
  nodebrewでstable版を再インストール。
2.yarn/icu4cも一旦uninstall。再度homebrewでインストール
  ※nodeはnodebrewでinstall済みのため、--ignore-dependencies オプションをつけること。

brew install yarn --ignore-dependencies

icu4cは--ignore-dependencies不要。

brew install icu4c

3.rails newで新しいプロジェクトを作成。
  このときすでにnode,yarn,icu4cがinstallされていれば、勝手にwebpackerもinstallされる。
4.rails sして、適当に作成したページを確認するとエラー発生せず。

これのせいで全く作業が進まなかったので改善してほんと良かった。

rails6以降は標準でwebpackerが導入されるようで、私のrailsもv6.0.3.4です。
今回の原因は、nodeのverの相性が悪かったのかな〜と思っていますが、そういうことにしときます。

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

enumの使い方(日本語表記の導入)

今回はECサイトの制作をしていた際の注文ステータスのカラムにenumを使用し、プルダウン表記を活用しました。enumを使用することが初めてであったので、忘れない様に自身のメモとして記事を残したいと思います。
以下の記事は実装の際に参考にさせていただきました。

(参考記事)
・【初心者向け】i18nを利用して、enumのf.selectオプションを日本語化する[Rails]
https://qiita.com/tanutanu/items/d44a92425188a4489ec6
・[初学者]Railsのi18nによる日本語化対応
https://qiita.com/shimadama/items/7e5c3d75c9a9f51abdd5

Gemfile

Gemfile.
gem 'enum_help'
terminal.
bundle install

まずは、enumを使用するのに必要なgemをGemfileに記述します。
上記のように記述ができたら、ターミナルでbundle installのコマンドを実行します。

model.rb

order_item.rb
 enum production_status: {cannot_be_started: 0, waiting_for_production: 1, in_production: 2, production_completed: 3}

次にモデルのファイルに記述を加えます。
enum カラム名: {名前(今回はステータス名): 0, 名前(今回はステータス名):1 ... n }
enumの後には、enumを使用するカラム名と波カッコ内にはその名目(英語表記)と各値に0から順に数字を振っていきます。
このように各名目に数字を対応させるため、テーブルを作成する際のenumのデータ型はinteger(整数型)となります。

日本語の設定に変更する

config/application.rb
module NaganoApp
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    # config.load_defaults 5.2
    config.i18n.default_locale = :ja #この1行のみ追加

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.
  end
end

この1行の記載がないと日本語表記に変換されません。

ja.yml
ja:
  enums:
    order_item: 
      production_status:
        cannot_be_started: "着手不可"    
        waiting_for_production: "制作待ち"
        in_production: "制作中"
        production_completed: "制作完了"

先ほどのmodelファイルの内容を日本語訳をして記述します。
断層やカラム名の記述に誤りがあると、表示されなくなってしまいます。

viewに表示する

<%= form_with model: @order_item, url: admin_order_item_path(order_item), method: :patch, local: true do |f| %>
 <%= f.select :production_status, OrderItem.production_statuses.keys.map {|k| [I18n.t("enums.order_item.production_status.#{k}"), k]} %>
 <%= f.submit "変更" %>
<% end %>

フォーム内にプルダウン表記でステータスを変更できるようにしました。
f.select以降は以下のような構成になっており、こちらもカラム名の単数、複数表記を誤ると表示がされなくなります。
:カラム名(単数形), モデル名.カラム名(複数形).keys.map {|k| [I18n.t("enums.モデル名.カラム名(単数形).#{k}"), k]} %
keys.map以降の表記により、設定されているenumの値の0から順に選択肢を入れてくれているようなイメージになります。

enumのメリット

今回のようにステータス管理など頻繁に更新が必要な際には特に有効な機能になります。
数値で管理を行うため、何か変更する必要がある時でも修正が容易に行えることが大きなメリットです。
model側とviewに必要な記述を少し書き加えるだけで、アプリケーション全体に変更を行うことが可能です。

終わり

今回は以上になります。
私自身もプログラミング初心者ですが、同じ様な立場の方に少しでも参考になれば幸いです。
また、もし内容に誤りなどがございましたら、ご指摘いただけますと幸いです。

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

【Rails AWS Docker】既存Ruby on Rails + MySQLアプリをDockerで構築し、AWSにデプロイする(6)

ポートフォリをとして作ったRuby on RailsアプリをDockerコンテナ化し本番環境をAWSで構築するまでの道のりです。
ポートフォリオ自体はこちらとなります。
【ポートフォリオ】転職活動時に作成したポートフォリオの概要(テッ◯キャンプ)

かなり苦しめられたので、どなたかのお役に立てれば。

タイトル
1 ローカル環境のRailsアプリをDockerコンテナ化
2 AWSにVPCを作成する。パブリックサブネットを作成する
3 プライベートサブネットを作成する
4 EC2インスタンスを作成する
5 RDSを作成する
6 DockerコンテナをAWSにアップロードする

DockerコンテナをAWSにアップロードします。

 この項目では、かなり苦しめられました。ngixやpuma、RDSなど接続しなくてはいけないものが多々あり、また、AWS内にコンテナを構築することにより今、自分がどこにいるのか、よくわからなくなる状態に陥ることがありました。
 この記事通りに、行ってもうまくいかないかもしれませんが、少しでもどなたかのお力になれればと思います。
 自分はできれば、再びあのような思いをしたく無いので忘れないうちに記事として残しておきました。

作成したAWSインスタンスにローカルdockerコンテナをアップロードしていきます。

コンテナ内での環境変数を設定します。

現状、database.ymlを確認すると

database.yml
  username: root
  password: password
  host: db

このように、セキュアな情報がハードコーディングされています。
このままではgithubのコードから漏洩する危険があるので、本番環境のコンテナ内で環境変数を用いてセキュアな情報を設定します。

環境変数については、参考までに下記をご覧下さい。
環境変数について

環境変数を利用するために、dotenv-railsというgemをインストールします。
Gemfileに以下を追記します。

Gemfile.
gem 'dotenv-rails'

dotenv-railsをインストールすることにより、「.env」ファイルに記載された環境変数をdockerコンテナ内で、下記のような記述で取り出せる様になります。

ENV['DATABASE_PASSWORD']

また、.envファイルをgithubに上げてしまうと元も子も無いので、gitignoreに追記します。

.env

.envを下記の様に記載してアプリ直下に配置します。

DB_USERNAME=root
DB_PASSWORD=gakjadfmoaeur
DB_HOST=fito2-db-instance.〇〇〇〇〇〇.ap-northeast-1.rds.amazonaws.com
DB_DATABASE=fitO2_db

DB_HOSTはRDSのエンドポイントです。
AWSのRDSダッシュボードのデーターベースをクリックし、一覧から該当のRDSを選択すると確認できます。

database.ymlの修正

database.ymlに下記を追加して、本番環境は環境変数から値を読み込むようにします。

database.yml
production:
  <<: *default
  database: <%= ENV['DB_DATABASE'] %>
  adapter: mysql2
  encoding: utf8mb4
  charset: utf8mb4
  collation: utf8mb4_general_ci
  host: <%= ENV['DB_HOST'] %>
  username: <%= ENV['DB_USERNAME'] %>
  password: <%= ENV['DB_PASSWORD'] %>

nginx.confの修正

以下の部分を本番用に修正します。

nginx.conf
server {
  listen 80;
# =========ローカルと本番切り替え===========
  server_name 固定IP;
  # server_name localhost;
# ======================================

docker-compose.ymlの修正

docker-compose.yml
version: '3'
services:
  app:
    build:
      context: .
# =========ローカルと本番切り替え===========
    command: bundle exec puma -C config/puma.rb -e production
    # command: bundle exec puma -C config/puma.rb
# ======================================
    volumes:
      - .:/fitO2
      - public-data:/fitO2/public
      - tmp-data:/fitO2/tmp
      - log-data:/fitO2/log
    networks:
      - fitO2-network
# =========ローカルと本番切り替え===========
  #   depends_on:
  #     - db

  # db:
  #   image: mysql:5.7
  #   environment:
  #     MYSQL_ROOT_PASSWORD: password
  #     MYSQL_USER: user
  #     MYSQL_PASSWORD: password
  #     MYSQL_DATABASE: fitO2_development
  #   volumes:
  #     - db-data:/var/lib/mysql
  #   networks:
  #     - fitO2-network
# ======================================

  web:
    build:
      context: ./nginx_docker
    volumes:
      - public-data:/fitO2/public
      - tmp-data:/fitO2/tmp
    ports:
      - 80:80
    depends_on:
      - app
    networks:
      - fitO2-network
volumes:
  public-data:

  tmp-data:
  log-data:
  db-data:

networks:
  fitO2-network:
    external: true

変更点
・dbコンテナは本番環境ではRDSを使用するので不要のため、コメントアウト
(appコンテナのdepend onもまとめてコメントアウトします)
・command のrails s に -e production を追記して、本番環境でサーバー立ち上げるようにした。

AWSインスタンス上にdockerコンテナを構築する。

今一度、AWSインスタンス上にdockerコンテナを構築するまでの流れを整理します。

今、現状ローカルでは、下記のようにfitO2というファイルを元に、下記の様なdockerコンテナをbuildできる状態でした。
IMG_E6E1E78616D4-1.jpeg

そして、fitO2というファイルを書き換えて、以下の様なコンテナが作成されるように修正しました。(DBコンテナ削除)
IMG_8878B35F0D0C-1.jpeg

その、fitO2をgithubにpushします。そして、AWS側からclone(2回目以降はpull)します。
IMG_5390174B203B-1.jpeg
cloneしたfitO2を元に、AWS内でbuildしてコンテナを構築し、AWS内に作成しているRDSと接続します。
IMG_025BB67DF9F9-1.jpeg

通信の流れはオレンジの矢印となります。

githubへプッシュ

まずは、修正したファイルをコミットしてgithubへpushします。

AWSにdockerをインストール

ssh接続でAWSにログインし、dockerをインストールします。

sudo yum install -y docker
  //インストール

sudo service docker start
  //docker起動

sudo usermod -G docker ec2-user
  //ec2-userに権限付与

exit
  //ログアウト
  //再度sshでログイン

docker info

下記の様な表示がされたらオッケーです。

Client:
 Debug Mode: false

Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 Images: 0
 Server Version: 19.03.13-ce
sudo chkconfig docker on
//EC2起動時にDockerを自動で立ち上げる

docker-composeをインストールします。

sudo curl -L "https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

docker-compose -v

docker-compose version 1.24.0, build 0aa59064
  //上記の様にバージョンが表示されたら成功

gitをインストールします。

sudo yum install -y git

秘密鍵を作成します。(質問は全てエンター)

ssh-keygen -t rsa -b 4096

作成した秘密鍵を表示し、コピーします。

cat ~/.ssh/id_rsa.pub

https://github.com/settings/keys
こちらにアクセスします。

右上の「new SSH key」をクリックします。
image.png

titleを適宜入力し、keyのところに先ほどコピーした文字列を貼り付けます。
(ssh-rsa~から含めます)

ssh -T git@github.com

上記を打ち込んで、途中yesと入力し、下記の様に表示されればAWSとgithubの認証が成立しました。

Hi yourname! You've successfully authenticated, but GitHub does not provide shell access.

以後、AWSインスタンス側から自身のgithubに対し、pull等ができる様になります。

githubからcloneします。(URLはリポジトリを表示したときのURL)

cd /
//ルート直下に移動します。homeディレクトリでコンテナを展開すると、pumaとnginxの連携でエラーが出る場合がありますl。puma.sockの読み取りエラー
sudo git clone https://github.com/〇〇〇〇〇〇/〇〇〇〇〇〇
ls
//lsと打って、ディレクトリがあるか確認する。

今、gitignoreに記載したファイル(.env master.key)は含まれていません。そこで、ssh通信を用いてローカルからAWSに直接ファイルを転送します。

exit
 //一旦ログアウトして、ローカルのfitO2ファイルに移動

sudo scp -i ~/.ssh/fitO2_key.pem .env ec2-user@固定IP:/home/ec2-user/

sudo scp -i ~/.ssh/fitO2_key.pem config/master.key ec2user@固定IP:/home/ec2-user

  //scpコマンドを用いて、AWSに.envを転送します。 .env のように転送したファイルが表示されたらオッケー。
  //権限の関係で一旦、ホームディレクトリに転送します。

再度、ログイン。

cd
ls -a

ホームディレクトリに移動して.envとmaster.keyが存在していたら問題ない。
.envとmaster.keyを移動させる。

sudo mv .env /fitO2
sudo mv master.key /fitO2/config

念のため移動してるか確認

ls -a  /fitO2/
ls -a  /fitO2/config

コンテナの起動

cd /fitO2

docker-compose build
//コンテナを作成します。
(Permition deniedになった場合は、sudo chmod 777 /usr/local/bin/docker-compose
を実行します。)

docker network create fitO2-network
//ネットワークを作成します。

docker-compose run app rails assets:precompile RAILS_ENV=production
//プリコンパイルを実施

docker-compose up
//コンテナを起動します。

下記のように表示されていれば問題なく起動しています。

Creating fito2_app_1 ... done
Creating fito2_web_1 ... done
Attaching to fito2_app_1, fito2_web_1
app_1  | Puma starting in single mode...
app_1  | * Version 3.12.6 (ruby 2.5.1-p57), codename: Llamas in Pajamas
app_1  | * Min threads: 5, max threads: 5
app_1  | * Environment: production
app_1  | * Listening on tcp://0.0.0.0:3000
app_1  | * Listening on unix:///fitO2/tmp/sockets/puma.sock
app_1  | Use Ctrl-C to stop

別タブを開いて、AWSにログインします。

データベースの作成

docker-compose exec app rails db:create db:migrate RAILS_ENV=production

docker-compose exec app rails db:seed RAILS_ENV=production
//(必要に応じて)

以上で、http://固定IPにアクセスした場合、正しく表示されるはずです。

RDSのデーターベース確認方法

dockerコンテナにログインし、mysqlを起動させます。

docker ps
//コンテナIDを調べます。

NAMESがfito2_app_1の方のCONTAINER IDをコピーします。

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
c5ec64f0ea76        fito2_web           "/bin/sh -c '/usr/sb…"   5 minutes ago       Up 5 minutes        0.0.0.0:80->80/tcp   fito2_web_1
442b9ddb3a20        fito2_app           "bundle exec puma -C…"   5 minutes ago       Up 5 minutes                             fito2_app_1

コンテナにログインします

docker exec -it 442b9ddb3a20 bash

mysqlを起動させます。

service mysql start
mysql -u root -h RDSのエンドポイント -p
//パスワードを入力してログイン

セキュリティーグループで制限んしているので、作成したEC2コンテナからしかアクセスできません。

ローカルから試してみてください。

以上となります。

参考

EC2上でRailsアプリケーションにDockerを導入する(Rails、Nginx、RDS)
開発環境において既存のRailsアプリにDockerを導入する方法(Rails、nginx、mysql)
無料!かつ最短?で Ruby on Rails on Docker on AWS のアプリを公開するぞ。
【画像付きで丁寧に解説】AWS(EC2)にRailsアプリをイチから上げる方法【その1〜ネットワーク,RDS環境設定編〜】
Nginx + Rails (Puma) on Docker のいくつかの実用パターン
Docker + Rails + Puma + Nginx + MySQL

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

【Rails AWS Docker 】既存Ruby on Rails + MySQLアプリをDockerで構築し、AWSにデプロイする(6)

ポートフォリをとして作ったRuby on RailsアプリをDockerコンテナ化し本番環境をAWSで構築するまでの道のりです。
ポートフォリオ自体はこちらとなります。
【ポートフォリオ】転職活動時に作成したポートフォリオの概要(テッ◯キャンプ)

かなり苦しめられたので、どなたかのお役に立てれば。

タイトル
1 ローカル環境のRailsアプリをDockerコンテナ化
2 AWSにVPCを作成する。パブリックサブネットを作成する
3 プライベートサブネットを作成する
4 EC2インスタンスを作成する
5 RDSを作成する
6 DockerコンテナをAWSにアップロードする

DockerコンテナをAWSにアップロードします。

 この項目では、かなり苦しめられました。ngixやpuma、RDSなど接続しなくてはいけないものが多々あり、また、AWS内にコンテナを構築することにより今、自分がどこにいるのか、よくわからなくなる状態に陥ることがありました。
 この記事通りに、行ってもうまくいかないかもしれませんが、少しでもどなたかのお力になれればと思います。
 自分はできれば、再びあのような思いをしたく無いので忘れないうちに記事として残しておきました。

作成したAWSインスタンスにローカルdockerコンテナをアップロードしていきます。

コンテナ内での環境変数を設定します。

現状、database.ymlを確認すると

database.yml
  username: root
  password: password
  host: db

このように、セキュアな情報がハードコーディングされています。
このままではgithubのコードから漏洩する危険があるので、本番環境のコンテナ内で環境変数を用いてセキュアな情報を設定します。

環境変数については、参考までに下記をご覧下さい。
環境変数について

環境変数を利用するために、dotenv-railsというgemをインストールします。
Gemfileに以下を追記します。

Gemfile.
gem 'dotenv-rails'

dotenv-railsをインストールすることにより、「.env」ファイルに記載された環境変数をdockerコンテナ内で、下記のような記述で取り出せる様になります。

ENV['DATABASE_PASSWORD']

また、.envファイルをgithubに上げてしまうと元も子も無いので、gitignoreに追記します。

.env

.envを下記の様に記載してアプリ直下に配置します。

DB_USERNAME=root
DB_PASSWORD=gakjadfmoaeur
DB_HOST=fito2-db-instance.〇〇〇〇〇〇.ap-northeast-1.rds.amazonaws.com
DB_DATABASE=fitO2_db

DB_HOSTはRDSのエンドポイントです。
AWSのRDSダッシュボードのデーターベースをクリックし、一覧から該当のRDSを選択すると確認できます。

database.ymlの修正

database.ymlに下記を追加して、本番環境は環境変数から値を読み込むようにします。

database.yml
production:
  <<: *default
  database: <%= ENV['DB_DATABASE'] %>
  adapter: mysql2
  encoding: utf8mb4
  charset: utf8mb4
  collation: utf8mb4_general_ci
  host: <%= ENV['DB_HOST'] %>
  username: <%= ENV['DB_USERNAME'] %>
  password: <%= ENV['DB_PASSWORD'] %>

nginx.confの修正

以下の部分を本番用に修正します。

nginx.conf
server {
  listen 80;
# =========ローカルと本番切り替え===========
  server_name 固定IP;
  # server_name localhost;
# ======================================

docker-compose.ymlの修正

docker-compose.yml
version: '3'
services:
  app:
    build:
      context: .
# =========ローカルと本番切り替え===========
    command: bundle exec puma -C config/puma.rb -e production
    # command: bundle exec puma -C config/puma.rb
# ======================================
    volumes:
      - .:/fitO2
      - public-data:/fitO2/public
      - tmp-data:/fitO2/tmp
      - log-data:/fitO2/log
    networks:
      - fitO2-network
# =========ローカルと本番切り替え===========
  #   depends_on:
  #     - db

  # db:
  #   image: mysql:5.7
  #   environment:
  #     MYSQL_ROOT_PASSWORD: password
  #     MYSQL_USER: user
  #     MYSQL_PASSWORD: password
  #     MYSQL_DATABASE: fitO2_development
  #   volumes:
  #     - db-data:/var/lib/mysql
  #   networks:
  #     - fitO2-network
# ======================================

  web:
    build:
      context: ./nginx_docker
    volumes:
      - public-data:/fitO2/public
      - tmp-data:/fitO2/tmp
    ports:
      - 80:80
    depends_on:
      - app
    networks:
      - fitO2-network
volumes:
  public-data:

  tmp-data:
  log-data:
  db-data:

networks:
  fitO2-network:
    external: true

変更点
・dbコンテナは本番環境ではRDSを使用するので不要のため、コメントアウト
(appコンテナのdepend onもまとめてコメントアウトします)
・command のrails s に -e production を追記して、本番環境でサーバー立ち上げるようにした。

AWSインスタンス上にdockerコンテナを構築する。

今一度、AWSインスタンス上にdockerコンテナを構築するまでの流れを整理します。

今、現状ローカルでは、下記のようにfitO2というファイルを元に、下記の様なdockerコンテナをbuildできる状態でした。
IMG_E6E1E78616D4-1.jpeg

そして、fitO2というファイルを書き換えて、以下の様なコンテナが作成されるように修正しました。(DBコンテナ削除)
IMG_8878B35F0D0C-1.jpeg

その、fitO2をgithubにpushします。そして、AWS側からclone(2回目以降はpull)します。
IMG_5390174B203B-1.jpeg
cloneしたfitO2を元に、AWS内でbuildしてコンテナを構築し、AWS内に作成しているRDSと接続します。
IMG_025BB67DF9F9-1.jpeg

通信の流れはオレンジの矢印となります。

githubへプッシュ

まずは、修正したファイルをコミットしてgithubへpushします。

AWSにdockerをインストール

ssh接続でAWSにログインし、dockerをインストールします。

sudo yum install -y docker
  //インストール

sudo service docker start
  //docker起動

sudo usermod -G docker ec2-user
  //ec2-userに権限付与

exit
  //ログアウト
  //再度sshでログイン

docker info

下記の様な表示がされたらオッケーです。

Client:
 Debug Mode: false

Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 Images: 0
 Server Version: 19.03.13-ce
sudo chkconfig docker on
//EC2起動時にDockerを自動で立ち上げる

docker-composeをインストールします。

sudo curl -L "https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

docker-compose -v

docker-compose version 1.24.0, build 0aa59064
  //上記の様にバージョンが表示されたら成功

gitをインストールします。

sudo yum install -y git

秘密鍵を作成します。(質問は全てエンター)

ssh-keygen -t rsa -b 4096

作成した秘密鍵を表示し、コピーします。

cat ~/.ssh/id_rsa.pub

https://github.com/settings/keys
こちらにアクセスします。

右上の「new SSH key」をクリックします。
image.png

titleを適宜入力し、keyのところに先ほどコピーした文字列を貼り付けます。
(ssh-rsa~から含めます)

ssh -T git@github.com

上記を打ち込んで、途中yesと入力し、下記の様に表示されればAWSとgithubの認証が成立しました。

Hi yourname! You've successfully authenticated, but GitHub does not provide shell access.

以後、AWSインスタンス側から自身のgithubに対し、pull等ができる様になります。

githubからcloneします。(URLはリポジトリを表示したときのURL)

cd /
//ルート直下に移動します。homeディレクトリでコンテナを展開すると、pumaとnginxの連携でエラーが出る場合がありますl。puma.sockの読み取りエラー
sudo git clone https://github.com/〇〇〇〇〇〇/〇〇〇〇〇〇
ls
//lsと打って、ディレクトリがあるか確認する。

今、gitignoreに記載したファイル(.env master.key)は含まれていません。そこで、ssh通信を用いてローカルからAWSに直接ファイルを転送します。

exit
 //一旦ログアウトして、ローカルのfitO2ファイルに移動

sudo scp -i ~/.ssh/fitO2_key.pem .env ec2-user@固定IP:/home/ec2-user/

sudo scp -i ~/.ssh/fitO2_key.pem config/master.key ec2user@固定IP:/home/ec2-user

  //scpコマンドを用いて、AWSに.envを転送します。 .env のように転送したファイルが表示されたらオッケー。
  //権限の関係で一旦、ホームディレクトリに転送します。

再度、ログイン。

cd
ls -a

ホームディレクトリに移動して.envとmaster.keyが存在していたら問題ない。
.envとmaster.keyを移動させる。

sudo mv .env /fitO2
sudo mv master.key /fitO2/config

念のため移動してるか確認

ls -a  /fitO2/
ls -a  /fitO2/config

コンテナの起動

cd /fitO2

docker-compose build
//コンテナを作成します。
(Permition deniedになった場合は、sudo chmod 777 /usr/local/bin/docker-compose
を実行します。)

docker network create fitO2-network
//ネットワークを作成します。

docker-compose run app rails assets:precompile RAILS_ENV=production
//プリコンパイルを実施

docker-compose up
//コンテナを起動します。

下記のように表示されていれば問題なく起動しています。

Creating fito2_app_1 ... done
Creating fito2_web_1 ... done
Attaching to fito2_app_1, fito2_web_1
app_1  | Puma starting in single mode...
app_1  | * Version 3.12.6 (ruby 2.5.1-p57), codename: Llamas in Pajamas
app_1  | * Min threads: 5, max threads: 5
app_1  | * Environment: production
app_1  | * Listening on tcp://0.0.0.0:3000
app_1  | * Listening on unix:///fitO2/tmp/sockets/puma.sock
app_1  | Use Ctrl-C to stop

別タブを開いて、AWSにログインします。

データベースの作成

docker-compose exec app rails db:create db:migrate RAILS_ENV=production

docker-compose exec app rails db:seed RAILS_ENV=production
//(必要に応じて)

以上で、http://固定IPにアクセスした場合、正しく表示されるはずです。

RDSのデーターベース確認方法

dockerコンテナにログインし、mysqlを起動させます。

docker ps
//コンテナIDを調べます。

NAMESがfito2_app_1の方のCONTAINER IDをコピーします。

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
c5ec64f0ea76        fito2_web           "/bin/sh -c '/usr/sb…"   5 minutes ago       Up 5 minutes        0.0.0.0:80->80/tcp   fito2_web_1
442b9ddb3a20        fito2_app           "bundle exec puma -C…"   5 minutes ago       Up 5 minutes                             fito2_app_1

コンテナにログインします

docker exec -it 442b9ddb3a20 bash

mysqlを起動させます。

service mysql start
mysql -u root -h RDSのエンドポイント -p
//パスワードを入力してログイン

セキュリティーグループで制限んしているので、作成したEC2コンテナからしかアクセスできません。

ローカルから試してみてください。

以上となります。

参考

EC2上でRailsアプリケーションにDockerを導入する(Rails、Nginx、RDS)
開発環境において既存のRailsアプリにDockerを導入する方法(Rails、nginx、mysql)
無料!かつ最短?で Ruby on Rails on Docker on AWS のアプリを公開するぞ。
【画像付きで丁寧に解説】AWS(EC2)にRailsアプリをイチから上げる方法【その1〜ネットワーク,RDS環境設定編〜】
Nginx + Rails (Puma) on Docker のいくつかの実用パターン
Docker + Rails + Puma + Nginx + MySQL

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

【Rails AWS Docker】既存Ruby on Rails + MySQLアプリをDockerで構築し、AWSにデプロイする(5)

ポートフォリをとして作ったRuby on RailsアプリをDockerコンテナ化し本番環境をAWSで構築するまでの道のりです。
ポートフォリオ自体はこちらとなります。
【ポートフォリオ】転職活動時に作成したポートフォリオの概要(テッ◯キャンプ)

かなり苦しめられたので、どなたかのお役に立てれば。

タイトル
1 ローカル環境のRailsアプリをDockerコンテナ化
2 AWSにVPCを作成する。パブリックサブネットを作成する
3 プライベートサブネットを作成する
4 EC2インスタンスを作成する
5 RDSを作成する
6 DockerコンテナをAWSにアップロードする

RDSを作成します。

 既存Ruby on Rails + MySQLアプリをDockerで構築し、AWSにデプロイする(1)においてデーターベースコンテナを作成しました。

 実はプライベートサブネット内にEC2インスタンスを配置して、その中にデーターベースコンテナを配置してもいいのですが、AWSにはRDSというサービスがあります。
ざっくりいうと、RDSとはデーターベース用のインスタンスで、自動的に可用性や耐障害性を高めた構成にしてくれます。

 また、異なるアベイラビリティーゾーンにバックアップ用のデーターベースを配置することにより、災害等により一箇所のデーターベースで障害が生じた際にも、問題なく稼働できる様になっています。プライベートサブネットを二つ作った理由はこれです。

今回は、このRDSを用いてデーターベースインスタンスを作成します。

サブネットグループの作成

まずは、プライベートサブネット2つをまとめた、サブネットグループを作成します。

RDSダッシュボードに入り、サブネットグループを選択し、「DBサブネットグループを作成」をクリックします。

image.png

サブネットに先ほど、作成した二つのプライベートサブネットを追加します。

今、下記のような異なるアベイラビリティーゾーンを持つサブネットグループが作成されました。

IMG_5121AE56F9A4-1.jpeg

パラメーターグループの設定

RDSでは直接データベースの設定を触れないので、パラメーターグループで設定します。

image.png

パラメーターグループファミリーは、開発環境のバージョンに合わせます。

オプショングループの設定

RDSダッシュボードに入り、オブショングループを選択し、「グループを作成」をクリックします。

image.png

開発環境に合わせてエンジンとメジャーエンジンバージョンを設定します。

RDSを作成する。

RDSダッシュボードに入り、データベースを選択し、「データーベースの作成」をクリックします。
下記は設定例です。ご自身の運用に合わせて設定してください。

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

「データーベースの作成」クリック

少し分かりずらいですが、下記の様にRDSがサブネットグループとdb用セキュリティーグループと結びつけることにより、
IMG_5255B692BA7F-1.jpeg

結果として下記の構成となります。

IMG_9352B52CF97B-1.jpeg

これでRDSインスタンスが作成できました。

次回(6)へ続く
DockerコンテナをAWSにアップロードする

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

【Rails】既存Ruby on Rails + MySQLアプリをDockerで構築し、AWSにデプロイする(5)

ポートフォリをとして作ったRuby on RailsアプリをDockerコンテナ化し本番環境をAWSで構築するまでの道のりです。
ポートフォリオ自体はこちらとなります。
【ポートフォリオ】転職活動時に作成したポートフォリオの概要(テッ◯キャンプ)

かなり苦しめられたので、どなたかのお役に立てれば。

タイトル
1 ローカル環境のRailsアプリをDockerコンテナ化
2 AWSにVPCを作成する。パブリックサブネットを作成する
3 プライベートサブネットを作成する
4 EC2インスタンスを作成する
5 RDSを作成する
6 DockerコンテナをAWSにアップロードする

RDSを作成します。

 既存Ruby on Rails + MySQLアプリをDockerで構築し、AWSにデプロイする(1)においてデーターベースコンテナを作成しました。

 実はプライベートサブネット内にEC2インスタンスを配置して、その中にデーターベースコンテナを配置してもいいのですが、AWSにはRDSというサービスがあります。
ざっくりいうと、RDSとはデーターベース用のインスタンスで、自動的に可用性や耐障害性を高めた構成にしてくれます。

 また、異なるアベイラビリティーゾーンにバックアップ用のデーターベースを配置することにより、災害等により一箇所のデーターベースで障害が生じた際にも、問題なく稼働できる様になっています。プライベートサブネットを二つ作った理由はこれです。

今回は、このRDSを用いてデーターベースインスタンスを作成します。

サブネットグループの作成

まずは、プライベートサブネット2つをまとめた、サブネットグループを作成します。

RDSダッシュボードに入り、サブネットグループを選択し、「DBサブネットグループを作成」をクリックします。

image.png

サブネットに先ほど、作成した二つのプライベートサブネットを追加します。

今、下記のような異なるアベイラビリティーゾーンを持つサブネットグループが作成されました。

IMG_5121AE56F9A4-1.jpeg

パラメーターグループの設定

RDSでは直接データベースの設定を触れないので、パラメーターグループで設定します。

image.png

パラメーターグループファミリーは、開発環境のバージョンに合わせます。

オプショングループの設定

RDSダッシュボードに入り、オブショングループを選択し、「グループを作成」をクリックします。

image.png

開発環境に合わせてエンジンとメジャーエンジンバージョンを設定します。

RDSを作成する。

RDSダッシュボードに入り、データベースを選択し、「データーベースの作成」をクリックします。
下記は設定例です。ご自身の運用に合わせて設定してください。

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

「データーベースの作成」クリック

少し分かりずらいですが、下記の様にRDSがサブネットグループとdb用セキュリティーグループと結びつけることにより、
IMG_5255B692BA7F-1.jpeg

結果として下記の構成となります。

IMG_9352B52CF97B-1.jpeg

これでRDSインスタンスが作成できました。

次回(6)へ続く
DockerコンテナをAWSにアップロードする

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

【Rails AWS Docker】既存Ruby on Rails + MySQLアプリをDockerで構築し、AWSにデプロイする(4)

ポートフォリをとして作ったRuby on RailsアプリをDockerコンテナ化し本番環境をAWSで構築するまでの道のりです。
ポートフォリオ自体はこちらとなります。
【ポートフォリオ】転職活動時に作成したポートフォリオの概要(テッ◯キャンプ)

かなり苦しめられたので、どなたかのお役に立てれば。

タイトル
1 ローカル環境のRailsアプリをDockerコンテナ化
2 AWSにVPCを作成する。パブリックサブネットを作成する
3 プライベートサブネットを作成する
4 EC2インスタンスを作成する
5 RDSを作成する
6 DockerコンテナをAWSにアップロードする

EC2インスタンスを作成する

EC2とは

Elastic Compute Cloudと呼ばれるもので、ざっくりというと仮想的なパソコンです。
それを、AWS上に置くことによって、クラウド上にシステムを構築します。

IMG_718A6999530D-1.jpeg

作成したEC2インスタンスは、パブリックサブネットに配置して、アプリ本体を置きます。

EC2インスタンスの作成

検索ウィンドウからEC2を検索し、コンソールに入ります。
image.png

インスタンスを起動をクリックします。
image.png

今回はインスタンスタイプにクイックスタートの「AMAZON Linux2」を利用します。
(ざっくり)どのタイプのコンピューターにするかということです。今回はOSがlinuxのものを選択します。

image.png

タイプは無料利用枠のt2.microを選択します。
(ざっくり)どの程度のスペックにするかとうことです。

image.png

→「次のステッップ:インスタンスの詳細設定」をクリックします。

項目 内容 説明(ざっくり)
インスタンス数 1 起動するインスタンスの数
購入のオプション チェックなし スポットインスタンスを選択すると安価に利用できる(常時起動しない場合)
ネットワーク fitO2_vpc 先ほど作成したVPCに配置する。
サブネット fitO2_public_subnet_1a 先ほど作成したパブリックサブネットに配置する。
自動割り当てパブリック IP サブネット設定を使用(無効) 後ほど固定IPを割り振るため
配置グループ チェックなし 複数のインスタンス間の通信を高速化する設定
キャパシティーの予約 なし リソースの上限を超えた時にインスタンスが起動できなくなることを回避(有料)
IAM ロール なし AWSのリソースに紐付けることができる権限設定を行うサービス
CPU オプション なし CPUの性能に対するオプション
シャットダウン動作 停止 シャットダウン時の動作
停止 - 休止動作 なし 停止動作に休止動作を追加する
終了保護の有効化 なし 誤った終了を防止します
モニタリング なし 5分間隔の監視を1分感覚にする。
テナンシー 共有 ハードディスクを占有するか否か
Elastic Inference なし 機械学習に効率化
クレジット仕様 なし 規定量の通信量が来た際に制限がなくなる

ネットワークインターフェイス

プライマリIP 10.0.10.10 サブネットにプライベートIPアドレスを設定する。

「次のステップ:ストレージの追加」をクリック

項目 内容 説明(ざっくり)
サイズ 8
ボリュームタイプ 汎用SSD
合わせて削除 チェック
暗号化 暗号化なし

「タグの追加」をクリック

項目 内容 説明(ざっくり)
name fitO2_web
ボリュームタイプ 汎用SSD
インスタンス チェック
ボリューム チェック

「セキュリティーグループの設定」をクリック

既存のセキュリティーグループを選択する。
先ほど作成した(fitO2_SG)を適用する。

「確認と作成」をクリック

「起動」をクリック

キーペアの作成

新しいキーペアの作成(すでにキーペアを作っている場合は既存のものを作っても良い)

キーペア名を設定し、ダウンロード。

(ざっくり)キーペアとは、ssh接続する際に、使用する鍵。ssh接続する際にダウンロードしたキーペアを用いて接続し、AWS側にで生成した鍵と合致した場合接続が許可される。

「インスタンスの作成」

image.png

作成されたら、分かりやすいようにインスタンスに名前を設定しておきます。
nameのところをクリックしたら名前が編集できます。

image.png

ダウンロードしたキーペアを適当な位置に移動させます。(今回は ~/.ssh)

Elastic IP(固定)アドレスを作成する。

現状インスタンスを停止したら、IPアドレスが変わってしまうので、固定IPを割り割り振ります。

EC2ダッシュボードから、Elastic IPを選択します。

「新しいアドレスの割り当て」をクリックします。

ネットワークボーダーグループ : ap-northease-1
パブリック IPv4 アドレスプール : Amazon の IPv4 アドレスプール

「割り当て」をクリック。

作成したElastic IPアドレスに分かりやすい様に名前を付けておく。

Elastic IP(固定)アドレスをインスタンスに紐づける。

作成したIPアドレスにチェックを入れてアクションから「アドレスの関連付け」を選択する。

リソース : インスタンス
インスタンス : 作成したインスタンスを選択
プライベートIPアドレス : 10.0.10.10

「関連付け」をクリック

ssh接続を試みて確認する。

インスタンスの状態が、runningになっていることを確認後

 sudo ssh -i ~/.ssh/fitO2_key.pem ec2-user@固定IPアドレス

~/.ssh/fitO2_key.pemは秘密鍵の保管している場所/秘密鍵の名前
固定IPは、インスタンス一覧から選択して、説明タグをクリックし、Elastic IPと表示されているものです。

Are you sure you want to continue connecting (yes/no/[fingerprint])?

yesと打ってenter
       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/

上記のように表示されれば接続成功。

図にすると下記の様なイメージで接続しました。

IMG_D7961C5FA634-1.jpeg

EC2には先ほど作成したセキュリティーグループ(fitO2_SG)が適用されています。

image.png

インバウンド(入ってくる方の通信)に対し、ssh接続の場合、22番ポートを使用し、全てのアクセス元(0.0.0.0/0)を許可しているので、ssh接続することが可能だったわけです。

EC2インスタンスを作成し、ssh接続を行うことができました。

80番ポートもhttp通信に対し、開放されているので、今作成したインスタンスは下記のような通信を許可する状態となっています。

IMG_378691F05F41-1.jpeg

これでパブリックサブネットEC2インスタンスに対し、上記のようなセキュリティーグループが設定されました。

次回(5)へ続く
RDSを作成する

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

【Rails AWS Docker】既存Ruby on Rails + MySQLアプリをDockerで構築し、AWSにデプロイする(3)

ポートフォリをとして作ったRuby on RailsアプリをDockerコンテナ化し本番環境をAWSで構築するまでの道のりです。
ポートフォリオ自体はこちらとなります。
【ポートフォリオ】転職活動時に作成したポートフォリオの概要(テッ◯キャンプ)

かなり苦しめられたので、どなたかのお役に立てれば。

タイトル
1 ローカル環境のRailsアプリをDockerコンテナ化
2 AWSにVPCを作成する。パブリックサブネットを作成する
3 プライベートサブネットを作成する
4 EC2インスタンスを作成する
5 RDSを作成する
6 DockerコンテナをAWSにアップロードする

プライベートサブネットを作成します。

実は後ほど、理由は説明するのですがプライベートサブネットは異なるアベイラビリティーゾーンで2つ作る必要がありあります。
アベイラビリティーゾーンとは、ざっくりいうとクラウドが保管されているサーバーが実際に置かれている地域です。

よって下記のような構成を作ります。

IMG_633C134D7C4A-1.jpeg

一つ目のプライベートサブネット作成します。

AWSコンソールからVPCダッシュボードに入り、サブネットを選択してサブネットを作成する。

VPC IDに先ほど作成したVPCを選択する。

アベイラビリティーゾーン:今回はap-northease-1a

名前 : fitO2_private_subnet_1a

IPv4 CIDR ブロック : 10.0.20.0/24

「サブネットを作成」をクリック

二つ目のプライベートサブネット作成します。

AWSコンソールからVPCダッシュボードに入り、サブネットを選択してサブネットを作成する。

VPC IDに先ほど作成したVPCを選択する。

アベイラビリティーゾーン:今回はap-northease-1c

名前 : fitO2_private_subnet_1c

IPv4 CIDR ブロック : 10.0.21.0/24

「サブネットを作成」をクリック

プライベートサブネット用のセキュリティーグループを作成する。

EC2ダッシュボードのセキュリティーグループをクリックして、「セキュリティーグループの作成」をクリックする。

image.png

「ルールの追加」をクリックします。

セキュリティーグループ名 : fitO2_db_SG
VPC : 先ほど作成したVPC
インバウンド :
 タイプ : MYSQL/Aurora
ポート : 3306
ソース :
カスタム
   パブリックサブネットに置かれたEC2インスタンスに適用する予定の(先ほど作った)セキュリティーグループを指定。(名前を打ち込んだら出てくる。出てこなければグループIDをコピペ)

これで、プライベートサブネット2つと、それに適用させる予定の下記の様なセキュリティーグループの作成が完了しました。

IMG_237B88A63A89-1.jpeg

次回(4)へ続く
EC2インスタンスを作成する |

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