- 投稿日:2019-05-26T22:06:44+09:00
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.rbSitemapGenerator::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 enddefault_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=xxxxxxxxxxxxxxxxxxlocalはdotenvで環境変数登録
gemのdotenvを入れて環境変数の登録
sitemap専用のルーティングを作る
get '/sitemap' => redirect('https://s3-ap-northeast-1.amazonaws.com/sitename/sitemap.xml.gz')robots.txtにsitemapの場所を記述
robots.txtSitemap: https://sitename/sitemap
- 投稿日:2019-05-26T21:08:51+09:00
Railsで開発したAPIをたたく from Vue.js/Vuex(ページング・アソシエーションを考慮してJSONで返す )
移転しました。今後はブログに書こうかと思います。
https://fuee72.hatenablog.com/entry/rails-api-vuex
- 投稿日:2019-05-26T20:51:52+09:00
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
- 投稿日:2019-05-26T19:16:36+09:00
herokuでrails + nodejs(puppeteer)のスクレイピングサイト構築
目的
herokuでテスト動作用に以下構成のスクレイピングサイトの構築を行う。
動作は1サーバ環境となるが、無料かつ迅速に、アルファ版等のテスト公開に用いることができる。
- Web公開用:ruby on rails
- スクレイピング用:nodejs (puppeteer)
- スクレイピングしたデータ格納用:postgresql
(自分メモ用を兼ねるので、前提に記載の設定事項は割愛します。)
前提
以下を行なっており、herokuでrails + postgresql環境が動いている状態であること。
- railsアプリをnewして作成
- herokuコマンドの導入
- herokuへのログイン&rails環境構築
- heroku postgresqlの起動&テーブル作成(以下手順では、testテーブルが作成されているとする)
手順
herokuにnode.jsとrailsの両方が動作する環境のパッケージを導入する
(※puppeteerに関わらず、これをしていないとnpmが動かない)$ heroku buildpacks:set heroku/nodejs $ heroku buildpacks:add --index 2 heroku/rubypuppeteer用の環境パッケージを導入する
$ heroku buildpacks:add https://github.com/CoffeeAndCode/puppeteer-heroku-buildpackjsライブラリを導入する
$ npm i puppeteer $ npm i pgrailsアプリのルートフォルダにpuppeteer用のディレクトリを作成し、puppeteerが動作するスプリプトを配置する。
puppeteer/example.jsconst 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 masterherokuで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スクリプトを定期的に実行する参考
- 投稿日:2019-05-26T19:16:36+09:00
herokuでRails + Node.js(puppeteer)のスクレイピングサイト構築
目的
herokuでテスト動作用に以下構成のスクレイピングサイトの構築を行う。
動作は1サーバ環境となるが、無料かつ迅速に、アルファ版等のテスト公開に用いることができる。
- Web公開用:Ruby on Rails
- スクレイピング用:Node.js (puppeteer)
- スクレイピングしたデータ格納用:postgresql
(自分メモ用を兼ねるので、前提に記載の設定事項は割愛します。)
前提
以下を行なっており、herokuでrails + postgresql環境が動いている状態であること。
- railsアプリをnewして作成
- herokuコマンドの導入
- herokuへのログイン&rails環境構築
- heroku postgresqlの起動&テーブル作成(以下手順では、testテーブルが作成されているとする)
手順
herokuにnode.jsとrailsの両方が動作する環境のパッケージを導入する
(※puppeteerに関わらず、これをしていないとnpmが動かない)$ heroku buildpacks:set heroku/nodejs $ heroku buildpacks:add --index 2 heroku/rubypuppeteer用の環境パッケージを導入する
$ heroku buildpacks:add https://github.com/CoffeeAndCode/puppeteer-heroku-buildpackjsライブラリを導入する
$ npm i puppeteer $ npm i pgrailsアプリのルートフォルダにpuppeteer用のディレクトリを作成し、puppeteerが動作するスプリプトを配置する。
puppeteer/example.jsconst 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 masterherokuで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スクリプトを定期的に実行する参考
- 投稿日:2019-05-26T18:43:44+09:00
~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の仕組みを理解していないため、わからん
- 投稿日:2019-05-26T18:07:32+09:00
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.rbclass 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と記述するだけ
- 投稿日:2019-05-26T17:26:16+09:00
Ruby on Rails 勉強日記1日目
1日目
作りたいもの
テストデータを手動で作成するのが面倒なので、それを簡略できるようなソフトを目指す。
環境整備
すでに以下の環境を揃えていたので、これをそのまま使うことにする。
ruby-version:2.6.3
rails-version:5.2.3rails projectの作成
以下のコマンドをシェルで打ち込んで、railsのプロジェクトを作成する。
rails new automation_application
初期動作の確認
以下のシェルでサーバーを立ち上げる。
rails server
localhost:3000検索して、railsの初期画面が表示されればOK。
終了させるときはctrl+Cで終了ホーム画面を作成する。
以下のコマンドで、ホーム画面となるviewとコントローラを作成する。
rails generate controller home top
- 投稿日:2019-05-26T17:25:27+09:00
~エラーとの格闘~ ヘルスケアwebサービスを自分で作る医者の日記
rails チュートリアルで途中わからなくなったため、
再度1.1から始める。gitの仕組みがわからない、
sshというものが理解できないpushできない
git remote addを何回か繰り返したが
push できない格闘したのち
remote rm〜で
一旦消して、githubを使うことにし、pushできた
朝からやって23時ごろここまで10時間以上一つのエラーと格闘長かった
- 投稿日:2019-05-26T16:32:06+09:00
[error]ActionController::InvalidAuthenticityToken
Postを削除しようとしたらエラーが、、、
ActionController::InvalidAuthenticityTokenRubyのバージョン
ruby -v '2.4.0'解決方法
application_controller.rbclass ApplicationController < ActionController::Base protect_from_forgery //ここを追加 end参考記事
https://qiita.com/_ayk_study/items/88269643c675fd4ca975
- 投稿日:2019-05-26T15:45:27+09:00
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 newmodelファイルの書き方
has_many
参照元から参照先へのリレーション1:Nを定義。
class User < ApplicationRecord has_many :tweets #1:Nを定義 endbelongs_to
参照先から参照元へのリレーションを定義。
class Tweet < ApplicationRecord belongs_to :user #1:Nを定義 endhas_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 enduser.favorite_blogs #あるユーザのお気に入りのブログ一覧を取得1対多を別名でアクセス
class User has_many :write_blogs, class_name: 'Blog', foreign_key: :user_id # 以下省略 endclass_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を管理できる
- 投稿日:2019-05-26T15:11:24+09:00
【Rails】v 5.2ではprotect_from_forgeryがapplication_controller内にない?
はじめに
先日参加した勉強会にて、
「application_controller.rb
に記述されているprotect_from_forgery with: :exception
が csrfを対策 している」(裏を返すと、ここを削除するとcsrf攻撃を受けるリスクが生まれる)
という説明を受けたのだが、
そもそもprotect_from_forgery with: :exception
なるコードを見た事が無い気がしたので調べてみた。比較
以下、Rails versionが5.1.7と5.2.3で
rails new
した直後のapplication_controller.rb
の比較です。v5.1.7
$ rails -v Rails 5.1.7application_controller.rbclass ApplicationController < ActionController::Base protect_from_forgery with: :exception end↑
protect_from_forgery with: :exception
がある!v5.2.3
$ rails -v Rails 5.2.3application_controller.rbclass 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間違いなどありましたらご指摘ください!
- 投稿日:2019-05-26T13:26:47+09:00
[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
- 投稿日:2019-05-26T11:57:56+09:00
RailsアプリのRSpecセットアップ方法
はじめに
Rails初心者です。
RSpecを初めて触るので、初めに行うRSpecのインストール〜アプリケーション設定について、備忘録としてまとめます。既存アプリに追加する前提です。
1から作る場合は、こちらが参考になります。
Railsアプリ作成手順まとめバージョン管理
ruby 2.7.0 rails 5.1.6RSpecセットアップ
①RSpecインストール
1-1 Gemfileに
rspec-rails
を追加Gemfilegroup :development, :test do # 元からあるコードは省略 gem 'rspec-rails', '~> 3.6.0' end1-2 bundle実行
terminal$ bundle install②テストデータベース確認
2-1 テスト用データベース確認
・SQLiteの場合
config/database.ymltest: <<: *default database: db/test.sqlite3・MySQL・PostgreSQLの場合
config/database.ymltest: <<: *default database: アプリ名があるはずなので確認。
もし書いてなければ、
config/database.yml
に上記のようなコードを追加
↓terminal$ bin/rails db:create:all
でテストデータベース作成。
※余談ですが、
rails
とbin/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 ./test3-3 デフォルトの形式→ドキュメント形式へ変更
必須ではないですが、RSpecの出力結果を見やすくします。
.rspec
ファイルに下記を追加。.rspec--require spec_helper --format documentation3-4
binstub
インストールアプリケーションの起動時間を早くする
Spring
を追加します。Gemfilegroup :development do # 元からあるコードは省略 gem 'spring-commands-rspec' endterminal$ 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.rbrequire_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速習実践ガイドとても分かりやすいです。
引き続き勉強していきます。
- 投稿日:2019-05-26T06:54:54+09:00
[学習用]Deviseにユーザーアバター(画像)を表示できる様に実装する
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:migratecontrollerに追加する
app/controllers/application_controller.rbbefore_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.rbclass 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 enduploaderの設定
app/uploader/avatar_uploader.rbclass 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を入れておけば、デザインも楽です。
- 投稿日:2019-05-26T06:34:40+09:00
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
- 投稿日:2019-05-26T01:50:41+09:00
[Rails]Markdown gem
RailsでMarkdown記法で書ける仕様に実装したいしかもコピペで
gem追加
Gemfilegem 'redcarpet'投稿機能の作成
ターミナル$ rails g scaffold post title:string body:text $ bundle $ bundle exec rails db:migratehelperに実装
post_helpermodule 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 endviewに実装
show.html.erb<%= markdown(@post.body).html_safe %>参考記事
https://qiita.com/hkengo/items/978ea1874cf7e07cdbfc
git
- 投稿日:2019-05-26T01:03:22+09:00
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をやりとりします。
1.2. 問題点
少し長目の記事なので最初にどこが問題であったのか書いておきます。
今回はNginxをReverseProxyとした際に、アップストリームサーバに必要なHTTPヘッダーを渡せていなかったことが問題でした。2. エラー発生
2.1. 422エラーが返ってきた
ブログを作成し、デプロイも完了。早速ログインしブログを投稿しよう、と思った矢先…
なんかエラーが出てる…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.rbrequire "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.rb
がinclude
しているRack::Request::Helpers
に定義されています。3.3. Rackを見てみる
request.rb
を一部添付します。rack/lib/rack/request.rbrequire '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_header
はenv
3という変数が保持しているハッシュから、引数に指定されたKey
が持つValue
を取得します。get_header
の引数となるKey
にはHTTPヘッダ4が指定されているようなので、Nginxの設定でヘッダを付与してみることにします。3.4. Nginxの設定を直す
/etc/nginx/conf.d/default.confupstream 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の言語にも挑戦しようかと思っています。
オススメがあれば教えてください。
最後まで読んでくださりありがとうございました。
Rackについては以下2つの記事によくまとまっています
Rails on Rack
What is Rack in Ruby? ↩EnvについてはStack over flowの質問を参照しました。「env is just a hash. Rack itself and various middlewares add values into it.」「envはハッシュで、Rackや様々なミドルウェアがこれにValueを加えていきます」 ↩
HTTPヘッダーの種類についてはMDN web docsを参照し、適切な値を探しました。 ↩
- 投稿日:2019-05-26T01:03:22+09:00
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をやりとりします。
1.2. 問題点
少し長目の記事なので最初にどこが問題であったのか書いておきます。
今回はNginxをReverseProxyとした際に、アップストリームサーバに必要なHTTPヘッダーを渡せていなかったことが問題でした。2. エラー発生
2.1. 422エラーが返ってきた
ブログを作成し、デプロイも完了。早速ログインしブログを投稿しよう、と思った矢先…
なんかエラーが出てる…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.rbrequire "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.rb
がinclude
しているRack::Request::Helpers
に定義されています。3.3. Rackを見てみる
request.rb
を一部添付します。rack/lib/rack/request.rbrequire '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_header
はenv
3という変数が保持しているハッシュから、引数に指定されたKey
が持つValue
を取得します。get_header
の引数となるKey
にはHTTPヘッダ4が指定されているようなので、Nginxの設定でヘッダを付与してみることにします。3.4. Nginxの設定を直す
/etc/nginx/conf.d/default.confupstream 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の言語にも挑戦しようかと思っています。
オススメがあれば教えてください。
最後まで読んでくださりありがとうございました。
Rackについては以下2つの記事によくまとまっています
Rails on Rack
What is Rack in Ruby? ↩EnvについてはStack over flowの質問を参照しました。「env is just a hash. Rack itself and various middlewares add values into it.」「envはハッシュで、Rackや様々なミドルウェアがこれにValueを加えていきます」 ↩
HTTPヘッダーの種類についてはMDN web docsを参照し、適切な値を探しました。 ↩