- 投稿日:2020-07-15T23:42:56+09:00
【Rails】link_toメソッドで指定する引数の順番について
- 投稿日:2020-07-15T23:04:39+09:00
Docker + Rails6 + PostgreSQL 環境構築
はじめに
今までRails5 + MySQL での開発しか行ってこなかった自分でしたが、初めてRails6 + PostgreSQL での環境構築を行ったため備忘録も兼ねて記述していきたいと思います。
必要なファイル
作業フォルダを作成します。
今回はsample_app
とします。$ mkdir sample_appそして必要なファイルを以下の構成で作成します。
$ touch ○○(ファイル名) - sample_app - Dockerfile - docker-compose.yml - Gemfile - Gemfile.lock - entrypoint.shではそれぞれを編集していきます。
Dockerfile
Rails6 から
Webpacker
がデフォルトで導入されたためインストールする必要があります。
そのため、Dockerfile
にはWebpacker
をインストールするために必要なyarn
とNode.js
をインストールするように記述します。FROM ruby:2.6.5 # 必要なライブラリインストール RUN apt-get update -qq && apt-get install -y nodejs postgresql-client # yarnパッケージ管理ツールをインストール RUN apt-get update && apt-get install -y curl apt-transport-https wget && \ 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 -y yarn # Node.jsをインストール RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - && \ apt-get install nodejs RUN mkdir /myapp WORKDIR /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myapp COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 CMD ["rails", "server", "-b", "0.0.0.0"]docker-compose.yml
docker-compose.ymlversion: '3' services: db: image: postgres volumes: - ./tmp/db:/var/lib/postgresql/data environment: - POSTGRES_PASSWORD=password web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp ports: - "3000:3000" depends_on: - dbGemfile
source 'https://rubygems.org' gem 'rails', '~> 6'Gemfile.lock
ファイルを作成だけして、編集は何もしません。
entrypoint.sh
entrypoint.shset -e rm -f /myapp/tmp/pids/server.pid exec "$@"Railsアプリの作成
作成した
Dockerfile
元にイメージがbuild
し、生成されたコンテナ内でrails new
を実行します$ docker-compose run web rails new . --force --no-deps --database=postgresql --skip-bundlebundle install
rails new
コマンドによりGemfile
内の記述が変更されているためbundle install
をする必要があります。
イメージをbuild
することでbundle install
も実行されるため以下のコマンドを実行します。$ docker-compose builddatabase.yml
rails new
で作成されたdatabase.yml
を以下のように編集します。config/database.ymldefault: &default adapter: postgresql encoding: unicode host: db username: postgres password: pool: 5 development: <<: *default database: myapp_development test: <<: *default database: myapp_testwebpackerのインストール
$ docker-compose run web bundle exec rails webpacker:installコンテナを起動
$ docker-compose upDB作成
$ docker-compose run web rake db:createおわり
これで
http://localhost:3000/
にアクセスするとサーバーが立ち上がっていることを確認できるかと思います。
最後まで見ていただき、ありがとうございました。参考記事
DockerでRuby on Railsの環境構築を行うためのステップ【Rails 6対応】
Rails6 開発時につまづきそうな webpacker, yarn 関係のエラーと解決方法
PostgreSQL(Docker)にRails(Docker)が接続できなくなったから調べてみた。(could not translate host name "db" to address: Name or service not known)
- 投稿日:2020-07-15T22:22:50+09:00
初心者がWeb開発するために Docker + Rails6 + PostgreSQL の環境構築をする話
はじめに
いろいろあって友人とWeb開発を行うことになりました。
初心者のため勉強しながらの開発となるので、
これからは勉強した内容のアウトプットのためにいろいろ記事にしようと思います。
まず第一歩として環境構築からです。環境
ubuntu 18.04
0.前提
Dockerインストール済み
まだの方はこちらから
https://docs.docker.com/compose/install/1.必要なディレクトリ・ファイル準備
開発したい場所に開発をするディレクトリを作成します。
今回ディレクトリの名前は 'myapp'$ mkdir myapp $ cd myapp作成したディレクトリの中にアプリケーション構築に必要なファイルを作成します。
- Dockerfile
- Gemfile
- Gemfile.lock
- entrypoint.sh
- docker-compose.yml
$ touch Dockerfile Gemfile Gemfile.lock entrypoint.sh docker-compose.yml作成したファイルに下記の内容を記述します。
DockerfileFROM ruby:2.6.6 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 RUN apt-get update -qq && apt-get install -y nodejs postgresql-client yarn RUN mkdir /myapp WORKDIR /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myapp # Add a script to be executed every time the container starts. COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 # Start the main process. CMD ["rails", "server", "-b", "0.0.0.0"]Gemfilesource 'https://rubygems.org' gem 'rails', '6.0.3'Gemfile.lockGemfileは何も書かなくていいです。
entrypoint.sh#!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /myapp/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@"docker-compose.ymlversion: '3' services: db: image: postgres volumes: - ./tmp/db:/var/lib/postgresql/data environment: - POSTGRES_PASSWORD=password web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp ports: - "3000:3000" depends_on: - dbこれで必要なファイルは揃いました。
2.プロジェクトの構築
作成した5つのファイルを利用してアプリケーションを生成します。
まずターミナルを使ってdocker-compose runを実行します。$ docker-compose run web rails new . --force --no-deps --database=postgresql最後に下記のようなWebpackerのインストール成功メッセージが表示されたら生成完了です。
Webpacker successfully installed ? ?次はdocker-compose builをします。
$ docker-compose build最後に下記のようなSuccess情報が表示されたら成功です。
Successfully built xxxxxxxxxx
Successfully tagged myapp_web:latest3.データベースの設定・作成
まずデータベースの設定を行うために以下のファイルを編集します。
- config/database.yml
編集内容は下記を記述してください
database.yml# 設定箇所のみ抜粋 default: &default adapter: postgresql encoding: unicode host: db username: postgres password: password pool: 5 development: <<: *default database: myapp_development test: <<: *default database: myapp_test編集を終えたら下記のコマンドを実行し、データベースを作成します。
$ docker-compose run web rake db:create最後に下記のようなメッセージが最後に表示されたら作成成功です。
Starting myapp_db_1 ... done
Created database 'myapp_development'
Created database 'myapp_test'4.Dockerを起動
dockerを起動するために下記のコマンドを実行します。
$ docker-compose up実行が完了すると下記の内容がコンソールに表示されます。
myapp_db_1 is up-to-date Starting myapp_web_1 ... done Attaching to myapp_db_1, myapp_web_1 (省略) web_1 | => Booting Puma web_1 | => Rails 6.0.3.2 application starting in development web_1 | => Run `rails server --help` for more startup options web_1 | Puma starting in single mode... web_1 | * Version 4.3.5 (ruby 2.6.6-p146), codename: Mysterious Traveller web_1 | * Min threads: 5, max threads: 5 web_1 | * Environment: development web_1 | * Listening on tcp://0.0.0.0:3000 web_1 | Use Ctrl-C to stop表示されたら http://localhost:3000 にアクセスして下の画像と同じページが表示されれば、構築完了です。
最後に
この環境構築で下記のような問題が発生して、3日ほど時間をかけてようやく解決しました。
この記事で書こうか迷ったのですが構築まできれいにまとめたかったので、解決方法は後日記事に書こうと思います。Creating network "myapp_default" with the default driver Creating myapp_db_1 ... done sh: 1: node: not found Webpacker requires Node.js >= 8.16.0 and you are using 4.8.2 Please upgrade Node.js https://nodejs.org/en/download/あと冒頭に書いてある”いろいろあって友人とWeb開発することになった”という話は、
Web開発が終了してリリースしたときに一緒に記事にして投稿します。
- 投稿日:2020-07-15T22:22:50+09:00
初心者がWeb開発するためにDocker+Rails6+Postgresqlの環境構築をする話
はじめに
いろいろあって友人とWeb開発を行うことになりました。
初心者のため勉強しながらの開発となるので、
これからは勉強した内容のアウトプットのためにいろいろ記事にしようと思います。
まず第一歩として環境構築からです。環境
ubuntu 18.04
0.前提
Dockerインストール済み
まだの方はこちらから
https://docs.docker.com/compose/install/1.必要なディレクトリ・ファイル準備
開発したい場所に開発をするディレクトリを作成します。
今回ディレクトリの名前は 'myapp'$ mkdir myapp $ cd myapp作成したディレクトリの中にアプリケーション構築に必要なファイルを作成します。
- Dockerfile
- Gemfile
- Gemfile.lock
- entrypoint.sh
- docker-compose.yml
$ touch Dockerfile Gemfile Gemfile.lock entrypoint.sh docker-compose.yml作成したファイルに下記の内容を記述します。
DockerfileFROM ruby:2.6.6 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 RUN apt-get update -qq && apt-get install -y nodejs postgresql-client yarn RUN mkdir /myapp WORKDIR /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myapp # Add a script to be executed every time the container starts. COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 # Start the main process. CMD ["rails", "server", "-b", "0.0.0.0"]Gemfilesource 'https://rubygems.org' gem 'rails', '6.0.3'Gemfile.lockGemfileは何も書かなくていいです。
entrypoint.sh#!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /myapp/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@"docker-compose.ymlversion: '3' services: db: image: postgres volumes: - ./tmp/db:/var/lib/postgresql/data environment: - POSTGRES_PASSWORD=password web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp ports: - "3000:3000" depends_on: - dbこれで必要なファイルは揃いました。
2.プロジェクトの構築
作成した5つのファイルを利用してアプリケーションを生成します。
まずターミナルを使ってdocker-compose runを実行します。$ docker-compose run web rails new . --force --no-deps --database=postgresql最後に下記のようなWebpackerのインストール成功メッセージが表示されたら生成完了です。
Webpacker successfully installed ? ?次はdocker-compose builをします。
$ docker-compose build最後に下記のようなSuccess情報が表示されたら成功です。
Successfully built xxxxxxxxxx
Successfully tagged myapp_web:latest3.データベースの設定・作成
まずデータベースの設定を行うために以下のファイルを編集します。
- config/database.yml
編集内容は下記を記述してください
database.yml# 設定箇所のみ抜粋 default: &default adapter: postgresql encoding: unicode host: db username: postgres password: password pool: 5 development: <<: *default database: myapp_development test: <<: *default database: myapp_test編集を終えたら下記のコマンドを実行し、データベースを作成します。
$ docker-compose run web rake db:create最後に下記のようなメッセージが最後に表示されたら作成成功です。
Starting myapp_db_1 ... done
Created database 'myapp_development'
Created database 'myapp_test'4.Dockerを起動
dockerを起動するために下記のコマンドを実行します。
$ docker-compose up実行が完了すると下記の内容がコンソールに表示されます。
myapp_db_1 is up-to-date Starting myapp_web_1 ... done Attaching to myapp_db_1, myapp_web_1 (省略) web_1 | => Booting Puma web_1 | => Rails 6.0.3.2 application starting in development web_1 | => Run `rails server --help` for more startup options web_1 | Puma starting in single mode... web_1 | * Version 4.3.5 (ruby 2.6.6-p146), codename: Mysterious Traveller web_1 | * Min threads: 5, max threads: 5 web_1 | * Environment: development web_1 | * Listening on tcp://0.0.0.0:3000 web_1 | Use Ctrl-C to stop表示されたら http://localhost:3000 にアクセスして下の画像と同じページが表示されれば、構築完了です。
最後に
この環境構築で下記のような問題が発生して、3日ほど時間をかけてようやく解決しました。
この記事で書こうか迷ったのですが構築まできれいにまとめたかったので、解決方法は後日記事に書こうと思います。Creating network "myapp_default" with the default driver Creating myapp_db_1 ... done sh: 1: node: not found Webpacker requires Node.js >= 8.16.0 and you are using 4.8.2 Please upgrade Node.js https://nodejs.org/en/download/あと冒頭に書いてある”いろいろあって友人とWeb開発することになった”という話は、
Web開発が終了してリリースしたときに一緒に記事にして投稿します。
- 投稿日:2020-07-15T22:15:17+09:00
【memberとcollection】
memberとcollectionとは
現在ツイッターの写真投稿版のようなアプリを作成しているのですが、その作成途中で投稿の検索機能を実装する際に出てきたmemberとcollectionについてとそれぞれの違いについてアウトプットします。
memberとcollectionはルーティングを設定する際に使用しすることができ、生成されるURLと実行されるコントローラを任意にカスタムすることができます。
collectionはルーティングに:idがつかない、memberはつくという違いがあります。
上の画像のようにtweetsコントローラーのネストに入れてcollectionを記述すると、
以下の画像のルーティングになります。URIを見てもidが入っていません。
一方、memberで記述した場合
この場合のルーティングは以下の画像のようになります。
このようにルーティングにidが入っています。
今回作成した写真投稿アプリの場合は検索結果としてあくまで一覧表示させたいだけでidごとの詳細ページは必要ないのでcollectionを使用しました。
- 投稿日:2020-07-15T20:49:36+09:00
初心者がはまりがちなDockerのエラーに対する解決法
記事の背景
Le Wagon Tokyo (https://www.lewagon.com/ja/tokyo) という、主にRubyとRailsを教えるフランス発の英語のプログラミングスクールを卒業後、Dockerを勉強し始めました。卒業後に取り組んだプロジェクトでDockerの構築を行ったのですが、多くのエラーに直面しました。独学で試行錯誤しながらエラー解決を行ったので、非常に多くの時間がかかってしまいました。私のように、Dockerを初めて触ってエラーに苦労した人も多いと思うので、私が使った解決方法を参考にしていただければと思い、この記事を書くことにしました。
1 PermissionError (Permission denied :‘~~~/your_app/tmp/db’)
Dockerの公式ドキュメントを参照する限り、このエラーはLinuxユーザーだけのようですが、LinuxユーザーにとってPermissionは厄介になりうるので共有します。このエラーは、Dockerが一時的なデータベース(dbフォルダ)を作るのですが、現在ログインしているLinuxユーザーがそのデータベースを使う権利がないため発生します。
まずはこのコマンドでそのテンポラリーフォルダに移動します。
$ cd ~~~(ご自身のアプリまでのパス)/(ご自身のアプリ名)/tmp以下のコマンドでどのユーザーがdbフォルダを使えるか確認します。
$ ls -la現在ログインしているLinuxユーザーの名前が表示されていなければ、そのユーザーに以下のコマンドで権限を与えます。
$ sudo chown -R (Linuxユーザーの名前) . password for (Linuxユーザーの名前):sudoコマンドなので、パスワードの入力が求められます。ここまで行けば、"docker-compose build"や"docker-compose up"など主要なDockerコマンドは問題なく実行できるはずです。
注:
似たようなエラーで、私は"FATAL: could not open file “
global/pg_filenode.map”: Permission denied"というエラーをよく見たのですが、こちらは"docker-compose stop"→"docker-compose up"でリスタートすれば大丈夫です。2 No space left on your device
Dockerfileの設定によって、Dockerイメージの容量が大きくなってしまった場合に起きるエラーです。不要なイメージ並びにコンテナは以下のコマンドで簡単に削除できます。
$ docker image prune $ docker container pruneこれらのコマンドを入力すると、以下のメッセージが表示されます。
WARNING! This will remove all dangling images (もしくはcontainers). Are you sure you want to continue? [y/N]dangling imagesは不要なイメージという意味なので、"y"を入力してください。容量が確保され、PermissionError解決時と同様の主要なDockerコマンドを実行できるようになっているはずです。
もしもさらに削除する必要がある場合は、以下で削除するイメージを探します。
$ docker images消したいイメージのIDを確認した後、以下で削除します。
$ docker image rm -f (消したいイメージのID)参考文献
英語ですが、この本はRuby on RailsのプロジェクトでDockerを使う方法をかなり詳細に論じているので、おすすめです。
Docker for Rails Developers: https://pragprog.com/titles/ridocker/
残念ながら、英語も含めてRails用のDockerの資料はまとまっているものが少ないので、特にRails歴が浅く、Dockerについての易しい資料が読みたいということであれば、英語ですが読む価値はあると思います。
Mediumの英語の記事
本テーマに関してより詳細に、Mediumに英語で記事を書いたので、そちらもご覧になってみてください。
- 投稿日:2020-07-15T20:36:32+09:00
そうだ、画像をプレビューしよう。~part5~
前提
- ruby on rails 6.0.0 を使用。
- ユーザー機能はdeviseにより導入されているものとする。
- viewファイルは全てhaml形式とする。
- ちなみに使っているのはMacBook Air(Retina, 13-inch, 2020)です。
はじめに
前回(part4)のあらすじです。複数画像を含んだ商品情報の登録、編集機能までが実装できました。ひと通りの作業は終わった感じですね。
ちなみに前置きや手順などは part1 に詳しく記載してあるので気になったら是非見てやってください。
さぁ!残るはプレビューのみだ!
プレビューします
それではまずjsから記述していきます。
やることしてはフォームのときと似たような感じです。html生成用の関数を作って、新しいフォーム同時に表示させます。画像を削除した場合はプレビューも消えるようにしましょう。
app/assets/javascripts/product.js$(function() { // ~省略~ const buildImg = (index, url)=> { const html = `<img data-index="${index}" src="${url}" width="100px" height="100px">`; return html; } // ~省略~ $('#image-box').on('change', '.file', function(e) { const targetIndex = $(this).parent().data('index'); const file = e.target.files[0]; const blobUrl = window.URL.createObjectURL(file); if (img = $(`img[data-index="${targetIndex}"]`)[0]) { img.setAttribute('src', blobUrl); } else { $('#image-box').append(buildFileField(fileIndex[0])); fileIndex.shift(); fileIndex.push(fileIndex[fileIndex.length - 1] + 1) } }); // ~省略~ $('#image-box').on('click', '.remove', function() { // ~省略~ $(`img[data-index="${targetIndex}"]`).remove(); }); });長くなってしまいましたがこんなところです。複雑なのは真ん中の部分だけなのでそう身構えることもないと思います。
それでは順番に解説をつけていきましょう。
const buildImg = (index, url)=> { const html = `<img data-index="${index}" src="${url}" width="100px" height="100px">`; return html;まずはhtmlを生成するための関数ですが、特に難しいことは何もないので大丈夫だと思います。引数として渡されたurlで、大きさを指定しながら画像を表示しています。
$('#image-box').on('change', '.file', function(e) { // フォームに割り振られた固有のインデックスを取得。 const targetIndex = $(this).parent().data('index'); // 画像ファイルのweb上におけるURLを取得。 const file = e.target.files[0]; const blobUrl = window.URL.createObjectURL(file); // 該当するインデックスを持つ画像の有無で条件分岐 if (img = $(`img[data-index="${targetIndex}"]`)[0]) { // 前行で取得した画像のURLを差し替える。 img.setAttribute('src', blobUrl); } else { $('#previews').append(buildImg(targetIndex, blobUrl)); $('#image-box').append(buildFileField(fileIndex[0])); fileIndex.shift(); fileIndex.push(fileIndex[fileIndex.length - 1] + 1)次に画像を選択した際の処理です。内容としてはこんな感じ。
難しいのは5,6行目のURLの取得ですが、ここに関してはいまいち自分でも理解できていないので後ほど詳しく調べようと思っています。動作自体はこれでうまくいくのでひとまず安心してください。
ちなみにelse以降の部分は元々あった記述にプレビュー表示を追加しただけなので詳しくは前のpartをご覧くださいな。
こいつらをelse以下に移動した理由としては、今までの状態では画像を差し替えるだけでも新しいフォームが表示されてしまっていたためです。なのでこいつらをelse以下に置くことで、画像追加時のみフォームが追加されるようにしてます。
$(`img[data-index="${targetIndex}"]`).remove();最後はこいつですが、ただ削除ボタンに合わせてプレビューを削除しているだけです。ほんとにそれだけ。
これでjsの処理が完成したので、今度は編集画面にあらかじめ表示されるべきプレビューを追加していきます。
app/views/products/_form.html.haml= form_with model: @product, local: true do |f| = f.text_field :name, placeholder: 'name' #image-box #previews - if @product.persisted? - @product.images.each_with_index do |image, i| = image_tag image.src.url, data: { index: i }, width: '100', height: '100' = f.fields_for :images do |i| // ~省略~ = f.submit 'SEND'追記したのは @product.persisted? の部分です。productに紐づいた画像をurlで1枚ごと取り出し、image_tagを用いて表示させています。
ちなみに.each_with_index とは、引数を二つ設定することで、.eachと同時に一つずつ番号を割り振るメソッドです。
さて、これでプレビュー機能も実装完了となります。
ということは、最後に
遂に完成です!!結構な時間がかかりましたが、なんとか終えることができました。ふぅ。
完成とは言いつつもできたのは機能面だけなので、あとはゆっくりマークアップをしていく感じになります。
とはいえ仕様書の内容は全て実装することができました。実際のところ大して難しいことをしてるわけでもないのに長々と書いてしまってすみません。
いないとは思いますが、最後まで読んだ方がいらっしゃいましたら長らくどうもありがとうございました。何かの参考になれば幸いです。
- 投稿日:2020-07-15T20:11:56+09:00
【Postmanを使用】APIレスポンステストで詰まったところ
APIレスポンステストとは
自身が設定したHTTPのレスポンスのステータスコードと実際のデータを含んだレスポンスボディが正しく返ってくるかテストします!
これらは、実際にAPI仕様書等見れば記載があると思いますので、確認してみてください※補足です
●ステータスコードとは
今回のAPIによって返されるステータスコードは以下になります。
APIの動きによってこちらのコードと照らし合わせる事でテストします。
- 200: OK - リクエストは成功し、レスポンスとともに要求に応じた情報が返される。
- 201: OK - POSTリクエストでデータが一つ追加される。
- 401: Unauthorized - 認証失敗。認証が必要である。
- 403: Forbidden - 禁止されている。リソースにアクセスすることを拒否された。
- 404: Not Found - 未検出。リソースが見つからなかった。
- 500: internal server error - インストールの失敗、互換性の問題、サーバーのアクセス権限の設定ミスなどが原因として上げられます。
●レスポンスボディとは
自身が設定したカラムのデータの内容です。
GETリクエストの場合は単にリクエストボディに要求したデータが入っているかをテストします。
POST、PUT、 DELETE リクエストの際にはそれぞれ要求したデータ通りの動きをするかテストします。Postmanとは
APIの動作を確認するツールです
それだけではなく認証、テスト、ドキュメント作成、バージョン管理など幅広く活用できる便利なツール&サービスになっています!今回は、認証やテストしか触れていませんが、他の機能も今後触れていきたいですね
まずは下記URLでPostmanをダウンロードしてください!!
※この記事では基本的な使い方は紹介していないので、詳しい使い方は最後の行に参考URL記載しています!!
https://www.postman.com/
APIレスポンステストで詰まったところの例
ここからはAPIレスポンステストを具体例を挙げて紹介します!
割と基本的なところで詰まってしまったりしたんですけど、同じ様なところで詰まっている方いれば、参考にしてください
- 画像の挿入の仕方 今回は、form_dataを用いて画像挿入しました もし、base64文字列を読み込む設定がアプリにあるなら画像をbase64のコードにデコードしてjson形式で読み込めます!
- ストロングパラメーター設定時の入れ方
下記の様に許可しているキーを指定してあげないと
param is missing or the value is empty
となってしまいます。
- 関連付けしているカラムの入れ方
見ての通り多対多の関連づけがされているものがあります。
こういう時Postmanではどの様に記載するのかといいますと下記の様になります。
- ログイン状態の維持 アプリに
brfore_action: authenticate!
の様な認証設定があれば、ログイン状態でテストしたい時があります。そういう時は、下記の様にAutherizaitonのキーを押してTokenTypeを選択して、認証をすればログイン状態でテストできます
取得したアクセストークンを下記の様に入力すれば、ログイン状態でAPIのテストが実施できます。
※今回はBarear Tokenを指定しています。アプリによって設定が違うと思いますので、確認してみてください!!
まとめ
Postmanをしようして詰まったところをまとめてみました!
これから同じ様な内容で詰まる方の参考になれれば嬉しいです参考URL
- 今回の記事ではPostmanの使い方を詳しく紹介していないので下記に簡単に紹介されています!
- 投稿日:2020-07-15T19:28:26+09:00
rubyのバージョン上げてrails sしようとしたらLibrary not loaded
エラーの抜粋
/Users/hoge-user/projects/freee-auth/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require': dlopen(/Users/hoge-user/projects/freee-auth/vendor/bundle/ruby/2.6.0/gems/mysql2-0.4.10/lib/mysql2/mysql2.bundle, 9): Library not loaded: /usr/local/opt/openssl/lib/libssl.1.0.0.dylib (LoadError)
Referenced from: /Users/hoge-user/projects/freee-auth/vendor/bundle/ruby/2.6.0/gems/mysql2-0.4.10/lib/mysql2/mysql2.bundle
Reason: image not found - /Users/hoge-user/projects/freee-auth/vendor/bundle/ruby/2.6.0/gems/mysql2-0.4.10/lib/mysql2/mysql2.bundle
Library not loaded: /usr/local/opt/openssl/lib/libssl.1.0.0.dylib (LoadError)
と言っているのでbundle doctorThe following gems are missing OS dependencies: * mysql2: /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib * mysql2: /usr/local/opt/openssl/lib/libssl.1.0.0.dylibmysqlが依存しているopenssl系の何かが足りないと言っている。
rubyのバージョンアップに伴ってopensslを入れ替えたからか。
ということでmysqlをinstallしなおして依存しているgemライブラリを入れ直すbundle exec gem uninstall mysql2 bundle install # mysqlのgemも一緒に入るはずrails s すると正常に動いた
$ bundle exec rails s -p 3099
=> Booting Puma
=> Rails 5.2.4.3 application starting in development
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 4.3.1 (ruby 2.6.6-p146), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://127.0.0.1:3099
* Listening on tcp://[::1]:3099
- 投稿日:2020-07-15T19:16:51+09:00
未経験初学者がRails Serverを立てられなくて困ってた話。
はじめまして。たくまです。
自分なりに困ったことがあり解決ができた経験をアウトプットしてみようと思います。経緯
ProgateのWeb開発パスを勧めていくうちにうまくいかない箇所が出て来たので自分で行った解決法を共有しようと思います。
理解が浅くフワッとした内容なのでご指摘があればよろしくお願いします。問題
Web開発パス(Ruby on Rails)のSTEP4、[Ruby on Railsの環境構築をしてみよう!(macOS)]では最終的にはブラウザにてRailsサーバを立てて終わりになります。
しかし、エディタにコマンドを入力しても立てることができない!!
具体的には~ Could not find a JavaScript runtime. ~といった内容が表示されブラウザにRailsの表示ができませんでした。
ターミナルをいじる経験もあまりなかったため、エラーコードをググってみると下記のような記事が見つかりました。解決
どうやらJavaScriptに関することを言っているようです。
そこでエラーコードにあった"therubyracer"についてググってみると以下のような記事が見つかりました。開発環境構築に関して知識のなかった自分としてはこの記事を見ることにより落ち込まずに学習を続けることができました。
開発環境構築が鬼門な点だ。rubyにはrubygemsというライブラリの管理システムがある。Railsはたくさんのライブラリ(gem)を起動前にインストールする必要があるのだが、これが理不尽なエラーとともに失敗することが頻発する。慣れると対応策は分かってくるのだが、初心者だと「立ち尽くすしかない」と思う。
内容としてはtherubyracerという部品が古くなっており、新しくmini_racerという部品が広く普及していると言った内容。
→なんとなくAppleのSidecarと乗り物繋がりでフロントよりの技術てことでJavaScriptと関係あるかもなんて思ってた。実施
改めてレッスンで作成した sample_app < Gemfile 内容を確認するとmini_racerがコメントアウトされてる...
こいつのコメントアウトを外してあげて編集点を保存してくれる?らしい"bundle update"を実施しアップデート。
後に"Rails s"を実施すると無事にブラウザでServerを立ち上げることができました。あとがき
今回、はじめて問題点に関する解決法を記事にしてみましたが、gemの内容や自分と一緒の場所でつまずいている方が仮にこの記事を参考にしてくれた場合の動線の意識など反省点がや改善点が多々ありました。
今後もこのようなアウトプットを続けて行こうと思います。
- 投稿日:2020-07-15T18:34:50+09:00
高速なのは Range#cover? or Range#include? or ActiveSupport::TimeWithZone#between? それとも?
rubocop-performance
を確認していたら、Performance/RangeInclude: Use Range#cover? instead of Range#include?.
が出たのでベンチマークを取ってみました。
require 'bundler/setup' require 'benchmark_driver' Benchmark.driver do |x| x.prelude <<~RUBY require 'active_support/time' t = Time.current + 5.minutes RUBY x.report 'include?', %[ (Time.current..Float::INFINITY).include?(t) ] x.report 'cover?', %[ (Time.current..Float::INFINITY).cover?(t) ] x.report 'between?', %[ t.between?(Time.current, Float::INFINITY) ] x.report 'raw_function', %[ t >= Time.current && t <= Float::INFINITY ] end結果
Warming up -------------------------------------- include? 85.071k i/s - 86.456k times in 1.016282s (11.75μs/i) cover? 131.933k i/s - 135.600k times in 1.027793s (7.58μs/i) between? 276.108k i/s - 285.615k times in 1.034434s (3.62μs/i) raw_function 285.200k i/s - 291.396k times in 1.021725s (3.51μs/i) Calculating ------------------------------------- include? 125.985k i/s - 255.212k times in 2.025732s (7.94μs/i) cover? 133.774k i/s - 395.799k times in 2.958713s (7.48μs/i) between? 304.505k i/s - 828.322k times in 2.720227s (3.28μs/i) raw_function 304.348k i/s - 855.600k times in 2.811255s (3.29μs/i) Comparison: between?: 304504.7 i/s raw_function: 304348.1 i/s - 1.00x slower cover?: 133774.0 i/s - 2.28x slower include?: 125985.1 i/s - 2.42x slower
include?
からcover?
に変更したほうが早いが、between?
や便利機能使わずに普通に書いたraw_function
の方が早くなりました。 後者の2つは実行するタイミングで順位がわかりましたが、ほぼ誤差の範囲だと思います。ちなみに
benchmark_driver
では、 ymlで入力することもサポートしているので、# range.yml prelude: | require 'bundler/setup' require 'active_support/time' t = Time.current + 5.minutes benchmark: include?: (Time.current..Float::INFINITY).include?(t) cover?: (Time.current..Float::INFINITY).cover?(t) between?: t.between?(Time.current, Float::INFINITY) raw_function: t >= Time.current && t <= Float::INFINITYのようなファイルを用意して、
$ benchmark-driver range.yml
と書いても似たような結果を得られました。
- 投稿日:2020-07-15T18:24:04+09:00
【form_withのmodel:の意味】
form_withに記述するmodel:の意味
いまいち理解できていなかったのですがようやく意味がわかったのでアウトプットとして記述していきます。
上の画像はツイートの詳細画面からコメントを投稿するためのフォームを表示させるためのコードを記述しているのですが、
form_withに続くmodel:@tweet, @commentの二つをなぜ使用しているのかいまいち理解できていませんでしたが解決できましたので以下に理由を記述します。1点目
今回の場合はツイートに対してコメントをするという状況でありコメントは一つのツイートに紐付いている状態なのでコメント単体でフォームの送信先に指定することはできないため@tweet, @commentと紐付いているものとセットにして送信先を指定しているという理由。
2点目
@tweet,@commentという記述自体がルーティングを指定しているということ。
どういうことかといいますと、以下の画像を見てください。今回の場合はコメントを作成したいので発動させたいアクションはcommentsコントローラーのcreateアクションです。
そして一番左のprefixの項目を見ると、tweet_commentsというパスが記述されています。
要するにフォームを送信してコメントを作成するのであればこのルーティングを指定しなければいけないということです。そのルーティングを指定しているのがmodel: [@tweet,@comment]だというのが二点目の理由です。
以上です。
- 投稿日:2020-07-15T18:05:34+09:00
Unicornを再起動する
前提
RailsアプリをEC2にデプロイし、Unicornをアプリケーションサーバーとして使用しています。
開発環境でしたら、
rails s
で起動すれば良かったのですが、本番環境はサーバーの起動&停止方法が変わったので、メモとしてまとめました。サーバーを停止したい場合の手順
Unicornの起動確認
ps -ef | grep unicorn | grep -v grepvinaka 15533 1 0 08:02 ? 00:00:01 unicorn_rails master -c /var/www/rails/ShitsumonWa-/config/unicorn.conf.rb -D -E production vinaka 15537 15533 0 08:03 ? 00:00:00 unicorn_rails worker[0] -c /var/www/rails/ShitsumonWa-/config/unicorn.conf.rb -D -E production vinaka 15538 15533 0 08:03 ? 00:00:00 unicorn_rails worker[1] -c /var/www/rails/ShitsumonWa-/config/unicorn.conf.rb -D -E production
master、worker[0]、worker[1]
の
三つ出てきた場合、Unicornが起動しているようです。killする
今確認した、三つのうちの一番上の番号(master)
15533
をkillします
(毎回番号が変わるので都度ps -ef | grep unicorn | grep -v grepで番号を確認)kill -9 15533Unicornの停止確認
ps -ef | grep unicorn | grep -v grepすると何も表示されないはず。
表示されていないと、Unicornが停止しています。再度サーバーを立ち上げる時の手順
Unicornの停止確認
ps -ef | grep unicorn | grep -v grepUnicorn起動!
bundle exec unicorn_rails -c /var/www/rails/アプリ名/config/unicorn.conf.rb -D -E production何も出ないと、ちゃんと起動しているようです。
一応確認もする。
ps -ef | grep unicorn | grep -v grepvinaka 15740 1 1 08:48 ? 00:00:01 unicorn_rails master -c /var/www/rails/ShitsumonWa-/config/unicorn.conf.rb -D -E production vinaka 15744 15740 0 08:48 ? 00:00:00 unicorn_rails worker[0] -c /var/www/rails/ShitsumonWa-/config/unicorn.conf.rb -D -E production vinaka 15745 15740 0 08:48 ? 00:00:00 unicorn_rails worker[1] -c /var/www/rails/ShitsumonWa-/config/unicorn.conf.rb -D -E production三つ表示されました!起動されている。
さっきと番号が変わっているはず!ちなみに
master failed to start, check stderr log for detailsとエラーが出た場合は
cat log/unicorn.logでエラーの内容を確認する。
Unicornのバージョン(5.5以上だとエラーが出るみたいなのでGem file
でバージョン指定するのが良いです。)Gemfilegroup :production, :staging do gem 'unicorn', '5.4.1' endまた
unicorn_rails -c /var/www/rails/アプリ名/config/unicorn.conf.rb -D -E productionだと怒られることがあるので、
bundle exec
をつけてあげた方が無難。bundle exec unicorn_rails -c /var/www/rails/アプリ名/config/unicorn.conf.rb -D -E productionnginxも再起動
Unicorn
が無事に立ち上がったら、nginxも再起動してあげて完了です。sudo nginx -s reload終わりです。
- 投稿日:2020-07-15T17:23:12+09:00
Progate Ruby on Rails5 振り返り
プロゲート Ruby on Railsコース所感
プログラミングの学習に200時間ほど費やし、Railsコースに着手しました。
今までの学習ではスムーズに進んでこれたものの、今回は苦戦。
道場コース4の内容は完全には理解できていません。
復習できるように、メモとして記録を残します。
マイグレーションファイル関連
マイグレーションファイルとは:データベースを作成するときの設計図。
モデルとは:テーブルを操作するための特殊なクラス
モデルの作成rails g model Post content:textrails g model モデル名 カラム名:データ型
※モデル名は単数系で指定する。
マイグレーションファイルの作成rails g migration add_image_nameURLから値を取得する
ルーティングでハッシュを指定する。
routes.rbget "posts/:id" => "posts#show"params[ハッシュ]とすることで取得できる
controller.rbdef show @id = params[:id] end※params[:~~]で取得できる値は文字列であるため、比較等を行う場合には整形すること。
フォームの送信先の設定
posts/new.html.erb<%= form_tag("/posts/create") do%> <% end %>doを忘れないこと
post時のリンク記述方法
第三引数としてメソッドのpostを指定する。
<%= link_to("削除する","/posts/#{@post.id}/destroy",{method: "post"})%> <% end %>データベースへの保存処理での分岐
if ~~~の部分で保存処理は実行されていることに注意
(真偽値を返すだけではない)def update @post = Post.find_by(id: params[:id]) if @post.save redirect_to("/posts/index") else redirect_to("/posts/#{@post.id}/edit") end endデータベースを経由せず、直接ビューに表示する(render)
render(コントローラー名/ビュー名)と指定する
def update @post = Post.find_by(id: params[:id]) if @post.save redirect_to("/posts/index") else render("posts/edit") end end画像の送信欄
typeの指定を忘れないこと
<input name="image" type="file">画像の送信フォーム
{multipart: true}を指定する
<%= form_tag("~~~",{multipart: true}) do%>送信された画像の保存処理
受け取ったparams[:image]に対して、readメソッドを活用し画像データの中身を取得する。
File.binwrite("ファイルの場所","ファイルの中身")def update @user = User.find_by(id: params[:id]) @user.name = params[:name] @user.email = params[:email] @user.image_name = "#{@user.id}.jpg" image = params[:image] if params[:image] File.binwrite("public/user_images/#{@user.image_name}",image.read) end endセッションの設定
ページを移動しても、ユーザー情報を保持し続けるために、sessionという特殊な変数を活用する。
sessionに代入された値はブラウザに保存される。session[:user_id] = @user.id共通変数の定義
application.html.erbは全アクションから呼び出される。
そのため、全アクションで活用する変数を定義すると効率が良い。
before_action :アクション名 とすることでアクションが呼び出される前に「アクション名」が実行される。application.rbbefore_action :set_current_user def set_current_user @current_user = User.find_by(id: sessino[:user_id]) end投稿テーブルに持たせたuser_idから別テーブルに存在するユーザー情報を取得する
使用するデータが存在するテーブル(ここではPostモデル)に対してアクションを定義する
post.rbdef user return User.find_by(id: self.user_id) end活用例(投稿から、それに紐づくユーザーを取得する)
posts_controller.rbdef show @post = Post.find_by(id: params[:id]) @user = @post.user endユーザーテーブルに持たせたidから別テーブルに存在する投稿情報を取得する
使用するデータが存在するテーブル(ここではUserモデル)に対してアクションを定義する
user.rbdef posts return Post.where(user_id: self.id) end活用例(ユーザーから、それに紐づく投稿を全て取得し表示する)
show.html.erb<% @user.posts.each do |post|%> <img src="<%= "/user_images/#{post.user.image_name}" %>"> <%= link_to(post.user.name, "/users/#{post.user.id}") %> <%= link_to(post.content, "/posts/#{post.id}") %> <% end%>各ユーザーがいいねした投稿の一覧表示
ルーティングの指定
routes.rbget "users/:id/likes" => "users#likes"アクションの定義
users.controller.rbdef likes @user = User.find_by(id: params[:id]) @likes = Like.where(user_id: @user.id) endビューでの表記
likes.html.erb<% @likes.each do |like|%> <% post = Post.find_by(id: like.post_id)%> <img src="<%= "/user_images/#{post.user.image_name}" %>"> <%= link_to(post.user.name, "/users/#{post.user.id}") %> <%= link_to(post.content, "/posts/#{post.id}") %> <% end%>二行目のLikeテーブルのpots_idを用いて、対応する投稿を取得する部分を忘れないように。
- 投稿日:2020-07-15T16:52:03+09:00
「商品画像を差し替える」と心の中で思ったならッ!その時スデに行動は終わっているんだッ!~part4~
前提
- ruby on rails 6.0.0 を使用。
- ユーザー機能はdeviseにより導入されているものとする。
- viewファイルは全てhaml形式とする。
- ちなみに使っているのはMacBook Air(Retina, 13-inch, 2020)です。
はじめに
前回(part3)のあらすじです。jQueryを使用して新規登録画面にあんなことやこんなことを実装しました。
現在の状態としては、画像を選択すると新しいフォームが出現する、削除を押したら消える、という感じです。一見出来上がったように見えますが、これらは全てjsによるアクションなのでリロードされた編集画面では消えてしまいます。
なので今回のpartでは、編集画面にも新しいフォームを出現させる、といった作業になります。
ちなみに前置きや手順などは part1 に詳しく記載してあるので気になったら是非見てやってください。
やってみよう
やることとしては主に以下の3つです。
- 編集画面に新しいフォームを表示させる。
- 削除ボタンで、すでに登録された情報も削除できるようにする。
- 画像フォームがもつ固有のインデックスが被らないようにする。
こんな感じですね。さっそくやっていきますが、ビューファイルをいじる前にコントローラに少し追記をする必要があるので、そちらからいきましょう。
app/controllers/products_controller.rb#~省略~ private def product_params params.require(:product).permit(:name, images_attributes: [:src, :_destroy, :id]).merge(user_id: current_user.id) end #~省略~ endストロングパラメータに少し記述を増やしました。この_destroyというのは、関連づいた子モデルの情報を削除してくれるキーです。見慣れない形ですがちゃんとしたキーなので安心しましょう。
それではビューファイルを記述していきます。
app/views/products/_form.html.haml= form_with model: @product, local: true do |f| = f.text_field :name, placeholder: 'name' #image-box // ~省略~ - if @product.persisted? .group{ data: { index: @product.images.count } } = file_field_tag :src, name: "product[images_attributes][#{@product.images.count}][src]", class: 'file' .remove 削除 = f.submit 'SEND'追記したのは@product.persisted?の部分です。難しく見えますが、やっていることとしては前回jQueryで追加したフォーム部分をhaml形式にしただけです。新しいフォームを表示させようってやつですね。
このpersisted?ですが、こいつは利用したインスタンスが保存済みか否かを判断してくれます。要は新規なのかすでに登録された情報なのかってことです。とても便利なので覚えておきましょう。
表示はできましたが、現在の削除ボタンではすでにデータベースに登録された情報を削除することができません。なので先ほど追加した_destroyキーを使って削除できるようにする必要があります。
app/views/products/_form.html.haml= form_with model: @product, local: true do |f| = f.text_field :name, placeholder: 'name' #image-box = f.fields_for :images do |i| // ~省略~ - if @product.persisted? = i.check_box :_destroy, data: { index: i.index }, class: 'hidden' - if @product.persisted? // ~省略~ = f.submit 'SEND'先ほどとは別に、image-boxの内部にもpersisted?で文を追加しました。_destroyキーを持ったチェックボックスにチェックを入れると、データベース上から対応するレコードが削除される、といった記述です。なぜそうなるのかは詳しく書きませんが(自分が理解しきれていないので...)、こういう書き方があるのか程度に覚えておくといいと思います。
さて、これで仕組み自体はできました。ですが削除ボタンと別にチェックボックスがあるのはよろしくないのでこいつらを連動させていきます。
app/assets/javascripts/product.js$(function() { // ~省略~ $('.hidden').hide(); $('#image-box').on('click', '.remove', function() { // フォームに割り振られた固有のインデックスを取得。 const targetIndex = $(this).parent().data('index') // 取得したインデックスに対応するチェックボックスを取得。 const hiddenCheck = $(`input[data-index="${targetIndex}"].hidden`); // チェックボックスが存在する場合チェックを入れる。 if (hiddenCheck) hiddenCheck.prop('checked', true); ~省略~ }); });image-boxのクリックイベントに処理を追加しました。一行ずつの解説も書いておきました。やっていること自体は簡単なので、jQueryの基礎が分かっていれば問題はないと思います。
削除ボタンを押せばチェックもできるようになったのでチェックボックスは非表示にしておきましょう。今回はjsの.hide()を使用してやりましたが、cssで display: none にしていただいても構いません。
それでは最後にインデックス被りを防止して実装は完了となります。
app/assets/javascripts/product.js$(function() { // ~省略~ let fileIndex = [1,2,3,4,5] lastIndex = $('.group:last').data('index'); fileIndex.splice(0, lastIndex); // ~省略~ });fileIndexの定義部分に記述を追加しましょう。考え方としては、現在使われている最後のインデックスを取得し、その値でfileindexの値を入れ替える、といった感じです。
ちなみに.spliceについてですが、こいつは指定した要素から数えて好きな分だけ取り除き、ついでに要素の追加もできてしまう優れものです。
今回は第一引数で指定した数以降の値を全て取り除き、第二引数で指定した値を挿入してくれています。やれることが多い分、書き方に多少複雑な部分があるので是非詳しく調べてみてください。
さて、以上で編集機能自体の実装は終了となります!
最後に
とうとう出来上がりましたね。いやぁ長かった。
仕様書でいう「画像をの差し替えを一枚ごとにできる。」は達成できたとしていいと思います。あとはわかりやすいように画像のプレビューをつければ完成です。
アプリで画像をプレビューする前に記事に画像をプレビューしろよって天の声が聞こえますが気にしないことにします。読みづらくてすみません。
それではまた次のpartで!!
- 投稿日:2020-07-15T16:25:15+09:00
アクセスが多いテーブルのカラムを安全に変更(削除やリネーム)する方法
概要
これはアクセスの多いテーブルのカラムを変更したときに、
カラムを新しく変更したはずなのに、なぜかコードは(すでに存在しない)古いカラムを参照してしまい、エラーが発生してしまう問題を解決するための方法を記したものです。前提条件
Rails 5.1
heroku解決策1「heroku restart」
先日、自分のカラム の英語の命名ミスに気づき、
AnswerHistoryテーブルのweekly_continuationというカラムを、continuation_all_weekに変更しました。その後、カラム変更に伴うコードも変更してデプロイし、
heroku run rails db:migrate
したのですが、その後しばらくSentryより、以下の報告を繰り返し繰り返し受け続けました。PG::UndefinedColumn: ERROR: column answer_histories.weekly_continuation does not exist
つまり、DBのカラム名はきちんと変更されているにも関わらず、コードは古いカラム名を参照し続けているということです。
僕は最初この警告を、一時的な不具合だとばかり思っており、無視していました。
「ユーザーがページを遷移したり更新すれば直るやろ」と。
しかし、自分の端末でも同じ問題が起こり、かつページを遷移しても更新しても解決しなかったとき、これがサービス運営者が解決すべき障害だと気づきました。
(それもこの障害は、自分のサービスの中でももっともアクセスが多いページで起きていました....無念....)結局このときは、heroku restartを行うことで障害を解決することができました。
「なぜheroku restartで障害を解決できたのか?」
解決しておきながら、自分はその理由がわかりませんでした。よりベテランの開発者の方にこのことを聞くと、
どうやら、heroku run rails db:migrate
する直前にデプロイされたとき、Railsのアプリケーションサーバーがschema informationのキャッシュを持ってしまっていたがために、アプリケーションサーバーを再起動してキャッシュを消す必要があり、
そのためheroku restartをして、dynoごとアプリケーションサーバーを再起動する必要があったそうです。なので、もし教科書的に、
git push heroku
heroku run rails db:migrate
を行いカラムを変更する場合には、
追加でheroku restart
もしておくことで、
heroku のdynoと一緒に、アプリケーションサーバーも再起動されるおかげで、schema informationのキャッシュもクリアされるので、コードがきちんと新しいカラムを参照してくれるようになります。解決策2「Procfileからリリース時にdb:migrateを行う」
今回の障害で初めて知ったのですが、
herokuではProcfileなるものを設定することで、
heroku run rails db:migrate
のようなコマンドをリリース時に自動で実行してくれるようになります。
参照: https://devcenter.heroku.com/articles/release-phaseこのProcfileでdb:migrateを行うメリットとしては、「デプロイした後」ではなく「デプロイ直前」にdb:migrateしてくれるという点です。
herokuではデプロイ時に、自動で真新しい別のDynoを立ててくれます。
そして真新しいDynoはheroku restartされた状態と同じなため、
新しいDynoにあるアプリケーションサーバーはschema informationのキャッシュをもつこともなく、結果として、heroku restartせずとも、カラムの変更によるアプリケーションのエラーに見舞われることがないそうです。ただしエラーが起きないのは、新しいDynoにアクセスしてきたユーザーのみで、古いDynoにアクセスしていたユーザーは、エラーに見舞われます。
Procfileからデプロイ直前に自動でdb:migrateまで行うには、次のように設定します。
Procfilerelease: bin/rails db:migrate解決策3『段階的にデプロイする』
より安全にカラムを変更するときに、実務においては段階的にデプロイする手法が採用されているそうです。
db:migrateを伴うリリースだけ先にデプロイし、本番環境でdb:migrateを行ってから、
別のタイミングで、変更したカラムを利用するソースコードをリリースするという形です。たとえば今回のように、AnswerHistoryのweekly_continuationをcontinuation_all_weekにリネームする場合には、次のように3回のデプロイします。
1, continuation_all_weekカラムを新しく追加するmigrationファイルを作成。
2, デプロイ&db:migrate。
3, weekly_continuationカラムではなく、continuation_all_weekカラムを参照するように、コードを変更する
4, デプロイ&weekly_continuationのデータをcontinuation_all_weekにコピー。
5, weekly_continuationがコードから呼ばれていない状態にする。
6, weekly_continuationカラムを削除するmigrationファイルを作成。
7, デプロイ&db:migrate。端的にまとめると、
テーブルへのアクセスが多い場合は、古いカラムをリネームするのではなく、新しいカラムを追加して、コードが古いカラムを参照しない状態をつくった後に、古いカラムを削除することで「リネームしたように見せること」が良い、ということらしいです。
面倒かもしれませんが、いちばん安全な方法ですね。
呼ばれる頻度の低いテーブルのカラムなら、これほど気を使わなくても良いかもしれませんが、
今回僕が障害を発生させたAnswerHistoryテーブルは、1日に1万件以上も生成されるほど利用頻度の高いものだったので、以降、カラムのリネームなどを行う場合は、こちらの段階的なデプロイを採用したいと思います。以上となります!
僕は恐ろしく無知なので、他にもアドバイスなどございましたら、ぜひ教えてください。
- 投稿日:2020-07-15T16:25:15+09:00
アクセスが多いテーブルのカラムを安全に変更(削除やリネーム)する方法【Rails,heroku】
概要
これはアクセスの多いテーブルのカラムを変更したときに、
カラムを新しく変更したはずなのに、なぜかコードは(すでに存在しない)古いカラムを参照してしまい、エラーが発生してしまう問題を解決するための方法を記したものです。前提条件
Rails 5.1
heroku解決策1「heroku restart」
先日、自分のカラム の英語の命名ミスに気づき、
AnswerHistoryテーブルのweekly_continuationというカラムを、continuation_all_weekに変更しました。その後、カラム変更に伴うコードも変更してデプロイし、
heroku run rails db:migrate
したのですが、その後しばらくSentryより、以下の報告を繰り返し繰り返し受け続けました。PG::UndefinedColumn: ERROR: column answer_histories.weekly_continuation does not exist
つまり、DBのカラム名はきちんと変更されているにも関わらず、コードは古いカラム名を参照し続けているということです。
僕は最初この警告を、一時的な不具合だとばかり思っており、無視していました。
「ユーザーがページを遷移したり更新すれば直るやろ」と。
しかし、自分の端末でも同じ問題が起こり、かつページを遷移しても更新しても解決しなかったとき、これがサービス運営者が解決すべき障害だと気づきました。
(それもこの障害は、自分のサービスの中でももっともアクセスが多いページで起きていました....無念....)結局このときは、heroku restartを行うことで障害を解決することができました。
「なぜheroku restartで障害を解決できたのか?」
解決しておきながら、自分はその理由がわかりませんでした。よりベテランの開発者の方にこのことを聞くと、
どうやら、heroku run rails db:migrate
する直前にデプロイされたとき、Railsのアプリケーションサーバーがschema informationのキャッシュを持ってしまっていたがために、アプリケーションサーバーを再起動してキャッシュを消す必要がありました。
heroku restartすることでdynoが再起動したので、アプリケーションサーバーのschema informationも新しくなり障害が解決したということでした。なので、もし教科書的に、
git push heroku
heroku run rails db:migrate
を行いカラムを変更する場合には、
追加でheroku restart
もしておくことで、
heroku のdynoと一緒に、アプリケーションサーバーも再起動されるおかげで、schema informationのキャッシュもクリアされるので、コードがきちんと新しいカラムを参照してくれるようになります。解決策2「Procfileからリリース時にdb:migrateを行う」
今回の障害で初めて知ったのですが、
herokuではProcfileなるものを設定することで、
heroku run rails db:migrate
のようなコマンドをリリース時に自動で実行してくれるようになります。
参照: https://devcenter.heroku.com/articles/release-phaseこのProcfileでdb:migrateを行うメリットとしては、「デプロイした後」ではなく「デプロイ直前」にdb:migrateしてくれるという点です。
herokuではデプロイ時に、自動で真新しい別のDynoを立ててくれます。
そして真新しいDynoはheroku restartされた状態と同じなため、
新しいDynoにあるアプリケーションサーバーはschema informationのキャッシュをもつこともなく、結果として、heroku restartせずとも、カラムの変更によるアプリケーションのエラーに見舞われることがないそうです。ただしエラーが起きないのは、新しいDynoにアクセスしてきたユーザーのみで、古いDynoにアクセスしていたユーザーは、エラーに見舞われます。
Procfileからデプロイ直前に自動でdb:migrateまで行うには、次のように設定します。
Procfilerelease: bin/rails db:migrate解決策3『段階的にデプロイする』
より安全にカラムを変更するときに、実務においては段階的にデプロイする手法が採用されているそうです。
db:migrateを伴うリリースだけ先にデプロイし、本番環境でdb:migrateを行ってから、
別のタイミングで、変更したカラムを利用するソースコードをリリースするという形です。たとえば今回のように、AnswerHistoryのweekly_continuationをcontinuation_all_weekにリネームする場合には、次のように3回のデプロイします。
1, continuation_all_weekカラムを新しく追加するmigrationファイルを作成。
2, デプロイ&db:migrate。
3, weekly_continuationカラムではなく、continuation_all_weekカラムを参照するように、コードを変更する
4, デプロイ&weekly_continuationのデータをcontinuation_all_weekにコピー。
5, weekly_continuationがコードから呼ばれていない状態にする。
6, weekly_continuationカラムを削除するmigrationファイルを作成。
7, デプロイ&db:migrate。端的にまとめると、
テーブルへのアクセスが多い場合は、古いカラムをリネームするのではなく、新しいカラムを追加して、コードが古いカラムを参照しない状態をつくった後に、古いカラムを削除することで「リネームしたように見せること」が良い、ということらしいです。
面倒かもしれませんが、いちばん安全な方法ですね。
呼ばれる頻度の低いテーブルのカラムなら、これほど気を使わなくても良いかもしれませんが、
今回僕が障害を発生させたAnswerHistoryテーブルは、1日に1万件以上も生成されるほど利用頻度の高いものだったので、以降、カラムのリネームなどを行う場合は、こちらの段階的なデプロイを採用したいと思います。以上となります!
僕は恐ろしく無知なので、他にもアドバイスなどございましたら、ぜひ教えてください。
- 投稿日:2020-07-15T15:41:29+09:00
Railsで学ぶXSS脆弱性
webセキュリティについて勉強中なので、学習内容を公開します
スカスカ記事ですがお許しください演習環境
- Ubuntu 18.04.4
- Ruby (2.6.6)
- Rails (5.2.4.3)
- database adapter mysql2 (0.5.3)
- mysql (5.7.30)
記事投稿、コメント投稿、ブックマークなどが出来る演習用アプリ「TARGET」
GitHubリポジトリ https://github.com/Fumitaka1/targetXSS脆弱性とは
XSSには反射型、持続型、DOMベースの3タイプに分類され、単にXSSというと反射型XSSを指すことが多いようです。
上記の3タイプに共通する性質として「外部からのパラメータを用いてwebページを出力しているアプリケーションにおいて、任意のhtml要素や属性が挿入出来てしまう」所がXSS脆弱性の肝です。
挿入されたスクリプトによってセッションや入力データが流出したり、有害な操作やリダイレクトが可能になるなど多くの被害が発生します。
スクリプトタグを含む不正なコメントを投稿すると、ブラウザはスクリプトタグとして解釈します。
演習用アプリではRailsの機能によりcookieのHttponly属性が付与されているので、Javascriptでセッションを取得することはできません。
しかし任意のページにリダイレクトしたり、不正なフォームをもとのページに重ねて表示することが出来ます。なぜ発生するのか
上記の例では外部からのパラメータを用いてHTML要素内容を出力する際に、特殊文字[" ' & < >]をエスケープしていないことが原因。
等号記号が2つのERB式<%== @comment.content %>
で意図的にエスケープを回避している。XSS脆弱性が発生しうる箇所には①HTML属性値②HTML要素内容③リンク④イベントハンドラなどがあり、それぞれエスケープの方法が異なるので場所にあったエスケープを行う必要がある。
各々のエスケープ方法については後日記事にします。
- 投稿日:2020-07-15T15:33:30+09:00
【Rails】本番環境でも開発環境と同じようにエラー画面を確認する方法【デプロイ】
【経緯】
本番環境だと、ローカル環境のようなエラー画面が出ないため、エラーが特定しにくいものです。と思っていたのですが、本番環境でもエラー画面を出力する方法がありました。この方法を行うことでエラー解決がグッと近くなります!
【バージョンやら環境】
Rails 5.2.4.3
Ruby 2.5.1
macOS Catalina 10.15.4
Capistrano 3.14.1【前提】
Capistranoが動く(Capistranoの自動デプロイが成功している)
【解決法】
config/environments/production.rbconfig.consider_all_requests_local = falseを
config/environments/production.rbconfig.consider_all_requests_local = trueにしましょう。こうすると、本番環境でも開発環境(ローカル)と同じエラー画面が出ます。
(ただし、最終的にfalseに戻すのを忘れないようにしましょう。
一般の方や企業の方が本番環境を見た時にエラー画面が出たら変ですからね?)
この後、①Githubのプッシュ(最新の状態に)
②デプロイを行う。(ローカルで直してもデプロイしないと意味がないので!)
ターミナル(アプリのディレクトリにて)bundle exec cap production deploy【一部参考にしたサイト】
【あとがき】
デプロイの作業は地道ですね。
成功した時の喜びはかなり大きい。
がんばれデプロイ担当者(いつもいってる)
- 投稿日:2020-07-15T15:01:38+09:00
【N+1問題】
N+1問題とは
アソシエーションでモデル同士を関連づけている場合に起こる問題のこと。
例えばツイッターのようなアプリでユーザーと投稿が紐付いている場合に
トップページで投稿を一覧表示させる時に各投稿ごとに紐付いているユーザーを検索するために毎回毎回データベースにアクセスしているとアプリのパフォーマンスが下がってしまう。1万投稿ある場合は1万回データベースにアクセスしなければいけないということ。
この問題のことをN+1問題という。
この問題を解決するためのメソッドが
includeメソッド
と言われるメソッド。
このメソッドは毎回毎回データベースにアクセスして紐づくデータを検索していたところを、
一回で全データを取得してくれる。そのため1万投稿あるとしてもデータベースへのアクセス「は一度で済む。
上の画像のようにindexアクションの処理内容の箇所にincludes(:紐づくモデル)とすれば使用できる。
これでツイートなどを一覧表示する際に投稿がいくら増えたとしてもパフォーマンスを下げずに済む。
- 投稿日:2020-07-15T14:05:59+09:00
rails generateコマンドで不要なファイルを生成しない
はじめに
rails generate ...
は必要な関連ファイルを自動生成してくれる便利なコマンドですが、「このファイルは不要だな」というものがあった場合、生成する度に削除していては手間がかかります。そこで、自動生成するファイルを設定する(不要なファイルは生成しない)方法について書いています。
参考までに環境はこちら。
- Ruby 2.5.1
- Rails 5.2.4
generateコマンドで自動生成されるファイル
まず、どのコマンドでどのファイルが自動生成されるかを次の一覧にまとめています。
コマンド名 コントローラ ビュー モデル マイグレーション アセット ルート テスト ヘルパー scaffold ○ ○ ○ ○ ○ ○ ○ ○ scaffold_controller ○ ○ × × × × ○ ○ controller ○ ○ × × ○ ○ ○ ○ model × × ○ ○ × × ○ × migration × × × ○ × × ○ × application.rbを編集する
設定方法は簡単で、
application.rb
に設定を書き込めばOKです。例えば何も設定せず次のコマンドを打った場合、
rails g controller StaticPages home次のファイルが生成されます。
create app/controllers/static_pages_controller.rb route get 'static_pages/home' invoke erb create app/views/static_pages create app/views/static_pages/home.html.erb invoke test_unit create test/controllers/static_pages_controller_test.rb invoke helper create app/helpers/static_pages_helper.rb invoke test_unit invoke assets invoke coffee create app/assets/javascripts/static_pages.coffee invoke scss create app/assets/stylesheets/static_pages.scssここで、以下のファイルが不要だと思ったとします。
- CSS, JavaScriptファイル
- testファイル
その場合、次のように設定します。
module XXX class Application < Rails::Application config.load_defaults 5.2 config.generators do |g| # ここから追記 g.assets false # CSS, JavaScriptファイル生成せず g.skip_routes false # trueならroutes.rb変更せず、falseなら通常通り変更 g.test_framework false # testファイル生成せず end # ここまで end endちなみに
g.assets
は、g.javascripts
とg.stylesheets
に分けて記述することもできます。この状態で再度
rails generate ...
を行うと、次のように生成されるファイルが減っていることがわかります。create app/controllers/static_pages_controller.rb route get 'static_pages/home' invoke erb create app/views/static_pages create app/views/static_pages/home.html.erb invoke helper create app/helpers/static_pages_helper.rbだいぶスッキリしました。以上。
- 投稿日:2020-07-15T13:34:15+09:00
【Rails】Scopeの使い方
開発環境
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina基本構文
class モデル名 < ApplicationRecord scope :スコープ名, -> { 条件式 } end使用例
下記のようユーザーのIDを降順にして5つだけ表示させたいと仮定します。
users_controller.rbUser.order(id: desc).limit(5)1.引数なし
models/user.rbclass User < ApplicationRecord scope :recent, -> { order(id: :desc).limit(5) } endusers_controller.rbUser.recent2.引数あり
models/user.rbclass User < ApplicationRecord scope :recent, -> (count) { order(id: :desc).limit(count) } endusers_controller.rbUser.recent(5)
- 投稿日:2020-07-15T12:57:52+09:00
railsアプリをawsでインフラ構築する際に出たエラー
今回はこちらの記事を参考に、始めて作成したrailsのappをAWSを使用してインフラ構築に挑戦した。
https://qiita.com/naoki_mochizuki/items/814e0979217b1a25aa3eEC2インスタンスの環境構築
$git make gcc-c++ patch
git: 'make' is not a git command. See 'git --help'.
The most similar commands are blame, merge, stageというエラーが出た。これはコメントを削除し、1行のコマンドとして実行する必要があった。
$sudo yum install git make gcc-c++ patch openssl-devel libyaml-devel libffi-devel libicu-devel libxml2 libxslt libxml2-devel libxslt-devel zlib-devel readline-devel mysql mysql-server mysql-devel ImageMagick ImageMagick-devel epel-release
$sudo mkdir www
mkdir: ディレクトリ `www' を作成できません: File exists
というエラーが出た。既に存在しているらしいが作成した記憶はない、、、
そのまま使用するか迷ったが、一応別名(XYZ)でファイルを作成して代用した。gitとの連携、アプリのクローン
$
cat aws_git_rsa.pub
cat: aws_git_rsa.pub: No such file or directoryこれは公開鍵を作成した際に通常のコマンド入力と操作が違ったため、:aws_git_rsaのうしろに半角スペースを付けてしまったのが原因だった。(凡ミス。。)
$ ssh-keygen -t rsa
Enter file in which to save the key ():aws_git_rsa(この部分に半角スペース)
鍵を再度作成したら無事に公開鍵の中身を出現させることができた。MySQLの設定
Mysqlの起動を試みたところ、次のエラーが発生
$sudo service mysqld start #mysqldの起動
Redirecting to /bin/systemctl start mysqld.service
Failed to start mysqld.service: Unit not found.
以下の記事を参考に次のコマンドを実行したところうまくいった。
https://qiita.com/yuta-38/items/4074f5ada9e22088c8dd
$ sudo yum install -y mariadb-server
$ sudo systemctl enable mariadb
$ sudo service mariadb start
また、Amazon Linux2ではyumでmysqlをインストールしようとするとmariaDBをインストールしようとするらしい。
私は実践しなかったが、以下の記事が参考になるかもしれない。
https://qiita.com/hamham/items/fd77bb0bb167a150dc8e#mysql57%E3%81%AE%E5%B0%8E%E5%85%A5Nginxの起動
以下のコマンドを入力したがNginxが起動しない。
$ sudo service nginx start
Redirecting to /bin/systemctl start nginx.service
Job for nginx.service failed because the control process exited with error code. See "systemctl status nginx.service" and "journalctl -xe" for details.エラーの指示通りに以下のコマンドを実行
$ systemctl status nginx.service
nginx.service - The nginx HTTP and reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; disabled; vendor preset: disabled)
Active: failed (Result: exit-code) since 月 2020-07-13 09:10:04 UTC; 57s ago
Process: 11360 ExecStart=/usr/sbin/nginx (code=exited, status=1/FAILURE)
Process: 11356 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=0/SUCCESS)
Process: 11355 ExecStartPre=/usr/bin/rm -f /run/nginx.pid (code=exited, status=0/SUCCESS)原因が分からなかったので以下も実行。
$sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful設定ファイルに問題はなさそうだったので、困ったことになった。
色々調べているうちに以下のコマンドを発見。
$ sudo service nginx status -l
Redirecting to /bin/systemctl status -l nginx.service
● nginx.service - The nginx HTTP and reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; disabled; vendor preset: disabled)
Active: failed (Result: exit-code) since 月 2020-07-13 09:10:04 UTC; 27min ago
Process: 11360 ExecStart=/usr/sbin/nginx (code=exited, status=1/FAILURE)
Process: 11356 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=0/SUCCESS)
Process: 11355 ExecStartPre=/usr/bin/rm -f /run/nginx.pid (code=exited, status=0/SUCCESS)
7月 13 09:10:02 ip-10-0-10-10.ap-northeast-1.compute.internal nginx[11360]: nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
7月 13 09:10:03 ip-10-0-10-10.ap-northeast-1.compute.internal nginx[11360]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
7月 13 09:10:03 ip-10-0-10-10.ap-northeast-1.compute.internal nginx[11360]: nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
7月 13 09:10:03 ip-10-0-10-10.ap-northeast-1.compute.internal nginx[11360]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
7月 13 09:10:03 ip-10-0-10-10.ap-northeast-1.compute.internal nginx[11360]: nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
7月 13 09:10:04 ip-10-0-10-10.ap-northeast-1.compute.internal nginx[11360]: nginx: [emerg] still could not bind()
7月 13 09:10:04 ip-10-0-10-10.ap-northeast-1.compute.internal systemd[1]: nginx.service: control process exited, code=exited status=1
7月 13 09:10:04 ip-10-0-10-10.ap-northeast-1.compute.internal systemd[1]: Failed to start The nginx HTTP and reverse proxy server.
7月 13 09:10:04 ip-10-0-10-10.ap-northeast-1.compute.internal systemd[1]: Unit nginx.service entered failed state.
7月 13 09:10:04 ip-10-0-10-10.ap-northeast-1.compute.internal systemd[1]: nginx.service failed.上記によると、設定した80番ポートは既に起動中のようだ。
原因を考えた結果、以前あるweb講座でApacheのインストール及び自動起動設定をしていたのを思い出した。
$ sudo systemctl status httpd.service
● httpd.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled)
Active: active (running) since 日 2020-07-12 06:24:44 UTC; 2 days ago
Docs: man:httpd.service(8)
Main PID: 21760 (httpd)
Status: "Total requests: 149; Idle/Busy workers 100/0;Requests/sec: 0.00084; Bytes served/sec: 2 B/sec"
CGroup: /system.slice/httpd.service
├─20766 /usr/sbin/httpd -DFOREGROUND
├─21049 /usr/sbin/httpd -DFOREGROUND
├─21055 /usr/sbin/httpd -DFOREGROUND
├─21056 /usr/sbin/httpd -DFOREGROUND
├─21760 /usr/sbin/httpd -DFOREGROUND
├─21761 /usr/sbin/httpd -DFOREGROUND
├─21762 /usr/sbin/httpd -DFOREGROUND
├─21763 /usr/sbin/httpd -DFOREGROUND
├─21764 /usr/sbin/httpd -DFOREGROUND
├─21765 /usr/sbin/httpd -DFOREGROUND
└─21887 /usr/sbin/httpd -DFOREGROUND
案の定Apacheが既に動いていた。自動起動を無効&停止後に、NginXの起動コマンドを実行したら無事に起動できた。インフラ構築前にUdemyで購入した以下の講座が効いてテンポよく理解できた。
https://www.udemy.com/course/aws-and-infra/
- 投稿日:2020-07-15T12:23:44+09:00
logrageではRails.loggerのログ出力がJSON形式にならない問題
はじめに
RailsログをJSON形式にしたいだけなんですが、解決方法が意外とめんどくさかったので、ググってたどり着いた誰かのためにメモを残しておきます。
「Railsログ JSON」で雑にググるとlogrageというgemをいれるとよさそうな感じです。
https://github.com/roidrage/logragegem入れるだけでバーンといいかんじのJSONになることを期待したのですが、以下の記事に書いてあるとおり、このgemはコントローラのアクセスログや例外しかJSON形式で出ません。
https://qiita.com/amanekey/items/b921ef73d871dac299ebおもむろに
Rails.logger.info("foo")
とかしてもJSONにならなくてそのまま出力されちゃう。
つまりこんなかんじ。{"method":"GET","path":"/","format":"html","controller":"RootPagesController","action":"index","status":200,"duration":1471.86,"view":1429.04,"db":11.39,"level":"INFO","tag":"rails.controller","uuid":"1ecfefb7-7c5e-4bb7-ada8-711e6743a634","ip":"172.20.0.1","time":"2020-07-10T13:42:11+09:00"} foo1行目がlogrageが出力するアクセスログ、
2行目が普通にアプリケーション内でおもむろにRails.logger.info("foo")
した場合のログです。
ダメじゃん。上記の参考記事では最終的にRails.loggerを使用せずに、独自に定義したMyLogger.runという専用のインターフェースを使ってログを出力していました。
しかしながら、既存のコード量が多いと、Rails.loggerを使っている箇所を全部書き換えるなど現実的ではないです。
一方、単純にLogger::Formatter
を継承した独自のログフォーマッタを定義してRails.loggerの出力をJSON形式にしても、logrageで出力できるようなrequest_id
などのメタデータを付与することができません。解決策
なんかよい方法はないかとlogrageのissueを漁っていたら以下を見つけました。
https://github.com/roidrage/lograge/issues/255#issuecomment-592212635
要約すると、まず
Logger::Formatter
を継承した独自のログフォーマッタを定義してRails.loggerの出力をJSON形式にしておきます。次にActiveSupport::TaggedLogging
経由でrequest_id
などのコンテキストのメタデータをログの本文に付与できるので、ログの本文からrequest_id
などのタグ部分をパースして、JSONを構築しようというハックです。
なるほど。その発想はなかった。上記のissueのコメントでは、
->
という区切り文字をマジックワードとしてタグの最後に使って、ログの本文(msg
変数)からタグ部分とメッセージ部分を分離しているようでしたが、もうちょっと調べると、Logger::Formatter#call
の中でcurrent_tags
が見えることに気づいたので、もうちょっとマシに書けそうです。
また上のissueのサンプルコードでは、Rails.loggerの出力全体をJSONにしているので、logrageが生成するJSON形式のログがmsg変数に入ってきて、JSON in JSONになってしまい若干イマイチです。できれば入れ子のJSONはパースしてマージしたい。というわけで、もうちょっとマシにしてみたのがこちらです。issueのコメントにも書いてみましたが、若干補足コメント付きで、コードのサンプルをここにも貼っておきます。
https://github.com/roidrage/lograge/issues/255#issuecomment-657328032
手元の環境はRails5.2+lograge v0.11.2です。
class JsonLogFormatter < Logger::Formatter def call(severity, _time, _progname, msg) log = { time: _time.iso8601(6), level: severity, progname: _progname, type: "default", } # タグが付いてる場合 unless current_tags&.empty? # タグのkey=valueのmapを作ってマージ tagged = Rails.application.config.log_tags.zip(current_tags).to_h log.merge!(tagged) # msgの先頭に入ってるタグを削除 msg = msg&.split(' ', current_tags.size + 1)&.last end begin # msgがJSON形式の場合は JSON in JSON をフラットにしてマージする parsed = JSON.parse(msg).symbolize_keys log.merge!(parsed) rescue JSON::ParserError # JSON形式ではない場合は、meessageというkeyに保存 log.merge!({message: msg}) end log.to_json + "\n" end endlograge自体の設定は、各自よしなにして下さい。
config/initializers/lograge.rbRails.application.configure do config.lograge.enabled = true config.lograge.formatter = Lograge::Formatters::Json.new config.lograge.custom_options = lambda do |event| data = { level: 'INFO', type: 'access', } # customize other fields # (snip.) data end end
config/environments/*.rb
などに定義したログフォーマッタを差し込みます。
付与するタグはコントローラのrequest
経由で見えるものなら付与できますが、最低限request_id
があれば紐付け可能。config/environments/development.rblogger = ActiveSupport::Logger.new(STDOUT) logger.formatter = ::JsonLogFormatter.new config.log_tags = [:request_id] config.logger = ActiveSupport::TaggedLogging.new(logger)
Rails.logger.info("foo")
のログ{"time":"2020-07-13T09:57:47.558838+09:00","level":"INFO","progname":null,"type":"default","request_id":"5a48d951-a04b-43fe-b462-f57277b55291","message":"foo"}logrageのログ
{"time":"2020-07-13T09:57:48.744866+09:00","level":"INFO","progname":null,"type":"access","request_id":"5a48d951-a04b-43fe-b462-f57277b55291","method":"GET","path":"/","format":"html","controller":"RootPagesController","action":"index","status":200,"duration":1675.8,"view":1150.5,"db":15.37,"user_id":null,"params":"{}"}原理的にlogrageのコンテキストをすべてRails.logger側のログに出力することはできないんですが、最低限request_idがどちらにも含まれていれば、紐付けできるしいいやという作戦です。
おわりに
RailsログをJSON形式にしたいだけなんですが、なんでこんなにめんどくさいんでしょうか?
もっとマシな解決方法を知ってる人がいたら教えて下さい。
- 投稿日:2020-07-15T11:53:34+09:00
[Rails][RSpec]導入と書き方 〜userモデル編〜
これはなに
RailsでのRSpec導入とテストコードの記述方法です。
初学者が備忘録のため書いています。ご指摘等あれば頂けると幸いです。では、いきましょう!
Gemをインストール
Gemfilegroup :development, :test do gem 'rspec-rails' gem 'factory_bot_rails' end group :development do gem 'web-console', '>= 3.3.0' endbundle installRSpecの基本設定
ターミナル#RSpec用設定ファイルの作成 rails g rspec:install #4つのファイルが作成されます create .rspec create spec create spec/spec_helper.rb create spec/rails_helper.rb.rspecに以下を追記します。
.rspec--format documentationRSpecを走らせるコマンド
bundle exec rspec
で動作確認。
まだなにもテストコード書いてないので、0 examples, 0 failures
と出ます。
これで動作確認完了です。ターミナル$ bundle exec rspec No examples found. Finished in 0.00031 seconds (files took 0.19956 seconds to load) 0 examples, 0 failuresファイル作成
次に、必要なファイルを作ります。
specフォルダ下に以下のように作成。モデルのテストなのでモデルのフォルダを作成。- app - spec - models - user_spec.rb次にfactory_botのフォルダとファイルを作成します。
- app - spec - factories -users.rbこれで必要なファイルは完成しました。
テストコード書き方
まず、
factories/users.rb
のファイルを以下のように編集。
とりあえず進みます。factories/users.rbFactoryBot.define do factory :user do name { "abc" } email { "aaa@bbb" } password { "000000" } password_confirmation { "000000" } end endmedels/user_spec.rbrequire 'rails_helper' describe User do describe '#create' do it "nameがない場合は登録できないこと" do user = build(:user, name: nil) user.valid? expect(user.errors[:name]).to include("を入力してください") end end end
一行目の
require 'rails_helper'
は、rails_helper.rb内の記述を読み込むこんでいます。この1行目の記述は、全ての~_specファイルに書くと思って頂いて大丈夫です。二行目の
describe User do ~ end
はUserモデルのテストであることを示しています。三行目の
describe '#create' do ~ end
はcreateメソッド(ユーザー新規登録)であることを示しています。四行目の
it "nameがない場合は登録できないこと" do ~ end
はテストの説明です。この間にテストコードを書いていきます。
user = build(:user, name: nil)
こちらは、buildメソッドでfactories/users.rbのuserを作成しています。作成した際、nameだけ空にしたいので、nameカラムを指定し、nilにしています。
user.valid?
こちらはbuildしたuserに対して、valid?
でバリデーションにより保存ができない状態であるか」を確かめることができます。
expect(user.errors[:name]).to include("を入力してください")
こちらは、valid?
メソッドを利用したuserに対してerrors
メソッドを利用し、なぜバリデーションにより保存ができないのかを確認することができます。
include
マッチャは引数にとった値が、expect
の引数である配列に含まれているかをチェックするマッチャです。
今回の場合、「nameがnilの場合は、を入力してください、というエラーが出るはずだ」ということがわかっているため、include("を入力してください")のように書くことができます。
実際にその通りになれば、このコードは意図した動作をすると確認出来ます。実際にテストを実行してみましょう。
ターミナル$ bundle exec rspec 〜省略〜 1 example, 0 failures特に赤文字エラーが出てなく、0 failuresとなっていれば成功です!
以上です!
ありがとうございました!
- 投稿日:2020-07-15T11:02:34+09:00
initializeメソッド
- 投稿日:2020-07-15T10:25:27+09:00
一部sassが当たらなかった時
解決したかった事
マイページ実装中に一部hamlにsassが当たらない
show.html.haml.MypageContents .MypageContents__side .MypageContents__side--nav %ul.MypageContents__side--nav--list %li.MypageContents__side--nav--list--item = link_to "商品一覧", root_path %h3.Mypage-nav-head 設定 %ul.MypageContents__side--nav--list %li.MypageContents__side--nav--list--item = link_to "ログアウト", root_path %li.MypageContents__side--nav--list--item = link_to "支払い方法", root_path .MypageContents__user = current_user.nickname %pさんのマイページ こちらの %li.MypageContents__side--nav--list--item にsassが当たらなかった該当sass&__side { float: left; width: 280px; margin: 0 40px 0 0; &__side--nav { &__side--nav--list { &__side--nav--list--item { display: block; min-height: 48px; padding: 16px; background-color: #fff; font-size: 14px; color: #333; .Mypage-nav-head { font-size: 16px; } } } } }解決
修正後sass&__side { float: left; width: 280px; margin: 0 40px 0 0; &--nav { &--list { &--item { border: 1px solid #eee; display: block; min-height: 48px; padding: 16px; background-color: #fff; font-size: 14px; color: #333; } .Mypage-nav-head { font-size: 16px; } } } }原因
どうやら&の使い方に誤りがあった模様。
sassでの&は親要素のクラス名まで省略してくれるものらしい。
また、.Mypage-nav-headがhamlの入れ子構造とマッチしていなかったのも原因であった。
かなりシンプルに記述する事ができた。