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

【Rails】Gemを初公開しました

Gemを初公開しました

ask_year_monthという、

レシーバが何月なのか、

スマートにチェックするメソッドを生やしただけのGemを公開しました。


例えば、

Time.current.month == 12
#=> true

よりも、

Time.current.december?
#=> true

のが見やすいよなーという所から。


ご興味がある方は良かったら使ってみてください。


RubyGems

https://rubygems.org/gems/ask_year_month

GitHub

https://github.com/mah666hhh/ask_year_month

参考記事

RailsプラグインGemの作成方法、RSpecテストまで含めたrails pluginの作り方

【Ruby】gemの作り方から公開まで

gem install に失敗するので https://rubygems.org/ を source に追加した

activesupport/lib/active_support/core_ext

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

【Rails】Gemを初公開しました。何月なのかを月名で確認するメソッドを生成

Gemを初公開しました

ask_year_monthという、

レシーバが何月なのか、

月名でスマートにチェックできるメソッドを生やすGemを公開しました。


月名で確認できなくて地味にモヤッとしたんですよね。


例えば、

Time.current.month == 12
#=> true

よりも、

Time.current.december?
#=> true

のが見やすいよなーという。


ご興味がある方は良かったら使ってみてください。


RubyGems

https://rubygems.org/gems/ask_year_month

GitHub

https://github.com/mah666hhh/ask_year_month

参考記事

RailsプラグインGemの作成方法、RSpecテストまで含めたrails pluginの作り方

【Ruby】gemの作り方から公開まで

gem install に失敗するので https://rubygems.org/ を source に追加した

activesupport/lib/active_support/core_ext

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

DockerでRails6(MySQL, Webpacker)を動かす。

Docker練習第二弾。Rails5は結構やっていたのでRails6を動かしてみようと思います。

環境

  • Docker 19.03.13
  • docker-compose 1.27.4
  • Windows10 Pro

ディレクトリ構成

rails_test/
    ┝ Dockerfile
    ┝ docker-compose.yml
    ┝ Gemfile
    ┝ Gemfile.lock
    ┝ environments/
              └ db.env

Dockerfile

Dockerfile
FROM ruby:2.6

RUN apt-get update -qq && \
    apt-get install -y build-essential \
                       nodejs

RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    apt-get update && apt-get install yarn

RUN mkdir /rails_test
WORKDIR /rails_test

ADD Gemfile /rails_test/Gemfile
ADD Gemfile.lock /rails_test/Gemfile.lock

RUN bundle install

ADD . /rails_test
  • FROM Ruby2.6をプルします。
  • RUN Railsに必要なNode.jsとyarnをインストール。 作業ディレクトリの作成。
  • WORKDIR 作業ディレクトリの指定をします。
  • ADD ローカルのGemfileとGemfile.lockをコンテナにコピーします。
  • RUN バンドルインストールをします。
  • ADD Gemfileをローカルに反映します。

今回webpackerを使うためにyarnをインストールするのポイントでした。

docker-compose.yml

今回のメインのdocker-compose.ymlの設定です。

docker-compose.yml
version: '3'
services: 
  app:
    build: .
    volumes: 
      - .:/rails_test
    command: bash -c "rm -f tmp/pids/server.pid && rails s -b 0.0.0.0"
    ports:
      - 3000:3000
    environment:
      WEBPACKER_DEV_SERVER_HOST: webpacker
    env_file: 
      - ./environments/db.env
    depends_on: 
      - db
  webpacker:
    build: .
    environment:
      NODE_ENV: development
      RAILS_ENV: development
      WEBPACKER_DEV_SERVER_HOST: 0.0.0.0
    volumes: 
      - .:/rails_test
    command: ./bin/webpack-dev-server
    ports:
      - 3035:3035
  db:
    image: mysql:5.7
    volumes: 
      - rails-db:/var/lib/mysql
    env_file: 
      - ./environments/db.env
volumes: 
  rails-db:

各コンテナの設定を説明します。

app
app:
  build: .
  volumes: 
    - .:/rails_test
  command: bash -c "rm -f tmp/pids/server.pid && rails s -b 0.0.0.0"
  ports:
    - 3000:3000
  environment:
    WEBPACKER_DEV_SERVER_HOST: webpacker
  env_file: 
    - ./environments/db.env
  depends_on: 
    - db

Railsの設定です。

  • volumes ローカルのディレクトリとマウントします。これによりコンテナ作成時にローカルでの変更点が反映されます。
  • command サーバーを立ち上げています。ポイントとしてdocker-compose downでコンテナを削除した際にserver.pidがローカルに残るため再度コンテナを作成した際にサーバーが立ち上げられなくなるため最初にserver.pidを削除します。
  • depends_on MySQLのコンテナとの起動順序を定義します。Railsが先に立ち上がるとDBと接続できないと怒られます。
  • env_file ここではDBのユーザーネームなど定義します。今回はrootユーザーで行うためルートユーザーのパスワードだけ設定しておきます。
  • environment webpackerの設定です。
webpacker
webpacker:
  build: .
  environment:
    NODE_ENV: development
    RAILS_ENV: development
    WEBPACKER_DEV_SERVER_HOST: 0.0.0.0
  volumes: 
    - .:/rails_test
  command: ./bin/webpack-dev-server
  ports:
    - 3035:3035

Webpackerの設定です。ほとんど公式に書いてある通り(Github)にしただけなので、特に言うこともありませんがここでもvolumesをRailsと合わせておかないとwebpack-dev-serverが見つけられなくなります。

db
db:
  image: mysql:5.7
  volumes: 
    - rails-db:/var/lib/mysql
  env_file: 
    - ./environments/db.env
volumes: 
  rails-db:

MySQLの設定です。rails-dbという名前付きボリュームにてDBのデータを永続化しています。また設定は別に用意(db.env)しています。もしrootユーザーでなく新しいユーザーを作りたい場合にはdb.envに書き加えます。

/environments/db.env
MYSQL_ROOT_PASSWORD=password

とりあえずこれだけ書いておきます。

Gemfile

この二行だけ書き加えます。Gemfile.lockは何も書きません。

Gemfile
source 'https://rubygems.org'
gem 'rails', '6.0'

アプリ作成

まずはrails newをしてアプリを作ります。

$ docker-compose run --rm app rails new . --force -d mysql

ディレクトリを新たに作らずにDBはMySQLを使い、Gemfileのオーバーライドをするコマンドを入力しています。ここで自分はyarnを入れていなかったためにwebpackerが途中でインストールが止まりました。

database.ymlの設定

ローカルにできたdatabase.ymlファイルにdocker-compose.ymlで設定したものに書き換えます。

database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV.fetch('MYSQL_USER') { 'root' } %>
  password: <%= ENV.fetch('MYSQL_PASSWORD') { 'password' } %>
  host: db

development:
  <<: *default
  database: rails_test_development

test:
  <<: *default
  database: rails_test_test

production:
  <<: *default
  database: rails_test_production
  username: rails_test
  password: <%= ENV['RAILS_TEST_DATABASE_PASSWORD'] %>

defaultのところを書き換えています。今回MYSQL_USERとMYSQL_PASSWORDは設定していないのでrootとpasswordになりますが、db.envにて設定できるようにしています。

コンテナ起動

これでRailsがMySQLに接続できるようになりました。コンテナを全て起動しましょう。

$ docker-compose up -d

コンテナが3つ(app, db, webpacker)停止していなければ成功かと思われます。

$ docker ps
CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                    NAMES
ab56f7caf110        rails_test_app         "bash -c 'rm -f tmp/…"   2 minutes ago       Up 2 minutes        0.0.0.0:3000->3000/tcp   rails_test_app_1
eb345957801c        mysql:5.7              "docker-entrypoint.s…"   3 minutes ago       Up 2 minutes        3306/tcp, 33060/tcp      rails_test_db_1
a64c29f979c1        rails_test_webpacker   "./bin/webpack-dev-s…"   13 minutes ago      Up 2 minutes        0.0.0.0:3035->3035/tcp   rails_test_webpacker_1

DB作成

最後にdb:createして http://localhost:3000 にてデフォルト画面が見れたら成功です。

$ docker-compose exec app rails db:create

所感

Qiitaの記事を見ながらコピペでやってできた気になっていましたが、改めて自分で書いてみるとMySQLに接続できなかったりWebpackerが起動できなかったりとトラブル続出でした。
また、node_modulesをコンテナの中に置いておくのってセンスが無い気もしました。何かいいアイデアがありそう。

とはいえひと月前までコピペでなんとなくやっていたところも大分自分の言葉で説明できるようになりました。
次は本番環境想定してnginxとの連携とかですかね...。

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

ActiveStorageのバリデーション

はじめに

Rails5.2から導入されたActive Storageは設定が簡単でとても導入しやすく、シンプルな機能を備えていて、少し使ってみた感じだとよい機能だと思っています。
ただ、実運用で使おうとすると、バリデーションの機能がなくて、惜しい感じです…。

そこで、自分が最低限欲しいと感じた、いくつかのバリデーションを作ったので紹介します。

サンプルアプリケーションのコードはこちらです。
Rails5.2で書いていますが、ほぼ同じものを6.0に持ってきても動きます。

必須

入力フォームで、ファイルの添付を必須強制したいときはごく普通にあると思います。
ただ、普通のpresenceのバリデーションは使えなかったので、別途用意しました。

localhost_3000_posts(PC(800x600)).png

設置例

attached_file_presenceで設定できるようにしています。

class Post < ApplicationRecord
  has_one_attached :main_image
  has_many_attached :other_images

  validates :main_image, attached_file_presence: true
  validates :other_images, attached_file_presence: true
end

コード

ファイルが添付されているかどうか検査できるattached?を使います。

app/validators/attached_file_presence_validator.rb
class AttachedFilePresenceValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    record.errors.add(attribute, :blank) unless value.attached?
  end
end

ファイル数

has_many_attachedを使うと、複数のファイルを添付できますが、個数の制限をしたいときがあると思います。

localhost_3000_posts(PC(800x600)) (1).png

設置例

attached_file_numbermaximumオプションで最大個数を設定できるようにしています。

class Post < ApplicationRecord
  has_many_attached :other_images

  validates :other_images, attached_file_number: { maximum: 3 }
end

コード

ファイル数はsizeで取れるので、それを使って検証しています。

app/validators/attached_file_number_validator.rb
class AttachedFileNumberValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return true unless value.attached?

    file_number = value.size

    if (limit = options[:maximum]).present? && file_number > limit
      record.errors.add(attribute, :too_many_files, count: limit)
    end
    if (limit = options[:minimum]).present? && file_number < limit
      record.errors.add(attribute, :too_few_files, count: limit)
    end
  end
end

エラーメッセージで指定している、too_many_filestoo_few_filesconfig/localesで設定しています。

config/locales/ja.yml
ja:
  errors:
    messages:
      too_many_files: は%{count}個以内で入力してください
      too_few_files: は%{count}個以上で入力してください

ファイルサイズ

ユーザに画像を登録できるようにすると、本格的なカメラで撮ったような高精細の画像が添付されてくることがしばしばありますが、リソース上の制約から受け取らないようにしたいときもあると思います。

localhost_3000_posts(PC(800x600)) (2).png

設置例

attached_file_sizemaximumオプションで最大ファイルサイズを設定できるようにしています。

class Post < ApplicationRecord
  has_one_attached :main_image
  has_many_attached :other_images

  validates :main_image, attached_file_size: { maximum: 5.megabytes }
  validates :other_images, attached_file_size: { maximum: 5.megabytes }
end

コード

ファイルのサイズを得るattachement.byte_sizeを使っています。
ファイルの単複で微妙に処理が変わるのが嫌で、単数のときは配列に詰めて複数と同じように処理できるようにしています。

app/validators/attached_file_size_validator.rb
class AttachedFileSizeValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return true unless value.attached?
    return true unless options&.dig(:maximum)

    maximum = options[:maximum]
    attachements = value.is_a?(ActiveStorage::Attached::Many) ? value.attachments : [value.attachment]
    if attachements.any? { |attachment| attachment.byte_size >= maximum }
      record.errors.add(attribute, :less_than, { count: maximum.to_s(:human_size) })
    end
  end
end

ファイルタイプ

例えば画像を登録してもらうつもりのところに、間違ってテキストファイルを登録しないようになど、アップロードするファイルの形式を保存する前にチェックしたいことは多いと思います。

localhost_3000_posts(PC(800x600)) (3).png

設置例

attached_file_typepatternオプションで最大ファイルサイズを設定できるようにしています。
patternは正規表現で設定します。

class Post < ApplicationRecord
  has_one_attached :main_image
  has_many_attached :other_images

  validates :main_image, attached_file_type: { pattern: /^image\// }
  validates :other_images, attached_file_type: { pattern: /^image\// }
end

コード

content_typeを指定のパターンに合致しているかチェックしています。
ファイルの単複で微妙に処理が変わるのが嫌で、単数のときは配列に詰めて複数と同じように処理できるようにしています。

class AttachedFileTypeValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return true unless value.attached?
    return true unless options&.dig(:pattern)

    pattern = options[:pattern]
    attachments = value.is_a?(ActiveStorage::Attached::Many) ? value.attachments : [value.attachment]
    if attachments.any? { |attachment| !attachment.content_type.match?(pattern) }
      record.errors.add(attribute, :invalid_file_type)
    end
  end
end

エラーメッセージで指定している、invalid_file_typeconfig/localesで設定しています。

config/locales/ja.yml
ja:
  errors:
    messages:
      invalid_file_type: は不正なファイル形式です

Validatorのテスト

RSpecで書いたものがありますが、全部載せるととても長いので、こちらを参照してください

ここでのポイントは、アプリケーションで実際に使っているテーブルを使ったモデルを定義して、そこにバリデーションを設置してテストに使うところです。
最初は使いそうなものをスタブやモックを使って作ろうとしていたのですが、ファイルの単複を扱うのでとても記述量が多くなり、わかりにくくなってしまったのでやめました。

最後に

バリデータもテストを書いたりすると、結構楽しいですね。これのおかげでActiveStorageと少し仲よくなれた気がします。
書きながらあれこれ探してたら、もっとスマートなactivestorage-validatorというGemがあったので、こちらを使ってもらってもいいかもしれません。

参照

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

PDF出力してみたいんじゃ! 【Thinreports + Rails】

どうも。とある六本木一丁目にある会社のエンジニアです。
というわけで SmartHR Advent Calendar 2020 の5日目だぜ。

目的

弊社プロダクトのPDF出力に Thinreports を使用しているのでお友達になりたかった。

使うもの

Thinreports + Rails でやっていきます

ゴール

タイトルと本文がある書類をPDFで出力できるようにする!
簡単!

やっていこう

とりあえず rails new

rails new thinreports_demo --skip-test

gem 追加

gem 'thinreports'
bundle install

サンプルPDF出力する準備

ThinreportsでのPDF出力には tlf というフォーマットのデータが必要なので後々用意する必要があるので先にrails側の準備を整える

コントローラーを用意

rails g controller pdfs index sample_doc

sample_doc用にroutesを修正

routes.rb
Rails.application.routes.draw do
  root 'pdfs#index'
  resources :pdfs, only: :index do
    get :sample_doc, on: :collection
  end
end

views を修正

views/pdfs/index.html.erb
<h1>PDFにしてくれる君</h1>
<p>
  <%= link_to 'サンプル書類がみたい', sample_doc_pdfs_path %>
</p>

リンクにアクセスするとPDFを見れるようにしていく

SS 123.png

Thinreports Editor で TLFファイル 作る

公式を見て Thinreports Editorのインストール(必要なら Generatorの方も)
Thinreports インストールガイド(公式)

何はともあれ Thinreports Editor を起動

SS 125.png

新規作成から新しくA4の書類を作ります

後々のことを考えて

  • タイトル
  • 本文
  • 著者

の3つを テキストブロックツール を使って作り出します

SS_126.png

app/pdfs 以下に保存(ファイル名は任意で)

SS 127.png

PDFとして出力する設定をしていく

pdfsコントローラーにPDFを出力するための設定をする
詳しくはここら辺のクイックスタート を参考にする

pdfs_controller.rb
class PdfsController < ApplicationController
:
:
  def sample_doc
    report = Thinreports::Report.new(layout: "#{Rails.root}/app/pdfs/sample_doc.tlf")

    report.start_new_page
    # さっき作った title に value つっこんでる
    report.page.item(:title).value('PDFやで')
    # title 以外はあとでやるのでここではスルーします

    file = report.generate
    send_data(
      file,
      filename: 'filename_sample.pdf',
      type: 'application/pdf',
      disposition: 'inline'
    )
  end
end

先ほどのリンクからアクセスすると PDF出てくる

SS 128.png

やったね✌️

書類らしきものを作る

ここら辺は scaffold しましょう

  • Doc
    • title
    • content
    • author
rails g scaffold doc title content:text author
rails db:migrate

新しく TLFファイル を複製

内容は sample_doc.tlf と同じでよいので もう一つ TLF作る(特に意味はないのでsample_doc.tlfを使い回しでいいと思います)

書類らしきものごとにPDF出力できるようにする

routes の修正

routes.rb
Rails.application.routes.draw do
  :
  :
  resources :docs do
    get :show_pdf
  end
  :
  :
end

コントローラー

docs_controller.rb
class DocsController < ApplicationController
  :
  :
  def show_pdf
    @doc = Doc.find(params[:doc_id])
    report = Thinreports::Report.new(layout: "#{Rails.root}/app/pdfs/doc.tlf")

    report.start_new_page
    # 以下で各カラムごとのデータを入れる
    report.page.item(:title).value(@doc.title)
    report.page.item(:content).value(@doc.content)
    report.page.item(:author).value(@doc.author)

    file = report.generate
    send_data(
      file,
      filename: "doc_#{@doc.id}.pdf",
      type: 'application/pdf',
      disposition: 'inline'
    )
  end
  :
  :
end

リンクも忘れず

views/docs/index.html.erb
<p id="notice"><%= notice %></p>

<h1>Docs</h1>

<table>
  :
  <tbody>
    <% @docs.each do |doc| %>
      <tr>
        :
        <td><%= link_to 'ShowPDF', doc_show_pdf_path(doc) %></td>
        # ☝️追加
        :
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Doc', new_doc_path %>

データを入れる

ダミーテキストジェネレータ とかを使ってデータ入れる

http://localhost:3000/docs/new から適当に

http://localhost:3000/docs はこんな感じ
SS 130.png

SS 129.png

完成!!(見た目は整える時間なかった!)
やったね✌️

まとめ

お友達になれました :blush:

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

DockerでRailsの環境構築をする

初心者がDockerでRailsの環境構築をしたのでメモとして残します。

Dockerのインストール

Dockerの公式ホームページからget startedをクリックしてDocker Desktopをクリックしダウンロードします。

Dockerの公式ホームページ https://www.docker.com/

ダウンロード後インストールが完了したらターミナルで
docker run hello-worldと打ちます。
以下のように表示されたらインストール成功です。

ターミナル
Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

ファイル設定

新規ディレクトリを作成しそこに以下の4つのファイルをコピーします。
https://github.com/sekine617/Rails-file

新しいRailsプロジェクトのファイル作成

ターミナルで以下のコマンドを打ちます。

ターミナル
docker-compose run web rails new . --force --database=mysql

docker-compose run webはDockerのwebサービスコンテナで右のコマンドを実行するためのものです。
rails new .で新しいRailsプロジェクトのファイル作成し、
--forceは既存のファイルの上書き、
--database=mysqlはデータベースにMySQLを使用するコマンドです。

Gemのインストールや新規作成されたファイルをDockerに取り込むために以下のコマンドを打ちます。

ターミナル
docker-compose build

ファイル設定2

作成したファイルの/config/database.ymlを編集します。

database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: root
  password: ********
  host: *****

17~18行目にあるpasswordとhostを/docker-compose.ymlのpasswordとhostの値と一致させます。

/Gemfileも編集します。
gem 'mysql2', '>= 0.3.18', '<= 8.0.22'
の部分の'<= 8.0.22'を自身のMySQLのバージョンに合わせてください。

/Gemfile
source 'https://rubygems.org'


# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.0.0', '>= 5.0.0.1'
# Use mysql as the database for Active Record
gem 'mysql2', '>= 0.3.18', '<= 8.0.22'
# Use Puma as the app server
gem 'puma', '~> 3.0'

コンテナの起動

以下のコマンドで現在のディレクトリでコンテナの起動します。

ターミナル
docker-compose up -d

起動の確認を次のコマンドで行います。

ターミナル
docker-compose ps

以下のように表示されれば正しく起動されています。

ターミナル
   Name                  Command               State           Ports         
-----------------------------------------------------------------------------
rails_db_1    docker-entrypoint.sh mysqld      Up      3306/tcp, 33060/tcp   
rails_web_1   bundle exec rails s -p 300 ...   Up      0.0.0.0:3000->3000/tcp

データベースの作成

以下のコマンドでデータベースの作成します。

ターミナル
docker-compose run web bundle exec rake db:create

rake db:createでデータベースが作成されていない場合新規に作成されます。
これでRailsサーバーにアクセスできます。

サーバーへアクセス

ブラウザのURLからlocalhost:3000と入力します。
スクリーンショット 2020-12-04 21.58.04.png

Yay!You're on Rails!
と表示されればうまくアクセスできています。

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

[Rails]Stripe Checkoutを利用して定期決済機能を実装する

 はじめに

この記事では、Stripeを用いた定期決済機能を実装します。
決済には、Ruby on RailsとCheckout Sessionを利用します。

 Stripeとは

決済サービスです。

WebサイトやWebアプリケーション、ネイティブアプリに決済システムを導入できます。似たようなサービスには、PayPal, Pay.JP, Omiseなどがあります。

Stripeは機能がとにかく豊富で、多彩な決済システムを構築できるのが特徴です。

 用語整理

Checkout
Stripeの決済ページには、Checkout と Elements の2つのタイプが用意されています。

Checkout:Stipeが用意した決済専用ページ。
Elements:決済フォームとシステムだけ用意され、決済ページはカスタマイズできる。

サクッと決済ページを作成したいときはCheckoutを利用するといいですね。定期決済、都度決済どちらにも対応しています。

Session ID
今回はセッションを用いた決済システムを構築します。こちらの決済システムでは、レスポンスにSession_idが返ってきます。
このSession_idにはcusntomer_id(顧客ID)subscription_id(定期決済ID)など、さまざまな決済情報が入っています。

 注意点

Stripeドキュメントに書かれているRubyコードの例は、主にSinatraで書かれているのでRailsではありません。

Sinatraとは、Rubyのフレームワークです。RubyのフレームワークはRailsだけではなくて、Sinatraもあります。以前StripeはRailsを使って例を出していましたが、、、現在ではSinatraに鞍替えしています。

RailsにSinatraを導入することはできないので、今回の実装では一部JavaScriptを利用しています。

 この記事でできること

  • チェックアウトを用いた決済ページの作成
  • 決済システムの構築
  • session_idを活用

 準備

商品を作成するためにStripe Dashboardに入る必要があります。Stripeアカウントを持っていない方は、ここで作成しましょう。

スクリーンショット 2020-12-04 19.56.56.png

本番環境ではなく、テスト環境で行うだけでしたらアカウントを有効にさせる必要はありません。今回作成する商品や、APIキーはテスト用のものですので、今回の実装も本番環境を使用することはありません。

すでに本番環境を有効にしている方
もしすでに有効にしている場合は、注意点があります。

ダッシュボードの左側に View test data もしくは Viewing test data と表示されている切り替えボタンがあると思います。View test dataとなっている時は本番モード、Viewing test dataとなっている時はテストモードです。
3.png

テストモードで実装を行いたい場合は、商品の作成からテストモードで行わなければなりません。本番モードの時に作成した商品は本番環境でしか、テストモードの時に作成した商品はテスト環境でしか使用できません。こちらはご注意ください。

商品の作成

商品の作成にはダッシュボード左側のProductsを選択します。

2.png

Add productを選択します。

4.png

最低限、名前と金額さえ選択できていれば、商品は作成できます。
5.png

One timeは一回限りの都度決済、Recurringは定期決済となります。

今回は毎月の定期決済を実装するので、Recurringを選択し、Billing perio(期間)はMonthlyと設定します。

設定が完了したら、右上のSave productをクリックします。

そうすると、PricingのところにAPI IDという蘭があり、そちらにはprice_idが発行されています。こちらが発行されていれば準備は完了です。

6.png

※補足

2020年8月27日以前のStripe APIのバージョンでは、定期決済の実装にはplan_idを用いていました。現在のバージョンではprice_idを用いるので、ご注意ください。

 実装

Gemをインストール

Gemfile
gem 'stripe'

APIのシークレットキーを設定

StripeダッシュボードのHomeに行くと、Get your test API keysという欄があります。そちらにあるSecret keyをRailsのディレクトリに記述します。

シークレットキーを記述するのにはcredentials.ymlを利用してもいいですが、今回はenvファイルを使って行います。

.env
STRIPE_TEST_SECRET_KEY = sk_test_xxxxxx

また、config/initializersの中にstripr.rbというファイルを作成し、その中にシークレットキーを設定します。

stripe.rb
Stripe.api_key = ENV['STRIPE_TEST_SECRET_KEY']

Paymentsコントローラーを作成する

名前はなんでもいいですが、今回はPaymentsと命名します。
メソッドはnew_subscriptioncreate_subscriptionの2つを作成することにします。

payments_controller.rb
def new_subscription

    @session = Stripe::Checkout::Session.create({
      payment_method_types: ['card'],
      line_items: [{
        price: 'price_XXXX',
        quantity: 1,
      }],
      mode: 'subscription',
      success_url: request.base_url + '/payments/create_subscription?session_id={CHECKOUT_SESSION_ID}',
      cancel_url: request.base_url + '/payments/subscription1',
    })

  end

def create_subscription
end

ドキュメントにはsessionに@はついていませんが、viewにsession_idを渡す必要があるので、@を付けます。
priceの欄に、先ほどダッシュボード上で商品を作成したときに発行されたprice_idを入力します。

今はcreate_subscriptionメソッドに何も書かなくていいです。ただ、今の設定ですと決済完了した時にはこのメソッドに遷移するようになっているので、viewファイルにcreate_subscription.html.erbを作成し、決済完了したことがわかるように何か書くといいかなと思います。

create_subscription.html.erb
<p>できたよ</p>

決済画面を実装

new_subscription.html.erb
<script src="https://js.stripe.com/v3"></script>

<script>
var stripe = Stripe('pk_test_XXXX');
stripe.redirectToCheckout({
sessionId: '<%= @session.id %>'
}).then(function (result) {
});
</script>

Stripe('pk_test_XXXX')のカッコ内には、公開可能APIキーを入れてください。シークレットキーとは別のものなので、注意してください。先ほどシークレットキーを取得したStripeダッシュボードからPublishable keyをコピーし、貼り付けましょう。

ドキュメントでは決済画面に遷移する前にボタンが実装されていますが、ボタンが不要だと思ったので今回は省いています。ボタンが必要な方はドキュメントを参考にしてください。

試しに決済

これで決済画面ができたので、遷移してみましょう。

8.png

こちらの画面に、テストカードを利用して決済してみましょう。
テストカードには、カード番号に4242 4242 4242 4242と入力すれば、後の記入欄はなんでもいいです。

決済完了し、この画面になるとおそらく決済できたと思います。Stripeダッシュボードでも確認できます。

create.subscription.html.erbの画面↓
9.png

session_id

レスポンスにはsession_idが返ってきています。これは、決済ごとに発行されるIDです。

こちらのsession_idを取り出すと、色々な情報が出てきます。取り出し方は以下です。

session_idを取り出す
Stripe::Checkout::Session.retrieve(
  'cs_test_XXXX',
)
レスポンス
{
  "id": "cs_test_XXXX",
  "object": "checkout.session",
  "allow_promotion_codes": null,
  "amount_subtotal": null,
  "amount_total": null,
  "billing_address_collection": null,
  "cancel_url": "https://example.com/cancel",
  "client_reference_id": null,
  "currency": null,
  "customer": null,
  "customer_email": null,
  "livemode": false,
  "locale": null,
  "metadata": {},
  "mode": "payment",
  "payment_intent": "pi_XXXX",
  "payment_method_types": [
    "card"
  ],
  "payment_status": "unpaid",
  "setup_intent": null,
  "shipping": null,
  "shipping_address_collection": null,
  "submit_type": null,
  "subscription": null,
  "success_url": "https://example.com/success",
  "total_details": null
}

この情報のどれかをDBに保存したい場合は、こちらから保存するといいかなと思います。例えば、customer_idを保存したい場合は以下のようになると思います。

payments_controller.rb
def create_subscription
  session = Stripe::Checkout::Session.retrieve(params[:session_id])
  Payment.create(customer_id: session.customer)
end

まとめ

StripeのAPIバージョンは2020年8月末で更新されています(おそらく)。このバージョンから仕組みが大きく変わっているところがあり、この日以前のQiitaの記事があまり参考にならない場合があります。例えば、price_idの登場だとか、ですね。

このバージョンの日本語記事が少なく、Stripeのドキュメントも英語で、かつドキュメントにあるRubyコードのサンプルはSinatra用となっているので、、Railsで実装をしようとすると難しい部分がありますね。

これからStripe系の記事もいくつか投稿していこうと思います。

参考資料

Stripeドキュメント: (Dynamic subscriptions の After 部分)
Stripe APIリファレンス: セッションを取得する
Qiita: Rails5.2でStripeを使う(v3)

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

RailsアプリのCI/CDを"GitHub Actions"で作ってみた

記事のきっかけ

初学者ながら自身のRailsアプリにCI/CDを入れるぞ❗️と考えました。
最近話題になっているGitHub Actionsってなんだろう❓
GitHubでCI/CDも管理できるらしい。凄い❗️
その気持ちから、他のCI/CDサービスと自分なりに比較選択をし、
GitHub Actionsを選択。実装に突き進みました。
本記事はその時のコード記録と実施内容の忘備録を兼ねて記事にしました。

アプリ開発環境

  • Rails v6.0.3.4
  • Ruby v2.6.6
  • PostgreSQL v12.4
  • Docker-compose v1.26.2
  • Docker v19.03.12

GitHub Actionsとは

言わずも知れた開発管理必須ツールであるGitHub。
そのGitHubが提供するCI/CDサービス。
GitHubと高度に連携されており、設定によりGitHub上のコードを
自動でビルド・テスト・デプロイが可能。

なぜGitHub Actionsなのか?

私なりにCIを選ぶにあたって他のサービスと検討しました。
他のサービスもありますが、私が比較したのは下記の2つのサービスです。

  • Circle CI
    SaaS型のCI/CDサービス。FacebookやCyberAgentでの活用事例がありCI/CDサービス大手
    QiitaでもCI/CDの記事が豊富にあり、アプリ作成事例での活用も多いイメージがあります。

  • Code Pipeline
    AWSのCI/CDサービス。各種AWSとの連携ができるのが強み

私が選択した理由

①:GitHub Actionsの圧倒的なメリット点

GitHubのリポジトリの場所でCI/CDが実行・確認できる事です。
アプリ開発はGitHubは開発では必然的に使うのでこれは非常に便利です。
またAWSのリソースを触る場合においてアクセスキーを登録する必要がありますが
リポジトリ管理環境とAWSのアクセスキーを一度に管理できるのは、
AWSを使用したデプロイを想定している中で
リポジトリと外部SaaSでCI/CDの管理を分けるより、
進捗及び危機管理しやすい点があると考えました。

②:他のCI/CDとのサービス比較

  • 上記でも触れましたが、Circle CIは外部SaaSへの権限移譲の問題などの下記資料も検証し
    GitHub Actions の self-hosted runner と Amazon EKS を使った Docker の Build Pipeline
    (freee株式会社)
    今後の実務の中でもこの事は問題になる可能性があるなら、CI/CDとして最初に使うのは
    GitHub Actionsでもいいのではないか?と考えました。

  • Codepipelineとの比較では今回はAPI側の変更作業が多くなる事が予想されたので
    GitHub上でCI/CDを随時実行し確認できた方が作業効率がいいと思われました。

以上を総合判断し、最終的にGitHub Actionsを使用する事としました。

ただこれは初心者が考えた事ですので、間違いやそれぞれのCI/CD側の言い分があるかもしれません。
Circle CIはGithub Actionsより優れていると宣伝してますし(そんな堂々と・・。)
CodepipelineもAWSとの連携をする中では大きなメリットを享受出来る事と思います。

スクリーンショット 2020-09-18 21.48.10.png

GitHub Actions 作成手順・コード

前置きが長くなりました。早速GitHubActionsでのCI/CDを作成して行きます。
公式サイトは下記のリンク先となります。
GitHub Actionsのドキュメント

1. 作成リスト

今回RailsでのアプリでCI/CDを組み立てます。

  • .github/workflows ディレクトリ配下(新規作成)
    • linter.yml
    • rspec_security.yml
    • main.yml
  • database.yml (修正)

2.CIセッティング

今回のCIは2種類でセッティングしました。
1. Linter (トリガー:GitHubにpushした時)
2. RSpec, security (トリガー:GitHubにpushした時 及び pull_request時)

linter.yml
name: Linter
on: [push]

jobs:
  linters:
    name: Linters
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.6

      - name: Ruby Bundler
        uses: actions/cache@v2
        with:
          path: vendor/bundle
          key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
          restore-keys: |
            ${{ runner.os }}-gems-
      - name: Bundle install
        run: |
          bundle config path vendor/bundle
          bundle install --jobs 4 --retry 3
      - name: Get yarn cache directory path
        id: yarn-cache-dir-path
        run: echo "::set-output name=dir::$(yarn cache dir)"

      - name: yarn install
        run: yarn install

      - uses: actions/cache@v2
        id: yarn-cache 
        with:
          path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
          key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-yarn-
      - name: Run linters
        run: |
          bundle exec rubocop --parallel
rspec_security.yml
name: Rails RSpec and security
on: [push, pull_request]
env:
  RAILS_ENV: test
  CI_HOST: localhost

jobs:
  build:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:12
        ports:
          - 5432:5432
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5

      chrome:
        image: selenium/standalone-chrome
        ports:
          - "4444:4444"
        volumes:
          - /dev/shm:/dev/shm

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.6

      - name: Ruby Bundler
        uses: actions/cache@v2
        with:
          path: vendor/bundle
          key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
          restore-keys: |
            ${{ runner.os }}-gems-
      - name: Bundle install
        run: |
          bundle config path vendor/bundle
          bundle install --jobs 4 --retry 3

      - name: Get yarn cache directory path
        id: yarn-cache-dir-path
        run: echo "::set-output name=dir::$(yarn cache dir)"

      - name: yarn install
        run: yarn install

      - uses: actions/cache@v2
        id: yarn-cache 
        with:
          path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
          key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-yarn-

      - name: Setup test database
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        run: |
          bundle exec rake db:create db:schema:load --trace

      - name: run rspec
        run: bundle exec rspec
        env:
          SELENIUM_REMOTE_URL: http://localhost:4444/wd/hub

      - name: Archive rspec result screenshots
        if: failure()
        uses: actions/upload-artifact@v2
        with:
          name: rspec result screenshots
          path: tmp/screenshots/

      - name: security check
        run: |
          bundle exec bundle-audit check --update
          bundle exec brakeman -q -w2

CD (ECR自動デプロイ)

CDはECRへのコンテナbuild&pushを行うセッティングです。
今回の設定でのトリガーはtag pushversion(v)で実行しております。

main.yml
name: Build and Push

on:
  push:
    tags:
      - v*

jobs:
  build-and-push:
    runs-on: ubuntu-18.04
    timeout-minutes: 300

    steps:
      - uses: actions/checkout@v1

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build, tag, and push image to Amazon ECR
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: ${{ secrets.AWS_ECR_REPO_NAME }}
        run: |
          IMAGE_TAG=$(echo ${{ github.ref }} | sed -e "s#refs/tags/##g")
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

database.yml

環境変数のセッティングが必要となります。
テスト部にENV.fetchで第一引数で指定した環境変数を確認し、
なければブロックで定義したデフォルト値が帰るようにしています。

database.yml
default: &default
  adapter: postgresql
  encoding: utf8
  min_messages: WARNING
  host: db
  port: 5432
  username: postgres
  password: postgres
  pool: 5
  timeout: 5000
  stats_execution_limit: 10

development:
  <<: *default
  database: development

test:
  <<: *default
  database: test
  host: <%= ENV.fetch('CI_HOST') { 'db' } %>

production:
  <<: *default
  database: myworkdb
  host: <%= ENV['DB_URL'] %>
  username: <%= ENV['DB_USERNAME'] %>
  password: <%= ENV['DB_PASSWORD'] %>

CI/CD内容

CI

  • Linter.yml
    コード内容としては rubocop でのLinterチェックとしました。
    ローカル開発環境ではrubocop prettier beautify の3種を併用しておりますが、
    push前のLinter整形忘れチェックとしてpush時にチェックする対応で対応しました。

    *ローカル環境下でのLinterセッティングについては下記記事にまとめております。
    よろしければご参照下さい。
    "Rails"でのフォーマット環境を整える(VScode)

  • RSpec, security

    • テストはRailsの定番RSpecを走らせました。
    • セキュリティは Gemの脆弱性診断bundle-auditと総合セキュリティbrakemanを活用しました。

      *Railsのセキュリティに関しては別記事でまとめております。
      よろしければご参照下さい。
      Rails6のセキュリティチェック環境を整える

CD

セッティングコードはClassmethodさんの下記の記事を参考にしました。
この記事のおかげで以後の開発が凄く捗りました。ありがとうございます。

最後に

最後まで読了頂きありがとうございます。
少しでも資料がどなたかの役に立てれば幸いです。
また今回GitHub Actionsの詳細な設定内容(コード)については触れておりませんので
別記事でGitHub Actionsの設定について個人的にまとめたいと思っております。

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

Mysql2::Error: Field '****' doesn't have a default valueに二回もハマった話

Mysql2::Error: Field 'tag_name' doesn't have a default value
とは、NOT NULL制約をかけているにも関わらず値が入っていないよーといった感じのエラーです

class CreateTags < ActiveRecord::Migration[6.0]
  def change
    create_table :tags do |t|
      t.string :name,null:false
      t.timestamps
    end
  end
end

とかでカラムに,null: falseで必ず値が入るように設定していたと思います

なので、rails側でも

validates :name,presence: true

と、バリデーションをかけていたと思います。

というわけで、まず考えられる原因が

マイグレーションファイルでnull: falseとNOT NULL制約を設けていたにも関わらず、Rails側でバリデーション をかけていなくて、form_withとか何とかで、値を保存しようとした!!

といったことが考えられます。

しかし自分は違いました、バリデーション もかけている

[しくじった原因1: ストロングパラメーターの記述が間違ってる]

{"authenticity_token"=>"T8/aOv7fmGDk6UO/GHiYkODYvpMTH/3tFP6sCW0QPVVaAVh6ZHcAd2xaTQzcuioVXdOOWYAFNHO8u3S0OmnG6Q==",
 "drink_tag"=>
  {"tag_name"=>"酸味",
   "image"=>
    #<ActionDispatch::Http::UploadedFile:0x00007f8a24b3d318
     @content_type="image/jpeg",
     @headers="Content-Disposition: form-data; name=\"drink_tag[image]\"; filename=\"ethiopia.jpg\"\r\n" + "Content-Type: image/jpeg\r\n",
     @original_filename="ethiopia.jpg",
     @tempfile=#<File:/var/folders/34/pbcy_n7j1q79hnpm7sbcgl0m0000gn/T/RackMultipart20201204-1452-13ocgpk.jpg>>,
   "name"=>"エチオピア",
   "explain"=>"酸味があってアイスもおすすめ!!",
   "price"=>"1000"},
 "commit"=>"投稿する"}

と、パラメーターがとんでいて

  private
  def drink_params
    params.require(:drink_tag).permit(:name,:price,:explain,:image,:tag_name).merge(user_id: current_user.id)
  end

とストロングパラメーターを書くのが正しいですが

自分の場合、ストロングパラメータくらい分かったつもりになって教材をコピペしたせいで,

  private
  def drink_params
    params.require(:tweet_tag).permit(:name,:price,:explain,:image,:tag_name).merge(user_id: current_user.id)
  end

と、

params.require(:drink_tag)

params.require(:tweet_tag)

にしていました、、、。

ストロングパラメーターが間違っていたら
正しい値を許容できていないので

  def create
    @drink = DrinkTag.new(drink_params)

    if @drink.save
      redirect_to drinks_path
    else
      render 'new'
    end
  end

@drinkは空,nilなので、
nilな物を保存すんな!って怒られてしまいます、、、。
form_withの使い方はあってたので、しっかりとしたパラメーターが作られて
railsのバリデーションの網はかいくぐったようですが、MySQLちゃんがしっかりと値の保存を防いでくれました、、、。

[しくじった原因2: Formオブジェクトを用いた値の保存で、一度に保存するためのmodelの記述が間違ってる!]

Formオブジェクトとは、一つのビューで複数の値を保存したいときに用いられるテクニック?的なものです。
( 詳しくは検索、検索♪)

class UserDonation

  include ActiveModel::Model
  attr_accessor :name, :name_reading, :nickname, :postal_code, :prefecture, :city, :house_number, :building_name, :price

   with_options presence: true do
    validates :name, format: { with: /\A[ぁ-んァ-ン一-龥]/, message: "は全角で入力してください。"}
    validates :name_reading, format: { with: /\A[ァ-ヶー-]+\z/, message: "は全角カタカナで入力して下さい。"}
    validates :nickname, format: { with: /\A[a-z0-9]+\z/i, message: "は半角英数で入力してください。"}
  end

  def save


    user = User.create(name: name, name_reading: name_reading, nickname: nickname)


    Address.create(postal_code: postal_code, prefecture: prefecture, city: city, house_number: house_number, 
    building_name: building_name, user_id: user.id)

    Donation.create(price: price, user_id: user.id)
  end

end

と、一つのビューで複数のテーブルに値を保存したい時に、こういったモデルをよく作りますが

saveメソッドでのキー、バリューの書き忘れ!!!

で自分はつまずきました、、、。

この定義したsaveメソッドはあとでコントローラーで

 def create
   @donation = UserDonation.new(donation_params)

   if @donation.valid?
     @donation.save  # バリデーションをクリアした時
     return redirect_to root_path
   else
     render "new"    # バリデーションに弾かれ時
   end
 end

とsaveメソッドを用いますが、

例えば、

  def save


    user = User.create(name: name, name_reading: name_reading, nickname: nickname)


    Address.create(postal_code: postal_code, prefecture: prefecture, city: city, house_number: house_number, 
    building_name: building_name, user_id: user.id)

    Donation.create(price: price, user_id: user.id)
  end

で、

Address.create(postal_code: postal_code, prefecture: prefecture, city: city, house_number: house_number,
building_name: building_name, user_id: user.id)



postal_code: postal_code

が抜け落ちてたら、このエラーが起きます、、、。
なので、しっかりsaveメソッドが定義できてるかみましょう!!

これもフォームで入力して、しっかりパラメーターが作られるので、railsのバリデーション の
網を抜けたのでしょうが、データベース側でも「nilはだめ!!」としていたので、助かりました、。

 まとめ

バリデーション をかけているにもかかわらず、このエラーが起きるということは、モデルかコントローラーに
何かしらの問題がある。

もっというと、コントローラーは、createで値を保存するので、
ストロングパラメーターに問題があったり、

formオブジェクトはmodelにsaveメソッドを定義してるので、saveメソッドに問題が
ないか確かめましょう、、、!!!

他にもこのエラーに対する考えられる原因と、それの対処法があればコメントお願いします!!

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

railsのdeviceではhas_secure_passwordはいらない

deviceを使用しており、password_digestを使おうとuserモデルにhas_secure_passwordを追加したら、「undefined method `password_digest=' for #<> Did you mean? password_digest」というエラーが出た。

結論

deviseはhas_secure_password の代わりにencrypt_passwordを使用している。
よって、userモデルにhas_secure_passwordを追加する必要はない。

マイグレーションファイルを見てみると、

t.string :encrypted_password, null: false, default: ""

と確かに書いてあった。

参考:https://qiita.com/kents1002/items/4079e3d05d322febe00e

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

railsのdeviseではhas_secure_passwordはいらない

deviseを使用しており、password_digestを使おうとuserモデルにhas_secure_passwordを追加したら、「undefined method `password_digest=' for #<> Did you mean? password_digest」というエラーが出た。

結論

deviseはhas_secure_password の代わりにencrypt_passwordを使用している。
よって、userモデルにhas_secure_passwordを追加する必要はない。

マイグレーションファイルを見てみると、

t.string :encrypted_password, null: false, default: ""

と確かに書いてあった。

参考:https://qiita.com/kents1002/items/4079e3d05d322febe00e

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

【Rails】ActionMailer のテストで Mail::Matchers を使う

はじめに

CBcloud Advent Calendar 2020 の2日目の記事です。

本記事では、メール送信の単体テストの際、ActionMailer が依存している Mail に含まれている Mail::Matchers を RSpec のマッチャとして利用する方法を紹介します。

また、比較対象として、以下の2つも同時に記載します。

  1. Rails Guides に記載されているテスト方法
  2. RSpec のドキュメントに記載されているテスト方法

最後に、Mail::Matchers を使った例の紹介と、実際に動くサンプルコードを添付します。

動作確認環境

  • ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin19]
  • actionmailer (6.0.3.4)
  • rspec (3.10.0)

テストコード

Rails Guides に記載されているテスト方法

https://guides.rubyonrails.org/testing.html#testing-your-mailers

require 'test_helper'

class UserMailerTest < ActionMailer::TestCase
  test "invite" do
    # Create the email and store it for further assertions
    email = UserMailer.create_invite('me@example.com',
                                     'friend@example.com', Time.now)

    # Send the email, then test that it got queued
    assert_emails 1 do
      email.deliver_now
    end

    # Test the body of the sent email contains what we expect it to
    assert_equal ['me@example.com'], email.from
    assert_equal ['friend@example.com'], email.to
    assert_equal 'You have been invited by me@example.com', email.subject
    assert_equal read_fixture('invite').join, email.body.to_s
  end
end

RSpec のドキュメントに記載されているテスト方法

https://relishapp.com/rspec/rspec-rails/v/3-9/docs/mailer-specs/mailer-spec

require "rails_helper"

RSpec.describe NotificationsMailer, :type => :mailer do
  describe "notify" do
    let(:mail) { NotificationsMailer.signup }

    it "renders the headers" do
      expect(mail.subject).to eq("Signup")
      expect(mail.to).to eq(["to@example.org"])
      expect(mail.from).to eq(["from@example.com"])
    end

    it "renders the body" do
      expect(mail.body.encoded).to match("Hi")
    end
  end
end

Mail::Matchers を使ったテスト方法

ActionMailer の依存で Mail gem は既にインストール済みなので、導入手順は簡単です。

  • RSpec の設定で、 Mail::Matchers を include する
  • ActionMailer のテストで Mail::Matchers が提供するマッチャを使う

以下が実際に Mail::Matchers を使って書いたコードです。
比較のため、最初のコンテキストでは先述した RSpec のドキュメントに記載されたサンプルを踏襲したコードを実装しています。

RSpec.configure do |config|
  config.include Mail::Matchers, type: :mailer
end

RSpec.describe NotificationsMailer, type: :mailer do
  before do
    ActionMailer::Base.deliveries.clear
  end

  describe '#signup' do
    # @see: https://relishapp.com/rspec/rspec-rails/v/3-9/docs/mailer-specs/mailer-spec
    context 'when using RSpec mailer examples' do
      subject(:mail) { described_class.signup }

      it 'renders the headers' do
        expect(mail.subject).to eq('Signup')
        expect(mail.to).to eq(['to@example.org'])
        expect(mail.from).to eq(['from@example.com'])
      end

      it 'renders the body' do
        expect(mail.body.encoded).to match('Hi')
      end

      it 'sends the mail' do
        expect { mail.deliver_now }.to change { ActionMailer::Base.deliveries.count }
      end
    end

    # @see: https://github.com/mikel/mail#using-mail-with-testing-or-specing-libraries
    context 'when using Mail::Matchers' do
      subject(:mail) { described_class.signup.deliver_now }

      it { is_expected.to have_sent_email }
      it { is_expected.to have_sent_email.from('from@example.com') }
      it { is_expected.to have_sent_email.to('to@example.org') }
      it { is_expected.to have_sent_email.with_subject('Signup') }
      it { is_expected.to have_sent_email.with_body('Hi') }
    end
  end
end

メール送信後のマッチャが全て have_sent_email に集約されているのがわかりますね。
ここで紹介したもの以外にも、添付ファイルに対するマッチャなど、メールのテストに必要なものが一通り揃っているので、ぜひドキュメントを確認して使ってみてください。
https://github.com/mikel/mail#using-mail-with-testing-or-specing-libraries

最後に

ここまで読んで、実際に自分で試してみたいと思われた方のために、動くサンプルコードを Gist で公開しています。
https://gist.github.com/tomohiro/dfa7362cd066dd87f25f40bdd6856513

実行例:
mail_matcher_example.png

余談

個人的には --format documentation で出力したときの表現が冗長に感じています。

 参考資料

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

Sprockets::DoubleLinkError

初心者です。備忘録のため。残します。
超基本的な内容です。

エラー内容

Sprockets::DoubleLinkError

Rails にて erb ファイルを作成して、表示しようとしたら上記発生。ターミナル確認すると以下記述あり。

ActionView::Template::Error (Multiple files with the same output path cannot be linked ("style.css")
In "/Users/○○/git/photo_submission/app/assets/config/manifest.js" these files were linked:
  - /Users/○○/git/photo_submission/app/assets/stylesheets/style.css
  - /Users/○○/git/photo_submission/app/assets/stylesheets/style.scss
):

原因

調べてみたら、Rails は rails new したときに自動で作成される views/layauts/application.html.erb を参照して HTML ファイルを作るようです。

Sprocketsの仕組み - Qiita

Rails初学者がつまずきやすい「アセットパイプライン」

assets ファイル以下のデータ群を一つに圧縮する(アセットパイプライン)。

views/layauts/application.html.erb

<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>

 あとで erb ファイルに変更しようと思って、先に普通の HTML ファイルで作成しており、深く考えずに style.scss を作成していた。VSCode の拡張機能の Live Sass Compiler でstyle.css も自動で作成されており、これがエラーの原因。

assets/stylesheets/application.css に style.scss の記述を写し、stylesheets 直下には application.css だけ残すようにファイルの構造を調整して、再度出力すると問題なく表示できた。

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

RSpec FactoryBotのアソシエーション

はじめに

FactoryBotのアソシエーションについてメモしていきます

アソシエーションの仕方

例えば、userモデルと紐付いてるpostモデルのテストデータが欲しい場合

FactoryBot.define do
  factory :post do
     content {"Ruby楽しい"}
     association :user, factory: :user  #アソシエーション
  end
end

この場合はuserFactoryBotも作成してる必要がある。

またこのように省略することも出来る。

FactoryBot.define do
  factory :post do
     content {"Ruby楽しい"}
     user                  #省略
  end
end

最後に

まだまだ勉強中なので訂正などありましたらご指摘いただけると幸いですm(__)m

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

【AmazonLinux2】Railsアプリケーション新規作成 rake db:createまで

初めに

AmazonLinux2でRailsアプリケーションを新規作成した際に、いろいろとエラーにハマったため、黙示録として書いています。

環境

OS:AmazonLinux2
Ruby:ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
Rails:Rails 5.0.0
DB:Mariadb

手順

前提として今回は、AWS(cloud9)でAmazonLinux2での開発を想定してます。またAWSのenvironmentの作成はできているものとします。

Ruby, Railsのバージョンの確認(AWSでは環境構築がされているため)
$ ruby -v
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]

$ rails -v
Rails 5.0.0
RubyのライブラリであるGemを管理するためのGem(bundler)をinstallする
$ gem install bundler
Fetching bundler-2.1.4.gem
Successfully installed bundler-2.1.4
Parsing documentation for bundler-2.1.4
Installing ri documentation for bundler-2.1.4
Done installing documentation for bundler after 3 seconds
1 gem installed
Marinadbを使用するために必要なパッケージをインストール

DBサーバーインストール

$ sudo yum install mariadb-server
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                                                                                                                                               | 3.7 kB  00:00:00     
220 packages excluded due to repository priority protections
Resolving Dependencies
--> Running transaction check
---> Package mariadb-server.x86_64 3:10.2.10-2.amzn2.0.3 will be installed
--> Processing Dependency: mariadb-tokudb-engine(x86-64) = 3:10.2.10-2.amzn2.0.3 for package: 3:mariadb-server-10.2.10-2.amzn2.0.3.x86_64
--> Processing Dependency: mariadb-server-utils(x86-64) = 3:10.2.10-2.amzn2.0.3 for package: 3:mariadb-server-10.2.10-2.amzn2.0.3.x86_64
--> Processing Dependency: mariadb-rocksdb-engine(x86-64) = 3:10.2.10-2.amzn2.0.3 for package: 3:mariadb-server-10.2.10-2.amzn2.0.3.x86_64
--> Processing Dependency: mariadb-gssapi-server(x86-64) = 3:10.2.10-2.amzn2.0.3 for package: 3:mariadb-server-10.2.10-2.amzn2.0.3.x86_64
--> Processing Dependency: mariadb-errmsg(x86-64) = 3:10.2.10-2.amzn2.0.3 for package: 3:mariadb-server-10.2.10-2.amzn2.0.3.x86_64
--> Processing Dependency: mariadb-cracklib-password-check(x86-64) = 3:10.2.10-2.amzn2.0.3 for package: 3:mariadb-server-10.2.10-2.amzn2.0.3.x86_64
--> Processing Dependency: mariadb-backup(x86-64) = 3:10.2.10-2.amzn2.0.3 for package: 3:mariadb-server-10.2.10-2.amzn2.0.3.x86_64
--> Running transaction check
---> Package mariadb-backup.x86_64 3:10.2.10-2.amzn2.0.3 will be installed
---> Package mariadb-cracklib-password-check.x86_64 3:10.2.10-2.amzn2.0.3 will be installed
---> Package mariadb-errmsg.x86_64 3:10.2.10-2.amzn2.0.3 will be installed
---> Package mariadb-gssapi-server.x86_64 3:10.2.10-2.amzn2.0.3 will be installed
---> Package mariadb-rocksdb-engine.x86_64 3:10.2.10-2.amzn2.0.3 will be installed
---> Package mariadb-server-utils.x86_64 3:10.2.10-2.amzn2.0.3 will be installed
--> Processing Dependency: perl(DBI) for package: 3:mariadb-server-utils-10.2.10-2.amzn2.0.3.x86_64
--> Processing Dependency: perl(DBI) for package: 3:mariadb-server-utils-10.2.10-2.amzn2.0.3.x86_64
--> Processing Dependency: perl(DBD::mysql) for package: 3:mariadb-server-utils-10.2.10-2.amzn2.0.3.x86_64
---> Package mariadb-tokudb-engine.x86_64 3:10.2.10-2.amzn2.0.3 will be installed
--> Processing Dependency: libjemalloc.so.1()(64bit) for package: 3:mariadb-tokudb-engine-10.2.10-2.amzn2.0.3.x86_64
--> Running transaction check
---> Package jemalloc.x86_64 0:3.6.0-1.amzn2.0.1 will be installed
---> Package perl-DBD-MySQL.x86_64 0:4.023-6.amzn2 will be installed
---> Package perl-DBI.x86_64 0:1.627-4.amzn2.0.2 will be installed
--> Processing Dependency: perl(RPC::PlServer) >= 0.2001 for package: perl-DBI-1.627-4.amzn2.0.2.x86_64
--> Processing Dependency: perl(RPC::PlClient) >= 0.2000 for package: perl-DBI-1.627-4.amzn2.0.2.x86_64
--> Running transaction check
---> Package perl-PlRPC.noarch 0:0.2020-14.amzn2 will be installed
--> Processing Dependency: perl(Net::Daemon) >= 0.13 for package: perl-PlRPC-0.2020-14.amzn2.noarch
--> Processing Dependency: perl(Net::Daemon::Test) for package: perl-PlRPC-0.2020-14.amzn2.noarch
--> Processing Dependency: perl(Net::Daemon::Log) for package: perl-PlRPC-0.2020-14.amzn2.noarch
--> Processing Dependency: perl(Compress::Zlib) for package: perl-PlRPC-0.2020-14.amzn2.noarch
--> Running transaction check
---> Package perl-IO-Compress.noarch 0:2.061-2.amzn2 will be installed
--> Processing Dependency: perl(Compress::Raw::Zlib) >= 2.061 for package: perl-IO-Compress-2.061-2.amzn2.noarch
--> Processing Dependency: perl(Compress::Raw::Bzip2) >= 2.061 for package: perl-IO-Compress-2.061-2.amzn2.noarch
---> Package perl-Net-Daemon.noarch 0:0.48-5.amzn2 will be installed
--> Running transaction check
---> Package perl-Compress-Raw-Bzip2.x86_64 0:2.061-3.amzn2.0.2 will be installed
---> Package perl-Compress-Raw-Zlib.x86_64 1:2.061-4.amzn2.0.2 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================================================================================================================================
 Package                                               Arch                         Version                                      Repository                                                Size
================================================================================================================================================================================================
Installing:
 mariadb-server                                        x86_64                       3:10.2.10-2.amzn2.0.3                        amzn2extra-lamp-mariadb10.2-php7.2                        17 M
Installing for dependencies:
 jemalloc                                              x86_64                       3.6.0-1.amzn2.0.1                            amzn2extra-lamp-mariadb10.2-php7.2                       109 k
 mariadb-backup                                        x86_64                       3:10.2.10-2.amzn2.0.3                        amzn2extra-lamp-mariadb10.2-php7.2                       5.9 M
 mariadb-cracklib-password-check                       x86_64                       3:10.2.10-2.amzn2.0.3                        amzn2extra-lamp-mariadb10.2-php7.2                        36 k
 mariadb-errmsg                                        x86_64                       3:10.2.10-2.amzn2.0.3                        amzn2extra-lamp-mariadb10.2-php7.2                       221 k
 mariadb-gssapi-server                                 x86_64                       3:10.2.10-2.amzn2.0.3                        amzn2extra-lamp-mariadb10.2-php7.2                        39 k
 mariadb-rocksdb-engine                                x86_64                       3:10.2.10-2.amzn2.0.3                        amzn2extra-lamp-mariadb10.2-php7.2                       4.0 M
 mariadb-server-utils                                  x86_64                       3:10.2.10-2.amzn2.0.3                        amzn2extra-lamp-mariadb10.2-php7.2                       1.6 M
 mariadb-tokudb-engine                                 x86_64                       3:10.2.10-2.amzn2.0.3                        amzn2extra-lamp-mariadb10.2-php7.2                       818 k
 perl-Compress-Raw-Bzip2                               x86_64                       2.061-3.amzn2.0.2                            amzn2-core                                                32 k
 perl-Compress-Raw-Zlib                                x86_64                       1:2.061-4.amzn2.0.2                          amzn2-core                                                58 k
 perl-DBD-MySQL                                        x86_64                       4.023-6.amzn2                                amzn2-core                                               141 k
 perl-DBI                                              x86_64                       1.627-4.amzn2.0.2                            amzn2-core                                               804 k
 perl-IO-Compress                                      noarch                       2.061-2.amzn2                                amzn2-core                                               260 k
 perl-Net-Daemon                                       noarch                       0.48-5.amzn2                                 amzn2-core                                                51 k
 perl-PlRPC                                            noarch                       0.2020-14.amzn2                              amzn2-core                                                36 k

Transaction Summary
================================================================================================================================================================================================
Install  1 Package (+15 Dependent packages)

Total download size: 31 M
Installed size: 136 M
Is this ok [y/d/N]: y
Downloading packages:
(1/16): jemalloc-3.6.0-1.amzn2.0.1.x86_64.rpm                                                                                                                            | 109 kB  00:00:00     
(2/16): mariadb-cracklib-password-check-10.2.10-2.amzn2.0.3.x86_64.rpm                                                                                                   |  36 kB  00:00:00     
(3/16): mariadb-errmsg-10.2.10-2.amzn2.0.3.x86_64.rpm                                                                                                                    | 221 kB  00:00:00     
(4/16): mariadb-backup-10.2.10-2.amzn2.0.3.x86_64.rpm                                                                                                                    | 5.9 MB  00:00:00     
(5/16): mariadb-gssapi-server-10.2.10-2.amzn2.0.3.x86_64.rpm                                                                                                             |  39 kB  00:00:00     
(6/16): mariadb-rocksdb-engine-10.2.10-2.amzn2.0.3.x86_64.rpm                                                                                                            | 4.0 MB  00:00:00     
(7/16): mariadb-server-utils-10.2.10-2.amzn2.0.3.x86_64.rpm                                                                                                              | 1.6 MB  00:00:00     
(8/16): mariadb-tokudb-engine-10.2.10-2.amzn2.0.3.x86_64.rpm                                                                                                             | 818 kB  00:00:00     
(9/16): mariadb-server-10.2.10-2.amzn2.0.3.x86_64.rpm                                                                                                                    |  17 MB  00:00:00     
(10/16): perl-Compress-Raw-Bzip2-2.061-3.amzn2.0.2.x86_64.rpm                                                                                                            |  32 kB  00:00:00     
(11/16): perl-Compress-Raw-Zlib-2.061-4.amzn2.0.2.x86_64.rpm                                                                                                             |  58 kB  00:00:00     
(12/16): perl-DBD-MySQL-4.023-6.amzn2.x86_64.rpm                                                                                                                         | 141 kB  00:00:00     
(13/16): perl-IO-Compress-2.061-2.amzn2.noarch.rpm                                                                                                                       | 260 kB  00:00:00     
(14/16): perl-Net-Daemon-0.48-5.amzn2.noarch.rpm                                                                                                                         |  51 kB  00:00:00     
(15/16): perl-PlRPC-0.2020-14.amzn2.noarch.rpm                                                                                                                           |  36 kB  00:00:00     
(16/16): perl-DBI-1.627-4.amzn2.0.2.x86_64.rpm                                                                                                                           | 804 kB  00:00:00     
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Total                                                                                                                                                            32 MB/s |  31 MB  00:00:00     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : 3:mariadb-errmsg-10.2.10-2.amzn2.0.3.x86_64                                                                                                                                 1/16 
  Installing : jemalloc-3.6.0-1.amzn2.0.1.x86_64                                                                                                                                           2/16 
  Installing : perl-Compress-Raw-Bzip2-2.061-3.amzn2.0.2.x86_64                                                                                                                            3/16 
  Installing : perl-Net-Daemon-0.48-5.amzn2.noarch                                                                                                                                         4/16 
  Installing : 1:perl-Compress-Raw-Zlib-2.061-4.amzn2.0.2.x86_64                                                                                                                           5/16 
  Installing : perl-IO-Compress-2.061-2.amzn2.noarch                                                                                                                                       6/16 
  Installing : perl-PlRPC-0.2020-14.amzn2.noarch                                                                                                                                           7/16 
  Installing : perl-DBI-1.627-4.amzn2.0.2.x86_64                                                                                                                                           8/16 
  Installing : perl-DBD-MySQL-4.023-6.amzn2.x86_64                                                                                                                                         9/16 
  Installing : 3:mariadb-tokudb-engine-10.2.10-2.amzn2.0.3.x86_64                                                                                                                         10/16 
  Installing : 3:mariadb-rocksdb-engine-10.2.10-2.amzn2.0.3.x86_64                                                                                                                        11/16 
  Installing : 3:mariadb-backup-10.2.10-2.amzn2.0.3.x86_64                                                                                                                                12/16 
  Installing : 3:mariadb-cracklib-password-check-10.2.10-2.amzn2.0.3.x86_64                                                                                                               13/16 
  Installing : 3:mariadb-gssapi-server-10.2.10-2.amzn2.0.3.x86_64                                                                                                                         14/16 
  Installing : 3:mariadb-server-10.2.10-2.amzn2.0.3.x86_64                                                                                                                                15/16 
  Installing : 3:mariadb-server-utils-10.2.10-2.amzn2.0.3.x86_64                                                                                                                          16/16 
  Verifying  : 1:perl-Compress-Raw-Zlib-2.061-4.amzn2.0.2.x86_64                                                                                                                           1/16 
  Verifying  : 3:mariadb-tokudb-engine-10.2.10-2.amzn2.0.3.x86_64                                                                                                                          2/16 
  Verifying  : perl-Net-Daemon-0.48-5.amzn2.noarch                                                                                                                                         3/16 
  Verifying  : 3:mariadb-rocksdb-engine-10.2.10-2.amzn2.0.3.x86_64                                                                                                                         4/16 
  Verifying  : perl-DBD-MySQL-4.023-6.amzn2.x86_64                                                                                                                                         5/16 
  Verifying  : 3:mariadb-backup-10.2.10-2.amzn2.0.3.x86_64                                                                                                                                 6/16 
  Verifying  : 3:mariadb-server-utils-10.2.10-2.amzn2.0.3.x86_64                                                                                                                           7/16 
  Verifying  : 3:mariadb-cracklib-password-check-10.2.10-2.amzn2.0.3.x86_64                                                                                                                8/16 
  Verifying  : perl-IO-Compress-2.061-2.amzn2.noarch                                                                                                                                       9/16 
  Verifying  : 3:mariadb-gssapi-server-10.2.10-2.amzn2.0.3.x86_64                                                                                                                         10/16 
  Verifying  : perl-Compress-Raw-Bzip2-2.061-3.amzn2.0.2.x86_64                                                                                                                           11/16 
  Verifying  : jemalloc-3.6.0-1.amzn2.0.1.x86_64                                                                                                                                          12/16 
  Verifying  : perl-DBI-1.627-4.amzn2.0.2.x86_64                                                                                                                                          13/16 
  Verifying  : perl-PlRPC-0.2020-14.amzn2.noarch                                                                                                                                          14/16 
  Verifying  : 3:mariadb-errmsg-10.2.10-2.amzn2.0.3.x86_64                                                                                                                                15/16 
  Verifying  : 3:mariadb-server-10.2.10-2.amzn2.0.3.x86_64                                                                                                                                16/16 

Installed:
  mariadb-server.x86_64 3:10.2.10-2.amzn2.0.3                                                                                                                                                   

Dependency Installed:
  jemalloc.x86_64 0:3.6.0-1.amzn2.0.1                        mariadb-backup.x86_64 3:10.2.10-2.amzn2.0.3                 mariadb-cracklib-password-check.x86_64 3:10.2.10-2.amzn2.0.3         
  mariadb-errmsg.x86_64 3:10.2.10-2.amzn2.0.3                mariadb-gssapi-server.x86_64 3:10.2.10-2.amzn2.0.3          mariadb-rocksdb-engine.x86_64 3:10.2.10-2.amzn2.0.3                  
  mariadb-server-utils.x86_64 3:10.2.10-2.amzn2.0.3          mariadb-tokudb-engine.x86_64 3:10.2.10-2.amzn2.0.3          perl-Compress-Raw-Bzip2.x86_64 0:2.061-3.amzn2.0.2                   
  perl-Compress-Raw-Zlib.x86_64 1:2.061-4.amzn2.0.2          perl-DBD-MySQL.x86_64 0:4.023-6.amzn2                       perl-DBI.x86_64 0:1.627-4.amzn2.0.2                                  
  perl-IO-Compress.noarch 0:2.061-2.amzn2                    perl-Net-Daemon.noarch 0:0.48-5.amzn2                       perl-PlRPC.noarch 0:0.2020-14.amzn2                                  

Complete!

DBクライアントインストール

$ sudo yum install mariadb-devel
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
220 packages excluded due to repository priority protections
Resolving Dependencies
--> Running transaction check
---> Package mariadb-devel.x86_64 3:10.2.10-2.amzn2.0.3 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================================================================================================================================
 Package                                 Arch                             Version                                            Repository                                                    Size
================================================================================================================================================================================================
Installing:
 mariadb-devel                           x86_64                           3:10.2.10-2.amzn2.0.3                              amzn2extra-lamp-mariadb10.2-php7.2                           1.0 M

Transaction Summary
================================================================================================================================================================================================
Install  1 Package

Total download size: 1.0 M
Installed size: 4.5 M
Is this ok [y/d/N]: y
Downloading packages:
mariadb-devel-10.2.10-2.amzn2.0.3.x86_64.rpm                                                                                                                             | 1.0 MB  00:00:00     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : 3:mariadb-devel-10.2.10-2.amzn2.0.3.x86_64                                                                                                                                   1/1 
  Verifying  : 3:mariadb-devel-10.2.10-2.amzn2.0.3.x86_64                                                                                                                                   1/1 

Installed:
  mariadb-devel.x86_64 3:10.2.10-2.amzn2.0.3                                                                                                                                                    

Complete!
データベースサーバーの起動
$ sudo systemctl start mariadb
サーバーの起動状況を確認
$ sudo systemctl status mariadb
● mariadb.service - MariaDB 10.2 database server
   Loaded: loaded (/usr/lib/systemd/system/mariadb.service; disabled; vendor preset: disabled)
   Active: active (running) since Thu 2020-12-03 15:35:26 UTC; 8s ago
  Process: 860 ExecStartPost=/usr/libexec/mysql-check-upgrade (code=exited, status=0/SUCCESS)
  Process: 638 ExecStartPre=/usr/libexec/mysql-prepare-db-dir %n (code=exited, status=0/SUCCESS)
  Process: 582 ExecStartPre=/usr/libexec/mysql-check-socket (code=exited, status=0/SUCCESS)
 Main PID: 778 (mysqld)
   Status: "Taking your SQL requests now..."
    Tasks: 45
   Memory: 169.8M
   CGroup: /system.slice/mariadb.service
           └─778 /usr/libexec/mysqld --basedir=/usr

Dec 03 15:35:25 ip-172-31-19-99.us-east-2.compute.internal mysql-prepare-db-dir[638]: MySQL manual for more instructions.
Dec 03 15:35:25 ip-172-31-19-99.us-east-2.compute.internal mysql-prepare-db-dir[638]: Please report any problems at http://mariadb.org/jira
Dec 03 15:35:25 ip-172-31-19-99.us-east-2.compute.internal mysql-prepare-db-dir[638]: The latest information about MariaDB is available at http://mariadb.org/.
Dec 03 15:35:25 ip-172-31-19-99.us-east-2.compute.internal mysql-prepare-db-dir[638]: You can find additional information about the MySQL part at:
Dec 03 15:35:25 ip-172-31-19-99.us-east-2.compute.internal mysql-prepare-db-dir[638]: http://dev.mysql.com
Dec 03 15:35:25 ip-172-31-19-99.us-east-2.compute.internal mysql-prepare-db-dir[638]: Consider joining MariaDB's strong and vibrant community:
Dec 03 15:35:25 ip-172-31-19-99.us-east-2.compute.internal mysql-prepare-db-dir[638]: https://mariadb.org/get-involved/
Dec 03 15:35:26 ip-172-31-19-99.us-east-2.compute.internal mysqld[778]: 2020-12-03 15:35:26 140397484732224 [Note] /usr/libexec/mysqld (mysqld 10.2.10-MariaDB) starting as process 778 ...
Dec 03 15:35:26 ip-172-31-19-99.us-east-2.compute.internal mysqld[778]: 2020-12-03 15:35:26 140397484732224 [Warning] Changed limits: max_open_files: 1024  max_connections: 151  ta...ache: 431
Dec 03 15:35:26 ip-172-31-19-99.us-east-2.compute.internal systemd[1]: Started MariaDB 10.2 database server.
Hint: Some lines were ellipsized, use -l to show in full.
OS起動時に自動的に起動するように設定
$ sudo systemctl enable mariadb
Created symlink from /etc/systemd/system/multi-user.target.wants/mariadb.service to /usr/lib/systemd/system/mariadb.service.
アプリケーション新規作成

ここでようやくアプリケーションを新規作成します。今回データベースはmysqlを指定するので、以下コマンドを打ち込みます。例としてアプリケーション名は"shift-management"としています。

$ rails new shift-management -d mysql
      create  
      create  README.md
      create  Rakefile
      create  config.ru
      create  .gitignore
      create  Gemfile
      create  app
      create  app/assets/config/manifest.js
      create  app/assets/javascripts/application.js
      create  app/assets/javascripts/cable.js
      create  app/assets/stylesheets/application.css
      create  app/channels/application_cable/channel.rb
      create  app/channels/application_cable/connection.rb
      create  app/controllers/application_controller.rb
      create  app/helpers/application_helper.rb
      create  app/jobs/application_job.rb
      create  app/mailers/application_mailer.rb
      create  app/models/application_record.rb
      create  app/views/layouts/application.html.erb
      create  app/views/layouts/mailer.html.erb
      create  app/views/layouts/mailer.text.erb
      create  app/assets/images/.keep
      create  app/assets/javascripts/channels
      create  app/assets/javascripts/channels/.keep
      create  app/controllers/concerns/.keep
      create  app/models/concerns/.keep
      create  bin
      create  bin/bundle
      create  bin/rails
      create  bin/rake
      create  bin/setup
      create  bin/update
      create  config
      create  config/routes.rb
      create  config/application.rb
      create  config/environment.rb
      create  config/secrets.yml
      create  config/cable.yml
      create  config/puma.rb
      create  config/spring.rb
      create  config/environments
      create  config/environments/development.rb
      create  config/environments/production.rb
      create  config/environments/test.rb
      create  config/initializers
      create  config/initializers/application_controller_renderer.rb
      create  config/initializers/assets.rb
      create  config/initializers/backtrace_silencers.rb
      create  config/initializers/cookies_serializer.rb
      create  config/initializers/cors.rb
      create  config/initializers/filter_parameter_logging.rb
      create  config/initializers/inflections.rb
      create  config/initializers/mime_types.rb
      create  config/initializers/new_framework_defaults.rb
      create  config/initializers/session_store.rb
      create  config/initializers/wrap_parameters.rb
      create  config/locales
      create  config/locales/en.yml
      create  config/boot.rb
      create  config/database.yml
      create  db
      create  db/seeds.rb
      create  lib
      create  lib/tasks
      create  lib/tasks/.keep
      create  lib/assets
      create  lib/assets/.keep
      create  log
      create  log/.keep
      create  public
      create  public/404.html
      create  public/422.html
      create  public/500.html
      create  public/apple-touch-icon-precomposed.png
      create  public/apple-touch-icon.png
      create  public/favicon.ico
      create  public/robots.txt
      create  test/fixtures
      create  test/fixtures/.keep
      create  test/fixtures/files
      create  test/fixtures/files/.keep
      create  test/controllers
      create  test/controllers/.keep
      create  test/mailers
      create  test/mailers/.keep
      create  test/models
      create  test/models/.keep
      create  test/helpers
      create  test/helpers/.keep
      create  test/integration
      create  test/integration/.keep
      create  test/test_helper.rb
      create  tmp
      create  tmp/.keep
      create  tmp/cache
      create  tmp/cache/assets
      create  vendor/assets/javascripts
      create  vendor/assets/javascripts/.keep
      create  vendor/assets/stylesheets
      create  vendor/assets/stylesheets/.keep
      remove  config/initializers/cors.rb
         run  bundle install
[DEPRECATED] `Bundler.with_clean_env` has been deprecated in favor of `Bundler.with_unbundled_env`. If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env` (called at /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/railties-5.0.0/lib/rails/generators/app_base.rb:374)
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies....
Fetching rake 13.0.1
Installing rake 13.0.1
Using concurrent-ruby 1.1.7
Fetching i18n 1.8.5
Installing i18n 1.8.5
Fetching minitest 5.14.2
Installing minitest 5.14.2
Using thread_safe 0.3.6
Fetching tzinfo 1.2.8
Installing tzinfo 1.2.8
Fetching activesupport 5.0.7.2
Installing activesupport 5.0.7.2
Using builder 3.2.4
Using erubis 2.7.0
Using mini_portile2 2.4.0
Using nokogiri 1.10.10
Using rails-dom-testing 2.0.3
Using crass 1.0.6
Fetching loofah 2.8.0
Installing loofah 2.8.0
Using rails-html-sanitizer 1.3.0
Fetching actionview 5.0.7.2
Installing actionview 5.0.7.2
Using rack 2.2.3
Using rack-test 0.6.3
Fetching actionpack 5.0.7.2
Installing actionpack 5.0.7.2
Fetching nio4r 2.5.4
Installing nio4r 2.5.4 with native extensions
Using websocket-extensions 0.1.5
Using websocket-driver 0.6.5
Fetching actioncable 5.0.7.2
Installing actioncable 5.0.7.2
Using globalid 0.4.2
Fetching activejob 5.0.7.2
Installing activejob 5.0.7.2
Using mini_mime 1.0.2
Using mail 2.7.1
Fetching actionmailer 5.0.7.2
Installing actionmailer 5.0.7.2
Fetching activemodel 5.0.7.2
Installing activemodel 5.0.7.2
Using arel 7.1.4
Fetching activerecord 5.0.7.2
Installing activerecord 5.0.7.2
Fetching bindex 0.8.1
Installing bindex 0.8.1 with native extensions
Using bundler 2.1.4
Fetching byebug 11.1.3
Installing byebug 11.1.3 with native extensions
Fetching coffee-script-source 1.12.2
Installing coffee-script-source 1.12.2
Fetching execjs 2.7.0
Installing execjs 2.7.0
Fetching coffee-script 2.4.1
Installing coffee-script 2.4.1
Using method_source 1.0.0
Using thor 1.0.1
Fetching railties 5.0.7.2
Installing railties 5.0.7.2
Fetching coffee-rails 4.2.2
Installing coffee-rails 4.2.2
Fetching ffi 1.13.1
Installing ffi 1.13.1 with native extensions
Fetching jbuilder 2.10.1
Installing jbuilder 2.10.1
Fetching jquery-rails 4.4.0
Installing jquery-rails 4.4.0
Fetching rb-fsevent 0.10.4
Installing rb-fsevent 0.10.4
Fetching rb-inotify 0.10.1
Installing rb-inotify 0.10.1
Fetching listen 3.0.8
Installing listen 3.0.8
Fetching mysql2 0.4.10
Installing mysql2 0.4.10 with native extensions
Fetching puma 3.12.6
Installing puma 3.12.6 with native extensions
Fetching sprockets 3.7.2
Installing sprockets 3.7.2
Using sprockets-rails 3.2.2
Fetching rails 5.0.7.2
Installing rails 5.0.7.2
Fetching sass-listen 4.0.0
Installing sass-listen 4.0.0
Fetching sass 3.7.4
Installing sass 3.7.4
Fetching tilt 2.0.10
Installing tilt 2.0.10
Fetching sass-rails 5.0.7
Installing sass-rails 5.0.7
Fetching spring 2.1.1
Installing spring 2.1.1
Fetching spring-watcher-listen 2.0.1
Installing spring-watcher-listen 2.0.1
Fetching turbolinks-source 5.2.0
Installing turbolinks-source 5.2.0
Fetching turbolinks 5.2.1
Installing turbolinks 5.2.1
Fetching uglifier 4.2.0
Installing uglifier 4.2.0
Fetching web-console 3.7.0
Installing web-console 3.7.0
Bundle complete! 15 Gemfile dependencies, 62 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
Post-install message from i18n:

HEADS UP! i18n 1.1 changed fallbacks to exclude default locale.
But that may break your application.

If you are upgrading your Rails application from an older version of Rails:

Please check your Rails app for 'config.i18n.fallbacks = true'.
If you're using I18n (>= 1.1.0) and Rails (< 5.2.2), this should be
'config.i18n.fallbacks = [I18n.default_locale]'.
If not, fallbacks will be broken in your app by I18n 1.1.x.

If you are starting a NEW Rails application, you can ignore this notice.

For more info see:
https://github.com/svenfuchs/i18n/releases/tag/v1.1.0

Post-install message from sass:

Ruby Sass has reached end-of-life and should no longer be used.

* If you use Sass as a command-line tool, we recommend using Dart Sass, the new
  primary implementation: https://sass-lang.com/install

* If you use Sass as a plug-in for a Ruby web framework, we recommend using the
  sassc gem: https://github.com/sass/sassc-ruby#readme

* For more details, please refer to the Sass blog:
  https://sass-lang.com/blog/posts/7828841

         run  bundle exec spring binstub --all
* bin/rake: Spring inserted
* bin/rails: Spring inserted
データベースの作成
$ rake db:create
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Created database 'shift-management_development'
Created database 'shift-management_test'

今回はここまで。

よくあるエラー

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

rbenvのインストールについて(Checking for rbenv shims in PATH: not found)

結論

シェルの確認

.zshrcファイルの作成

eval "$(rbenv init -)"
export PATH="~/.rbenv/bin:$PATH"
export PATH="~/.rbenv/bin:~/.rbenv/shims:$PATH"
の三行を追加

source ~/.zshrc

環境

  • Mac

問題点

$ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash

を実行すると、

Checking for `rbenv' in PATH: /usr/local/bin/rbenv
Checking for rbenv shims in PATH: not found
  The directory `/Users/ユーザー名/.rbenv/shims' must be present in PATH for rbenv to work.
  Please run `rbenv init' and follow the instructions.

Checking `rbenv install' support: /usr/local/bin/rbenv-install (ruby-build 20201118)
Counting installed Ruby versions: none
  There aren't any Ruby versions installed under `/Users/ユーザー名/.rbenv/versions'.
  You can install Ruby versions like so: rbenv install 2.2.4
Checking RubyGems settings: OK
Auditing installed plugins: OK

となり、

Checking for rbenv shims in PATH: not found

この部分のパスが通っていないこと。

解決法

rbenv shimsの環境パスの設定が正しくできていない、とのことなので
.bash_profile内で、

export RBENV_ROOT="$HOME/.rbenv"
if [ -d "${RBENV_ROOT}" ]; then
  export PATH="$HOME/.rbenv/bin:$PATH"
  eval "$(rbenv init -)"
fi

を記述する。これで再度確認するとOK!になるのだが、ターミナルを再起動するとまた

Checking for rbenv shims in PATH: not found

のエラーになる。

ここからが具体的な解決法。
今現在シェルがzshになっている。その上で、bash_profileファイル内に記述したのだがそれではターミナルを起動するごとにsource ~/.bash_profileをする必要がある。
結論として新たにzshrcファイル(zsh_profileファイルでもいい)を作成し、同じような記述をするとターミナルを再起動してもパスが通っている状態になっている。

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

RailsでWeb APIを実装しよう!

はじめに

PORT Advent Calendar 2020 の4日目の記事です!

はじめまして! PORT新卒エンジニアのしゅんいち(shxun6934) です!

業務でのとあるタスクにて、Railsを使用してWebAPIを実装することになりました。
今までは、WebAPI(QiitaとかTwitterとかお世話になりました。)を使用する側でしたが、WebAPIを作成する側になったので、いろいろわからないところが多かったです。

簡単にですが、そのタスクに臨むにあたって学んだこと、意識していたことを皆さんに共有しておきたいと思います。

そもそも、APIって何?

API(Application Programming Interface) は、アプリケーションとアプリケーション、人、組織をつなげてくれるものです。

APIと言っても種類はいくつかあります。
(例:Twitterから「いいね」の情報を取得するためのもの、iOSアプリでカメラの機能を使用するためのもの、画像の色を反転させるためのもの、などなど。。。)

一般的に言われている「API」は、WebAPIを指すことが多いです。

WebAPIは、ネットワークを使用して他のアプリケーションと通信する APIのことです。

RESTful API

RESTの原則に従ったAPI。

ここで思うことは、「RESTって何???」

簡単に言うと、RESTは複数のソフトウェアを連携させるための設計原則の考え方のことです。
RESTの原則は以下の4つ。

URIを通して提供できる情報が表現できていること = アドレス可能性
インターフェースが統一されていること = 統一インターフェース
やり取りする情報で全てを完結させること = ステートレス性
情報の内部に別の情報や状態へのリンクを含めることができること = 接続性

これらに従って設計すれば、使いやすくAPIになります。

では、実際にWebAPIで意識することを見ていきます。

URI

WebAPIなので、URIは必須になります。

ここでは、REST原則のアドレス可能性が鍵になります。
URIはユーザーが実際に入力するものなので、わかりやすく、どんな機能を持つURIがひと目でわかるようにしたほうがベストです。

例として、犬の種別の一覧を取得するAPIを設計します。

GET https://example.com/api/get-dogs-type-all-list

これでは、読みにくく、直感的ではありません。(何の機能なのかはなんとなくわかる気がする。。。)

dogstypeが同列に並んでいるので、階層的にわかりづらいですし、listallでは意味がほぼ同じなので、重複してます。
また、WebAPIを使用するユーザーが想定なので、URIにわざわざgetという単語を入れなくてもいいです。(後述のHTTPメソッドより)

なので、階層的にわかりやすく、シンプルにしたほうが使いやすそうですね。
以下のように修正します。

GET https://example.com/api/dogs/type/list

dogstypelistを取得できると直感的にわかるようになったと思います。(多分)

HTTPメソッド

REST原則の統一インターフェースに一番密な要素です。
標準化されたHTTPメソッドの個々の役割を理解して、それらをどのAPIでも適用するものです。

基本的なHTTPメソッドは、以下。

HTTPメソッド 役割
GET リソースを取得する、検索する
POST リソースを作成する、追加する
PUT リソースのデータを更新する
DELET リソースを削除する

もし、POSTで「リソースのデータを更新する」役割をも持たせたとして、
記事を投稿するAPI記事の内容を更新するAPIがあったとします。

2つのAPIを以下のように設計します。

POST /api/articles
POST /api/articles/:article_id

上記の設計だと、同じアプリケーションに「リソースを作成する」POSTと「リソースのデータを更新する」POSTが存在してしまいます。

これでは、POSTに関して同じ意味をなさなくなるので、インターフェースが統一されていないことになり、使いづらいAPIになってしまいます。

この例では、HTTPメソッドの役割に沿って、記事の内容を更新するAPIPUTで行うようにしたほうがいいと思います。

HTTPレスポンス

APIを使用して返ってくるものにもしっかりと制約をつけ、インターフェースを統一しましょう。
(HEADは割愛させていただきます。すいません。)

ステータスコード

200とか、404とか、500とかのあれです。
ステータスコード単体でも意味を成しているので、HTTPメソッド同様、理解する必要があります。

よく目にするステータスコードは以下。

ステータスコード 意味
200 リクエストが成功した
201 リクエストが成功し、リソースが新規作成された
400 リクエストパラメータに不足・不備があった
401 アクセストークンが無効、認証されていない
404 リクエストされたリソースが存在しなかった
500 何らかのエラーがサーバー側で発生している

WebAPIの設計において、基本的には想像しうる状態を全て網羅しなくてはいけません。
網羅した上で、必要なステータスコードはなんなのかを考えていきましょう。

ボディ

ユーザーが実際に扱うデータのフォーマットや構造を考えて置く必要があります。

最近のWebAPIは、JSONで返すことが多いです。JSONのフォーマットにしたがっていけば問題なさそうですね。

レスポンスのボディは2種類あると思います。
リクエストが成功した場合リクエストが失敗した場合です。

リクエストが成功した場合

ユーザーが欲しい情報をJSONのフォーマットに沿って構造化していきます。

今回、個人的に意識したことは、階層構造です。

例として、ユーザー一覧を取得するAPIのJSONを考えます。
ユーザーを取得する際に、住所の情報も欲しいとします。

もし、住所の情報に、prefecturecityが欲しいとき、
以下のように階層ごとにグループ化することで、わかりやすく、データもまとまります。

users.json
{
  {
    "id": 1
    "first_name": "AAAA",
    "middle_name": null,
    "last_name": "BBBB",
    "sex": "man",
    // 住所として一つのグループにする。
    "address": {
      "prefecture": "東京都",
      "city": "新宿区"
    },
    "favorite_tags": ["Ruby", "Rails", "WebAPI"]
    "url": "https://example.com/users/1"
  },
  {
    "id": 2
    "first_name": "CCCC",
    "middle_name": "1111",
    "last_name": "DDDD",
    "sex": "woman",
    // 同列だとわかりづらい
    "prefecture": "東京都"
    "city": "渋谷区"
    "favorite_tags": []
    "url": "https://example.com/users/2"
  },
}

リクエストが失敗した場合

ユーザーに何のエラーなのか、どうして起きたのかを正しく提供しなくてはいけません。

ユーザー一覧を取得するAPIのパラメーターに、ソートをかけるとします。(例:favorites_tagsで"Ruby"を持っているユーザーの一覧)

ここで間違えて、"Ruby"ではなく、数字を入れてしまったときのレスポンスは、
以下のようにする必要があると思います。

https://example.com/api/users?favorites_tags=65847
{
  "message": "Invalid Request",
  "errors": [
    {
      "type": "bad_type",
      "message": "favorites_tags is String, is not Integer"
    }
  ]
}

エラーが複数存在する場合は、グループ化して、わかりやすくするのも一つの手ですね。

Railsでは、、、

Webアプリを作成するために最適なRailsでも当然、WebAPIの実装はできます。

RailsでのAPI実装

Railsには、APIモードなるものが存在していて、プロジェクトを新規作成する場合は、

$ rails new my_api --api

でAPIモードになります。

また、既存のアプリをAPI専用にしたい場合は、config/application.rbにて

config/application.rb
config.api_only = true

を設定するとできるみたいです。(超便利!!)

ただ、今回は既存のプロジェクトの一部でAPIが実装されているので、上二つのやり方はできませんでした。

RailsのAPI実装の特徴は、APIモードではない、いつも通りのRailsとあまり変わらずに実装することができることです。

MVCのView層が使用されないくらいで、あとは同じ感じでコードを書けるので、楽にかけます。
ただ、APIではいつもよりも制限がかかっているので、仕様については理解しないといけないところがあります。

今回の実装で詰まった、ActionController::APIを簡単に説明します。

ActionController::API

APIモードにする際に、app/application.rbを以下のように変更する必要があります。

app/application.rb
# ActionController::Base → ActionController::APIに変更する。
class ApplicationController < ActionController::API
end

これでAPI専用になるのですが、、、
このActionController::APIは、ActionController::BaseをAPI専用にしたものであることを認識しないといけませんでした。

上記でいつも通りに開発できると言いましたが、厳密には少し違います。

このActionController::APIのリファレンスのこの部分。

Keep in mind that templates are not going to be rendered

APIモードでも、renderメソッドでhtmlやxmlのフォーマットを指定すれば、読み出せます。しかし、templateの読み出しはできないため、jsonをviewとして扱うことが結構難しくなっています。

まとめ

APIを実装する上では、ユーザー目線に立つことが何よりも大事だと思いました。
実際に使う人の目線に立って、使いづらくないか、わかりやす構造になっているかを気にしてAPIを組み立てていく必要があると思います。

RailsでWebAPIを実装するときは、ドキュメントを読んで仕様を理解する必要があります。
便利な機能が揃っていて、ブラウザ向けアプリケーションの開発とあまり大差ないので楽にかける分、ブラウザ向けアプリケーションでできていたことができないことみたいな制約もあるので、注意が必要です。

参考

以下、参考にしたもの。

書籍

Web APIの設計

サイト

Rails API

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

Rails における JSON のシリアライズと向き合う

Rails での JSON API 実装まとめ

API設計

翻訳: WebAPI 設計のベストプラクティス

RESTful APIとは何なのか

リソース指向アーキテクチャ(ROA)とは何なのか

RESTful APIのURI設計(エンドポイント設計)

RESTful APIのリソース設計

PORTについて

「世界中に、アタリマエとシアワセを。」 を目的に、就活や金融領域などの各産業領域に特化したメディアを展開しています。

PORT-INC. 公式ホームページ

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

$rails s でサーバーが動かない

開発環境

Ruby 2.5.0
Ruby on Rails 6.0.3.4
rbenv 1.1.2
macOS Big Sur v11.0.1

エラー内容

rails sとコマンドを打つと

[WARNING] Could not load command "rails/commands/server/server_command". Error: uninitialized constant URI::Generic
/Users/user/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/bootsnap-1.5.1/lib/bootsnap/load_path_cache/core_ext/active_support.rb:00in 'block in load_missing_constant'
(同様の文が何行も出るため中略)
/User/user/Desktop/rails_testbin/spring:15:in `<top (required)>'
bin/rails:3:in `load'
bin/rails:3:in `<main>'
(See full trace by running task with --trace)

上記のようなメッセージが出て、http://localhost:3000 が起動できない

解決方法

結論から言うと、Rubyのバージョンを2.5.0から2.7.0に変更したら動きました。

参考記事:
https://info-wcn.com/ruby-changeversion-mac/
https://qiita.com/kamillle/items/5a7befd0ebad47378832
https://qiita.com/chihiro/items/efdf8b88865b7a93971f

1.Rubyのバージョン変更

rbenvを最新バージョンへとアップデート

$ brew upgrade rbenv

現在のRubyのバージョンを確認

$ ruby -v
ruby 2.5.0 

インストール済みのRubyのバージョンを確認

$ rbenv versions
  system
* 2.5.0 (set by /Users/user/.rbenv/version)

これで、現在自分が使っているRubyのバージョンは2.5.0であることが分かりました。
これを2.7.0へとアップデートしていきます。

インストールできるRubyのバージョンを確認

$ rbenv install -l
バージョンがずらっと表示される

Rubyのインストール

$ rbenv install 2.7.0 
# インストールするバージョンを2.7.0に指定
$ rbenv rehash
$ rbenv versions 
  system
  2.5.0
* 2.7.0 (set by /Users/user/.rbenv/version)

2.7.0が追加されたことを確認。

Rubyのバージョン切り替え(ローカル)

$ rbenv local 2.7.0 
 バージョンを指定
$ ls -a 
 .ruby-versionが作成されているか確認
$ less .ruby-version
 2.7.0と表示される

バージョンの確認から戻るときは、qを押して戻る

Rubyのバージョン切り替え(グローバル)

$ rbenv global 2.7.0 # バージョンを指定
  2.7.0と表示される

確認から戻るときは、ローカル同様qを押す

これで、使用するRubyのバージョン切り替えが完了しました。

2.Railsのバージョン変更

1の終了後、

$rails s

と打つと(真似しなくてよい)、

rbenv: rails: command not found

The `rails' command exists in these Ruby versions:
  2.5.0

このようになり、上手く動きません。

そこで、https://qiita.com/kamillle/items/5a7befd0ebad47378832 の「対処」を参考にして、新しくインストールしたruby環境にrailsをインストールする

$ gem update --system
$ gem install bundler
$ gem install rails
# 各コマンド間、色々なものがインストールされるため時間がかかるかも
$ rails --version
# Rails 6.0.3.4と表示された(2020-12-04時点)

これでRailsのバージョン変更が完了しました。

3.サーバーが動くか確認

試しに、デスクトップへrails_appという名前のアプリケーションを作成してみます。

$ cd Desktop
$ rails new rails_app

インストールが完了したら、

$ cd rails_app

で移動し、

$ bundle install

を実行。

最後に

$ rails s

を実行すると

=> Booting Puma
=> Rails 6.0.3.4 application starting in development 
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 4.3.7 (ruby 2.7.0-p0), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://127.0.0.1:3000
* Listening on tcp://[::1]:3000
Use Ctrl-C to stop

となり、無事http://localhost:3000 にアクセスすることが出来ました!!

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

投稿データとユーザー情報の結びつけ方法

まず用意したのはpost.rb(models/post)

class Post < ApplicationRecord

  def user
    return User.find_by(id: self.user_id)
  end

end

そしてposts_controller

def show
@post = Post.find_by(id: params[:id])
@user = @post.user
end

・ポイント

post(投稿)のところにuserインスタンスメソッドを用意してあげる。

そしてposts_controllerの処理したいアクションにさっき用意したuserのインスタンスメソッドを入れてあげる。

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

【Rails】注文・カート機能のER図について(初学者向け)

はじめに

本記事は、 駆け出しエンジニアの第一歩!AdventCalendar2020 4日目の記事です。
この記事では、注文・カート機能を実装する上で欠かせないER図の考え方について、自分が理解できなかったことをまとめてみました。
Railsを学習し始めて3ヶ月で、まだまだ理解が浅い部分があると思いますが、もし間違っているところ等ありましたら、ご指摘いただけると幸いです。

対象

・注文・カート機能にはどんなテーブルが必要なのか分からない人
・それぞれのテーブルの役割が分からない人

概要

ご紹介する注文機能の基本的な流れは、通販と同様ですが、商品をカートに入れる → 注文情報を入力する → 注文確定する、です。
それぞれの工程で自分が分からなかったことについて、解説します。
1.png

1. カートに商品を入れる

1-1.カートテーブルの成り立ち

まず、通販というのはユーザーが商品を購入する行為をおこなう場所です。そのため、どのユーザーがどんな商品を購入するのか、という風にユーザーと商品の間で関係性を持たせることが必要になります。しかし、ユーザーテーブルと商品テーブルは直接結ぶことができません。なぜなら、ユーザーはどの商品にも関係性がないからです。直接結び付けられる例として、ユーザーとブログの関係が挙げられます。_2020-11-26_18.28.30.png
でも、ユーザーと商品を関連づけなければ、商品を買うことはできません。そこで、ユーザーと商品の間を取り持つテーブルが必要になってきます(ここではカートテーブルとします)。カートテーブルは、ユーザーと商品を結びつける上で必然的に発生した中間テーブルです。_2020-11-26_18.29.38.png

1-2. カートとは?

カートを理解するには、まず1つのカートは複数種類の商品をいっぱい入れておけるもの、というイメージを捨てましょう。上記のER図のカートをよく見ると、カートID1つに対して、商品IDやユーザーIDはそれぞれ1つずつ対応しています。なので、1つのカートには、1種類の商品が入ります。
2.png

2. 注文情報を入力する

2-1. 注文テーブルに商品情報を直接入れる

注文情報の入力では、主に配送先住所、送料、支払い方法などを決定します。ここで注意したいのが、商品も注文情報の一部だからという考えから、注文テーブルの中に商品を入れてしまわないことです。
_2020-11-27_17.13.53.png

2-2. 注文テーブルと商品テーブルを直接関連づける

注文テーブルと商品テーブルを直接関連づければいいという考えから、以下のようにしてみるのもよくありません。よくない点は、いくつかありますが、一例をあげると、サイト管理者が商品を登録する際に、注文idを設定しいなければいけないことです。注文idは、注文が完了した後に、事後的に決定するものであり、サイト管理者が事前に設定するのは、少しおかしな話です。
_2020-11-27_18.04.29.png
そこで、注文テーブルと商品テーブルを結びつけるためには、間に中間テーブル(注文詳細テーブル)を作る必要があります。
_2020-11-27_20.17.02.png

3. 注文確定ボタンをクリックする

カートと注文の仕組みについて解説してきましたが、この2つのテーブル同士の関係はまだありません。しかし、結論から言うと関係づけるというよりは、カートの情報を注文詳細へ移動させるということです。
_2020-11-27_21.23.22.png
カートの情報は、移動後に削除されるようにすれば、注文確定後にカート内は空にできます。通販でも注文確定後のカート内は、空になっていますよね。カートと注文詳細の決定的な違いは、注文が未確定のものか確定済みのものかということです。

まとめ

自分は当初この仕組みが全く理解できていませんでした。初学者の方で理解に苦しんでいる人の力に少しでもなったら幸いです。

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

ユーザーと投稿の結びつけ「@を使ったlink_toの使い方」

<%=link_to(@user.name, "/users/#{@user.id}") %>

・概要説明

 目的:ユーザーの投稿詳細を表示したいのでユーザーと投稿を結びつけたい。
 ポイント:link_toのやり方は様々だが今回はpostsコントローラで定義した@userを使ってのことだったので少しややこしかった。
 

指定のcontroller(僕はcontrollers/posts_controller)

def show   
  @user = User.find_by(id: @post.user_id)
end

解説

User.find_by(id: @post.user_id)のUserはMysquelなどで作ったデータベースのusers。
find_byはその指定した(User)データから入手するものを定義。
@postはデータベースの投稿要素。
userのidとpostのidを結びつけたいので@user = User.find_by(id: @post.user_id)とする。

指定のview(僕はviews/posts/show.html.erb)に

<%= link_to("表示したいもの", "表示したい内容の場所") %>

例えば:
<%=link_to(@user.name, "/users/#{@user.id}") %>

とすればできます。

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

prawnで請求書っぽいPDFを作る

本記事はiCARE Advent Calender 2020 の5日目です。

はじめに

本記事ではprawnを用いた請求書っぽいPDFの作成方法を解説します。

RailsでPDF作成する場合だとwicked_pdfも使えますが、wicked_pdfがhtmlライクにPDF作成ができるのに対して、prawnはゴリゴリのDSLなので、学習コストがかかりますが、wicked_pdfよりも柔軟にPDFを作ることができるので、個人的にはprawnの方が好きだったりします。

セットアップ

今回の請求書PDFではテーブル表示を行いたいので、prawn-tableを使います。

# Gemfile

gem 'prawn'
gem 'prawn-table'

でbundle install。

your_app $ bundle install

主要なメソッドの紹介

請求書の作成の前に、今回用いる主要なメソッドを紹介したいと思います。

Prawn::Document#generate

https://prawnpdf.org/docs/0.11.1/Prawn/Document.html#method-c-generate

公式の説明文を引用します。

Creates and renders a PDF document.

When using the implicit block form, Prawn will evaluate the block within an instance of Prawn::Document, simplifying your syntax. However, please note that you will not be able to reference variables from the enclosing scope within this block.

PDFドキュメントを作成してレンダリングします。

暗黙的なブロックフォームを使用する場合、PrawnはPrawn :: Documentのインスタンス内のブロックを評価し、構文を簡素化します。 ただし、このブロック内の囲んでいるスコープから変数を参照することはできないことに注意してください。

PDFドキュメントの雛形を作成するメソッドですね。

オプションとして、ページサイズ(A4, B5等)や余白(上下左右)を指定することが出来ます。

# 例

Prawn::Document.generate(
  'sample.pdf',
  page_size: 'A4',
  top_margin: 35,
  bottom_margin: 35,
  left_margin: 35,
  right_margin: 35
) do |pdf|
  # 処理
end

Gem内の実装箇所はこちら

Prawn::Document#bounding_box

https://prawnpdf.org/docs/0.11.1/Prawn/Document.html#method-i-bounding_box

こちらも公式を引用

A bounding box serves two important purposes:
・ Provide bounds for flowing text, starting at a given point
・ Translate the origin (0,0) for graphics primitives
A point and :width must be provided. :height is optional. (See stretchyness below)

bounding_box には、次の2つの重要な目的があります。
・ 特定のポイントから開始して、流れるテキストの境界を指定します
・ グラフィックスプリミティブの原点(0,0)を変換します
ポイントと:widthを指定する必要があります。 :heightはオプションです。

PDF内にボックス要素を作成するのに用いるメソッドですね。

# 例

Prawn::Document.generate(
  'sample.pdf',
  page_size: 'A4',
  top_margin: 35,
  bottom_margin: 35,
  left_margin: 35,
  right_margin: 35
) do |pdf|
  pdf.bounding_box([50, 75], width: 200, height: 300) do
    # 処理
  end
end

後ほど紹介しますが、テキストを書き込んだり、tableを作成して表示したりできます。

Gem内の実装箇所はこちら

Prawn::Text#text

https://prawnpdf.org/docs/0.11.1/Prawn/Text.html#method-i-text

公式引用

If you want text to flow onto a new page or between columns, this is the method
to use. If, instead, if you want to place bounded text outside of the flow of a ?document (for captions, labels, charts, etc.), use Text::Box or its convenience method text_box.

Draws text on the page. Prawn attempts to wrap the text to fit within your current bounding box (or margin_box if no bounding box is being used). Text will flow onto the next page when it reaches the bottom of the bounding box. Text wrap in Prawn does not re-flow linebreaks, so if you want fully automated text wrapping, be sure to remove newlines before attempting to draw your string.

テキストを新しいページまたは列間で流したい場合は、これが使用する方法です。 代わりに、ドキュメントのフローの外側に境界付きテキストを配置する場合(キャプション、ラベル、グラフなど)、Text :: Boxまたはその便利なメソッドtext_boxを使用します。

ページにテキストを描画します。 Prawnは、現在のバウンディングボックス(またはバウンディングボックスが使用されていない場合はmargin_box)内に収まるようにテキストを折り返そうとします。 テキストは、バウンディングボックスの下部に到達すると、次のページに流れます。 Prawnでのテキストの折り返しは改行をリフローしないため、完全に自動化されたテキストの折り返しが必要な場合は、文字列を描画する前に必ず改行を削除してください。

小難しいことが書いてありますが、要は文字をPDF内に記述したい時に使うメソッドになります。

# 例

Prawn::Document.generate(
  'sample.pdf',
  page_size: 'A4',
  top_margin: 35,
  bottom_margin: 35,
  left_margin: 35,
  right_margin: 35
) do |pdf|
  pdf.bounding_box([50, 75], width: 200, height: 300) do
    pdf.text 'サンプルだよ'
    pdf.text 'サンプル 左寄りだよ', align: :left
    pdf.text 'サンプル 右寄りだよ', align: :right
    pdf.text 'サンプル 文字大きいよ', size: 30
  end
end

例のように、文字の開始位置を決められたり、文字サイズを変更したりすることができます。

Prawn::Document#move_down

引用

https://prawnpdf.org/docs/0.11.1/Prawn/Document.html#method-i-move_down

Moves down the document by n points relative to the current position inside the current bounding box.

現在のbounding_box内の現在の位置を基準にして、ドキュメントをnポイント下に移動します。

ドキュメントを下に移動したい時に用いるメソッドです。

# 例

Prawn::Document.generate(
  'sample.pdf',
  page_size: 'A4',
  top_margin: 35,
  bottom_margin: 35,
  left_margin: 35,
  right_margin: 35
) do |pdf|
  pdf.bounding_box([50, 75], width: 200, height: 300) do
    pdf.text 'サンプルだよ'
    pdf.move_down(5)
    pdf.text 'サンプル 左寄りだよ', align: :left
    pdf.move_down(10)
    pdf.text 'サンプル 右寄りだよ', align: :right
    pdf.move_down(20)
    pdf.text 'サンプル 文字大きいよ', size: 30
  end
end

テキストとテキストの間を空けたいときなんかに使います。

Gem内の実装箇所はこちら

Prawn::Table#table

Quote from the official Doc

If a block is passed to methods that initialize a table (Prawn::Table.new, Prawn::Document#table, Prawn::Document#make_table), it will be called after cell setup but before layout. This is a very flexible way to specify styling and layout constraints. This code sets up a table where the second through the fourth rows (1-3, indexed from 0) are each one inch (72 pt) wide:

pdf.table(data) do |table|
  table.rows(1..3).width = 72
end

表形式でデータを表示したい場合に使えるメソッドになります。

# 例

Prawn::Document.generate(
  'sample.pdf',
  page_size: 'A4',
  top_margin: 35,
  bottom_margin: 35,
  left_margin: 35,
  right_margin: 35
) do |pdf|
  pdf.table(
    [['小計', "11000円"], ['消費税', "1000円"], ['合計金額', "11000円"]], 
    column_widths: [50, 100],
    position: :right
  ) do |table|
    table.cells.size = 10
  end
end

Gem内の実装箇所はこちら

請求書を作成する

お待たせしました。今まで紹介したメソッドを用いて、
簡単な請求書めいたPDFを作成したいと思います。

invoice_pdf_exporter.rbの作成

app/services下にinvoice_pdf_exporter.rbを作成し、
以下のコードを記述します。

require 'prawn'

class InvoicePdfExporter
  FONT_PATH = Rails.root + 'public/fonts/任意のフォントファイル.ttf'

  def initialize
    # Prawnドキュメントを生成
    # ページサイズやマージンを指定
    Prawn::Document.generate(
      Rails.root + 'invoice.pdf',
      page_size: 'A4',
      top_margin: 35,
      bottom_margin: 35,
      left_margin: 35,
      right_margin: 35
    ) do |pdf|

      # フォントを指定しないと Prawn::Errors::IncompatibleStringEncoding 例外が発生する
      pdf.font FONT_PATH

      # 本文の生成
      self.create_contents pdf
    end
  end
end

fontメソッドは初出なので公式Docから引用します。

Prawn::Document#font

https://prawnpdf.org/docs/0.11.1/Prawn/Document.html#method-i-font

Without arguments, this returns the currently selected font. Otherwise, it sets the current font. When a block is used, the font is applied transactionally and is rolled back when the block exits.

引数がない場合、これは現在選択されているフォントを返します。 それ以外の場合は、現在のフォントを設定します。 ブロックが使用されると、フォントはトランザクションで適用され、ブロックが終了するとロールバックされます。

Gem内の実装箇所はこちら

ドキュメントに記載がありませんが、fontの指定をしないで実行すると、
Prawn::Errors::IncompatibleStringEncoding
の例外が発生します。

サンプルコードではpublicディレクトリに配置していますが、app/assets/fontsでもいいと思います。

フォントファイルは、
https://fontfree.me/
に無料フォントがありますので、お好きなものをお使いください。

今回はほのか明朝を使います。

create_contentsメソッドを実装する

では具体的な処理を記述します。

コード全晒しです。

  def create_contents(doc)
    # bunding_boxメソッドでボックスを生成
    # 引数にはボックス生成位置、横、縦のサイズを指定
    doc.bounding_box([50, 750], width: 300, height: 150) do

      # textメソッドでテキストを挿入。引数には文字サイズとalignを指定できる。:left, :right, :center
      doc.text "〒123-4567", size: 10, align: :left

      # move_downメソッドで次のテキストの書き出し位置を下げている
      doc.move_down 10
      doc.text "東京都新宿区新宿1-1-1", size: 10, align: :left

      doc.move_down 10
      doc.text "ご担当者 様", size: 12, align: :left
    end

    doc.bounding_box([300, 750], width: 300, height: 150) do
      doc.text 'HogeHoge株式会社', size: 12, align: :left
      doc.move_down 5
      doc.text '〒103-0021', size: 10, align: :left
      doc.move_down 5
      doc.text '大阪府大阪市中央区中央1-1-1', size: 10, align: :left
      doc.move_down 5
      doc.text '大阪中央ビル4F 経理部経理課', size: 10, align: :left
      doc.move_down 5
      doc.text 'TEL: 03-1234-5678', size: 10, align: :left
    end

    doc.text '請求書', size: 20, align: :center

    doc.bounding_box([0, 550], width: 300, height: 60) do
      doc.text '下記の通りご請求申し上げます。', size: 10, align: :left
      doc.move_down 10
      doc.text "合計金額 11,000円", size: 16, align: :left
    end

    doc.bounding_box([320, 550], width: 300, height: 60) do
      doc.text "日付: 2020年10月01日", size: 10, align: :left
    end

    rows = [['詳細', '数量', '単価', '金額'], ['雑費', '1', '10000', '10000']]

    # tableメソッドでテーブルを生成する
    # rowsは多重配列
    # 多重配列でない場合 Prawn::Errors::InvalidTableData 例外が発生する
    doc.table(rows, column_widths: [370, 30, 60, 60], position: :center) do |table|
      # セルのサイズの指定
      table.cells.size = 10

      # 1行目のalignを真ん中寄せにしている
      table.row(0).align = :center
    end

    doc.bounding_box([0, 300], width: 300, height: 100) do
      doc.text "振込期限   202X年10月31日", size: 10, align: :left
      doc.move_down 10
      doc.text '振込先    日本銀行 本店 0000', size: 10, align: :left
      doc.move_down 10
      doc.text '       普通: 1234567', size: 10, align: :left
    end

    doc.bounding_box([373, 300], width: 150, height: 100) do
      doc.table [['小計', "11000円"], ['消費税', "1000円"], ['合計金額', "11000円"]], column_widths: [50, 100], position: :right do |table|
        table.cells.size = 10
      end
    end
  end

サンプルコードなので金額や配列の中身をベタ書きにしていますが、
initializeメソッドの引数にデータを渡してやれば、動的なPDFを作成することができます。

あとはコンソール上で実行してやりましょう。

your_app $ rails c

$ InvoicePdfExporter.new

実際に出来上がったPDFはこちら
invoice_sample.png

紹介していないけど便利なメソッド

Prawn::Document#start_new_page

Creates and advances to a new page in the document.

Page size, margins, and layout can also be set when generating a
new page. These values will become the new defaults for page creation

ドキュメント内の新しいページを作成して進みます。

ページサイズ、余白、およびレイアウトは、生成時に設定することもできます。
新しいページ。 これらの値は、ページ作成の新しいデフォルトになります

次のページを作成するメソッドです。

Prawn::Document.generate(
  'sample.pdf',
  page_size: 'A4',
  top_margin: 35,
  bottom_margin: 35,
  left_margin: 35,
  right_margin: 35
) do |pdf|
  pdf.table(
    [['小計', "11000円"], ['消費税', "1000円"], ['合計金額', "11000円"]], 
    column_widths: [50, 100],
    position: :right
  ) do |table|
    table.cells.size = 10
  end

  pdf.start_new_page

  pdf.text '次のページですよ'
end

Prawn::Image#image

Add the image at filename to the current page. Currently only
JPG and PNG files are supported. (Note that processing PNG
images with alpha channels can be processor and memory intensive.)

ファイル名の画像を現在のページに追加します。
JPGおよびPNGファイルがサポートされています。 (PNGの処理に注意してください
アルファチャネルを備えた画像は、プロセッサとメモリを大量に消費する可能性があります。)

PDF内に写真を貼るメソッドです。横縦の幅や寄せる位置などを指定できます。

 Prawn::Document.generate("image2.pdf", :page_layout => :landscape) do     
    pigs = "#{Prawn::BASEDIR}/data/images/pigs.jpg" 
    image pigs, :at => [50,450], :width => 450                                      

    dice = "#{Prawn::BASEDIR}/data/images/dice.png"
    image dice, :at => [50, 450], :scale => 0.75 
  end   

まとめ

今回紹介したメソッドの他にも様々な機能がありますので、
詳しく知りたい方はドキュメントをご覧ください!

https://prawnpdf.org/manual.pdf

iCAREテックブログもよろしくね!!

https://www.icare.jpn.com/dev_cat/

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

これから投稿していきます。

Web開発エンジニアを目指すためにいまから頑張ります。

まずは、Ruby、Railsを用いたポートフォリオの作成に挑みます。

事前知識がないので少しアウトプット
ブラウザにプログラムを反映するために

ルーティング→コントローラ→ビューの順を踏む

▼ルーティング
コントローラー、ビューがどういった定義で順で実行されていくか
定める役割

▼コントローラー
ルーティングを参照しどのビューを実行するか決める役割

▼ビュー
具体的なHTMLファイルなど実行されるプログラムを格納する場所

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

Railsで新規アプリケーションを作成した際、mysql2のインストール中にエラーが発生した時の解決策

環境

OS:AmazonLinux2
Ryby:ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
Rails:Rails 5.0.0

問題

データベースをmysqlに指定してRailsアプリケーションを新規作成した際、mysql2のGemのインストール中にエラーが発生した。

結論

DBクライアントというパッケージをインストールしていなかったことが原因だった。

やったこと

以下コマンドで、データベースをmysqlに指定して、新規アプリケーションを作成
$ rails new (アプリケーション名) -d mysql

$ rails new shift-management -d mysql
      create  
      create  README.md
      create  Rakefile
      create  config.ru
      create  .gitignore
      create  Gemfile
      create  app
      create  app/assets/config/manifest.js
      create  app/assets/javascripts/application.js
      create  app/assets/javascripts/cable.js
      create  app/assets/stylesheets/application.css
      create  app/channels/application_cable/channel.rb
      create  app/channels/application_cable/connection.rb
      create  app/controllers/application_controller.rb
      create  app/helpers/application_helper.rb
      create  app/jobs/application_job.rb
      create  app/mailers/application_mailer.rb
      create  app/models/application_record.rb
      create  app/views/layouts/application.html.erb
      create  app/views/layouts/mailer.html.erb
      create  app/views/layouts/mailer.text.erb
      create  app/assets/images/.keep
      create  app/assets/javascripts/channels
      create  app/assets/javascripts/channels/.keep
      create  app/controllers/concerns/.keep
      create  app/models/concerns/.keep
      create  bin
      create  bin/bundle
      create  bin/rails
      create  bin/rake
      create  bin/setup
      create  bin/update
      create  config
      create  config/routes.rb
      create  config/application.rb
      create  config/environment.rb
      create  config/secrets.yml
      create  config/cable.yml
      create  config/puma.rb
      create  config/spring.rb
      create  config/environments
      create  config/environments/development.rb
      create  config/environments/production.rb
      create  config/environments/test.rb
      create  config/initializers
      create  config/initializers/application_controller_renderer.rb
      create  config/initializers/assets.rb
      create  config/initializers/backtrace_silencers.rb
      create  config/initializers/cookies_serializer.rb
      create  config/initializers/cors.rb
      create  config/initializers/filter_parameter_logging.rb
      create  config/initializers/inflections.rb
      create  config/initializers/mime_types.rb
      create  config/initializers/new_framework_defaults.rb
      create  config/initializers/session_store.rb
      create  config/initializers/wrap_parameters.rb
      create  config/locales
      create  config/locales/en.yml
      create  config/boot.rb
      create  config/database.yml
      create  db
      create  db/seeds.rb
      create  lib
      create  lib/tasks
      create  lib/tasks/.keep
      create  lib/assets
      create  lib/assets/.keep
      create  log
      create  log/.keep
      create  public
      create  public/404.html
      create  public/422.html
      create  public/500.html
      create  public/apple-touch-icon-precomposed.png
      create  public/apple-touch-icon.png
      create  public/favicon.ico
      create  public/robots.txt
      create  test/fixtures
      create  test/fixtures/.keep
      create  test/fixtures/files
      create  test/fixtures/files/.keep
      create  test/controllers
      create  test/controllers/.keep
      create  test/mailers
      create  test/mailers/.keep
      create  test/models
      create  test/models/.keep
      create  test/helpers
      create  test/helpers/.keep
      create  test/integration
      create  test/integration/.keep
      create  test/test_helper.rb
      create  tmp
      create  tmp/.keep
      create  tmp/cache
      create  tmp/cache/assets
      create  vendor/assets/javascripts
      create  vendor/assets/javascripts/.keep
      create  vendor/assets/stylesheets
      create  vendor/assets/stylesheets/.keep
      remove  config/initializers/cors.rb
         run  bundle install
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies....
Fetching rake 13.0.1
Installing rake 13.0.1
Using concurrent-ruby 1.1.7
Fetching i18n 1.8.5
Installing i18n 1.8.5
Fetching minitest 5.14.2
Installing minitest 5.14.2
Using thread_safe 0.3.6
Fetching tzinfo 1.2.8
Installing tzinfo 1.2.8
Fetching activesupport 5.0.7.2
Installing activesupport 5.0.7.2
Using builder 3.2.4
Using erubis 2.7.0
Using mini_portile2 2.4.0
Using nokogiri 1.10.10
Using rails-dom-testing 2.0.3
Using crass 1.0.6
Fetching loofah 2.8.0
Installing loofah 2.8.0
Using rails-html-sanitizer 1.3.0
Fetching actionview 5.0.7.2
Installing actionview 5.0.7.2
Using rack 2.2.3
Using rack-test 0.6.3
Fetching actionpack 5.0.7.2
Installing actionpack 5.0.7.2
Fetching nio4r 2.5.4
Installing nio4r 2.5.4 with native extensions
Using websocket-extensions 0.1.5
Using websocket-driver 0.6.5
Fetching actioncable 5.0.7.2
Installing actioncable 5.0.7.2
Using globalid 0.4.2
Fetching activejob 5.0.7.2
Installing activejob 5.0.7.2
Using mini_mime 1.0.2
Using mail 2.7.1
Fetching actionmailer 5.0.7.2
Installing actionmailer 5.0.7.2
Fetching activemodel 5.0.7.2
Installing activemodel 5.0.7.2
Using arel 7.1.4
Fetching activerecord 5.0.7.2
Installing activerecord 5.0.7.2
Fetching bindex 0.8.1
Installing bindex 0.8.1 with native extensions
Using bundler 1.17.3
Fetching byebug 11.1.3
Installing byebug 11.1.3 with native extensions
Fetching coffee-script-source 1.12.2
Installing coffee-script-source 1.12.2
Fetching execjs 2.7.0
Installing execjs 2.7.0
Fetching coffee-script 2.4.1
Installing coffee-script 2.4.1
Using method_source 1.0.0
Using thor 1.0.1
Fetching railties 5.0.7.2
Installing railties 5.0.7.2
Fetching coffee-rails 4.2.2
Installing coffee-rails 4.2.2
Fetching ffi 1.13.1
Installing ffi 1.13.1 with native extensions
Fetching jbuilder 2.10.1
Installing jbuilder 2.10.1
Fetching jquery-rails 4.4.0
Installing jquery-rails 4.4.0
Fetching rb-fsevent 0.10.4
Installing rb-fsevent 0.10.4
Fetching rb-inotify 0.10.1
Installing rb-inotify 0.10.1
Fetching listen 3.0.8
Installing listen 3.0.8
Fetching mysql2 0.4.10
Installing mysql2 0.4.10 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/mysql2-0.4.10/ext/mysql2
/home/ec2-user/.rvm/rubies/ruby-2.6.3/bin/ruby -I /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0 -r ./siteconf20201203-7896-3nmivb.rb extconf.rb
checking for rb_absint_size()... yes
checking for rb_absint_singlebit_p()... yes
checking for ruby/thread.h... yes
checking for rb_thread_call_without_gvl() in ruby/thread.h... yes
checking for rb_thread_blocking_region()... no
checking for rb_wait_for_single_fd()... yes
checking for rb_hash_dup()... yes
checking for rb_intern3()... yes
checking for rb_big_cmp()... yes
checking for mysql_query() in -lmysqlclient... yes
checking for mysql.h... no
checking for mysql/mysql.h... no
-----
mysql.h is missing. You may need to 'apt-get install libmysqlclient-dev' or 'yum install mysql-devel', and try again.
-----
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
        --with-opt-dir
        --without-opt-dir
        --with-opt-include
        --without-opt-include=${opt-dir}/include
        --with-opt-lib
        --without-opt-lib=${opt-dir}/lib
        --with-make-prog
        --without-make-prog
        --srcdir=.
        --curdir
        --ruby=/home/ec2-user/.rvm/rubies/ruby-2.6.3/bin/$(RUBY_BASE_NAME)
        --with-mysql-dir
        --without-mysql-dir
        --with-mysql-include
        --without-mysql-include=${mysql-dir}/include
        --with-mysql-lib
        --without-mysql-lib=${mysql-dir}/lib
        --with-mysql-config
        --without-mysql-config
        --with-mysql-dir
        --without-mysql-dir
        --with-mysql-include
        --without-mysql-include=${mysql-dir}/include
        --with-mysql-lib
        --without-mysql-lib=${mysql-dir}/lib
        --with-mysqlclientlib
        --without-mysqlclientlib

To see why this extension failed to compile, please check the mkmf.log which can be found here:

  /home/ec2-user/.rvm/gems/ruby-2.6.3/extensions/x86_64-linux/2.6.0/mysql2-0.4.10/mkmf.log

extconf failed, exit code 1

Gem files will remain installed in /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/mysql2-0.4.10 for inspection.
Results logged to /home/ec2-user/.rvm/gems/ruby-2.6.3/extensions/x86_64-linux/2.6.0/mysql2-0.4.10/gem_make.out

An error occurred while installing mysql2 (0.4.10), and Bundler cannot continue.
Make sure that `gem install mysql2 -v '0.4.10' --source 'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  mysql2
         run  bundle exec spring binstub --all
bundler: command not found: spring
Install missing gem executables with `bundle install`

mysql2がインストールできないと言われる

原因

どうやらDBクライアントをインストールしていないことが原因だった
参考:AmazonLinux2にMariaDBをインストールする

解決策

DBクライアントをインストール(パッケージインストール)

$ sudo yum install mariadb-devel
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
220 packages excluded due to repository priority protections
Resolving Dependencies
--> Running transaction check
---> Package mariadb-devel.x86_64 3:10.2.10-2.amzn2.0.3 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================================================================================================================================
 Package                                 Arch                             Version                                            Repository                                                    Size
================================================================================================================================================================================================
Installing:
 mariadb-devel                           x86_64                           3:10.2.10-2.amzn2.0.3                              amzn2extra-lamp-mariadb10.2-php7.2                           1.0 M

Transaction Summary
================================================================================================================================================================================================
Install  1 Package

Total download size: 1.0 M
Installed size: 4.5 M
Is this ok [y/d/N]: y
Downloading packages:
mariadb-devel-10.2.10-2.amzn2.0.3.x86_64.rpm                                                                                                                             | 1.0 MB  00:00:00     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : 3:mariadb-devel-10.2.10-2.amzn2.0.3.x86_64                                                                                                                                   1/1 
  Verifying  : 3:mariadb-devel-10.2.10-2.amzn2.0.3.x86_64                                                                                                                                   1/1 

Installed:
  mariadb-devel.x86_64 3:10.2.10-2.amzn2.0.3                                                                                                                                                    

Complete!

インストール完了(mysql-develでも同じみたい)

もう一度bundle installすると

$ bundle install 
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies....
Using rake 13.0.1
Using concurrent-ruby 1.1.7
Using i18n 1.8.5
Using minitest 5.14.2
Using thread_safe 0.3.6
Using tzinfo 1.2.8
Using activesupport 5.0.7.2
Using builder 3.2.4
Using erubis 2.7.0
Using mini_portile2 2.4.0
Using nokogiri 1.10.10
Using rails-dom-testing 2.0.3
Using crass 1.0.6
Using loofah 2.8.0
Using rails-html-sanitizer 1.3.0
Using actionview 5.0.7.2
Using rack 2.2.3
Using rack-test 0.6.3
Using actionpack 5.0.7.2
Using nio4r 2.5.4
Using websocket-extensions 0.1.5
Using websocket-driver 0.6.5
Using actioncable 5.0.7.2
Using globalid 0.4.2
Using activejob 5.0.7.2
Using mini_mime 1.0.2
Using mail 2.7.1
Using actionmailer 5.0.7.2
Using activemodel 5.0.7.2
Using arel 7.1.4
Using activerecord 5.0.7.2
Using bindex 0.8.1
Using bundler 1.17.3
Using byebug 11.1.3
Using coffee-script-source 1.12.2
Using execjs 2.7.0
Using coffee-script 2.4.1
Using method_source 1.0.0
Using thor 1.0.1
Using railties 5.0.7.2
Using coffee-rails 4.2.2
Using ffi 1.13.1
Using jbuilder 2.10.1
Using jquery-rails 4.4.0
Using rb-fsevent 0.10.4
Using rb-inotify 0.10.1
Using listen 3.0.8
Fetching mysql2 0.4.10
Installing mysql2 0.4.10 with native extensions
Fetching puma 3.12.6
Installing puma 3.12.6 with native extensions
Fetching sprockets 3.7.2
Installing sprockets 3.7.2
Using sprockets-rails 3.2.2
Fetching rails 5.0.7.2
Installing rails 5.0.7.2
Fetching sass-listen 4.0.0
Installing sass-listen 4.0.0
Fetching sass 3.7.4
Installing sass 3.7.4
Fetching tilt 2.0.10
Installing tilt 2.0.10
Fetching sass-rails 5.0.7
Installing sass-rails 5.0.7
Fetching spring 2.1.1
Installing spring 2.1.1
Fetching spring-watcher-listen 2.0.1
Installing spring-watcher-listen 2.0.1
Fetching turbolinks-source 5.2.0
Installing turbolinks-source 5.2.0
Fetching turbolinks 5.2.1
Installing turbolinks 5.2.1
Fetching uglifier 4.2.0
Installing uglifier 4.2.0
Fetching web-console 3.7.0
Installing web-console 3.7.0
Bundle complete! 15 Gemfile dependencies, 62 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
Post-install message from sass:

Ruby Sass has reached end-of-life and should no longer be used.

* If you use Sass as a command-line tool, we recommend using Dart Sass, the new
  primary implementation: https://sass-lang.com/install

* If you use Sass as a plug-in for a Ruby web framework, we recommend using the
  sassc gem: https://github.com/sass/sassc-ruby#readme

* For more details, please refer to the Sass blog:
  https://sass-lang.com/blog/posts/7828841

無事mysql2がインストールされました。
以下に、Railsアプリケーションの作成時の手順を書いているので参考にしてみてください

【AmazonLinux2】Railsアプリケーション新規作成 rake db:createまで

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

Vue.js チュートリアル for Rails エンジニア(Vue3 version)

この記事は クラウドワークス Advent Calendar 2020 の9日目の記事です。

はじめに

この記事は、普段 Rails を使用して開発を行なっているエンジニアが、 Vue.js (Vue3) を触り始めようとする時に見て役に立ったら嬉しいものです。

今回は Vue3 での開発を想定したものになります。 Vue2 での開発に関して知りたい方は下記の記事をご覧ください。
Vue.js チュートリアル for Rails エンジニア(Vue2 version)

また、 Vue.js 自体を触るのは今回が初めてという方は、先に下記のようなチュートリアル記事を参照されることをお勧めします。
Vue.js を vue-cli を使ってシンプルにはじめてみる

対象読者

  • Rails に関しての知識がある程度ある方(Rails チュートリアルをやっていれば OK)
  • Vue.js に関しての知識がある程度ある方(なんらかのチュートリアルなどをやっていれば OK。Vue3 の知識は必須でないです)
  • Rails + Vue3 のアプリケーションをどうやって開発すればいいかを知りたい方

作るもの

今回は Webpacker を使用して Rails + Vue3 の簡単なアプリケーションを作っていきます。
そもそも Webpacker を使用しない手法だったり、Docker を使ったり、 TypeScript を使ったりといったことは本稿では扱わないのでご了承ください。
(Vue.js を使った開発のとっかかりとしてのチュートリアルという位置付けにしたいため)

作成予定の画面は ↓ にあるような、簡単なテーブル構造を持つ画面になります。

サンプルコードこちらに置いています: https://github.com/t0yohei/rails-vue3-app

では、早速環境構築からチュートリアルに入っていきましょう。

環境構築

環境構築には、 homebrew , rbenv を使用します。インストールがまだな方は、各自インストールをお願いします。

使用する環境

  • macOS: 10.15.7 (Catalina)
  • Ruby: 2.7.2
  • Rails: 6.0.3
  • Webpacker: 4.3.0
  • yarn: 1.22.10
  • Vue: 3.0.3

※ macOS を除いて、記事作成時点で最新のバージョンになります。

アプリケーションを作成するディレクトリを作成

はじめに、これからアプリケーションを実装していくディレクトリを rails-vue3-app という名前で作成します。今回はこの rails-vue3-app がアプリケーションの名前になります。

$ mkdir rails-vue3-app
$ cd rails-vue3-app

rbenv を使用して Ruby 2.7.2 をインストール

Ruby 2.7.2 をインストールします。

$ brew update && brew install ruby-build // または brew update && brew upgrade ruby-build
$ rbenv install 2.7.2
$ rbenv local 2.7.2
$ rbenv rehash
$ ruby -v
ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin19]

yarn 1.22.10 のインストール

yarn をインストールします。
今回は 1.22.10 を前提に進めていきますが、 1.22.10 以上であれば基本問題ないはずです。

$ brew install yarn // または brew upgrade yarn
$ yarn -v
1.22.10

Vue.js devtools (Vue3 用)のインストール

Installation | Vue Devtools から devtools をインストールします。
以前に Vue.js devtools をインストールしている場合(Vue2 の Vue.js devtools を使っていた場合)、Vue3 の Vue.js devtools とは併用できなくなっています(2020 年 12 月時点)。
そのため、拡張機能の設定から既存の Vue.js devtools (Vue2 用) を OFF にする必要があります。
Chrome の場合は chrome://extensions/ を開き、 Vue2 の Vue.js devtools を OFF にしてください。

以上で環境構築は終わりです。次からはプロジェクトの作成を進めていきます。

プロジェクトの作成

カレントディレクトリ(rails-vue3-app)を起点に Rails プロジェクトを作成します。

$ bundle init
$ echo 'gem "rails", "~> 6.0.3"' >> Gemfile
$ bundle install --path vendor/bundle
$ bundle exec rails new . --skip-turbolinks --skip-coffee
Overwrite ()/rails-vue3-app/Gemfile? (enter "h" for help) [Ynaqdhm] y

--skip-turbolinks --skip-coffee のオプションをつけて、今回は不要となるものを削ぎ落としています。 1

Vue2 では、 rails new . --webpack=vue として実行していたのですが、Vue3 ではオプションが提供されていないため --webpack=vue のオプションを付けずに実行します。 2

Vue3 のセットアップ

--webpack=vue3 といった形での Vue3 のセットアップができないため、独自で Vue3 のセットアップを行っていきます。

Vue3 用の各種パッケージをインストール

$ yarn add --dev vue@next vue-loader@next @vue/compiler-sfc

インストール完了後、 package.json は以下のようになっているはずです(細かいバージョンはインストールのタイミングで違ってきます)

package.json
{
  "name": "rails_vue3_app",
  "private": true,
  "dependencies": {
    "@rails/actioncable": "^6.0.0",
    "@rails/activestorage": "^6.0.0",
    "@rails/ujs": "^6.0.0",
    "@rails/webpacker": "4.3.0",
    "@vue/compiler-sfc": "^3.0.3",
    "vue": "^3.0.3",
    "vue-loader": "^16.0.0"
  },
  "version": "0.1.0",
  "devDependencies": {
    "webpack-dev-server": "^3.11.0"
  }
}

Webpack の設定

次に .vue の拡張子がついた Vue ファイルを、 Webpack でコンパイルするための設定を追加します。
config/webpack/environment.js を開いてみると、現状は次のような状態になっているはずです。

config/webpack/environment.js
const { environment } = require('@rails/webpacker')

module.exports = environment

以下のように書き変えて、 Vue ファイルに対して vue-loader を使用するように指定します。

config/webpack/environment.js
  const { environment } = require('@rails/webpacker')
+ const { VueLoaderPlugin } = require('vue-loader')
+
+ environment.plugins.prepend(
+     'VueLoaderPlugin',
+     new VueLoaderPlugin()
+ )
+
+ environment.loaders.prepend('vue', {
+     test: /\.vue$/,
+     use: [{
+         loader: 'vue-loader'
+     }]
+ })

  module.exports = environment


補足: ファイルを分けて定義することも可能

config/webpack/loaders/vue.js を作成して、 loader の定義ファイルを切り出すことも可能です。

config/webpack/loaders/vue.js
module.exports = {
  test: /\.vue(\.erb)?$/,
  use: [{
    loader: 'vue-loader'
  }]
}
config/webpack/environment.js
  const { environment } = require('@rails/webpacker')
+ const { VueLoaderPlugin } = require('vue-loader')
+ const vue = require('./loaders/vue')

+ environment.plugins.prepend('VueLoaderPlugin', new VueLoaderPlugin())
+ environment.loaders.prepend('vue', vue)
  module.exports = environment


config/webpacker.ymlextensions.vue を追加します。

config/webpacker.yml
default: &default
  # ...


  extensions:
    # ...
+    - .vue

以上で Webpack の設定は終わりです。

一番シンプルな Rails + Vue を使った画面の実装

環境構築や設定周りの変更が完了したので、ここからは実際にアプリケーションの実装に入っていきます。
最初に、 Rails + Vue を使った一番シンプルな画面を実装をしていきます。

コントローラーの作成

まずは rails g controller コマンドでコントローラーやルーティングを作成します。

$ bundle exec rails g controller HelloVue index --no-helper --no-assets

※ 今回は、ヘルパーやアセットファイルなど不要なものを生成しないオプションを指定しています。 3

一旦サーバーを起動して、画面が表示するか見てみましょう。

$ bundle exec rails s

http://localhost:3000/hello_vue/index にアクセスして、下記のページが表示されると ok です。

javascript_pack_tag を使用して、 js ファイルを読み込めるようにする

javascript_pack_tag を使用することで app/javascript/packs 配下にあるファイルの読み込みが可能になります(この辺は Webpacker の設定によるもの)。
今回は、読み込む対象としてこの後作成する hello_vue.js を指定します。

app/views/hello_vue/index.html.erb
  <h1>HelloVue#index</h1>
  <p>Find me in app/views/hello_vue/index.html.erb</p>
+ <%= javascript_pack_tag 'hello_vue.js' %>

index.html.erb から読み込む js ファイルを作成

先ほど作成した index.html.erb から読み込む js ファイルを作成します。

app/javascript/packs/hello_vue.js
document.addEventListener("DOMContentLoaded", () => {
  console.log('this is hello_vue.js')
});

再度 http://localhost:3000/hello_vue/index にアクセスして開発者ツールを開くと、 Console に this is hello_vue.js と出力されます。

Vue ファイルを作成

次に画面表示に使用するシンプルな Vue ファイル(Vue コンポーネント)を作成します。

app/javascript/app.vue
<template>
  <p>
    {{ message }}
  </p>
</template>

<script>
export default {
  data() {
    return {
      message: "Hello Vue!"
    }
  }
}
</script>

<style scoped>
p {
  font-size: 2em;
  text-align: left;
}
</style>


補足: Composition API を使って書く場合

Composition APIは、 Vue3 で新たに提供された API です。 Composition API を使うことで、大規模な Vue アプリケーションにおけるコンポーネントの実装をシンプルに保つことができます。

今回はそもそもコンポーネントが大きくないので、Composition API を使うメリットはそんなにありませんが、 Composition API を使用した実装をする場合は下記のようになります。

app/javascript/app.vue
<template>
  <p>
    {{ message }}
  </p>
</template>

<script>
import { ref } from 'vue'

export default {
  setup() {
    const message = ref('Hello World')
    return { message }
  }
}
</script>

<style scoped>
p {
  font-size: 2em;
  text-align: left;
}
</style>

余談ですが、 data 関数など使用した従来の Vue コンポーネントの実装方法は Options API を使用した実装と呼ばれています。


app コンポーネントを index.html.erb にマウントする

最後に、先ほど作成した app.vue のコンポーネントを index.html.erb にマウントします。

まずはマウント対象の要素を index.html.erb に作成します。

app/views/hello_vue/index.html.erb
  <h1>HelloVue#index</h1>
  <p>Find me in app/views/hello_vue/index.html.erb</p>
  <%= javascript_pack_tag 'hello_vue.js' %>
+ <div id='vue-app'></div>

この要素に対して、 App コンポーネントをマウントする実装を hello_vue.js に追加します。

app/javascript/packs/hello_vue.js
+ import { createApp } from "vue";
+ import App from "../app.vue";

  document.addEventListener("DOMContentLoaded", () => {
-    console.log('this is hello_vue.js')
+    const app = createApp(App);
+    app.mount("#vue-app");
  });

http://localhost:3000/hello_vue/index にアクセスしてみると、下記のような画面が表示されるはずです。

この時点で、一番の基本となる Rails + Vue のアプリケーション実装が完了しました :tada:

bin/webpack を使用した js, vue ファイルの事前ビルド

今までのやり方では、 http://localhost:3000/hello_vue/index にアクセスがあった時点で、毎回 js, vue ファイルのビルドを行うため、画面が表示されるのに少し時間がかかってしまいます。
下記のコマンドを実行することで、 app/JavaScript 配下の js, vue ファイルを事前にビルドしておくことができます。

$ bin/webpack

rails server を立ち上げ直した後に http://localhost:3000/hello_vue/index に再度アクセスすると、先ほどよりも早く画面が表示されるようになるはずです。
※ 必ず rails server を立ち上げ直してください

bin/webpack コマンドの詳細について知りたい方はこちら: https://qiita.com/chimame/items/8d3d6f4afea675cffa7d

webpack の自動ビルド

先ほどは、 bin/webpack のビルドを行いましたが、これでは js, vue ファイルを変更するたびに再度コマンドを叩いてビルドをする必要があります。
それでは面倒なので、開発中は bin/webpack の代わりに下記コマンドを実行して、ファイルを保存するたびに自動ビルドが走るようにしておくといいでしょう(rails server を実行しているタブとは別のタブで実行してください)。

$ bin/webpack-dev-server

Vue.js devtools

せっかくなので Vue.js devtools の使い方をここで確認しておきます。
使い方は簡単で、 chrome の developer ツールを開いて、 Vue のタブを選択するだけです。

Vue のタブを選択し、コンポーネントを選択してみると、内部の data などを確認できます。

※ Vue.js devtools がうまく表示されない場合は、 chrome://extensions/ で複数の Vue.js devtools が ON になっていないかを確認してください。あるいは、 chrome の developer ツールを開き直してみてください。

実践的な実装

先ほど実装では、本番アプリケーションを実装する上で必須になってくるバックエンド(controller など)からのデータの受け渡しを行なっていなかったので、ここからはその点を深掘っていきます。

Webpacker を使用した、 Rails + Vue のアプリケーションを作成する際、データの渡し方には色々あるのですが、今回は個人的にデータフローがわかりやすかった以下の 2 通りの方法でサンプルアプリケーションを作っていきます。

  • 1. HTML のデータ属性に値を設定して渡す方法
  • 2. API を使用して渡す方法

1. HTML のデータ属性に値を設定して渡す方法

まずはページを表示するためのコントローラーを、 HomeController という名前で作成します。

$ bundle exec rails g controller Home index --no-helper --no-assets

次にコントローラー内部の実装を進めていきます。 index メソッドのインスタンス変数として、 title description そして Hash 形式の contents を用意します。

app/controllers/home_controller.rb
  class HomeController < ApplicationController
    def index
+     @title = 'Home#index'
+     @description = 'トップページ'
+     @contents = get_contents
    end
+
+   private
+
+   def get_contents
+     {
+       outer_links: [
+         {
+           name: 'Qiitaページ',
+           text: 'Qiita',
+           url: 'https://qiita.com/t0yohei/items/cd11b15642fbb26f71e2'
+         },
+         {
+           name: 'ソースコード',
+           text: 'GitHub',
+           url: 'https://github.com/t0yohei/rails-vue3-app'
+         }
+       ],
+     }
+   end
  end

続いて view の実装です。ここでポイントとなるのが、 Rails の content_tag ヘルパーを利用して、 div タグの data 属性に vue 側へ受け渡したいデータを設定している点です。 vue へ受け渡すデータは json 形式に変換しておきます。

app/views/home/index.html.erb
- <h1>Home#index</h1>
- <p>Find me in app/views/home/index.html.erb</p>
+ <%= javascript_pack_tag 'home/index.js' %>
+ <%= content_tag :div,
+   id: "homeIndex",
+   data: {
+     title: @title,
+     description: @description,
+     contents: @contents
+   }.to_json do %>
+ <% end %>

生成される html は下記画像のようになります。
image.png

data 属性に設定した情報は、 developer tool などを使うことで閲覧可能です。そのため API 同様にユーザーのプライペート情報など、秘匿情報は公開しないよう注意してください。

data 属性に設定した値を読み込む js ファイルを作成

先ほど view で設定したデータを、 js 側から読み取ってみましょう。 app/javascript/packs/home/index.js を下記の通り実装します。

app/javascript/packs/home/index.js
document.addEventListener("DOMContentLoaded", () => {
  const node = document.getElementById("homeIndex");
  const initialData = JSON.parse(node.getAttribute("data"));
  console.log(initialData);
});

http://localhost:3000/home/index にアクセスして、 developer tool の console を開けてみます。

このように、 Rails の view ファイルで設定したデータが、 object として取得できていることがわかります。
この object のデータを使用して、実装を進めていきましょう。

読み取った値を表示する Vue コンポーネントの実装

props として initialData を受け取る Index コンポーネントを作成します。

app/javascript/components/home/Index.vue
<template>
  <div>
    <h1>{{ initialData.title }}</h1>
    <p>{{ initialData.description }}</p>
    <table class="contents-table">
      <tr>
        <th>名前</th>
        <th>リンク</th>
      </tr>
      <tr v-for="outer_link in initialData.contents.outer_links" v-bind:key="outer_link.name">
        <td>{{ outer_link.name }}</td>
        <td>
          <a v-bind:href="outer_link.url">{{ outer_link.text }}</a>
        </td>
      </tr>
    </table>
  </div>
</template>

<script>
export default {
  props: {
    initialData: {
      type: Object,
      default: () => {}
    }
  },
};
</script>

<style scoped>
.contents-table {
  border: 1px solid gray;
  margin: 10px;
}
.contents-table th,
.contents-table td {
  border: 1px solid gray;
}
</style>


補足: data プロパティを設定する場合

今回の実装では、 props で受け取った値をそのまま画面に表示するだけなので今の実装で十分ですが、props の値は immutable (書き換え不可)なので任意のタイミングで値を変更したりできません。
(どこかのボタンを押すと description の値を変更させる、といった処理はできません。)

そう言った場合は、 props で受け取った値を初期値として data 属性を設定することで、 mutable な state (書き換え可能な値)を設定することができます。

app/javascript/components/home/Index.vue
  <template>
    <div>
-     <h1>{{ initialData.title }}</h1>
-     <p>{{ initialData.description }}</p>
+     <h1>{{ title }}</h1>
+     <p>{{ description }}</p>
      <table class="contents-table">
        <tr>
          <th>名前</th>
          <th>リンク</th>
        </tr>
-       <tr v-for="outer_link in initialData.contents.outer_links" v-bind:key="outer_link.name">
+       <tr v-for="outer_link in contents.outer_links" v-bind:key="outer_link.name">
          <td>{{ outer_link.name }}</td>
          <td>
            <a v-bind:href="outer_link.url">{{ outer_link.text }}</a>
          </td>
        </tr>
      </table>
    </div>
  </template>

  <script>
  export default {
    props: {
      initialData: {
        type: Object,
        default: () => {}
      }
    },
+   data() {
+     return {
+       title: this.initialData.title,
+       description: this.initialData.description,
+       contents: this.initialData.contents
+     }
+   },
  };
  </script>



補足: data プロパティの設定を spread 構文を使って書く場合

spread 構文を使うことで、 data プロパティ設定の処理をよりシンプルに書くことができます。

app/javascript/components/home/Index.vue
...
<script>
export default {
  props: {
    initialData: {
      type: Object,
      default: () => {}
    }
  },
  data() {
    return {
      ...this.initialData
    }
  },
};
</script>
...



補足: data プロパティの設定を Composition API を使って書く場合

toRefs をでの分割代入を使用して実装します。

app/javascript/components/home/Index.vue
...
<script>
import { toRefs } from 'vue'

export default {
  props: {
    initialData: {
      type: Object,
      default: () => {}
    }
  },
  setup(props) {
    const { title, description, contents } = toRefs(props.initialData)
    return { title, description, contents }
  }
};
</script>
...


Index コンポーネントをマウントする

index.js で Index コンポーネントに props として initialData を渡し初期化し4index.html.erb の div 要素にマウントします。

app/javascript/packs/home/index.js
+ import { createApp } from "vue";
+ import Index from "../../components/home/Index.vue";

  document.addEventListener("DOMContentLoaded", () => {
    const node = document.getElementById("homeIndex");
    const initialData = JSON.parse(node.getAttribute("data"));
+   const app = createApp(Index, { initialData: initialData })
+   app.mount("#homeIndex");
-   console.log(initialData);
  });

ページの表示

app/javascript/packs 配下に追加した js ファイルを読み込ませるためには、 webpack-dev-server の再起動が必要です。 webpack-dev-server を実行中の場合は一度停止し、再度下記コマンドを実行しましょう。

 $ bin/webpack-dev-server

http://localhost:3000/home/index にアクセスした時、下記のようなページが表示されていると ok です。

実装のリファクタリング

とりあえず表示させることを優先で、 Index.vue に全てを書いていたので、簡単にコンポーネントの分割します。分割のイメージは下記になります。
image.png

ちょっとずつ実践的な実装になってきましたね。

HeaderView.vue の作成

Index コンポーネントの title , description 部分を切り出した HeaderView コンポーネントを作成します。このコンポーネントは、他のコンポーネントでも使いやすいように、1 階層上 (app/javascript/components 直下) に配置します。

app/javascript/components/HeaderView.vue
<template>
  <div>
    <h1>{{ title }}</h1>
    <p>{{ description }}</p>
  </div>
</template>

<script>
export default {
  props: {
    title: {
      type: String,
      default: () => ""
    },
    description: {
      type: String,
      default: () => ""
    }
  }
};
</script>

<style></style>

Contents.vue の作成

次に、 Index コンポーネントの table 部分を切り出した Contents コンポーネントを作成します。

app/javascript/components/home/Contents.vue
<template>
  <div>
    <table class="contents-table">
      <tr>
        <th>名前</th>
        <th>リンク</th>
      </tr>
      <tr v-for="outer_link in contents.outer_links" v-bind:key="outer_link.name">
        <td>{{ outer_link.name }}</td>
        <td>
          <a v-bind:href="outer_link.url">{{ outer_link.text }}</a>
        </td>
      </tr>
    </table>
  </div>
</template>

<script>
export default {
  props: {
    contents: {
      type: Object,
      default: () => {}
    }
  }
};
</script>

<style scoped>
.contents-table {
  border: 1px solid gray;
  margin: 10px;
}
.contents-table th,
.contents-table td {
  border: 1px solid gray;
}
</style>

Index.vue の修正

最後に、 Index コンポーネントで HeaderView コンポーネント、 Contents コンポーネントを使用する形に修正します。

app/javascript/components/home/Index.vue
  <template>
    <div>
-     <h1>{{ initialData.title }}</h1>
-     <p>{{ initialData.description }}</p>
-     <table class="contents-table">
-       <tr>
-         <th>名前</th>
-         <th>リンク</th>
-       </tr>
-       <tr v-for="outer_link in initialData.contents.outer_links" v-bind:key="outer_link.name">
-         <td>{{ outer_link.name }}</td>
-         <td>
-           <a v-bind:href="outer_link.url">{{ outer_link.text }}</a>
-         </td>
-       </tr>
-     </table>
+     <HeaderView v-bind:initialData.title="title" v-bind:description="initialData.description" />
+     <Contents v-bind:contents="initialData.contents" />
    </div>
  </template>

  <script>
+ import HeaderView from "../HeaderView.vue";
+ import Contents from "./Contents.vue";

  export default {
+   components: {
+     HeaderView,
+     Contents
+   },
    props: {
      initialData: {
        type: Object,
        default: () => {}
      }
    },
  };
  </script>
- <style scoped>
- .contents-table {
-   border: 1px solid gray;
-   margin: 10px;
- }
- .contents-table th,
- .contents-table td {
-   border: 1px solid gray;
- }
- </style>
+ <style scoped></style>

Index コンポーネントがだいぶスッキリしましたね。
念の為、再度 http://localhost:3000/home/index にアクセスして、画面がちゃんと表示されることを確認しておきましょう。

2. API を使用して渡す方法での実装

次に、API を使用して渡す方法での実装を進めていきましょう。
下記の手順で整数リテラルの分類表を作成してみます。

  • 2-1. API を叩くコンポーネントの実装
  • 2-2. API エンドポイントの実装
  • 2-3. コンポーネントから API を叩いてデータを取得
  • 2-4. 取得したデータをコンポーネント内で描画

2-1. API を叩くコンポーネントの実装

まずはページを表示するためのコントローラーを作成します。

$ bundle exec rails g controller IntegerLiteralDescriptions index --no-helper --no-assets

コントローラーの実装

今回は何もしないです。

View の実装

javascript_pack_tag と、コンポーネントをマウントするための div 要素を追加します。

app/views/integer_literal_descriptions/index.html.erb
- <h1>IntegerLiteralDescriptions#index</h1>
- <p>Find me in app/views/integer_literal_descriptions/index.html.erb</p>
+ <%= javascript_pack_tag 'integerLiteralDescriptions/index.js' %>
+ <div id="integerLiteralDescriptionsIndex"></div>

コンポーネントの実装

index.html.erb にマウントする Index コンポーネントと Contents コンポーネントを追加します。

app/javascript/components/integerLiteralDescriptions/Index.vue
<template>
  <div>
    <HeaderView v-bind:title="title" v-bind:description="description" />
    <Contents v-bind:contents="contents" />
  </div>
</template>

<script>
import HeaderView from "../HeaderView.vue";
import Contents from "./Contents.vue";

export default {
  components: {
    HeaderView,
    Contents
  },
  data() {
    return {
      title: "title",
      description: "description",
      contents: []
    };
  }
};
</script>

<style scoped>
</style>
app/javascript/components/integerLiteralDescriptions/Contents.vue
<template>
  <div>
    <table class="contents">
      <tr>
        <th>名前</th>
        <th>英語訳</th>
        <th>表記例</th>
        <th>用途</th>
      </tr>
      <tr v-for="content in contents" v-bind:key="content.name">
        <td>{{ content.name }}</td>
        <td>{{ content.english }}</td>
        <td>{{ content.sample }}</td>
        <td>{{ content.usage }}</td>
      </tr>
    </table>
  </div>
</template>

<script>
export default {
  props: {
    contents: {
      type: Array,
      default: () => []
    }
  }
};
</script>

<style scoped>
.contents {
  border: 1px solid gray;
}
.contents th,
.contents td {
  border: 1px solid gray;
}
</style>

Vue コンポーネントをマウントする実装

Index コンポーネントと Contents コンポーネントを index.html.erb にマウントします。

app/javascript/packs/integerLiteralDescriptions/index.js
import { createApp } from "vue";
import Index from "../../components/integerLiteralDescriptions/Index.vue";

document.addEventListener("DOMContentLoaded", () => {
  const app = createApp(Index)
  app.mount("#integerLiteralDescriptionsIndex");
});

再び bin/webpack-dev-server を実行し直し、 http://localhost:3000/integer_literal_descriptions/index にアクセスすると、下記画像のようなページが表示されます。

これで API を叩いて取得したデータを受け取り、表示するためのコンポーネントが完成しました。

次は、先ほど作成したコンポーネントからリクエストを受け、レスポンスを返す処理を実装していきます。

2-2. API エンドポイントの実装

コントローラーを、 Api::V1::IntegerLiteralDescriptionsController という名前で作成します。

$ bundle exec rails g controller api/v1/integer_literal_descriptions index --no-helper --no-assets --no-view-specs

コントローラーの実装

整数リテラルの分類表の json を返却する index メソッドを実装します。

app/controllers/api/v1/integer_literal_descriptions_controller.rb
 class Api::V1::IntegerLiteralDescriptionsController < ApplicationController
   def index
+    title = 'IntegerLiteralDescriptions#index'
+    description = '整数リテラルの分類表'
+    contents = get_integer_literals
+    result_values = {
+      title: title,
+      description: description,
+      contents: contents
+    }
+    render json: result_values
   end
-end
+
+  private
+
+  def get_integer_literals
+    [
+      {
+        name: '10進数',
+        english: 'decimal',
+        sample: '42',
+        usage: '数値'
+      },
+      {
+        name: '2進数',
+        english: 'binary digits',
+        sample: '0b0001',
+        usage: 'ビット演算など'
+      },
+      {
+        name: '8進数',
+        english: 'octal',
+        sample: '0o777',
+        usage: 'ファイルのパーミッションなど'
+      },
+      {
+        name: '16進数',
+        english: 'hexadecimal, hex',
+        sample: '0xEEFF',
+        usage: '文字のコードポイント、RGB値など'
+      }
+    ]
+  end
+end

アクセスの確認

この状態で、 http://localhost:3000/api/v1/integer_literal_descriptions/index にアクセスしてみると、下記のような画面が表示されるはずです。

image.png

一旦これで、 API のエンドポイントが完成しました。

2-3. コンポーネントから API を叩いてデータを取得

コンポーネントと API の Ajax 通信は axios というライブラリを使用します。5
まずは axios をインストールします。

$ yarn add --dev axios

install に成功していると、 package.json axios の項目が追記されているはずです。

package.json
   "devDependencies": {
+    "axios": "^0.21.0",
     "webpack-dev-server": "^3.11.0"
   }

次に integerLiteralDescriptions/Index.vue を書き換えて、 axios でのデータ取得を実装します。

app/javascript/components/integerLiteralDescriptions/Index.vue
 import Contents from "./Contents.vue";
+import Axios from "axios";

 export default {
   components: {
     HeaderView,
      Contents
   },
   data: function() {
     return {
       title: "title",
       description: "description",
       contents: []
     };
+  },
+
+  created: function() {
+    this.updateContents();
+  },
+
+  methods: {
+    updateContents() {
+      Axios.get("/api/v1/integer_literal_descriptions/index.json").then(
+        response => {
+          const responseData = response.data;
+          console.log(responseData);
+        }
+      );
+    }
   }


補足: Composition API を使って書く場合
app/javascript/components/integerLiteralDescriptions/Index.vue
<script>
import HeaderView from "../HeaderView.vue";
import Contents from "./Contents.vue";
import Axios from "axios";
import { ref, onMounted } from "vue";

export default {
  components: {
    HeaderView,
    Contents
  },

  setup() {
    const title = ref('')
    const description = ref('')
    const contents = ref([])

    const updateContents = () => {
      Axios.get("/api/v1/integer_literal_descriptions/index.json").then(
        response => {
          const responseData = response.data;
          console.log(responseData);
        }
      );
    }

    onMounted(updateContents)

    return { title, description, contents }
  }
};
</script>


created で vue コンポーネントが作成されたタイミングで axios によるデータ取得を走らせるようにしています。
console.log によって、取得したデータが出力されているはずなので、 http://localhost:3000/integer_literal_descriptions/index を見てみましょう。
↓ のようなログが出て、データ取得に成功しているはずです。
image.png


Tips: JavaScript のデバッグ

ご存知な方も多いと思いますが、 JavaScript では debugger を仕込むことで、デバッグ実行が可能になります。
debugger ステートメント | MDN

具体的には下記のように仕込むことができます。

app/javascript/components/integerLiteralDescriptions/Index.vue
   Axios.get("/api/v1/integer_literal_descriptions/index.json").then(
     response => {
       const responseData = response.data;
-      console.log(responseData);
+      debugger;
     }
   );

開発者ツールを開きながら、再度先ほどの http://localhost:3000/integer_literal_descriptions/index にアクセスしてみると、
image.png

debugger を仕込んだ部分で処理が止まり、 Console からその時点の各種データを覗くことができます。(↑ 画像の場合、画像下部で responseData の値を確認しています。)


2-4. 取得したデータをコンポーネント内で描画

先ほど axios で取得したデータを、画面に反映させます。
今回の場合、下記の部分を書き換えるだけです。

app/javascript/components/integerLiteralDescriptions/Index.vue
   Axios.get("/api/v1/integer_literal_descriptions/index.json").then(
     response => {
       const responseData = response.data;
-      console.log(responseData);
+      this.title = responseData.title;
+      this.description = responseData.description;
+      this.contents = responseData.contents;
     }
   );

http://localhost:3000/integer_literal_descriptions/index にアクセスしてみると、整数リテラル分類表が出てくるはずです。


Tips: 整数リテラルとは

JavaScript で表現できる 整数リテラル には 10 進数、2 進数、8 進数、16 進数があります。
JavaScript では 0 で始まる数値の直後に b, o, x をつけることで、それぞれ 2 進数、8 進数、16 進数が表現できます。
b, o, x は英語訳を見てみると、それぞれ binary digits, octal, hex となっており、それぞれの頭文字から来ていることがわかります。今回の実装で整数リテラル分類表をサンプルに組み込んだ理由はただの思いつきです。


リンクボタンを追加

せっかくなので、 home/index から http://localhost:3000/integer_literal_descriptions/index に飛べるよう、リンクボタンを追加しておきましょう。

app/controllers/home_controller.rb
           url: 'https://github.com/t0yohei/rails-vue-app'
         }
       ],
+      inner_links: [{
+        label: '整数リテラル分類表',
+        url: url_for(action: 'index', controller: 'integer_literal_descriptions')
+      }]
     }
app/javascript/components/home/Contents.vue
...
         </td>
       </tr>
     </table>
+    <div v-for="inner_link in contents.inner_links" v-bind:key="inner_link.label">
+      <button v-on:click="changeLocation(inner_link.url)" class="btn-push">{{ inner_link.label }}</button>
+    </div>
   </div>
 </template>

 <script>
 export default {
   props: {
     contents: {
       type: Object,
       default: () => {}
     }
+  },
+  methods: {
+    changeLocation(url) {
+      window.location.href = url;
+    }
   }
 };
 </script>

 <style scoped>
...
+.btn-push {
+  margin: 10px;
+  max-width: 180px;
+  text-align: left;
+  background-color: rgb(24, 174, 238);
+  font-size: 14px;
+  color: #fff;
+  text-decoration: none;
+  font-weight: bold;
+  padding: 10px 24px;
+  border-radius: 4px;
+  border-bottom: 4px solid rgb(24, 174, 238);
+}
+.btn-push:active {
+  transform: translateY(4px);
+  border-bottom: none;
+}
 </style>


補足: Composition API を使って書く場合
app/javascript/components/home/Contents.vue
<script>
import { ref } from "vue";

export default {
  props: {
    contents: {
      type: Object,
      default: () => {}
    }
  },

  setup(props) {
    const changeLocation = url => {
      window.location.href = url;
    }
    return { changeLocation }
  }
};
</script>


http://localhost:3000/home/index を開いて、こんな感じのボタンができていたら ok です。

最後に

今回のチュートリアルは以上で終了です。
このチュートリアルで学習したのは Webpacker を使用した Vue + Rails アプリケーションの基本となる部分で、大きなアプリケーションに取り組むときにも、これからアプリケーションを作り始める時にも大きな武器になるはずです。
このチュートリアルが、 Rails や Vue を学習される方の助けに少しでもなっていれば幸いです。
では。

参考

注釈


  1. 今回の実装では CoffeeScriptturbolinks のセットアップを省いています。既存の rails プロジェクトで Vue.js を使用する場合は、 turbolinks と戦う必要がありそうです。 

  2. Webpacker 6.0 でそもそも --webpack=vue といったオプション自体が廃止されるとのこと。ちなみの --webpack のオプションを指定しない場合でも、 rails new 時に default で webpacker:install が実行されます。 

  3. オプションの詳細は、 こちらrails g controller -h コマンドで確認できます。 

  4. createApp の第二引数に root props を渡せる機能を活用しています。 

  5. axios を利用した API の使用。 Ajax 通信をする手段としては、他のも fetch API などを使用する方法が有名です。 

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

[Rails] アプリケーションの型作成 データベース作成 サーバー起動 の具体的方法

まず初めに、サーバーサイドの準備を整えます。
サーバーサイドという箱には、[アプリケーション] [データベース]という2つのオブジェクトを入れてあげる必要があります。

それでは、サーバーサイドを立ち上げる際の初期操作を3つ見ていきましょう

①アプリケーションの型作成

「 rails version new アプリケーション名 -d データベース管理システム名 」

②データベース作成

「 rails db : create 」

③サーバー起動

「 rails s 」  ※control + C でサーバーを停止できます。

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

RailsのDBをmysqlに変える(メモ)

RailsでデフォルトのDB'SQlite'から'mysql'(ver8.0.22)に変更する際に詰まったので、
mysqlインストールから実際にDB作成をするところまでの手順をメモ。

1.mysqlインストーラでmysqlをインストール。
http://dev.mysql.com/downloads/installer/
からインストーラをダウンロード。
mysqlインストーラ.PNG

2.ダウンロードしたインストーラを起動。
あとは流れに沿ってインストールしていく。(rootのパスワード設定等。)
そして、この流れの中で後にRailsからmysqlを起動しようとするとErrorになってしまう設定項目があった。それがこの設定。
Authenticate.png

ここで上の認証方式を選択してしまうと、認証方式が'caching_sha2_password'になるが、Railsではこの認証方式に対応していないらしい。(この認証方式はmysql8で使用可能。)
なので、下の設定を選択して認証方式を'mysql_native_password'にする必要があった。

3.gemファイルでmysqlをinstall。

Gemfile
# Use mysql as the database for Active Record
gem "mysql2"
bundel install

4.railsアプリケーションのDB設定

config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
  host: localhost
  timeout: 5000

development:
  adapter: mysql2
  encoding: utf8
  reconnect: false
  database: <%= ENV['DATABASE_DEV_NAME'] %>
  pool: 5
  username: <%= ENV['DATABASE_DEV_USER'] %>
  password: <%= ENV['DATABASE_DEV_PASSWORD'] %>
  host: <%= ENV['DATABASE_DEV_HOST'] %>

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  adapter: mysql2
  encoding: utf8
  reconnect: false
  database: アプリケーション名_test
  pool: 5
  username: root
  password: <%= ENV['DATABASE_TST_PASSWORD'] %>
  host: localhost

production:
  adapter: mysql2
  encoding: utf8
  reconnect: false
  database: アプリケーション名_production
  pool: 5
  username: アプリケーション名
  password: <%= ENV['アプリケーション名_DATABASE_PASSWORD'] %>
  host: localhost

のように変更。

5.パスワードを環境変数で管理する。
dotenvというgemをinstall。

Gemfile
gem "dotenv-rails"
bundle install

アプリケーションのルートディレクトリに.envファイルを作成し、その中に環境変数を設定する。

.env
 DATABASE_DEV_NAME = 'アプリケーション名_development'
 DATABASE_DEV_PASSWORD = 'パスワードを記入'
 DATABASE_DEV_USER = 'MySQLユーザー名を記入'
 DATABASE_DEV_HOST = 'localhost'
 DATABASE_TST_PASSWORD = 'パスワードを記入'

.gitignoreファイルに.envを追加する。

.gitignore
# Ignore env config
/.env

※mysqlユーザを作成しておく。

ここで

rails db:create

完了。

※mysqlユーザを作成する。

1.mysqlに接続する。
コマンドプロンプトを管理者権限で開き、以下を実行する。

net start mysql80
mysql -u root -p

mysqlインストール時に設定したパスワードを入力。

2.実際にmysqlユーザを作成する。

mysql> create user 'test-user'@'localhost' identified by 'password';

3.mysqlユーザに権限を与える。

mysql> grant all on *.* to 'test-user'@'localhost';
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsのSystem SpecでJavaScriptエラーを検出する方法

はじめに

最近のWebサイトであれば、JavaScriptを利用していないページを探すほうが難しいかと思います。
そんな中で単純にRailsのSystem Specによる検証だけだと、テストを通ったのでリリースしたら実は以下のようなJavaScriptエラーが発生していたみたいな見落としが起こることが時々ありますよね。

Image from Gyazo

解決方法としての理想はJavaScriptが影響する全ての箇所を網羅するようなテストを書くことかもしれませんが、現実的にはそこまで書けないというケースが多いかと思います。
また、全てを網羅していたつもりでも実際には漏れがあって本番環境のコンソールにJavaScriptエラーが表示されていたみたいなことは起こりえます。

そこで今回の記事ではRailsのSystem SpecでJavaScriptエラーを検出する方法を紹介します。

設定例

spec/support/system_test.rb
module Capybara
  module Matchers
    class JsNoErrorMatcher
      def matches?(page)
        errors = page.driver.browser.manage.logs.get(:browser)
        errors.find_all { |error| error.level == 'WARNING' }.each do |error|
          STDERR.puts 'WARN: javascript warning'
          STDERR.puts error.message
        end

        @severe_errors = errors.find_all { |error| error.level == 'SEVERE' }
        @severe_errors.empty?
      end

      def description
        'have no js errors'
      end

      def failure_message
        @severe_errors.map(&:message).join("\n")
      end
    end

    def have_no_js_errors
      JsNoErrorMatcher.new
    end
  end
end

RSpec.configure do |config|
  config.include Capybara::Matchers, type: :system

  driven_by :selenium, using: :headless_chrome

  config.after(:each, type: :system) do |example|
    if example.metadata[:skip_js_error]
      # ログリセット
      page.driver.browser.manage.logs.get(:browser)
    else
      expect(page).to have_no_js_errors
    end
  end
end

※説明を簡単にする為に1ファイルにまとめて書いていますが実際は分けたほうが綺麗だと思います。

使い方

これまで通りSystem Specを実行することで、JavaScriptエラーが発生した場合には、発生したエラーメッセージが表示されてテストが失敗するようになります。

Image from Gyazo

もしも外部のスクリプトに依存している等の理由により、エラーを無視するしかないみたいなケースでは以下のように書くことで特定のテストだけJavaScriptエラーの検証をスキップすることが出来ます。

it 'ログインできること', skip_js_error: true do
  visit root_path
  # ...
end

まとめ

というわけで、この記事では「System SpecでJavaScriptエラーを検出する方法」を紹介しました。
既存のテストに設定を足すだけで、JavaScriptエラーに気付けるようになったり、不安定なテストの原因が特定しやすくなったりと役立つことが多いかと思います。

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

【Rails】 備忘録:Deviseのモデル名を間違えて生成してしまったときの対処法

タイトルの通り、Rails Deviseのモデル名を間違えて生成してしまったときの対処法をメモします。

$rails generate devise MODEL #任意のモデル名

このモデル名を間違えて生成してしまいました。
結果、以下のように複数ファイルが、間違えたモデル名で生成されました。

Running via Spring preloader in process 12067
      invoke  active_record
      create    db/migrate/20201204011415_devise_create_models.rb
      create    app/models/model.rb
      invoke    test_unit
      create      test/models/model_test.rb
      create      test/fixtures/models.yml
      insert    app/models/model.rb
       route  devise_for :models

ではこれを削除するにはどうするかというと

$rails destroy devise MODEL #任意のモデル名  

先ほどのgenerateの部分をdestroyにすればいいだけです。
これで先ほど尽かされたファイルや追記されたコードが削除されます。
ターミナルの結果にて確認できます↓

Running via Spring preloader in process 12224
      invoke  active_record
      remove    db/migrate/20201204011415_devise_create_models.rb
      remove    app/models/model.rb
      invoke    test_unit
      remove      test/models/model_test.rb
      remove      test/fixtures/models.yml
       route  devise_for :models

もし、$rails db:migrateコマンドでデータベースを反映させてしまった場合はrollbackを実行して、データベースを元に戻してください。
と、その前にまずはどこまでmigrateされているか現在の状況を把握するところから始めます。
(現在の状況を知らずにとにかくrollbackを行うと、何をrollbackしてしまったのかわからなくなるからです。特にチームでやる場合は必須。)←だそうです(実務未経験です)

このコマンドで確認します。

$rails db:version
Current version: 20201129095028

20201129095028(migrateした最新の日付)までmigrateされていることが確認できます。それでrollbackしても問題なければ、以下を実行します。

$ rails db:rollback

もう一度同じ手順で、正しいモデル名でモデルを生成し、$rails db:migrate したら完了です。

引用元:http://taremimi.hatenablog.jp/entry/2018/02/16/120343

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

【備忘録】Rails Deviseのモデル名を間違えて生成してしまったときの対処法

タイトルの通り、Rails Deviseのモデル名を間違えて生成してしまったときの対処法をメモします。

$rails generate devise MODEL #任意のモデル名

このモデル名を間違えて生成してしまいました。
結果、以下のように複数ファイルが、間違えたモデル名で生成されました。

Running via Spring preloader in process 12067
      invoke  active_record
      create    db/migrate/20201204011415_devise_create_models.rb
      create    app/models/model.rb
      invoke    test_unit
      create      test/models/model_test.rb
      create      test/fixtures/models.yml
      insert    app/models/model.rb
       route  devise_for :models

ではこれを削除するにはどうするかというと

$rails destroy devise MODEL #任意のモデル名  

先ほどのgenerateの部分をdestroyにすればいいだけです。
これで先ほど尽かされたファイルや追記されたコードが削除されます。
ターミナルの結果にて確認できます↓

Running via Spring preloader in process 12224
      invoke  active_record
      remove    db/migrate/20201204011415_devise_create_models.rb
      remove    app/models/model.rb
      invoke    test_unit
      remove      test/models/model_test.rb
      remove      test/fixtures/models.yml
       route  devise_for :models

もし、$rails db:migrateコマンドでデータベースを反映させてしまった場合はrollbackを実行して、データベースも削除してください。
と、その前にまずはどこまでmigrateされているか現在の状況を把握するところから始めます。
(現在の状況を知らずにとにかくrollbackを行うと、何をrollbackしてしまったのかわからなくなるからです。特にチームでやる場合は必須。)←だそうです(実務未経験です)

このコマンドで確認します。

$rails db:version
Current version: 20201129095028

20201129095028(migrateした最新の日付)までmigrateされていることが確認できます。それでrollbackしても問題なければ、以下を実行します。

$ rails db:rollback

もう一度同じ手順で、正しいモデル名でモデルを生成し、$rails db:migrate したら完了です。

引用元:http://taremimi.hatenablog.jp/entry/2018/02/16/120343

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