20190526のRailsに関する記事は19件です。

heroku+railsでs3にsitemapを置く

目的

herokuだとsitemapを置けないためs3に置き、そこから読み込みをgoogleにさせる。

仕様概要

  • sitemapgeneratorでsitemapを作成
  • aws-sdkでs3に保存
  • sitemap専用のルーティングを作ってそれをsearchconsoleに送信
  • robots.txtにもsitemapの場所を記載

aws-sdkでs3に保存

sitemap.rbにsitemapの置き場所を記載

sitemap.rb
SitemapGenerator::Sitemap.default_host = ""
SitemapGenerator::Sitemap.sitemaps_host = "https://s3-ap-northeast-1.amazonaws.com/#{ENV['S3_BUCKET_NAME']}"
SitemapGenerator::Sitemap.adapter = SitemapGenerator::AwsSdkAdapter.new(
  ENV['S3_BUCKET_NAME'],
  aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'],
  aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
  aws_region: 'ap-northeast-1'
)
SitemapGenerator::Sitemap.create do
  # '/articles/:id' を追加する
  Post.find_each do |post|
    add post_path(post), :lastmod => post.updated_at
  end
end

default_hostでwebsiteのURLを登録。sitemaps_hostでsitemapのURLを登録する。

herokuに環境変数登録

$ heroku config:add S3_BUCKET_NAME=bucket_name
$ heroku config:add AWS_ACCESS_KEY_ID=xxxxxxxxxxxxx
$ heroku config:add AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxx

localはdotenvで環境変数登録

gemのdotenvを入れて環境変数の登録

sitemap専用のルーティングを作る

get '/sitemap' => redirect('https://s3-ap-northeast-1.amazonaws.com/sitename/sitemap.xml.gz')

robots.txtにsitemapの場所を記述

robots.txt
Sitemap: https://sitename/sitemap
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsで開発したAPIをたたく from Vue.js/Vuex(ページング・アソシエーションを考慮してJSONで返す )

移転しました。今後はブログに書こうかと思います。
https://fuee72.hatenablog.com/entry/rails-api-vuex

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

dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicui18n.59.dylibというエラー発生

ローカル環境(macOS)にて、Railsアプリケーションのlocalhost:3000にアクセスすると、
application.htmlのstylesheet_link_tagにてエラーが発生した。

doctype html
html
  head
    = csrf_meta_tags
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload'
    = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
  body
    = yield

エラーメッセージを見ると、libicui18n.59.dylibがロードされていないとのこと。

ActionView::Template::Error (dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicui18n.59.dylib
  Referenced from: /usr/local/bin/node
  Reason: image not found

よくわからないが、nodeの再インストールをすると直るらしい。
https://qiita.com/SuguruOoki/items/3f4fb307861fcedda7a5

以下のコマンドを実行すると解消した。

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

herokuでrails + nodejs(puppeteer)のスクレイピングサイト構築

目的

herokuでテスト動作用に以下構成のスクレイピングサイトの構築を行う。
動作は1サーバ環境となるが、無料かつ迅速に、アルファ版等のテスト公開に用いることができる。

  • Web公開用:ruby on rails
  • スクレイピング用:nodejs (puppeteer)
  • スクレイピングしたデータ格納用:postgresql

(自分メモ用を兼ねるので、前提に記載の設定事項は割愛します。)

前提

以下を行なっており、herokuでrails + postgresql環境が動いている状態であること。

  1. railsアプリをnewして作成
  2. herokuコマンドの導入
  3. herokuへのログイン&rails環境構築
  4. heroku postgresqlの起動&テーブル作成(以下手順では、testテーブルが作成されているとする)
    1. Heroku PostgresをRailsアプリで利用する手順

手順

herokuにnode.jsとrailsの両方が動作する環境のパッケージを導入する
(※puppeteerに関わらず、これをしていないとnpmが動かない)

$ heroku buildpacks:set heroku/nodejs
$ heroku buildpacks:add --index 2 heroku/ruby

puppeteer用の環境パッケージを導入する

$ heroku buildpacks:add https://github.com/CoffeeAndCode/puppeteer-heroku-buildpack

jsライブラリを導入する

$ npm i puppeteer
$ npm i pg

railsアプリのルートフォルダにpuppeteer用のディレクトリを作成し、puppeteerが動作するスプリプトを配置する。

puppeteer/example.js
const pg = require('pg');
const puppeteer = require('puppeteer');

// 接続先文字列(既にherokuの環境変数に設定されているdatabase情報を参照する)
const connectionString = process.env.DATABASE_URL;

console.log(`接続開始 : ${connectionString}`);
const pool = new pg.Pool({
    connectionString: connectionString
});

// DBにSELECT してみる
pool.query('SELECT * FROM test')
    .then((result) => {
        console.log('Success', result);
        // 結果データの表示
        if(result.rows) {
            result.rows.forEach((row, index) => {
                console.log(index + 1, row);
            });
        }
    })
    .catch((error) => {
        console.log('Failure', error);
    })
    .then(() => {
        console.log('切断');
        pool.end();
    });

//puppeteer起動
(async() => {
    const browser = await puppeteer.launch({
        args: [
            '--no-sandbox',
            '--disable-setuid-sandbox'
        ],
        slowMo: 50
    });

    const page = await browser.newPage();

    await page.setViewport({
        width: 1200,
        height: 800
    });

    const stockCode = 6670;

    await page.goto(`https://www.nikkei.com/nkd/company/?scode=${stockCode}`);

    //銘柄
    const stockName = await page.evaluate(() =>
        document.querySelector('h1.m-headlineLarge_text').textContent
    );

    //株価
    const stockPrice = await page.evaluate(() =>
        document.querySelector('.m-stockPriceElm_value.now').textContent
    );

    //結果の取得
    console.log(`銘柄コード ${stockCode} (${stockName}) の株価は ${stockPrice} です。`);

    browser.close();

})();

herokuにプッシュする

$ git push heroku master

herokuでpuppeteerの動作スクリプトを起動する

heroku run node puppeteer/example.js

実行結果

DB情報と株価情報が取得できる。

接続開始 : postgres://...
Success Result {
  command: 'SELECT',
  rowCount: 1,
  oid: null,
  rows:
   [ { id: 1...} ],
  fields:
   [ Field {
       }
  ]...
切断
銘柄コード 6670 (MCJ) の株価は 671 円 です。

補足

以下を参考にすればスクリプトの定期実行(バッチ処理)とかもできるはず。
Herokuで単純なrubyスクリプトを定期的に実行する

参考

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

herokuでRails + Node.js(puppeteer)のスクレイピングサイト構築

目的

herokuでテスト動作用に以下構成のスクレイピングサイトの構築を行う。
動作は1サーバ環境となるが、無料かつ迅速に、アルファ版等のテスト公開に用いることができる。

  • Web公開用:Ruby on Rails
  • スクレイピング用:Node.js (puppeteer)
  • スクレイピングしたデータ格納用:postgresql

(自分メモ用を兼ねるので、前提に記載の設定事項は割愛します。)

前提

以下を行なっており、herokuでrails + postgresql環境が動いている状態であること。

  1. railsアプリをnewして作成
  2. herokuコマンドの導入
  3. herokuへのログイン&rails環境構築
  4. heroku postgresqlの起動&テーブル作成(以下手順では、testテーブルが作成されているとする)
    1. Heroku PostgresをRailsアプリで利用する手順

手順

herokuにnode.jsとrailsの両方が動作する環境のパッケージを導入する
(※puppeteerに関わらず、これをしていないとnpmが動かない)

$ heroku buildpacks:set heroku/nodejs
$ heroku buildpacks:add --index 2 heroku/ruby

puppeteer用の環境パッケージを導入する

$ heroku buildpacks:add https://github.com/CoffeeAndCode/puppeteer-heroku-buildpack

jsライブラリを導入する

$ npm i puppeteer
$ npm i pg

railsアプリのルートフォルダにpuppeteer用のディレクトリを作成し、puppeteerが動作するスプリプトを配置する。

puppeteer/example.js
const pg = require('pg');
const puppeteer = require('puppeteer');

// 接続先文字列(既にherokuの環境変数に設定されているdatabase情報を参照する)
const connectionString = process.env.DATABASE_URL;

console.log(`接続開始 : ${connectionString}`);
const pool = new pg.Pool({
    connectionString: connectionString
});

// DBにSELECT してみる
pool.query('SELECT * FROM test')
    .then((result) => {
        console.log('Success', result);
        // 結果データの表示
        if(result.rows) {
            result.rows.forEach((row, index) => {
                console.log(index + 1, row);
            });
        }
    })
    .catch((error) => {
        console.log('Failure', error);
    })
    .then(() => {
        console.log('切断');
        pool.end();
    });

//puppeteer起動
(async() => {
    const browser = await puppeteer.launch({
        args: [
            '--no-sandbox',
            '--disable-setuid-sandbox'
        ],
        slowMo: 50
    });

    const page = await browser.newPage();

    await page.setViewport({
        width: 1200,
        height: 800
    });

    const stockCode = 6670;

    await page.goto(`https://www.nikkei.com/nkd/company/?scode=${stockCode}`);

    //銘柄
    const stockName = await page.evaluate(() =>
        document.querySelector('h1.m-headlineLarge_text').textContent
    );

    //株価
    const stockPrice = await page.evaluate(() =>
        document.querySelector('.m-stockPriceElm_value.now').textContent
    );

    //結果の取得
    console.log(`銘柄コード ${stockCode} (${stockName}) の株価は ${stockPrice} です。`);

    browser.close();

})();

herokuにプッシュする

$ git push heroku master

herokuでpuppeteerの動作スクリプトを起動する

heroku run node puppeteer/example.js

実行結果

DB情報と株価情報が取得できる。

接続開始 : postgres://...
Success Result {
  command: 'SELECT',
  rowCount: 1,
  oid: null,
  rows:
   [ { id: 1...} ],
  fields:
   [ Field {
       }
  ]...
切断
銘柄コード 6670 (MCJ) の株価は 671 円 です。

補足

以下を参考にすればスクリプトの定期実行(バッチ処理)とかもできるはず。
Herokuで単純なrubyスクリプトを定期的に実行する

参考

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

~git~ヘルスケアwebサービスを自分で作る医者の日記

'''
To github.com:shutainer/hello_app.git
! [rejected] master -> master (non-fast-forward)
error: failed to push some refs to 'git@github.com:shutainer/hello_app.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
'''

再びプッシュできない
Qiita
gitの先端 云々、pull やmerge等でなんとかなると思うのだが
gitの仕組みを理解していないため、わからん

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

Rails Tutorial(2週目)-6-

Railsはリレーショナル・データベース(RDB)を使う

リレーショナルデータベースとは、事前定義された、関連があるデータ項目の集合体です。 この項目は、列と行を持つテーブルのセットとして構成されます。

このデータベースの縦列の事を、カラムという

モデルの作成

$rails generate model User name:string email:string
コントローラ名には複数形を使い、モデル名には単数を使うという慣習がある
(参考)
$rails generate controller Users new

上のコマンドによってマイグレーションファイルが作成されるので、
$rails db:migrate
でマイグレーションの適用を行う

あとからカラムの追加などを行いたいときは、
rails generate migration add_カラム名_to_テーブル名(複数) データ型の指定

有効性の検証

モデルファイル内でvalidatesメソッドを記述する。
validatesメソッドは、第1引数に対象となるカラム、第2引数以降にオプションを取る。

存在性の検証

app/models/user.rb
class User < ApplicationRecord
  validates :name, presence: true
end

オプションをpresence: trueとすることで、存在性を検証できる

長さの検証

length: { (maximum or minimum): 長さ}で長さを検証できる

フォーマットの検証

オプションをformat: { with: /<regular expression>/ }とすることで、フォーマットを検証できる
正規表現を利用する。

一意性の検証

オプションをuniqueness: true とすることで、検証できる。
さらに、:uniquenessのオプションとして :case_sensitiveを設定することで、大文字と小文字の区別をなくすことができる。
case_sensitive: true で大文字小文字の区別をする

データベースのインデックスの生成

検索の高速化のために、データベースのカラムにインデックス(索引)をつける事ができる。

マイグレーションファイルに
add_index モデル名, カラム名, オプション
(例)add_index :users, :email, unique: true

Active Recordのコールバック

Railsアプリケーションを普通に操作すると、その内部でオブジェクトが作成されたり、更新されたりdestroyされたりします。Active Recordはこのオブジェクトライフサイクルへのフックを提供しており、これを用いてアプリケーションやデータを制御できます。
コールバックは、オブジェクトの状態が切り替わる「前」または「後」にロジックをトリガします。

コールバックとは、オブジェクトのライフサイクル期間における特定の瞬間に呼び出されるメソッドのことです。コールバックを利用することで、Active Recordオブジェクトが作成/保存/更新/削除/検証/データベースからの読み込み、などのイベント発生時に常に実行されるコードを書くことができます。

(例)before_save { self.email = email.downcase }

上はコールバックにブロックを渡している。他の方法として、
before_save :(メソッド名)としておいて、メソッドを他で定義しておく事もできる。

セキュアなパスワード

パスワードの安全性を保つために、
ユーザーによって入力されたパスワードはハッシュ化してデータベースに保存される。

ユーザーの認証を行う際には、パスワードの送信→パスのハッシュ化→先にデータベースに保存されているハッシュ化されたパスワードの値との比較 という流れ。

セキュアなパスワードの実装

モデルに has_secure_password というメソッドを追加する。

・セキュアにハッシュ化したパスワードを、データベース内のpassword_digestという属性に保存できるようになる。
・2つのペアの仮想的な属性 (passwordとpassword_confirmation) が使えるようになる。また、存在性と値が一致するかどうかのバリデーションも追加される18 。
・authenticateメソッドが使えるようになる (引数の文字列がパスワードと一致するとUserオブジェクトを、間違っているとfalseを返すメソッド) 。

has_secure_passwordメソッドを使うための条件は、
データベースにpassword_digest属性を追加すること。よって、
$rails generate migration add_password_digest_to_users password_digest:string

また最先端のハッシュ関数を使うためにGemfileにbcrypt gemを追加する。

あとは、モデル内にhas_secure_passwordと記述するだけ

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

Ruby on Rails 勉強日記1日目

1日目

作りたいもの

テストデータを手動で作成するのが面倒なので、それを簡略できるようなソフトを目指す。

環境整備

すでに以下の環境を揃えていたので、これをそのまま使うことにする。
ruby-version:2.6.3
rails-version:5.2.3

rails projectの作成

以下のコマンドをシェルで打ち込んで、railsのプロジェクトを作成する。
rails new automation_application

初期動作の確認

以下のシェルでサーバーを立ち上げる。
rails server
localhost:3000検索して、railsの初期画面が表示されればOK。
終了させるときはctrl+Cで終了

ホーム画面を作成する。

以下のコマンドで、ホーム画面となるviewとコントローラを作成する。
rails generate controller home top

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

~エラーとの格闘~ ヘルスケアwebサービスを自分で作る医者の日記

rails チュートリアルで途中わからなくなったため、
再度1.1から始める。

gitの仕組みがわからない、
sshというものが理解できない

pushできない
git remote addを何回か繰り返したが
push できない

格闘したのち
remote rm〜で
一旦消して、githubを使うことにし、pushできた
朝からやって23時ごろここまで10時間以上一つのエラーと格闘

長かった

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

[error]ActionController::InvalidAuthenticityToken

Postを削除しようとしたらエラーが、、、

ActionController::InvalidAuthenticityToken

Rubyのバージョン

ruby -v '2.4.0'

解決方法

application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery         //ここを追加
end

参考記事
https://qiita.com/_ayk_study/items/88269643c675fd4ca975

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

rails基本コマンド集

migrationファイル操作

migrationファイル作成

# マイグレーションファイル作成コマンド
$ rails generate migration クラス名[例:AddColumnTitles]

# モデル作成
$ rails generate model モデル名

migration実行

rails db:migrate

ステータス確認

#マイグレーションの現在のステータスを確認
rails db:migrate:status
#マイグレーションの現在のバージョンを確認
rails db:version

マイグレーションの実行をロールバック

#ロールバック(1つ前のバージョンにロールバック)
rails db:rollback
#ロールバック(ステップ数分、前のバージョンにロールバック)
rails db:rollback STEP=2
#指定したバージョンのマイグレーションのみ実行
rails db:migrate:up VERSION=xxxxxxx
#指定したバージョンのマイグレーションのみを戻す
rails db:migrate:down VERSION=xxxxxxx

コントローラーファイル操作

コントローラファイル作成

rails g controller [コントローラ名(複数系)] [アクション名]
例)rails generate controller Sessions new

modelファイルの書き方

has_many

参照元から参照先へのリレーション1:Nを定義。

class User < ApplicationRecord 
    has_many :tweets #1:Nを定義
end

belongs_to

参照先から参照元へのリレーションを定義。

class Tweet < ApplicationRecord 
    belongs_to :user #1:Nを定義
end

has_one

1:1の関係。
参照元のいずれかは、参照先へ1:1へリレーションする場合はhas_one。
参照元の全ては、参照先へ1:1へリレーションする場合はbelongs_to。
Userのいずれかは著者(Auther)へ1:1へ関連するのでhas_oneを使う。

class User < ApplicationRecord 
    has_one :auther #1:Nを定義
end

中間テーブルを通したモデルとのリレーション

through

https://kimuraysp.hatenablog.com/entry/2017/09/05/235816
blogsテーブルから中間テーブルfavaritesを通して、usersを取得したい時の定義。

class Blog < ApplicationRecord
  belongs_to :user
  has_many :favorites
  has_many :users, through: :favorites
end

中間テーブルを通して別名でアクセス

usersテーブルから中間テーブルfavoritesを経由して、blogsテーブルへアクセスする時の定義

class User < ApplicationRecord
  has_many :blogs
  has_many :favorites
  has_many :favorite_blogs, through: :favorites, source: :blog
end
user.favorite_blogs #あるユーザのお気に入りのブログ一覧を取得

1対多を別名でアクセス

class User
  has_many :write_blogs, class_name: 'Blog', foreign_key: :user_id
  # 以下省略
end

class_nameには、アクセスしたいモデル名。foreign_keyには、アクセスする時に使用する参照元の外部キーを定義。

user.write_blogs #あるユーザが投稿したブログ一覧を取得

ルーティング確認

rails routes #ルーティングを一覧表示
rails routes | grep XXXX #特定のルーティングのみ表示

railsとrakeの違いについて

rake:rubyで書かれたタスクをビルドして実行するツール
rails:railsアプリケーションに必要なファイル、フォルダを作成・削除するツール

rails4以前はコマンドの種類によって、rake用・rails用に分かれていたが、
rails4以降はすべてrailsコマンドで対応できるようになった。

bundleとは

Gemのパッケージ管理ツール。
・Gem間の依存関係を考慮してGemをインストールできる
・プロジェクトごとにGemを管理できる

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

【Rails】v 5.2ではprotect_from_forgeryがapplication_controller内にない?

はじめに

先日参加した勉強会にて、
application_controller.rb に記述されているprotect_from_forgery with: :exceptioncsrfを対策 している」(裏を返すと、ここを削除するとcsrf攻撃を受けるリスクが生まれる)
という説明を受けたのだが、
そもそも protect_from_forgery with: :exception
なるコードを見た事が無い気がしたので調べてみた。

比較

以下、Rails versionが5.1.75.2.3rails newした直後のapplication_controller.rbの比較です。

v5.1.7

$ rails -v
Rails 5.1.7
application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
end

protect_from_forgery with: :exceptionがある!

v5.2.3

$ rails -v
Rails 5.2.3
application_controller.rb
class ApplicationController < ActionController::Base
end

protect_from_forgery with: :exceptionが無い!

結論

確かに、rails5.1台まではprotect_from_forgery with: :exceptionがapplication_controller.rbで記述されていることが確認できる。

どうやらRails version5.2 以降ではActionController::Base内でprotectしているようです。
https://stackoverflow.com/questions/50905654/rails-5-2-actioncontrollerinvalidauthenticitytoken

間違いなどありましたらご指摘ください!

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

[yarn error] Your Yarn packages are out of date! Please run `yarn install` to update.

yarn installでエラー

========================================
  Your Yarn packages are out of date!
  Please run `yarn install` to update.
========================================


To disable this check, please add `config.webpacker.check_yarn_integrity = false`
to your Rails development config file (config/environments/development.rb).


yarn check v1.16.0
error Lockfile does not contain pattern: "bootstrap@^4.3.1"
error Lockfile does not contain pattern: "jquery@^3.4.1"
error Couldn't find an integrity file
error Found 3 errors.
info Visit https://yarnpkg.com/en/docs/cli/check for documentation about this command.

解決コマンド

$ rm -rf yarn.lock
$ yarn install
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsアプリのRSpecセットアップ方法

はじめに

Rails初心者です。
RSpecを初めて触るので、初めに行うRSpecのインストール〜アプリケーション設定について、備忘録としてまとめます。

既存アプリに追加する前提です。
1から作る場合は、こちらが参考になります。
Railsアプリ作成手順まとめ

バージョン管理

ruby 2.7.0
rails 5.1.6

RSpecセットアップ

①RSpecインストール

1-1 Gemfileにrspec-railsを追加

Gemfile
group :development, :test do
  # 元からあるコードは省略

  gem 'rspec-rails', '~> 3.6.0'

end

1-2 bundle実行

terminal
$ bundle install

②テストデータベース確認

2-1 テスト用データベース確認

・SQLiteの場合

config/database.yml
test:
 <<: *default
database: db/test.sqlite3

・MySQL・PostgreSQLの場合

config/database.yml
test:
 <<: *default
database: アプリ名

があるはずなので確認。

もし書いてなければ、
config/database.ymlに上記のようなコードを追加

terminal
$ bin/rails db:create:all

でテストデータベース作成。

※余談ですが、 railsbin/rails の違いについては、
こちらが分かりやすいです。
Rails 4.1以降のコンソールコマンドは必ず bin/ を付けなきゃいけないの?

③RSpec設定

3-1 RSpecインストール

terminal
$ bin/rails generate rspec:install

これにより、

・RSpec用の設定ファイル(.rspec
・私たちが作成したスペックファイルを格納するディレクトリ(spec
・RSpecの動きをカスタマイズするヘルパーファイル

の3つが作成されます。

3-2 testディレクトリの削除

RSpecではspecディレクトリのspecファイルに書いてくので、Railsアプリケーション作成時に作られたtestファイルたちを削除します。

terminal
$ rm -r ./test

3-3 デフォルトの形式→ドキュメント形式へ変更

必須ではないですが、RSpecの出力結果を見やすくします。

.rspecファイルに下記を追加。

.rspec
--require spec_helper
--format documentation

3-4 binstubインストール

アプリケーションの起動時間を早くするSpringを追加します。

Gemfile
group :development do

  # 元からあるコードは省略

  gem 'spring-commands-rspec'

end
terminal
$ bundle install


新しいbinstubを作成

terminal
$ bundle exec spring binstub rspec

実行すると、
binディレクトリ内にrspecという実行用ファイルが作成されます。

④正常にRSpecがインストールできてるか確認

4-1 コマンド実行

terminal
$ bin/rspec


Running via Spring preloader in process 28279
No examples found.

Finished in 0.00074 seconds (files took 0.14443 seconds to load)
0 examples, 0 failures

このように出力されていれば成功です。

⑤テストファイル自動作成設定

5-1 ファイル作成に応じてテストファイルを作成

rails generateコマンドのようにジェネレータを使うと、
現時点で既に、
デフォルトのMinitestファイルがtestディレクトリには作成されず、
RSpecファイルがspecディレクトリに作成されます。
特に設定は不要です。

5-2 不要なテストファイルが作成されない設定

あとは、
不要なファイルが自動で作成されないように
config/application.rbに設定を加えます。

config/application.rb
require_relative 'boot'
require 'rails/all'

Bundler.require(*Rails.groups)

module Testapps # 自分が作成したアプリ名
  class Application < Rails::Application
    config.load_defaults 5.1

    # 元からあるコードは省略

    config.generators do |g|
      g.test_framework :rspec,
       fixtures: false, # テストDBにレコード作成するファイルの作成をスキップ(初めだけ、のちに削除)。
       view_specs: false, # ビューファイル用のスペックを作成しない。
       helper_specs: false, # ヘルパーファイル用のスペックを作成しない。
       routing_specs: false # routes.rb用のスペックファイル作成しない。
    end
  end
end

上のコードでは、
モデルスペックとコントローラスペックが
デフォルトで自動作成されるようになっています。

他に自動作成してほしいファイルがあれば、
上の該当するコードは書かなくて良いです。

参考

Everyday Rails - RSpecによるRailsテスト入門
現場で使える Ruby on Rails 5速習実践ガイド

とても分かりやすいです。
引き続き勉強していきます。

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

[学習用]Deviseにユーザーアバター(画像)を表示できる様に実装する

Firefox_Screenshot_2019-05-24T07-08-32.813Z.png

Devise Userまでインストールしていることが前提
- gem 'devise'
- gem 'devise-i18n'
- gem 'devise-i18n-views'

$ bundle
$ rails g devise:install
$ rails g devise user
$ rails g devise:controllers users
$ rails g devise:i18n:views user

前提終わり

名前欄と画像欄を追加する

$ rails g migration AddNameToUsers name:string
$ rails g migration AddAvatarToUsers avatar:string
$ rails g uploader Avatar
$ rails db:migrate

controllerに追加する

app/controllers/application_controller.rb
  before_action :configure_permitted_parameters, if: :devise_controller?
  protected
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
    devise_parameter_sanitizer.permit(:account_update, keys: [:name])
    devise_parameter_sanitizer.permit(:account_update, keys: [:avatar, :avatar_cache, :remove_avatar])
  end

他のサイトでは、user_controller.rbに入れていたけど、それだと動かなかった。

userと関連付け

app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  # ユーザー画像を関連付け
  mount_uploader :avatar, AvatarUploader
end

uploaderの設定

app/uploader/avatar_uploader.rb
class AvatarUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick

  if Rails.env.development?
    # ローカルに保存する
    storage :file
  elsif Rails.env.test?
    # ローカルに保存する
    storage :file
  else
    # S3などの外部に保存する
    storage :fog
  end

  # S3のディレクトリ名
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # 許可する画像の拡張子
  def extension_whitelist
    %w(jpg jpeg gif png)
  end

  # 画像名をリネームさせる(日付時間はダメ絶対)
  def filename
    "#{secure_token}.#{file.extension}" if original_filename.present?
  end

  protected
  # 一意となるトークンを作成
  def secure_token
    var = :"@#{mounted_as}_secure_token"
    model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.uuid)
  end

  # 画像サムネイル
  version :thumb do
    process resize_to_fill: [316, 316]
  end

  # アップロードの時点で指定サイズに収まるようにしておく
  process :resize_to_limit => [700, 700]

end


ユーザー編集画面に追加

app/users/registrations/edit.html.erb
    <div class="field">
      <% if current_user.persisted? && current_user.avatar? %>
        <%= image_tag(current_user.avatar.to_s, :class => "img-fluid", :alt => "ユーザーアバター") %>
        <label><%= f.check_box :remove_avatar %> 画像を削除</label>
      <% else %>
        <%= image_tag("favicon.png", :class => "img-thumbnail", :size => "230x230", :alt => "アイコン") %>
        <%= f.hidden_field :avatar_cache %>
      <% end %>

      <div class="input-group input-group-sm mb-3">
        <div class="input-group-prepend">
          <span class="input-group-text">アップロード</span>
        </div>
        <div class="custom-file">
          <%= f.file_field :avatar, class: 'custom-file-input' %>
          <label class="custom-file-label" for="customFile" data-browse="参照">ファイル選択...</label>
        </div>
      </div>
    </div>

user show.html.erbを作っておけばユーザー詳細画面にも表示できる。
Bootstrap4を入れておけば、デザインも楽です。

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

Railsでのフォロー機能実装の際のother_userとは?

小学生です
Railsチュートリアルやってser.follow!(other_user)と出てきたのですがこのother_userはいつ定義されたのでしょうか?自分以外のユーザーだとは思うのでですがother_モデル名という書き方をすれば他のデーターを参照できるとか?それ以外では

def follow!(other_user)
relationships.create!(followed_id: other_user.id)

このコードはなにをしているのでしょうか?

https://railstutorial.jp/chapters/following-users?version=4.0

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

[Rails]Markdown gem

RailsでMarkdown記法で書ける仕様に実装したいしかもコピペで

gem追加

Gemfile
gem 'redcarpet'

投稿機能の作成

ターミナル
$ rails g scaffold post title:string body:text
$ bundle
$ bundle exec rails db:migrate

helperに実装

post_helper
module PostHelper
    require "redcarpet"
    require "coderay"

    class HTMLwithCoderay < Redcarpet::Render::HTML
        def block_code(code, language)
            language = language.split(':')[0]

            case language.to_s
            when 'rb'
                lang = 'ruby'
            when 'yml'
                lang = 'yaml'
            when 'css'
                lang = 'css'
            when 'html'
                lang = 'html'
            when ''
                lang = 'md'
            else
                lang = language
            end

            CodeRay.scan(code, lang).div
        end
    end

    def markdown(text)
        html_render = HTMLwithCoderay.new(filter_html: true, hard_wrap: true)
        options = {
            autolink: true,
            space_after_headers: true,
            no_intra_emphasis: true,
            fenced_code_blocks: true,
            tables: true,
            hard_wrap: true,
            xhtml: true,
            lax_html_blocks: true,
            strikethrough: true
        }
        markdown = Redcarpet::Markdown.new(html_render, options)
        markdown.render(text)
    end
end

viewに実装

show.html.erb
  <%= markdown(@post.body).html_safe %>

参考記事

https://qiita.com/hkengo/items/978ea1874cf7e07cdbfc

git

https://github.com/vmg/redcarpet

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

NginxをReverseProxyにしてRailsアプリをデプロイしたらHTTPヘッダが原因で422Errorが返ってきた

0. 導入

これがQiita初投稿となります。
Railsでアプリケーション1を作成しデプロイしました。しかし、Nginxの設定が原因でPOST Requestを行ったタイミングでエラーが発生してしまいました。改めて当時の状態を振り返り、どのようにエラーを解消したのかということをまとめてゆきます。

1. 概要

1.1. 構成

SakuraのVPSを借りてCentOS7系をインストールしています。
NginxをReverse Proxyとして動作させ、HTTPS通信を終端化し、リクエストをアップストリームサーバ(puma)に転送します。
pumaとRailsアプリはRack2を介してrequestとresponseをやりとりします。
diagram.png

1.2. 問題点

少し長目の記事なので最初にどこが問題であったのか書いておきます。
今回はNginxをReverseProxyとした際に、アップストリームサーバに必要なHTTPヘッダーを渡せていなかったことが問題でした。

2. エラー発生

2.1. 422エラーが返ってきた

ブログを作成し、デプロイも完了。早速ログインしブログを投稿しよう、と思った矢先…
error_cap.png
なんかエラーが出てる…Status Code 422って何だ?

3. トラブルシュート

3.1. Status Code 422 とは?

MDN web docsを参照してみます。
422 Unprocessable Entity:

The HyperText Transfer Protocol (HTTP) の 422 Unprocessable Entity 応答状態コードは、サーバーが要求本文のコンテンツ型を理解でき、要求本文の構文が正しいものの、中に含まれている指示が処理できなかったことを表します。

わからない。「中に含まれている指示が処理できなかった」という表現がいささか抽象的です。

If you are the application owner check the logs for more information.

「アプリのオーナーなら詳細についてログを見てください」とのことなのでVPSにログインしRailsのログを確認してみます。

3.2. Logの確認

VPSにログインし下記のログを確認します。

/var/www/app_name/shared/log/production.log
(省略)
W, [*]  WARN -- : [*] HTTP Origin header (https://self-ref-penguin.com) didn't match request.base_url (http://self-ref-penguin.com)
I, [*]  INFO -- : [*] Completed 422 Unprocessable Entity in 2ms
F, [*] FATAL -- : [*]   
F, [*] FATAL -- : [*] ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
F, [*] FATAL -- : [*]   
F, [*] FATAL -- : [*] actionpack (5.2.3) lib/action_controller/metal/request_forgery_protection.rb:211:in `handle_unverified_request'
(省略)

最初のWARNと最後のFATALに、具体的な情報がありそうに思えます。

W, [〜] WARN -- : [〜] HTTP Origin header (https://self-ref-penguin.com) didn't match request.base_url (http://self-ref-penguin.com)
F, [〜] FATAL -- : [〜] actionpack (5.2.3) lib/action_controller/metal/request_forgery_protection.rb:211:in `handle_unverified_request'

WARNでは「HTTPのオリジンヘッダーがrequest.base_urlにマッチしていない。」と言われています。FATALではrequest_forgery_protection.rbがエラーを出しています。まずはエラーを出しているメソッドを確認してゆきます。

3.3. request_forgery_protection.rbを見てみる

request_forgery_protection.rbを一部添付します。

rails/actionpack/lib/action_controller/metal/request_forgery_protection.rb
require "rack/session/abstract/id"
require "action_controller/metal/exceptions"
require "active_support/security_utils"
require "active_support/core_ext/string/strip"

module ActionController #:nodoc:
  class InvalidAuthenticityToken < ActionControllerError #:nodoc:
  end

  class InvalidCrossOriginRequest < ActionControllerError #:nodoc:
  end

  module RequestForgeryProtection
    extend ActiveSupport::Concern
(省略)
      def verify_authenticity_token # :doc:
        mark_for_same_origin_verification!

        if !verified_request?
          if logger && log_warning_on_csrf_failure
            if valid_request_origin?
              logger.warn "Can't verify CSRF token authenticity."
            else
              logger.warn "HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})"
            end
          end
          handle_unverified_request
        end
      end
(省略)
      def valid_request_origin? # :doc:
        if forgery_protection_origin_check
          # We accept blank origin headers because some user agents don't send it.
          raise InvalidAuthenticityToken, NULL_ORIGIN_MESSAGE if request.origin == "null"
          request.origin.nil? || request.origin == request.base_url
        else
          true
        end
      end
(省略)
  end
end

valid_request_origin?メソッドでrequest.origin == request.base_urlを比較しています。この比較がfalseを返すとverify_authenticity_tokenメソッドが上述のWARNメッセージを出すようです。
base_urlメソッドは、lib/action_dispatch/http/request.rbincludeしているRack::Request::Helpersに定義されています。

3.3. Rackを見てみる

request.rbを一部添付します。

rack/lib/rack/request.rb
require 'rack/utils'
require 'rack/media_type'

module Rack

  class Request
    class << self
      attr_accessor :ip_filter
    end
(省略)
    module Env
(省略)
      def get_header(name)
        @env[name]
      end
(省略)
    end

    module Helpers
(省略)
      DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }

      HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
      HTTP_X_FORWARDED_PROTO  = 'HTTP_X_FORWARDED_PROTO'
(省略)
      def scheme
        if get_header(HTTPS) == 'on'
          'https'
        elsif get_header(HTTP_X_FORWARDED_SSL) == 'on'
          'https'
        elsif forwarded_scheme
          forwarded_scheme
        else
          get_header(RACK_URL_SCHEME)
        end
      end
(省略)
      def base_url
        url = "#{scheme}://#{host}"
        url = "#{url}:#{port}" if port != DEFAULT_PORTS[scheme]
        url
      end
(省略)
      def forwarded_scheme
        scheme_headers = [
          get_header(HTTP_X_FORWARDED_SCHEME),
          get_header(HTTP_X_FORWARDED_PROTO).to_s.split(',')[0]
        ]

        scheme_headers.each do |header|
          return header if ALLOWED_SCHEMES.include?(header)
        end

        nil
      end
    end
    include Env
    include Helpers
  end
end

base_urlメソッドを見つけました。
このメソッドの中でschemeメソッドが呼び出されています。get_headerenv3という変数が保持しているハッシュから、引数に指定されたKeyが持つValueを取得します。 get_headerの引数となるKeyにはHTTPヘッダ4が指定されているようなので、Nginxの設定でヘッダを付与してみることにします。

3.4. Nginxの設定を直す

/etc/nginx/conf.d/default.conf
upstream app-name {
    server unix:/var/www/app-name/shared/tmp/sockets/devcamp-portfolio-puma.sock fail_timeout=0;
}

server {

    if ($host = self-ref-penguin.com) {
        return 301 https://$host$request_uri;
    }
    listen 80;
    server_name self-ref-penguin.com;
    root /var/www/app-name/current/public;
}

server {
    listen       443 ssl http2 default_server;
    # listen       [::]:443 ssl http2 default_server;
    server_name self-ref-penguin.com;
    ssl_certificate ***;
    ssl_certificate_key ***;
    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout  10m;
    ssl_ciphers ***;
    ssl_protocols ***;

    location ~ ^/assets/ {
      root /var/www/app-name/current/public;
    }

    try_files $uri/index.html $uri @app-name;

    location / {
        proxy_pass http://app-name;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        # この設定が抜けていました
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect off;
    }
(省略)
}

X-Forwarded-Protoヘッダーの設定を追記し、「ユーザのリクエストが使用したHTTPスキームを指定する」ように修正。systemctl restart nginx.serviceを実行します。再度ページにアクセスしログインを実行。今度はログイン(POST)が成功したことを確認できました。

今回の構成ではNginxがReverse ProxyとなりHTTPSのアクセスを終端化していたのでした。今回のエラーの原因は下記の説明に要約されるかと思います。

アップストリームに伝える必要がある情報にクライアントのリクエスト情報があります。アップストリームサーバーへのリクエストはすべてプロキシを経由するため、そのままではクライアントの送信元アドレスや使用したプロトコルがわからなくなってしまいます。このため、クライアントのリクエスト情報をいくつかのヘッダを付与することでアップストリームへ伝えることができます。これらのヘッダは標準化されていませんが、Squid、Apache HTTPサーバなどでデファクトスタンダードとして使用されており、RubyのRackインタフェースもこれらのヘッダを解釈します。5

4. まとめ

初めて自分でアプリケーションを作成しデプロイを行ってみました。アプリケーションの開発スキルは勿論のこと、まだまだインフラ観点でも至らない点が多々あることを実感しています。特にエラーの原因についてはシステムの構成をしっかりと理解していれば、もっと簡単にあたりをつけることが可能であったと思います。

フロントの開発をしていたつもりが、その副産物としてインフラの知見も得ることができ、中々良い経験になりました。

私について

普段はネットワークエンジニアをしています。
プログラミング未経験で、半年ほど前からRubyを勉強し始めました。
Scriptingを1つ経験したので、次は並行してFunctionalの言語にも挑戦しようかと思っています。
オススメがあれば教えてください。
最後まで読んでくださりありがとうございました。


  1. Udemyでチュートリアルを見ながらブログを作成しました。成果物はこちら 

  2. Rackについては以下2つの記事によくまとまっています
    Rails on Rack
    What is Rack in Ruby? 

  3. EnvについてはStack over flowの質問を参照しました。「env is just a hash. Rack itself and various middlewares add values into it.」「envはハッシュで、Rackや様々なミドルウェアがこれにValueを加えていきます」 

  4. HTTPヘッダーの種類についてはMDN web docsを参照し、適切な値を探しました。 

  5. 久保達彦, 道井俊介(2016)『nginx 実践入門』p.139-140, 技術評論社. 

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

NginxをReverseProxyにしてRailsアプリをデプロイしたらヘッダの扱いにハマった

0. 導入

これがQiita初投稿となります。
Railsでアプリケーション1を作成しデプロイしました。しかし、Nginxの設定が原因でPOST Requestを行ったタイミングでエラーが発生してしまいました。改めて当時の状態を振り返り、どのようにエラーを解消したのかということをまとめてゆきます。

1. 概要

1.1. 構成

SakuraのVPSを借りてCentOS7系をインストールしています。
NginxをReverse Proxyとして動作させ、HTTPS通信を終端化し、リクエストをアップストリームサーバ(puma)に転送します。
pumaとRailsアプリはRack2を介してrequestとresponseをやりとりします。
diagram.png

1.2. 問題点

少し長目の記事なので最初にどこが問題であったのか書いておきます。
今回はNginxをReverseProxyとした際に、アップストリームサーバに必要なHTTPヘッダーを渡せていなかったことが問題でした。

2. エラー発生

2.1. 422エラーが返ってきた

ブログを作成し、デプロイも完了。早速ログインしブログを投稿しよう、と思った矢先…
error_cap.png
なんかエラーが出てる…Status Code 422って何だ?

3. トラブルシュート

3.1. Status Code 422 とは?

MDN web docsを参照してみます。
422 Unprocessable Entity:

The HyperText Transfer Protocol (HTTP) の 422 Unprocessable Entity 応答状態コードは、サーバーが要求本文のコンテンツ型を理解でき、要求本文の構文が正しいものの、中に含まれている指示が処理できなかったことを表します。

わからない。「中に含まれている指示が処理できなかった」という表現がいささか抽象的です。

If you are the application owner check the logs for more information.

「アプリのオーナーなら詳細についてログを見てください」とのことなのでVPSにログインしRailsのログを確認してみます。

3.2. Logの確認

VPSにログインし下記のログを確認します。

/var/www/app_name/shared/log/production.log
(省略)
W, [*]  WARN -- : [*] HTTP Origin header (https://self-ref-penguin.com) didn't match request.base_url (http://self-ref-penguin.com)
I, [*]  INFO -- : [*] Completed 422 Unprocessable Entity in 2ms
F, [*] FATAL -- : [*]   
F, [*] FATAL -- : [*] ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
F, [*] FATAL -- : [*]   
F, [*] FATAL -- : [*] actionpack (5.2.3) lib/action_controller/metal/request_forgery_protection.rb:211:in `handle_unverified_request'
(省略)

最初のWARNと最後のFATALに、具体的な情報がありそうに思えます。

W, [〜] WARN -- : [〜] HTTP Origin header (https://self-ref-penguin.com) didn't match request.base_url (http://self-ref-penguin.com)
F, [〜] FATAL -- : [〜] actionpack (5.2.3) lib/action_controller/metal/request_forgery_protection.rb:211:in `handle_unverified_request'

WARNでは「HTTPのオリジンヘッダーがrequest.base_urlにマッチしていない。」と言われています。FATALではrequest_forgery_protection.rbがエラーを出しています。まずはエラーを出しているメソッドを確認してゆきます。

3.3. request_forgery_protection.rbを見てみる

request_forgery_protection.rbを一部添付します。

rails/actionpack/lib/action_controller/metal/request_forgery_protection.rb
require "rack/session/abstract/id"
require "action_controller/metal/exceptions"
require "active_support/security_utils"
require "active_support/core_ext/string/strip"

module ActionController #:nodoc:
  class InvalidAuthenticityToken < ActionControllerError #:nodoc:
  end

  class InvalidCrossOriginRequest < ActionControllerError #:nodoc:
  end

  module RequestForgeryProtection
    extend ActiveSupport::Concern
(省略)
      def verify_authenticity_token # :doc:
        mark_for_same_origin_verification!

        if !verified_request?
          if logger && log_warning_on_csrf_failure
            if valid_request_origin?
              logger.warn "Can't verify CSRF token authenticity."
            else
              logger.warn "HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})"
            end
          end
          handle_unverified_request
        end
      end
(省略)
      def valid_request_origin? # :doc:
        if forgery_protection_origin_check
          # We accept blank origin headers because some user agents don't send it.
          raise InvalidAuthenticityToken, NULL_ORIGIN_MESSAGE if request.origin == "null"
          request.origin.nil? || request.origin == request.base_url
        else
          true
        end
      end
(省略)
  end
end

valid_request_origin?メソッドでrequest.origin == request.base_urlを比較しています。この比較がfalseを返すとverify_authenticity_tokenメソッドが上述のWARNメッセージを出すようです。
base_urlメソッドは、lib/action_dispatch/http/request.rbincludeしているRack::Request::Helpersに定義されています。

3.3. Rackを見てみる

request.rbを一部添付します。

rack/lib/rack/request.rb
require 'rack/utils'
require 'rack/media_type'

module Rack

  class Request
    class << self
      attr_accessor :ip_filter
    end
(省略)
    module Env
(省略)
      def get_header(name)
        @env[name]
      end
(省略)
    end

    module Helpers
(省略)
      DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }

      HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
      HTTP_X_FORWARDED_PROTO  = 'HTTP_X_FORWARDED_PROTO'
(省略)
      def scheme
        if get_header(HTTPS) == 'on'
          'https'
        elsif get_header(HTTP_X_FORWARDED_SSL) == 'on'
          'https'
        elsif forwarded_scheme
          forwarded_scheme
        else
          get_header(RACK_URL_SCHEME)
        end
      end
(省略)
      def base_url
        url = "#{scheme}://#{host}"
        url = "#{url}:#{port}" if port != DEFAULT_PORTS[scheme]
        url
      end
(省略)
      def forwarded_scheme
        scheme_headers = [
          get_header(HTTP_X_FORWARDED_SCHEME),
          get_header(HTTP_X_FORWARDED_PROTO).to_s.split(',')[0]
        ]

        scheme_headers.each do |header|
          return header if ALLOWED_SCHEMES.include?(header)
        end

        nil
      end
    end
    include Env
    include Helpers
  end
end

base_urlメソッドを見つけました。
このメソッドの中でschemeメソッドが呼び出されています。get_headerenv3という変数が保持しているハッシュから、引数に指定されたKeyが持つValueを取得します。 get_headerの引数となるKeyにはHTTPヘッダ4が指定されているようなので、Nginxの設定でヘッダを付与してみることにします。

3.4. Nginxの設定を直す

/etc/nginx/conf.d/default.conf
upstream app-name {
    server unix:/var/www/app-name/shared/tmp/sockets/devcamp-portfolio-puma.sock fail_timeout=0;
}

server {

    if ($host = self-ref-penguin.com) {
        return 301 https://$host$request_uri;
    }
    listen 80;
    server_name self-ref-penguin.com;
    root /var/www/app-name/current/public;
}

server {
    listen       443 ssl http2 default_server;
    # listen       [::]:443 ssl http2 default_server;
    server_name self-ref-penguin.com;
    ssl_certificate ***;
    ssl_certificate_key ***;
    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout  10m;
    ssl_ciphers ***;
    ssl_protocols ***;

    location ~ ^/assets/ {
      root /var/www/app-name/current/public;
    }

    try_files $uri/index.html $uri @app-name;

    location / {
        proxy_pass http://app-name;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        # この設定が抜けていました
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect off;
    }
(省略)
}

X-Forwarded-Protoヘッダーの設定を追記し、「ユーザのリクエストが使用したHTTPスキームを指定する」ように修正。systemctl restart nginx.serviceを実行します。再度ページにアクセスしログインを実行。今度はログイン(POST)が成功したことを確認できました。

今回の構成ではNginxがReverse ProxyとなりHTTPSのアクセスを終端化していたのでした。今回のエラーの原因は下記の説明に要約されるかと思います。

アップストリームに伝える必要がある情報にクライアントのリクエスト情報があります。アップストリームサーバーへのリクエストはすべてプロキシを経由するため、そのままではクライアントの送信元アドレスや使用したプロトコルがわからなくなってしまいます。このため、クライアントのリクエスト情報をいくつかのヘッダを付与することでアップストリームへ伝えることができます。これらのヘッダは標準化されていませんが、Squid、Apache HTTPサーバなどでデファクトスタンダードとして使用されており、RubyのRackインタフェースもこれらのヘッダを解釈します。5

4. まとめ

初めて自分でアプリケーションを作成しデプロイを行ってみました。アプリケーションの開発スキルは勿論のこと、まだまだインフラ観点でも至らない点が多々あることを実感しています。特にエラーの原因についてはシステムの構成をしっかりと理解していれば、もっと簡単にあたりをつけることが可能であったと思います。

フロントの開発をしていたつもりが、その副産物としてインフラの知見も得ることができ、中々良い経験になりました。

私について

普段はネットワークエンジニアをしています。
プログラミング未経験で、半年ほど前からRubyを勉強し始めました。
Scriptingを1つ経験したので、次は並行してFunctionalの言語にも挑戦しようかと思っています。
オススメがあれば教えてください。
最後まで読んでくださりありがとうございました。


  1. Udemyでチュートリアルを見ながらブログを作成しました。成果物はこちら 

  2. Rackについては以下2つの記事によくまとまっています
    Rails on Rack
    What is Rack in Ruby? 

  3. EnvについてはStack over flowの質問を参照しました。「env is just a hash. Rack itself and various middlewares add values into it.」「envはハッシュで、Rackや様々なミドルウェアがこれにValueを加えていきます」 

  4. HTTPヘッダーの種類についてはMDN web docsを参照し、適切な値を探しました。 

  5. 久保達彦, 道井俊介(2016)『nginx 実践入門』p.139-140, 技術評論社. 

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