Quantcast
Channel: 初心者タグが付けられた新着記事 - Qiita
Viewing all 21373 articles
Browse latest View live

SpaceVimでイケてるVim環境を手に入れる

$
0
0

概要

  • SpaceVimとは?
  • インストール
  • 設定の変更方法

SpaceVimとは?

こんな感じのVimのインターフェースを簡単に自分のVimに適用出来ます。

https://spacevim.org/

インストール

下記の方法でインストールを行うとルートディレクトリ配下に.SpaceVim.SpaceVim.dディレクトリが作成されます。
基本的にはインストールするだけでSpaceVimがかっこいいVimにしてくれます。

Mac

$ curl -sLf https://spacevim.org/install.sh | bash

Windows

こちらをクリックしてインストールを行います。

設定

SpaceVimには便利な機能がいくつか用意されているので、私が行なっている設定を少し紹介します。

~/.SpaceVim.d/init.tomlに設定を追加していきます。

colorscheme

SpaceVimにはいくつかテーマが用意されておりそれらを使用する設定を以下で行うことが出来ます。
詳細はcolorschemeを参照ください

設定例:

[options]colorscheme="jellybeans"colorscheme_bg="dark"[[layers]]name="colorscheme"

File Tree

VSCodeとかGUIのエディタには必ずあるディレクトリツリーを表示してくれるやつです。

標準ではVimfilerが設定されており、私はそのままVimfilerを使っています。
Vimfiler以外だと、NERDTreedefxが設定可能です。

詳しくはFile Treeを参照ください。

[option]filetree_direction='left'enable_vimfiler_gitstatus=true

上記設定でファイルツリーを左に配置し、vimfilerにgitのステータスを反映出来るようにしています。

git系

git系の機能のサポートをしてくれます。
git ,githubのlayerを使用します。
詳しくは上記ページを参照ください。

[[layers]]name="git"[[layers]]name="github"

行番号

SpaceVimの初期設定では行番号が現在のカーソル位置から行番号が振られてしまうため、その機能をオフにします。

[option]relativenumber=false

完成形

#=============================================================================# dark_powered.toml --- dark powered configuration example for SpaceVim# Copyright (c) 2016-2017 Wang Shidong & Contributors# Author: Wang Shidong < wsdjeg at 163.com ># URL: https://spacevim.org# License: GPLv3#=============================================================================# All SpaceVim option below [option] section[options]automatic_update=true# set spacevim theme. by default colorscheme layer is not loaded,# if you want to use more colorscheme, please load the colorscheme# layercolorscheme="jellybeans"colorscheme_bg="dark"# Disable guicolors in basic mode, many terminal do not support 24bit# true colorsenable_guicolors=true# Disable statusline separator, if you want to use other value, please# install nerd fontsstatusline_separator="arrow"statusline_inactive_separator="arrow"buffer_index_type=4enable_tabline_filetype_icon=trueenable_statusline_mode=truefiletree_direction='left'enable_vimfiler_gitstatus=truerelativenumber=falseenable_cursorline=truefiletype_icon=trueenable_tabline_filetype_icon=true# Enable autocomplete layer[[layers]]name='autocomplete'auto-completion-return-key-behavior="complete"auto-completion-tab-key-behavior="smart"[[layers]]name="lsp"[[layers]]name='shell'default_position='top'default_height=30[[layers]]name="japanese"[[layers]]name="colorscheme"[[layers]]name="ui"enable=trueenable_sidebar=true[[layers]]name="git"[[layers]]name="github"[[layers]]name="fzf"[[layers]]name="tools"[[layers]]name="lang#dockerfile"[[layers]]name="lang#go"[[layers]]name="lang#html"[[layers]]name="lang#javascript"auto_fix=trueenable_flow_syntax=true[[layers]]name="lang#sh"

以上を設定した私のVimは以下のようになっています。

image.png

参考になれば幸いです!
素敵なVimライフを!


AWS試験対策(⑦ネットワーク)

$
0
0

自分用メモ、ネットワーク編。ポチポチ構築できるので一回やってみると意外と楽しいかもー。
但し、試験となると覚えることは多い。

VPC

論理的に分割された仮想ネットワーク。
ルータとかを物理的に調達する必要がない。
VPC作成→サブネット作成→ルートテーブル作成→セキュリティ設定の流れ。

まず、VPCはリージョンを跨げない。AZは跨げるよ。
CIDR(サイダー)をこのとき決める。シュワシュワして美味しそ。
サイダーってのは、ネットワークアドレスの部分を決めれる。たとえば16なら前の16ビットがネットワークアドレスになる。
10.0.0.0とかなら8ビットが、172.16.0.0とかなら16ビットが、192.168.0.0とかなら24ビットがネットワークアドレスだったのが、サイダーって技術でなくなって自分で指定できるようになったと思えばよき。

その次はサブネットを作成する。
AZごとにつくる。AZを跨ぐことはできない。
サブネット作成するときに、サイダーブロックを指定する。
計算は深く考えないで、VPC側が16ビットでサブネットマスクが18なら、サブネットの数は18-16=2、2の2乗になり、IPの数は32-18=14、2の14乗になる。
一番多いのはサブネットマスクが24。第3オクテット目で区切れるからわかりやすい。
サブネットの数は24-16で2の8乗。IPの数は32-24でこちらも2の8乗になる。気をつけなきゃいけないのは、オンプレは2IP使えなかったが、AWSは5IP使えない。
また、サブネットはインターネットにルートがあるパブリックサブネットと、ルートがないプライベートサブネットがある。

次に、ルートテーブルを設定することによって、サブネットを出るアウトバウンドトラフィックは送信先が指定される。
サブネットにルートテーブルを作るというよりは、ルートテーブルを作ってそれをサブネットに関連付けてるイメージ。

次に、ゲートウェイを作って、VPCにアタッチする。
主要なゲートウェイは以下。

  • インターネットゲートウェイ
    インターネットと通信するためのゲートウェイ。

  • 仮想プライベートゲートウェイ
    他の拠点(オンプレとか?)とVPN接続する際の、VPC側に作るゲートウェイ。

  • カスタマーゲートウェイ
    オンプレとVCPをVPN接続する際に、クライアント側に作るゲートウェイ。

  • NATゲートウェイ
    プライベートサブネットからインターネットにアクセスする際に、プライベートIP→パブリックIPにアドレス変換してくれるゲートウェイ。インターネットからはプライベートサブネットにアクセスしないようにできる。
    要するにこれをパブリックサブネットに置いて、
    プライベートサブネット→パブリックサブネット→インターネットゲートウェイ→インターネットって順で繋ぐ。

サブネットの種類は、ルートテーブルにインターネットゲートウェイが設定されてるかどうかで決まる。
送信先を0.0.0.0/0とするとインターネットゲートウェイを指定したことになるので、パブリックサブネットになる。逆にそうじゃないならプライベートになる。

セキュリティ設定
ファイアーウォールをセキュリティグループとネットワークACLで設定する。

セキュリティグループ
インスタンス単位で、許可する通信のみをインバウンドとアウトバウンドのトラフィックについて設定する。
ステートフルなので戻りのトラフィックは気にしなくていい。設定したすべてのルールが適用される。

ネットワークACL
サブネット単位で、許可、拒否する通信をインバウンドとアウトバウンドのトラフィックについて設定する。
ステートレスなので戻りのトラフィックを気にしないといけない。設定した番号の順番通りにルールが適用される。

エンドポイントの作成

プライベートIPで使える空間とパブリックIPで使える空間がある。パブリックIPを持つサービス(S3とか)にVPC内部からアクセスしたいときに、このエンドポイントという中継地点を使う。S3の時に書いた。略。

ピア接続

VPC同士の接続ができる。異なるAWSアカウントでも関係ない。
しかし、これで通信できるのは、直接ピア接続してるVPCのみ。友達の友達は友達じゃないのだ。その人と友好関係を築ければ別だが。

ELB

ロードバランサー。3種類あるよ。ALB、NLB、CLBの3つ。アプリかネットワークかクラシックか。CLBはレガシーでもうほとんど使わないらしい。
また、作るときは2つ以上のAZを指定しないとだめだよ。

ALBNLBCLB
種類L7 リバースプロキシL4 NATロードバランサL4/L7 リバースプロキシ
サポートプロトコルHTTP、HTTPS(Layer7)TCP、TLS(Layer4/5)HTTP、HTTPS(Layer7) TCP、TLS(Layer4/5)
セキュリティグループ設定あり設定なし設定あり
SSL TerminationHTTPS TerminationTLS TerminationSSL Termination
主なユースケース柔軟なアプリ管理とHTTP Terminationが必要な場合高度なパフォーマンスと静的IP(固定IP)が必要な場合昔の。

特徴は次の通り

  • ALB

レイヤー7(なんのこっちゃ)をサポートする。同一インスタンスでも複数のポートに負荷分散したり、パスベース、ホストベースのルーティングサポートがあったり、ネイティブHTTP/2(なんのこっちゃ)対応してたりする。

パスベースのルーティングとは、リクエスト内のURLに基づいてリクエストを転送してくれるルーティング方式。要するに、http://hoge/app1からきたリクエストも、http://hoge/app2からきたリクエストもすべてこいつが各コンテナに振り分けてくれる。今まではhttp://hoge/app1用のロードバランサー、http://hoge/app2用のロードバランサー…と一つ一つ必要だった。

ホストベースのルーティングは、HTTPヘッダー内のホストフィールドに基づいてリクエストを転送する仕組み。要するに、http://www1.hoge/からきたリクエストも、http://www2.hoge/からきたリクエストもすべてこいつが各ホストに振り分けてくれる。今まではこっちも一つ一つ必要だった。

  • NLB

レイヤー4/5をサポート(なんのこっちゃ)する。NLBは固定IPを持つことができる。
また、NATタイプのロードバランサーなので、トラフィックの宛先のIPを書き換えて転送するため、帰りのトラフィックはロードバランサー経由せずターゲットから直接クライアントへ行く。なのでパフォーマンスが高い。
要するに、クライアント→ALB→ターゲット→ALB→クライアントって戻るのが普通のところ、
クライアント→NLB→ターゲット→クライアントへ直に戻る。

また、セキュリティグループが使えないため、ターゲットのインスタンスで設定する必要がある。

  • CLB 古い。以上。

スケーラビリティ

負荷が増えると自動的にELBを増やしてくれるよ。負荷増えすぎて間に合わないほどやばかったら503エラーを返してくれるよ。
ただ、こうなるのを防ぐために、Pre-Warmingという暖機運転機能がある。やばくなりそうな時間帯がわかってるなら予め増やしておけばいいってわけ。しかしこれはサポートに申請する必要がある。ビジネスかエンプラじゃないと使えないってよ…

ヘルスチェック

健康診断。インスタンスがちゃんと動いてるか見る。TCP/HTTP/HTTPSで応答確認する。正常ならリクエストを転送する。障害やメンテで異常になったらそこへは転送しない。検知するだけで再起動とかはしてくれない。

SSL Termination

Webサーバの場合、外部通信をSSLで暗号化するのが普通。これを使えばクライアントとELBの間をSSL通信にしてくれる。ELBとターゲット間はHTTP通信。これを使うには、証明書をロードバランサに入れる必要がある。
また、NLBではSSLアクセラレーション機能が提供されていないため、ターゲット側でSSLターミネートを行う必要がある。

スティッキーセッション

ELBによってクライアントからのリクエストを毎回同一のインスタンスへ送信する方法。
同じクライアントから来たリクエストを同じインスタンスへ送信しなきゃいけない場合とかに使う。Cookieに設定した値を利用しながら動作するとき等。
株買うときとかに使われるアレかと。

  • 任意の有効期限を指定する方法

任意に指定された有効期限までの間、同一インスタンスとのセッションを維持する。チケット買うときとか、10分以内に手続き済ませてくださいみたいなやつ。
有効期限指定しなかった場合はCookieをクライアント側で意図的に削除しないと永続。

  • アプリケーションのCookieに従う方法

セッションの維持を行うCookie名を指定することで、そのCookie名を含むリクエストがあった場合、同一のインスタンスとのセッションを維持する。
要するに顔パス的な。最初は覚える必要あるけど二回目は前の続きからすっと入れる。

Auto Scaling

需要に応じてインスタンスを増やしたり減らしたりできる機能。また、異常が起きたらそれを切り離ししたりしてくれる。
コスト最適化と高可用性。
これ自体に課金は発生しない。増えたインスタンスなどには課金される。
対象は以下。

  • EC2
  • EC2スポットフリート
  • ECS
  • DynamoDB
  • Aurora

起動設定

どのようなインスタンスを起動するかの設定。自分でインスタンス作る時と同じように設定していく。設定しておけばこれが起動される、いわばAutoscalingのテンプレ。

Auto Scalingグループ

グループに対してスケーリングしてくれる。起動したインスタンスは複数のAZで均一にバランシングされる。AZ自体が逝ってしまった場合はその他のAZで起動してくれる。
しかし、リージョンをまたぐことはできない。
設定項目は以下。

  • インスタンスの希望起動数(最大、最小起動数)
  • VPC/サブネット
  • ELB/ヘルスチェック
  • スケジューリングポリシー
  • スケジュールされたアクション

スケジューリングプラン

手動、動的、スケジュールの三種類がある。
手動はその名の通り、バッチ処理があるときとかに手動で設定する。
動的はCloudWatchで閾値に近づいたらインスタンス数を変更する。みたいなことができる。

  • Target tracking scaling
    平均使用率が50%になるようにスケーリング的な

  • Step scaling
    使用率40超えたら一台追加、90超えたら二台目追加のような、ステップ(段階)を踏んで増やせる。

  • Simple scaling
    使用率が40超えたら一台追加みたいな感じ。

の三種類がある。維持するのか、段階を踏むのか、やばくなってきたから増やすのか。

スケジュールスケーリングは、毎日21時にアクセス多いからあらかじめ毎日21時に増やす設定しておこう!みたいな感じ。繰り返す系ならこれ。

ELB/ヘルスチェックの設定

ELB配下のインスタンスを対象にする場合、ヘルスチェックのタイプをEC2もしくはELBにする。
EC2の場合はステータスがrunning以外となった場合、また、システムステータスがimpairedとなったときに異常と判断される。
ELBの場合はEC2の場合プラス、ELBのヘルスチェックが正常とならない場合、異常と判断される。
異常になった場合、該当インスタンスは削除され、新しいインスタンスを起動することで±0する。代わりはいくらでもいるのよ。
これをAuto Healingという。
ちなみに、この管理下のインスタンスを停止すると、インスタンスはTerminate(削除)される。
これは、「EC2がrunning以外になったから異常だ!増やさなきゃ!!」ってなるのを防ぐため。
自分で停止させたのに、AutoScalingちゃんが「一台減っちゃった!増やさなきゃ!!」って増やしちゃったらまったく意味ないから。便利なようでおちゃめな子だよね。

いったん区切り。次回Route53、Direct Connect、CloudFrontをやってネットワークは終わり。DBへ行く。

マウスクリックをキー入力(FやGなど)の代わりにしたい

$
0
0

こんにちは、私はUnityを初めて二カ月の初心者です。
D,F,G,H,J,の5つのキーを使ってリズムゲーを作っていましたが、
今日、Android用にビルドしたところ、スマホではこの5つの入力が使えない、という超初歩的なことに気づきました。

他のUIのクリックボタンは、当然タッチで出来ますが、キー入力で作ったものをタッチ入力に変える方法がどうしてもわかりません。

初心者なので、この対処法は、
①5つのGameObjectをつくる。
②これらにタッチすると、D,F,G,H,J,それぞれのキー入力をしたことにするスクリプトを書き、アタッチする。
という方法にしたいと思っています。

この実現の為、色々調べまして、様々な入力方法のコードの書き方は分かったのですが、その先の、
「このInputがあったらキー入力をしたことにするスクリプト」の書き方が全く分かりません。

知っているかたがいらっしゃましたら、ご教授いただけますでしょうか。
よろしくお願いいたします。

RailsアプリをHerokuにデプロイしたらクラッシュしてたときの対処法

$
0
0

開発環境では問題なく動いていたアプリが、満を持してデプロイしたときにクラッシュしてたら精神的に結構きますよね

正直、辛い。

今回は、Rails5のアプリをHerokuにデプロイした際にクラッシュを起こしていたので、そのとき取った行動をメモとして残しておきます。

エラー画面

image.png

We're sorry, but something went wrong.
If you are the application owner check the logs for more information.

日本語でおk(死語)

このエラー文自体を検索し、色んなエラー時対応の記事を読み込んでいくと、
何にしろログを見なっせ(書いてある)ということなので、
$ heroku logsを叩きました。

こちらの記事はとても参考になりました。

Qiita/[Ruby on Rails Tutorial]Herokuにデプロイ後Application error[H10 (App crashed)]が発生した時の対処法

$heroku logs
2020-02-19T13:24:10.854459+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/runner.rb:138:in `load_and_bind'
2020-02-19T13:24:10.854460+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/single.rb:87:in `run'
2020-02-19T13:24:10.854460+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/launcher.rb:174:in `run'
2020-02-19T13:24:10.854461+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/cli.rb:77:in `run'
2020-02-19T13:24:10.854461+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/bin/puma:10:in `<top (required)>'
2020-02-19T13:24:10.854462+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/bin/puma:23:in `load'
2020-02-19T13:24:10.854462+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/bin/puma:23:in `<top (required)>'
2020-02-19T13:24:10.941495+00:00 heroku[web.1]: State changed from starting to crashed
2020-02-19T13:24:10.946163+00:00 heroku[web.1]: State changed from crashed to starting
2020-02-19T13:24:10.919655+00:00 heroku[web.1]: Process exited with status 1
2020-02-19T13:24:14.912178+00:00 heroku[web.1]: Starting process with command `bundle exec puma -C config/puma.rb`
2020-02-19T13:24:17.577966+00:00 app[web.1]: Puma starting in single mode...
2020-02-19T13:24:17.587312+00:00 app[web.1]: * Version 3.9.1 (ruby 2.5.7-p206), codename: Private Caller
2020-02-19T13:24:17.587332+00:00 app[web.1]: * Min threads: 5, max threads: 5
2020-02-19T13:24:17.587335+00:00 app[web.1]: * Environment: production
2020-02-19T13:24:19.870761+00:00 app[web.1]: ! Unable to load application: LoadError: No such file to load -- URI.rb
2020-02-19T13:24:19.870872+00:00 app[web.1]: bundler: failed to load command: puma (/app/vendor/bundle/ruby/2.5.0/bin/puma)
2020-02-19T13:24:19.870924+00:00 app[web.1]: LoadError: No such file to load -- URI.rb
2020-02-19T13:24:19.870926+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:292:in `require'
2020-02-19T13:24:19.870926+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:292:in `block in require'
2020-02-19T13:24:19.870927+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:258:in `load_dependency'
2020-02-19T13:24:19.870927+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:292:in `require'
2020-02-19T13:24:19.870928+00:00 app[web.1]: /app/app/controllers/application_controller.rb:5:in `<class:ApplicationController>'
2020-02-19T13:24:19.870928+00:00 app[web.1]: /app/app/controllers/application_controller.rb:1:in `<top (required)>'2020-02-19T13:24:19.870929+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:292:in `require'
2020-02-19T13:24:19.870929+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:292:in `block in require'
2020-02-19T13:24:19.870930+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:258:in `load_dependency'
2020-02-19T13:24:19.870930+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:292:in `require'
2020-02-19T13:24:19.870930+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:379:in `block in require_or_load'
2020-02-19T13:24:19.870931+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:36:in `block in load_interlock'
2020-02-19T13:24:19.870931+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies/interlock.rb:12:in `block in loading'
2020-02-19T13:24:19.870932+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/concurrency/share_lock.rb:149:in `exclusive'
2020-02-19T13:24:19.870932+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies/interlock.rb:11:in `loading'
2020-02-19T13:24:19.870933+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:36:in `load_interlock'
2020-02-19T13:24:19.870933+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:357:in `require_or_load'
2020-02-19T13:24:19.870934+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:335:in `depend_on'
2020-02-19T13:24:19.870934+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/activesupport-5.1.6/lib/active_support/dependencies.rb:251:in `require_dependency'
2020-02-19T13:24:19.870934+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/engine.rb:476:in `block (2 levels) in eager_load!'
2020-02-19T13:24:19.870935+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/engine.rb:475:in `each'
2020-02-19T13:24:19.870935+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/engine.rb:475:in `block in eager_load!'
2020-02-19T13:24:19.870935+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/engine.rb:473:in `each'
2020-02-19T13:24:19.870936+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/engine.rb:473:in `eager_load!'
2020-02-19T13:24:19.870936+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/engine.rb:354:in `eager_load!'
2020-02-19T13:24:19.870937+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/application/finisher.rb:67:in `each'
2020-02-19T13:24:19.870937+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/application/finisher.rb:67:in `block in <module:Finisher>'
2020-02-19T13:24:19.870937+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/initializable.rb:30:in `instance_exec'
2020-02-19T13:24:19.870938+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/initializable.rb:30:in `run'
2020-02-19T13:24:19.870938+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/initializable.rb:59:in `block in run_initializers'
2020-02-19T13:24:19.870939+00:00 app[web.1]: /app/vendor/ruby-2.5.7/lib/ruby/2.5.0/tsort.rb:228:in `block in tsort_each'
2020-02-19T13:24:19.870939+00:00 app[web.1]: /app/vendor/ruby-2.5.7/lib/ruby/2.5.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'
2020-02-19T13:24:19.870939+00:00 app[web.1]: /app/vendor/ruby-2.5.7/lib/ruby/2.5.0/tsort.rb:431:in `each_strongly_connected_component_from'
2020-02-19T13:24:19.870940+00:00 app[web.1]: /app/vendor/ruby-2.5.7/lib/ruby/2.5.0/tsort.rb:349:in `block in each_strongly_connected_component'
2020-02-19T13:24:19.870940+00:00 app[web.1]: /app/vendor/ruby-2.5.7/lib/ruby/2.5.0/tsort.rb:347:in `each'
2020-02-19T13:24:19.870941+00:00 app[web.1]: /app/vendor/ruby-2.5.7/lib/ruby/2.5.0/tsort.rb:347:in `call'
2020-02-19T13:24:19.870941+00:00 app[web.1]: /app/vendor/ruby-2.5.7/lib/ruby/2.5.0/tsort.rb:347:in `each_strongly_connected_component'
2020-02-19T13:24:19.870942+00:00 app[web.1]: /app/vendor/ruby-2.5.7/lib/ruby/2.5.0/tsort.rb:226:in `tsort_each'
2020-02-19T13:24:19.870942+00:00 app[web.1]: /app/vendor/ruby-2.5.7/lib/ruby/2.5.0/tsort.rb:205:in `tsort_each'
2020-02-19T13:24:19.870947+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/initializable.rb:58:in `run_initializers'
2020-02-19T13:24:19.870947+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/railties-5.1.6/lib/rails/application.rb:353:in `initialize!'
2020-02-19T13:24:19.870948+00:00 app[web.1]: /app/config/environment.rb:5:in `<top (required)>'
2020-02-19T13:24:19.870948+00:00 app[web.1]: config.ru:3:in `require_relative'
2020-02-19T13:24:19.870948+00:00 app[web.1]: config.ru:3:in `block in <main>'
2020-02-19T13:24:19.870949+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/rack-2.2.2/lib/rack/builder.rb:116:in `eval'
2020-02-19T13:24:19.870949+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/rack-2.2.2/lib/rack/builder.rb:116:in `new_from_string'
2020-02-19T13:24:19.870950+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/rack-2.2.2/lib/rack/builder.rb:105:in `load_file'
2020-02-19T13:24:19.870950+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/rack-2.2.2/lib/rack/builder.rb:66:in `parse_file'
2020-02-19T13:24:19.870951+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/configuration.rb:313:in `load_rackup'
2020-02-19T13:24:19.870951+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/configuration.rb:242:in `app'
2020-02-19T13:24:19.870951+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/runner.rb:138:in `load_and_bind'
2020-02-19T13:24:19.870952+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/single.rb:87:in `run'
2020-02-19T13:24:19.870952+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/launcher.rb:174:in `run'
2020-02-19T13:24:19.870952+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/lib/puma/cli.rb:77:in `run'
2020-02-19T13:24:19.870952+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/gems/puma-3.9.1/bin/puma:10:in `<top (required)>'
2020-02-19T13:24:19.870953+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/bin/puma:23:in `load'
2020-02-19T13:24:19.870953+00:00 app[web.1]: /app/vendor/bundle/ruby/2.5.0/bin/puma:23:in `<top (required)>'
2020-02-19T13:24:20.204439+00:00 heroku[web.1]: State changed from starting to crashed
2020-02-19T13:24:20.185683+00:00 heroku[web.1]: Process exited with status 1
2020-02-19T13:24:21.120419+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/" host=example.herokuapp.com request_id=eb4b6551-949a-4693-9f76-2b0bcd0e92dd fwd="219.100.188.52" dyno= connect= service= status=503 bytes= protocol=https

心折れそう

折れてました。

辛かったので、エラー文の一番最後にあった
heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/"
を、エラーコード記載されてるし後はグーグル先生がどうにかしてくれるだろうと脳死で検索しました。

結果的に分かったのは、

エラー文見ろ

厳しい!!!

目から溢れる汁で滲む画面を読み進めたところ、次の部分に目が留まる(とはいっても序盤の方)。

2020-02-19T13:24:19.870761+00:00 app[web.1]: ! Unable to load application: LoadError: No such file to load -- URI.rb
2020-02-19T13:24:19.870872+00:00 app[web.1]: bundler: failed to load command: puma (/app/vendor/bundle/ruby/2.5.0/bin/puma)
2020-02-19T13:24:19.870924+00:00 app[web.1]: LoadError: No such file to load -- URI.rb

ご丁寧にまでつけてある。

ここでピーンと来て、そういえばネットから次のコードをコピペしたときに、

example_controller.rb
require'net/https'require'uri'# 'URI'と記載していた

で実行したところ、uriなんてねえよバーカ!と怒られたので、
'uri'=>'URI'と訂正したことを思い出しました。

お ま え か。

その後、正しくrequire 'uri'と訂正すると無事動いてくれました。

エラー文を読もう(結論)

心折れかかったら息抜きも忘れないでください。

【Go】Lambda + RDSをIAM認証で接続する

$
0
0

はじめに

現在、API Gateway + Lambda + RDSを使ってWebアプリケーションを作っています。LambdaとRDSのIAM接続というのを見つけて、Goで試してみたので備忘録です。

IAM認証で接続するメリット・デメリット

メリット

コールドスタート問題の解決

アプリケーションでLambdaとRDSを接続することが避けられていた理由は、とにかく接続に時間がかかるためです。Webアプリを使っていて、DB接続に10秒も20秒も待ってられないですよね。(通称VPC Lambdaの10秒の壁?)

LambdaがVPC内で通信を行うにはENI生成を行う必要があり、コールドスタートとなってしまいます。しかし、IAM認証で接続することで、LambdaをVPC内に設置する必要がなくなるので、問題とされていた10秒の壁が解決できるのです!

【朗報】
Lambda関数のVPC環境でのコールドスタートが改善されたようです?

参考 : [発表] Lambda 関数が VPC 環境で改善されます

デメリット

同時接続数が限られる

RDSの同時接続数に上限があり、それ以上は接続できません。RDSのサイズによりますが、db.t2.microの場合、1秒間に10接続まで。

参考:IAM データベース認証の MySQL の制限事項

私が今回作っているアプリは20人程度しか使わない、かつ使用頻度も低い、かつ社内のシステムなので、とりあえず大丈夫かなという軽い気持ちで考えていますが、もっと大人数が使うWebアプリだと厳しい制限です…。

【朗報】
2019年末に行われたre:Invent 2019で発表された、RDS Proxyを利用すると、この同時接続数の制限から解放されるかもしれないという朗報が!!(現在はプレビュー版です。)

LambdaとRDSを接続するデメリットはなくなったかもしれないです!

手順

LambdaからRDSのIAM認証での接続は下記の流れで進めていきます。

  1. RDSの構築
  2. IAM認証用のユーザーの作成
  3. テーブルの作成
  4. SSL証明書のダウンロード
  5. IAMポリシーの作成
  6. Lambda関数の作成
  7. ソースコードの実装
  8. ソースコードのアップロード
  9. MySQLのIP制限

やってみる

1. RDSの構築

私が使用したMySQLのバージョン:MySQL 5.7.22
(MySQL 8.0.16ですると、SSL経由でデータベースに接続を許可する設定が後で出てくるんですが、私の力ではできなかったので5.7系で作り直しました…。)

設定内容のうち、特別な箇所のみご紹介します。

スクリーンショット 2020-02-19 9.14.21.png

サブネットグループ:パブリックサブネットでサブネットグループを作成し、選択
パブリックアクセス可能:ありを選択

スクリーンショット 2020-02-19 9.17.14.png

データベース認証:パスワードと IAM データベース認証を選択

セキュリティグループでIP制限をすると、Lambdaからアクセスできません。ここでも躓いてしまったので、お気をつけください!!

その他は扱いたいデータ量などを鑑みて設定してください!

2. IAM認証用のユーザーの作成

前項で作成したDBにマスターユーザーでログインします。

$ mysql -h [エンドポイント] -u [マスターユーザー名] -p

IAM認証用のユーザーを作成します。Hostは %を指定します。

> CREATE USER '[ユーザー名]'@'%' IDENTIFIED WITH AWSAuthenticationPlugin as 'RDS';

SSL経由でデータベースに接続を許可します。下記では、SELECT、INSERT、UPDATE、DELETEを許可しています。

> GRANT SELECT,INSERT,UPDATE,DELETE ON [DB名].* to '[ユーザー名]'@'%' REQUIRE SSL;

3. テーブルの作成

今回は以下のようなテーブルを作成しました。

> select * from Info;
+----+-------------+-----------+------------------+
| ID | Name        | Address   | TEL              |
+----+-------------+-----------+------------------+
|  1 | 山田太郎     | 東京都     | 090-0000-0000    |
+----+-------------+-----------+------------------+

4. SSL証明書のダウンロード

main.goと同じディレクトリにダウンロードしてください。

$ wget https://s3.amazonaws.com/rds-downloads/rds-ca-2019-root.pem

5. IAMポリシーの作成

以下のポリシーを作成します。

{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["rds-db:connect"],"Resource":["arn:aws:rds-db:[リージョン]:[アカウントID]:dbuser:[リソースID]/[DBに作成したユーザー名]"]}]}

6. Lambda関数の作成

Lambda関数は特に複雑な設定をする必要がなく、通常通り作成するため省略します。
前項で作成したポリシーをアタッチし、VPCは非VPCのままにします。

7. ソースコードの実装

main.go
packagemainimport("crypto/tls""crypto/x509""database/sql""encoding/json""fmt""io/ioutil""os""github.com/aws/aws-lambda-go/lambda""github.com/aws/aws-sdk-go/aws/credentials""github.com/aws/aws-sdk-go/service/rds/rdsutils""github.com/go-sql-driver/mysql")typeResponsestruct{UserIDint`json:"userId"`UserNamestring`json:"userName"`Addressstring`json:"address"`TELstring`json:"tel"`}// os.Getenv()でLambdaの環境変数を取得varpemFile="rds-ca-2019-root.pem"// SSL証明書vardbEndpoint=os.Getenv("dbEndpoint")// エンドポイント:ポート番号vardbUser=os.Getenv("dbUser")// DBに作成したユーザ名vardbName=os.Getenv("dbName")// テーブルを作ったDB名varawsRegion=os.Getenv("awsRegion")// RDSのリージョンfuncRDSConnect()(interface{},error){awsCredentials:=credentials.NewEnvCredentials()authToken,err:=rdsutils.BuildAuthToken(dbEndpoint,awsRegion,dbUser,awsCredentials,)iferr!=nil{panic(err.Error())}connectStr:=fmt.Sprintf("%s:%s@tcp(%s)/%s?tls=rds&allowCleartextPasswords=true",dbUser,authToken,dbEndpoint,dbName,)db,err:=sql.Open("mysql",connectStr)iferr!=nil{panic(err.Error())}deferdb.Close()fmt.Println("--- データの取得")// データの取得info,err:=db.Query("SELECT * FROM Info")deferinfo.Close()iferr!=nil{panic(err.Error())}varUserIDintvarUserNamestringvarAddressstringvarTELstringforinfo.Next(){iferr:=info.Scan(&UserID,&UserName,&Address,&TEL);err!=nil{panic(err.Error())}fmt.Println(UserID,UserName,Address,TEL)}responseMap:=Response{}responseMap.UserID=UserIDresponseMap.UserName=UserNameresponseMap.Address=AddressresponseMap.TEL=TELparams,_:=json.Marshal(responseMap)fmt.Println(string(params))returnstring(params),nil}funcRegisterTLSConfig(){caCertPool:=x509.NewCertPool()pem,err:=ioutil.ReadFile(pemFile)iferr!=nil{panic(err.Error())}ifok:=caCertPool.AppendCertsFromPEM(pem);!ok{panic(err.Error())}mysql.RegisterTLSConfig("rds",&tls.Config{ClientCAs:caCertPool,InsecureSkipVerify:true,})}funcrun()(interface{},error){fmt.Println("--- tsl.Configの登録開始")RegisterTLSConfig()fmt.Println("--- tsl.Configの登録終了")fmt.Println("--- RDS接続開始")response,err:=RDSConnect()iferr!=nil{panic(err.Error())}fmt.Println("--- RDS接続終了")returnresponse,nil}/**************************
   メイン
**************************/funcmain(){lambda.Start(run)}

8. ソースコードのアップロード

ビルドして

$ GOOS=linux GOARCH=amd64 go build -o main

zipして

$ zip main.zip main rds-ca-2019-root.pem

アップロード!!!
ハンドラはmainに変更します。

実行結果

値を取得することができました!

Lambda.png

レイテンシーも気になりません!

9. MySQLのIP制限

IAM認証と言えどパブリックサブネットにRDSを置いて、セキュリティグループ全開ってセキュリティどうなんだろう?マスターユーザーはパスワード認証やん!!!って思いますよね。そこで、DBのマスターユーザーのIP制限をしました。

> RENAME USER '[マスターユーザー名]'@'%' TO '[マスターユーザー名]'@'[許可したいIPアドレス]';

私は会社のIPアドレスにして、/32まで書いたらログインできずに焦りました…。お気をつけください。VPC内のIPアドレスに設定して、そのVPCの中に立てたEC2からの接続を許可するっていう方法でもいいですね。

複数IPを追加したい!

上記で設定したIPアドレス以外にも、複数の場所からマスターユーザーでアクセスしたく、接続できるIPアドレスを増やす必要があったのですが、RENAMEだと先ほど指定したIPアドレスに上書きされてしまうので…以下の方法でマスターユーザーを追加(複製)しました!

> CREATE USER '[マスターユーザー名]'@'[許可したいIPアドレス]' IDENTIFIED BY '[パスワード]';
> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, PROCESS, REFERENCES, INDEX, ALTER, SHOW DATABASES, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER ON *.* TO '[マスターユーザー名]'@'[許可したいIPアドレス]' WITH GRANT OPTION;

おまけ

ローカルからRDSに接続してみた

IAMユーザの作成

LambdaにアタッチしたポリシーをIAMユーザーにアタッチします。

AWS CLIの設定

作成したユーザーで、CLIの設定を行います。

$ aws configure

先ほど作成したIAMユーザーのアクセスキーとシークレットキー、リージョンを入力してください。

ターミナルから接続

ターミナルからプログラムアクセスをします。SSL証明書のパスは置いてあるところに変更してください。

$ RDSHOST="[エンドポイント]"
$ TOKEN="$(aws rds generate-db-auth-token --hostname $RDSHOST --port 3306 --region [リージョン] --username [IAMユーザー名] )"
$ mysql --host=$RDSHOST --port=3306 --ssl-ca=${GOPATH}/src/qiita/rds-ca-2019-root.pem --enable-cleartext-plugin --user=[IAMユーザー名] --password=$TOKEN

上記を実行して証明書チェーンを受け入れない場合は、次のコマンドを実行して、古いルート証明書と新しいルート証明書の両方を含む証明書バンドルをダウンロードしてください。

その後、再度上記のコマンドを実行してみてください。(証明書名は変更する。)

$ wget https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem

実行

実行結果は以下の通りとなりました。先ほどと同じですね!

> select * from Info;
+----+-------------+-----------+------------------+
| ID | Name        | Address   | TEL              |
+----+-------------+-----------+------------------+
|  1 | 山田太郎     | 東京都     | 090-0000-0000    |
+----+-------------+-----------+------------------+

おわりに

無事、RDSから取得したデータをLambdaに返すことができました!次回はAPI Gatewayを使ってWebブラウザで表示するところまでいきたいです!

RDS Proxyの発表やLambdaのコールドスタートの改善など、もっとLambda + RDSがこれから利用しやすくなりそうですね!また、RDS Proxyも試してみたいなと思ってます。

「AppStream」と「MySQL Yumリポジトリ」、それぞれのMySQLインストール方法について

$
0
0

Centos8にいろいろ入れてみるシリーズ。
今回はMySQL8.0をインストールする方法を記載します。

1.インストール

1.1 AppStreamからインストール

Centos8からは、AppStreamからMySQLをインストールできるようになりました。
AppStreamはデフォルトで入っているリポジトリなので、MySQLのインストールもさくっと終えられるようになっています。

1.1.1 MySQLのインストール

以下のコマンドを実行1するだけです。

$ sudo dnf -y install @mysql:8.0/server

1.2 MySQL Yumリポジトリからインストール

毎度おなじみ、MySQL公式のリポジトリを利用したインストール方法です。
AppStreamより最新バージョンが利用できるようですので、こちらを利用するのも手です。

公式のインストール手順はこちらです。

Yumリポジトリはこちらから手に入れましょう。
本記事ではmysql80-community-release-el8-1.noarch.rpmを利用します。

1.2.1 MySQL Yumリポジトリのインストール

まずはMySQL公式からYumリポジトリをインストールします。

$ sudo dnf -y localinstall https://dev.mysql.com/get/mysql80-community-release-el8-1.noarch.rpm

Yumリポジトリが正常に追加されたことを確認します。

$ sudo dnf repolist enabled | grep "mysql.*-community.*"
mysql-connectors-community MySQL Connectors Community                         42
mysql-tools-community      MySQL Tools Community                              19
mysql80-community          MySQL 8.0 Community Server                         31

1.2.2 MySQLのモジュールストリームの無効化

mysqlのモジュールストリームを無効化します。
これを実施しないと、mysql-community-*-debuginfoパッケージがしか表示されません。

$ sudo dnf -y module disable mysql

1.2.3 MySQLのインストール

mysql-community-serverが表示されるのを確認してから、

$ sudo dnf search mysql-community-server
====================================== 名前 完全一致: mysql-community-server =======================================
mysql-community-server.x86_64 : A very fast and reliable SQL database server

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

$ sudo dnf -y install mysql-community-server

2.事後設定

2.1 サービス起動&自動起動設定

インストールが完了したら、サービスを起動しましょう。
自動起動の設定も忘れずに。

$ sudo systemctl start mysqld
$ sudo systemctl enable mysqld

2.2 rootユーザ初期パスワード確認 (MySQL Yumリポジトリによるインストールのみ)

rootユーザの初期パスワードを/var/log/mysqld.logから確認します。

$ sudo grep 'temporary password' /var/log/mysqld.log
2020-02-20T06:24:44.514163Z 5 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: ************

なお、AppStreamからインストールした場合は、rootユーザの初期パスワードは設定されていませんのでご注意ください。

2.3 mysql_secure_installationによる初期設定

mysql_secure_installationを利用して、MySQLの初期設定を行います。

$ mysql_secure_installation

2.3.1 AppStreamでMySQLをインストールした場合

最初に「VALIDATE PASSWORD COMPONENT」をセットアップのセットアップ有無を聞かれます。
今回は設定するため、Yで返答します。
※不要であればN返答でもOKです。

VALIDATE PASSWORD COMPONENT can be used to test passwords
and improve security. It checks the strength of password
and allows the users to set only those passwords which are
secure enough. Would you like to setup VALIDATE PASSWORD component?

Press y|Y for Yes, any other key for No: Y

「VALIDATE PASSWORD COMPONENT」をセットアップします。
パスワードポリシーを聞かれるので、適切なポリシーを設定します。

There are three levels of password validation policy:

LOW    Length >= 8
MEDIUM Length >= 8, numeric, mixed case, and special characters
STRONG Length >= 8, numeric, mixed case, special characters and dictionary                  file

Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 0

rootユーザの新しいパスワードを設定します。

Please set the password for root here.

New password: ********

Re-enter new password: ********

パスワードの推定強度とともに、先ほど入力したパスワードで続行するか聞かれます。
問題がなければ、Yで返答します。

Estimated strength of the password: 100
Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y

2.3.2 MySQL YumリポジトリでMySQLをインストールした場合

最初にrootの初期パスワードを求められます。
2.2 rootユーザ初期パスワード確認」で確認した初期パスワードを入力します。

Enter password for user root: ********

rootユーザの新しいパスワードを入力します。

The existing password for the user account root has expired. Please set a new password.

New password: ********

Re-enter new password: ********

「validate_password」コンポーネントがインストールされているため、これ以降はコンポーネントの構成にそって実行されます。
そのため、今しがた設定したrootユーザのパスワードを変更するかの確認があります。
ですが、新しいパスワード設定の際にコンポーネントの構成にそったパスワードを入力しないと怒られたしたので、Nで返答します。

The 'validate_password' component is installed on the server.
The subsequent steps will run with the existing configuration
of the component.
Using existing password for root.

Estimated strength of the password: 100
Change the password for root ? ((Press y|Y for Yes, any other key for No) : n

2.3.3 ここから共通手順となります。

匿名ユーザは不要のため、削除します。

By default, a MySQL installation has an anonymous user,
allowing anyone to log into MySQL without having to have
a user account created for them. This is intended only for
testing, and to make the installation go a bit smoother.
You should remove them before moving into a production
environment.

Remove anonymous users? (Press y|Y for Yes, any other key for No) : y
Success.

rootユーザのリモートログインは拒否します。

Normally, root should only be allowed to connect from
'localhost'. This ensures that someone cannot guess at
the root password from the network.

Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y
Success.

テストデータベースは不要のため、削除します。

By default, MySQL comes with a database named 'test' that
anyone can access. This is also intended only for testing,
and should be removed before moving into a production
environment.


Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y
 - Dropping test database...
Success.

 - Removing privileges on test database...
Success.

特権テーブルをリロードして、設定を反映します。

Reloading the privilege tables will ensure that all changes
made so far will take effect immediately.

Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y
Success.

All done!

All done!と表示されれば、初期設定は完了です。

お疲れ様でした。


  1. $ sudo dnf -y install @mysqlと省略してもよいですが、その場合はデフォルト値が採用されるので、$ sudo dnf module list mysqlで事前に確認しておきましょう。 

画像認識にチャレンジ(ver.1.0)

$
0
0

1 はじめに

 画像認識ができるプログラムをKerasにより作りました。
 実行環境は下記になります。

・python 3.5.6
・Keras 2.2.0
・tensorflow 1.5.
・matplotlib 2.2.2
・numpy 1.15.2
・opencv 3.4.2

参考にさせて頂いたURLは下記一覧となります。
https://qiita.com/ayumiya/items/e1e87df54c41519be6b4

2 画像を収集する

 今回flickr APIを利用して大量の画像を収集しました。こちらに関しては、下記URLを参考にflickrの登録・API, secret KEYの発行などを行うことで画像を収集させました。
http://ykubot.com/2017/11/05/flickr-api/

 収集に関してもpython上で下記プログラムを実行することで自動で画像を集めることができました。300枚収集するのに必要な時間は凡そ今回のケースだと5分程度でした。

下記がプログラム全文になります。

collection.py
importosimporttimeimporttracebackimportflickrapifromurllib.requestimporturlretrieveimportsysfromretryimportretryflickr_api_key="xxxx"#ここに発行されたAPI KEYを入力secret_key="xxxx" #ここに発行されたSECRET KEYを入力keyword=sys.argv[1]@retry()defget_photos(url,filepath):urlretrieve(url,filepath)time.sleep(1)if__name__=='__main__':flicker=flickrapi.FlickrAPI(flickr_api_key,secret_key,format='parsed-json')response=flicker.photos.search(text='child',#検索したいワードを入力per_page=300,#何枚収集したいか指定media='photos',sort='relevance',safe_search=1,extras='url_q,license')photos=response['photos']try:ifnotos.path.exists('./image-data/'+'child'):os.mkdir('./image-data/'+'child')forphotoinphotos['photo']:url_q=photo['url_q']filepath='./image-data/'+'child'+'/'+photo['id']+'.jpg'get_photos(url_q,filepath)exceptExceptionase:traceback.print_exc()

 今回、「子供:child」と「老人:elder」を認識すべくそれら画像を300枚ずつ収集しました。
 先にも述べますが検索ワードelderは、老人だけでなく植物、若い女性等の画像も収集されたため、それにより認識率が高くなかったものと推定しています。

3 画像認識プログラムの作成

 以下記事にて、画像認識プログラムについて説明します。全体像から私がつまづいてしまったところや、注意すべきポイントに絞って抜粋し、そののちにプログラム全文を記載します。

インポートしたライブラリ、モジュール等

import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
from keras.utils.np_utils import to_categorical
from keras.layers import Dense, Dropout, Flatten, Input
from keras.applications.vgg16 import VGG16
from keras.models import Model, Sequential
from keras import optimizers

モデル関数を作るうえでの注意

model = Model(input=vgg16.input, output=top_model(vgg16.output))

元々、仕様環境はtensorflowをインストールしており、その中のライブラリであるkerasとして、引用していました。
例:from tensorflow.keras.utils import to_categorical

このまま進めるていると、このModel関数を定義するうえで、
('Keyword argument not understood:', 'input')
となるエラーが発生しました。

これはどうもkerasのversionが古いと起こるとのことで、
keras自体のアップデートなども行ったのですが、エラーは出続けました。
従い、keras自体を読み込む書き方をしてみたら、解決しました。

本エラー概要及び、keras自体のアップデート
https://sugiyamayoshiaki.jp/%E3%80%90%E3%82%A8%E3%83%A9%E3%83%BC%E3%80%91typeerror-keyword-argument-not-understood-data_format/

モデルの選定

# モデルにvggを使います
input_tensor = Input(shape=(50, 50, 3))
vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)

# vggのoutputを受け取り、2クラス分類する層を定義します
top_model = Sequential()
top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
top_model.add(Dense(256, activation='relu'))
top_model.add(Dropout(0.5))
top_model.add(Dense(2, activation='softmax'))

今回選定したモデルはVGG16と呼ばれるモデルです。
http://cedro3.com/ai/mini-vgg-net/

VGGチームが作った畳み込み13層+全結合層3層=16層のニューラルネットワークとのことです。
詳細は下記に記載されていますが、なるほど理解できていません。
https://qiita.com/MuAuan/items/86a56637a1ebf455e180

子供か老人かを判別する関数を作る

def child(img):
    img = cv2.resize(img, (50, 50))
    pred = np.argmax(model.predict(np.array([img])))
    if pred == 0:
        return 'これは子供です'
    else:
        return 'これは老人です'

全文プログラム

train.py
importosimportcv2importnumpyasnpimportmatplotlib.pyplotaspltfromkeras.utils.np_utilsimportto_categoricalfromkeras.layersimportDense,Dropout,Flatten,Inputfromkeras.applications.vgg16importVGG16fromkeras.modelsimportModel,Sequentialfromkerasimportoptimizersnumber=100path_child=os.listdir('image-data/child')path_elder=os.listdir('image-data/elder')path_test=os.listdir('image-data/test')path_child_test=os.listdir('image-data/child_test')img_child=[]img_elder=[]img_test=[]img_child_test=[]input_tensor=Input(shape=(50,50,3))foriinrange(len(path_child)):img=cv2.imread('image-data/child/'+path_child[i])if(type(img)!=type(None)):img=cv2.resize(img,(50,50))img_child.append(img)foriinrange(len(path_elder)):img=cv2.imread('image-data/elder/'+path_elder[i])if(type(img)!=type(None)):img=cv2.resize(img,(50,50))img_elder.append(img)foriinrange(len(path_test)):img=cv2.imread('image-data/test/'+path_test[i])if(type(img)!=type(None)):img=cv2.resize(img,(50,50))img_test.append(img)foriinrange(len(path_child_test)):img=cv2.imread('image-data/child_test/'+path_child_test[i])if(type(img)!=type(None)):img=cv2.resize(img,(50,50))img_child_test.append(img)X=np.array(img_child+img_elder)y=np.array([0]*len(img_child)+[1]*len(img_elder))X_t=np.array(img_child_test+img_test)y_t=np.array([0]*len(img_test)+[1]*len(img_child_test))rand_index=np.random.permutation(np.arange(len(X)))rand_index_2=np.random.permutation(np.arange(len(X_t)))X=X[rand_index]y=y[rand_index]X_t=X_t[rand_index_2]y_t=y_t[rand_index_2]# データの分割X_train=X[:int(len(X)*0.8)]y_train=y[:int(len(y)*0.8)]X_test=X_t[int(len(X_t)*0.8):]y_test=y_t[int(len(y_t)*0.8):]# 正解ラベルをone-hotの形にしますy_train=to_categorical(y_train)y_test=to_categorical(y_test)# モデルにvggを使いますinput_tensor=Input(shape=(50,50,3))vgg16=VGG16(include_top=False,weights='imagenet',input_tensor=input_tensor)# vggのoutputを受け取り、2クラス分類する層を定義しますtop_model=Sequential()top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))top_model.add(Dense(256,activation='relu'))top_model.add(Dropout(0.5))top_model.add(Dense(2,activation='softmax'))model=Model(input=vgg16.input,output=top_model(vgg16.output))# vggの層の重みを変更不能にしますforlayerinmodel.layers[:15]:layer.trainable=False# コンパイルしますmodel.compile(loss='categorical_crossentropy',optimizer=optimizers.SGD(lr=1e-4,momentum=0.9),metrics=['accuracy'])# 学習を行いますmodel.fit(X_train,y_train,batch_size=100,epochs=20,validation_data=(X_test,y_test))#関数定義defchild(img):img=cv2.resize(img,(50,50))pred=np.argmax(model.predict(np.array([img])))ifpred==0:return'これは子供です'else:return'これは老人です'scores=model.evaluate(X_test,y_test,verbose=1)print('Test loss 間違い具合:',scores[0])print('Test accuracy 正解率:',scores[1])# cream関数に写真を渡して性別を予測しますforiinrange(10):img=cv2.imread('child_test/'+path_child_test[i])if(type(img)!=type(None)):b,g,r=cv2.split(img)img=cv2.merge([r,g,b])plt.imshow(img)plt.show()print(child(img))

以上になります。
Test loss 間違い具合: 2.303262777328491
Test accuracy 正解率: 0.5199999809265137

となり、2択なのに正解率52%とまったく当たっていませんので、改善が必要です。

Java言語で学ぶデザインパターン入門のはじめにから1章までのまとめ

$
0
0

エンジニアの先輩にデザインパターンをご存知でない!?という冷静なツッコミを頂き、その後結城浩さんが書かれた「Java言語で学ぶデザインパターン入門」を渡されたので勉強することにしました。
ただ、本を一読しても覚えられないので覚書として記事にしようと思いました。
完走できるように頑張ります。
また、「Java言語で学ぶデザインパターン入門」には、サンプルプログラムも有りますが、著作権の都合上省かせて頂きます。御了承ください。

そもそもデザインパターンって?

「Java言語で学ぶデザインパターン入門」では、

デザインパターンは、日々書いているプログラムを新しい観点から見直し、再利用しやすく、機能拡張しやすいソフトウェアを作るための有益な技法なのです。

と記載が有りました。

この記事を読んでいる皆さんは、プログラムをたくさん書いていると似たようなコードを書くことは有りませんか?私はあります。なので良く部分的にコピペしながらコードを書いています。
このようにプログラムでよく使われるパターンってたかだか23パターンしか無いよね?と言った4人の人がいて、the Gang of FourやGoFと呼ばれているそうです。
GoFは、23個のパターンに名前をつけてカタログと整理した本が「Design Patterns: Elements of Reusable Object-Oriented Software」だそうです。
該当の本について触れているリンクは以下です。
https://en.wikipedia.org/wiki/Design_Patterns

グダグダ書きましたが、再利用性と機能拡張しやすいプログラムを書くために、プログラムの書き方のパターンを覚えようねってことだと思います。

第1部 デザインパターンに慣れる

第1章 Iterator —1つ1つ数え上げる

for文やwhile文のように、順序をつけて繰り返し処理するプログラムがあります。
このように繰り返し処理していくパターンをIteratorパターンと言うそうです。

なんでこんなパターンが必要なの?

「Java言語で学ぶデザインパターン入門」にも記載が有りましたが、配列を使って処理するようなものは、for文を使えばいいという意見もあると思います。
結論を言えば、順に実行するようなものでも抽象化しておけば、実行対象のデータ構造が変わった場合でも、順に実行するメソッドは変更しないで済むということです。

以下は、私が考えた例です。ただし、IteratorインタフェースとAggregateインタフェースは、「Java言語で学ぶデザインパターン入門」に記載があったものを使用しています。

例えば、大学で学生を管理しているとします。

「次を探すメソッド」と「次のオブジェクトを取得するメソッド」を宣言しているIteratorのインタフェースがあるとします。
また、このIteratorのインタフェースを宣言している、Aggregateインタフェースがあるものとします。

1つの学科には複数人の学生がいて、学生は、名前と学籍番号を持っているとします。
学生の学籍番号と名前を管理する学生クラスがあるとし、学科に所属している学生をList型で管理している学科クラスがあるとします。
学科クラスは、Aggregateインタフェースを実装したものとします。
学科クラスの中には、「List型の変数に入っている学生を取得するメソッド」と、「何人の学生がいるのかを取得できるメソッド」と、「学生を追加するメソッド」と、後述の「Iteratorクラスのインスタンスを返しているメソッド」があるものとします。

学科内の学生を学籍番号順に取得する学科Iteratorクラスがあるとします。
この学科Iteratorクラスは、Iteratorのインタフェースを実装したものとします。
この学科Iteratorクラスには、現在どこまで検索したかを管理する整数型の変数aと、「次の学籍番号を持っている学生がいるかどうかを判断するメソッド」と、「学生を取得し、変数aの値にプラス1するメソッド」があるものとします。
この学科Iteratorクラスが、まだ学生がいるかどうか判断するために、学科クラスが持っている「何人の学生がいるかを取得できるメソッド」の値と学科Iteratorクラスの変数aを比較して、変数aの値の方が小さいときまだ学生がいるものとします。
また、学科Iteratorクラスが、学生を取得するときは、学科クラスの「List型の変数に入っている学生を取得するメソッド」を使用するものとします。

このとき、学科クラスで学生の管理方法をList型からArrayList型に変えたとしても、学科Iteratorクラスの実装には影響が無いため、影響範囲が学科クラス内のみとなります。
このようなときにIteratorのデザインは必要とされています。

「Java言語で学ぶデザインパターン入門」には、
>抽象クラスやインターフェースを使ってプログラミングをする考え方を頭の隅に置いておいてください。

という記載が有ったので、忘れないようにします。

イテレータの種類

順序つけて繰り返し処理するものは、最初から順に処理する以外にも以下のような種類があります。
- 逆順
- 双方向
- 途中から最後まで処理する

まとめ

集合体をひとつづつ数えるパターンをIteratorというということが分かりました。

第1章感想

簡単な処理でも抽象化しておくことで、変更箇所が少なくて済むので楽だなと思いました。
また、クラスを日本語で表現しようと頑張りましたが、割と簡単な概念なのにすごく難しかったので、プログラムって偉大だなと思いました。
後日Javaでそれっぽく書くかもしれません。

最後に

何か間違っているところがあれば、ご指摘いただけると嬉しいです!


Rails6 テキストを一発でコピーできるボタンを実装する

$
0
0

目的

  • テキストを一発でコピーできるボタンの実装方法をまとめる

作業をする前に

  • 今回の作業方法は公式のGithubのREADMEに沿って説明する。
  • clipboard-rails

実施方法

  1. Gemzeroclipboard-railsをインストール

    1. Gemfileに下記を追記する。

      gem 'jquery-rails'
      gem 'clipboard-rails'
      
    2. 下記コマンドを実行してGemfileを元にGemをインストールする。

      $bundle update
      
  2. 設定ファイル記載

    1. 下記に存在するファイルをエディタで開く。
      • アプリ名/app/assets/javascripts
        • /application.js
    2. 下記の内容をapplication.jsに追記する。

      //= require jquery//= require jquery_ujs//= require clipboard$(document).ready(function(){varclipboard=newClipboard('.clipboard-btn');console.log(clipboard);});
    3. 下記に存在するファイルをエディタで開く。

      • アプリ名/app/views/layouts
        • application.html.erb
    4. <head>の一番最初に下記を追記する。

      <%=javascript_include_tag'application','data-turbolinks-track':'reload'%>
  3. ボタンを実装

    1. 任意のビューファイルに下記の記載を行う。

      <div><!-- Target --><textareaid="bar"name=""rows="7"cols="100"><p>コピーターゲット</p></textarea><!-- Trigger --><buttonclass="clipboard-btn"data-clipboard-action="copy"data-clipboard-target="#bar">
            Copy to clipboard
        </button></div>

Railsチュートリアル 第14章 ユーザーをフォローする - 演習「フォローをテストする」

$
0
0

1. リスト 14.36respond_toブロック内の各行を順にコメントアウトしていき、テストが正しくエラーを検知できるかどうか確認してみましょう。実際、どのテストケースが落ちたでしょうか?

RelationshipsController#createにおいて、format.html以下の行が欠落している場合

RelationshipsController#createに以下の変更を加えた場合の動作から見てみましょう。

app/controllers/relationships_controller.rb#create
  def create
    @user = User.find(params[:followed_id])
    current_user.follow(@user)
    respond_to do |format|
-     format.html { redirect_to @user }
      format.js
    end
  end

上記変更を適用した上で、test/integration/following_test.rbを対象としてテストを実行した結果は以下です。

# rails test test/integration/following_test.rb
Running via Spring preloader in process 1590
Started with run options --seed 42916

ERROR["test_should_follow_a_user_the_standard_way", FollowingTest, 3.4918121000082465]
 test_should_follow_a_user_the_standard_way#FollowingTest (3.49s)
ActionController::UnknownFormat:         ActionController::UnknownFormat: ActionController::UnknownFormat
            app/controllers/relationships_controller.rb:7:in `create'
            test/integration/following_test.rb:30:in `block (2 levels) in <class:FollowingTest>'
            test/integration/following_test.rb:29:in `block in <class:FollowingTest>'

  6/6: [===================================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.89168s
6 tests, 13 assertions, 0 failures, 1 errors, 0 skips

エラー内容がActionController::UnknownFormatであることがポイントです。上記エラーログでは端折られていますが、今回発生したActionController::UnknownFormatエラーのより詳細なエラーメッセージは以下のようになります。

ActionController::UnknownFormat: RelationshipsController#create is missing a template for this request format and variant.

        request.formats: ["text/html"]
        request.variant: []

「RelationshipsController#create is missing a template(略)」とありますね。createコントローラーに対するビューのテンプレート、ファイル名としてはapp/views/relationships/create.html.erbが存在しないためのエラーです。

RelationshipsController#createにおいて、format.js以下の行が欠落している場合

続いて、RelationshipsController#createに以下の変更を加えた場合の動作です。

app/controllers/relationships_controller.rb#create
  def create
    @user = User.find(params[:followed_id])
    current_user.follow(@user)
    respond_to do |format|
      format.html { redirect_to @user }
-     format.js
    end
  end

上記変更を適用した上で、test/integration/following_test.rbを対象としてテストを実行した結果は以下です。

# rails test test/integration/following_test.rb
Running via Spring preloader in process 1604
Started with run options --seed 30420

  6/6: [===================================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.26699s
6 tests, 14 assertions, 0 failures, 0 errors, 0 skips

テストは正常に完了します。テスト「should follow a user with Ajax」が落ちることを期待していたのですが、違いますね。

RelationshipsController#destroyにおいて、format.html以下の行が欠落している場合

今度はRelationshipsController#destroyに以下の変更を加えた場合の動作です。

app/controllers/relationships_controller.rb#destroy
  def destroy
    @user = Relationship.find(params[:id]).followed
    current_user.unfollow(@user)
    respond_to do |format|
-     format.html { redirect_to @user }
      format.js
    end
  end

上記変更を適用した上で、test/integration/following_test.rbを対象としてテストを実行した結果は以下です。

# rails test test/integration/following_test.rb
Running via Spring preloader in process 559
Started with run options --seed 5514

ERROR["test_should_unfollow_a_user_the_standard_way", FollowingTest, 4.025347000000693]
 test_should_unfollow_a_user_the_standard_way#FollowingTest (4.03s)
ActionController::UnknownFormat:         ActionController::UnknownFormat: ActionController::UnknownFormat
            app/controllers/relationships_controller.rb:16:in `destroy'
            test/integration/following_test.rb:46:in `block (2 levels) in <class:FollowingTest>'
            test/integration/following_test.rb:45:in `block in <class:FollowingTest>'

  6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04

Finished in 4.16709s
6 tests, 13 assertions, 0 failures, 1 errors, 0 skips

ActionController::UnknownFormatエラーでテストが落ちています。「RelationshipsController#createにおいて、format.html以下の行が欠落している場合」と同様の挙動ですね。

RelationshipsController#destroyにおいて、format.js以下の行が欠落している場合

最後はRelationshipsController#destroyに以下の変更を加えた場合の動作です。

app/controllers/relationships_controller.rb#destroy
  def destroy
    @user = Relationship.find(params[:id]).followed
    current_user.unfollow(@user)
    respond_to do |format|
      format.html { redirect_to @user }
-     format.js
    end
  end

上記変更を適用した上で、test/integration/following_test.rbを対象としてテストを実行した結果は以下です。

# rails test test/integration/following_test.rb
NOTE: Gem::Specification#rubyforge_project= is deprecated with no replacement. It will be removed on or after 2019-12-01.
Gem::Specification#rubyforge_project= called from /usr/local/bundle/specifications/i18n-0.9.5.gemspec:17.
NOTE: Gem::Specification#rubyforge_project= is deprecated with no replacement. It will be removed on or after 2019-12-01.
Gem::Specification#rubyforge_project= called from /usr/local/bundle/specifications/i18n-0.9.5.gemspec:17.
Running via Spring preloader in process 573
Started with run options --seed 63825

  6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04

Finished in 4.95860s
6 tests, 14 assertions, 0 failures, 0 errors, 0 skips

テストは正常に完了します。

発展 -

2. リスト 14.40xhr: trueがある行のうち、片方のみを削除するとどういった結果になるでしょうか? このとき発生する問題の原因と、なぜ先ほどの演習で確認したテストがこの問題を検知できたのか考えてみてください。

test/integration/following_test.rbの内容を以下のように改変した場合、テストの結果はどうなるでしょうか。「テスト『should follow a user with Ajax』において、xhr: trueがある行を削除する」という改変です。

test/integration/following_test.rb
  require 'test_helper'

  class FollowingTest < ActionDispatch::IntegrationTest
    def setup
      @user  = users(:rhakurei)
      @other = users(:mkirisame)
      log_in_as(@user)
    end

    ...略

    test "should follow a user the standard way" do
      assert_difference '@user.following.count', 1 do
        post relationships_path, params: { followed_id: @other.id }
      end
    end

    test "should follow a user with Ajax" do
      assert_difference '@user.following.count', 1 do
-       post relationships_path, xhr: true, params: { followed_id: @other.id }
      end
    end

    test "should unfollow a user the standard way" do
      @user.follow(@other)
      relationship = @user.active_relationships.find_by(followed_id: @other.id)
      assert_difference '@user.following.count', -1 do
        delete relationship_path(relationship)
      end
    end

    test "should unfollow a user with Ajax" do
      @user.follow(@other)
      relationship = @user.active_relationships.find_by(followed_id: @other.id)
      assert_difference '@user.following.count', -1 do
        delete relationship_path(relationship), xhr: true
      end
    end
  end

上記テストの実行結果は以下のようになります。

# rails test test/integration/following_test.rb
Running via Spring preloader in process 586
Started with run options --seed 63280

 FAIL["test_should_follow_a_user_with_Ajax", FollowingTest, 1.9365238999998837]
 test_should_follow_a_user_with_Ajax#FollowingTest (1.94s)
        "@user.following.count" didn't change by 1.
        Expected: 3
          Actual: 2
        test/integration/following_test.rb:36:in `block in <class:FollowingTest>'

  6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04

Finished in 4.76945s
6 tests, 14 assertions, 1 failures, 0 errors, 0 skips

上記テストでは、テスト「should follow a user with Ajax」において、POSTリクエスト自体が発行されていません。POSTリクエストが発行されないことには、RelationshipsController#createメソッドも実行されません。当該RelationshipsController#createの内容は以下の通りです。

RelationshipsController#create
defcreate@user=User.find(params[:followed_id])current_user.follow(@user)respond_todo|format|format.html{redirect_to@user}format.jsendend

RDB上のデータにフォローを反映する処理は、上記コードのうちcurrent_user.follow(@user)となります。current_user.follow(@user)が実行されなければ、@user.following.countの値も変化することはありません。結果、「@user.following.countの値が想定と違う」という理由でテストが落ちることになります。

逆に、「テスト『should unfollow a user with Ajax』において、xhr: trueがある行を削除する」という改変を加えた場合(下記ソース)も、同様の形でテストが落ちます。

test/integration/following_test.rb
  require 'test_helper'

  class FollowingTest < ActionDispatch::IntegrationTest
    def setup
      @user  = users(:rhakurei)
      @other = users(:mkirisame)
      log_in_as(@user)
    end

    ...略

    test "should follow a user the standard way" do
      assert_difference '@user.following.count', 1 do
        post relationships_path, params: { followed_id: @other.id }
      end
    end

    test "should follow a user with Ajax" do
      assert_difference '@user.following.count', 1 do
        post relationships_path, xhr: true, params: { followed_id: @other.id }
      end
    end

    test "should unfollow a user the standard way" do
      @user.follow(@other)
      relationship = @user.active_relationships.find_by(followed_id: @other.id)
      assert_difference '@user.following.count', -1 do
        delete relationship_path(relationship)
      end
    end

    test "should unfollow a user with Ajax" do
      @user.follow(@other)
      relationship = @user.active_relationships.find_by(followed_id: @other.id)
      assert_difference '@user.following.count', -1 do
-       delete relationship_path(relationship), xhr: true
      end
    end
  end

上記テストの実行結果は以下のようになります。

# rails test test/integration/following_test.rb
Running via Spring preloader in process 599
Started with run options --seed 27288

 FAIL["test_should_unfollow_a_user_the_standard_way", FollowingTest, 4.007498499999201]
 test_should_unfollow_a_user_the_standard_way#FollowingTest (4.01s)
        "@user.following.count" didn't change by -1.
        Expected: 2
          Actual: 3
        test/integration/following_test.rb:45:in `block in <class:FollowingTest>'

  6/6: [===================================] 100% Time: 00:00:04, Time: 00:00:04

Finished in 4.10546s
6 tests, 14 assertions, 1 failures, 0 errors, 0 skips

今度は、テスト「should unfollow a user with Ajax」中でDELETEリクエストが発行されなくなりました。RelationshipsController#destroy中にあるcurrent_user.unfollow(@user)という処理が実行されず、@user.following.countの数が変化しなかった結果、テストが落ちたのですね。

RelationshipsController#destroy
defdestroy@user=Relationship.find(params[:id]).followedcurrent_user.unfollow(@user)respond_todo|format|format.html{redirect_to@user}format.jsendend

発展 - FollowingTestで、より適切なテストを行えるようにしてみる

Railsチュートリアル本文リスト 14.40のテストでは、例えばcreate.js.erbdestroy.js.erbの記述内容にまで踏み込んだテストができていませんでした。別記事「Railsチュートリアル 第14章 ユーザーをフォローする - 演習「フォローをテストする」 - FollowingTestの問題点と、その改良」にて、当該テストの問題点や改良策について記述しています。

Railsチュートリアル 第14章 ユーザーをフォローする - [Follow] のWebインターフェイス

$
0
0

フォローのサンプルデータ

別記事で解説します。

演習 - フォローのサンプルデータ

1.コンソールを開き、User.first.followers.countの結果がリスト 14.14で期待している結果と合致していることを確認してみましょう。

「最初のユーザーをフォローしている人の数」ということですね。「4番目から41番めのユーザーの合計数」、すなわち(3..40).countの値と一致するはずです。

>> User.first.followers.count
=> 38

>> (3..40).count
=> 38

>> User.first.followers.count == (3..40).count
=> true

一致していますね。

2. 先ほどの演習と同様に、User.first.following.countの結果も合致していることを確認してみましょう。

「最初のユーザーがフォローしている人の数」ということですね。「3番目から51番めのユーザーの合計数」、すなわち(2..50).countの値と一致するはずです。

>> User.first.following.count
=> 49

>> (2..50).count
=> 49

>> User.first.following.count == (2..50).count
=> true

一致していますね。

統計と [Follow] フォーム

別記事で解説します。

演習 - 統計と [Follow] フォーム

1.1. ブラウザから /users/2 にアクセスし、フォローボタンが表示されていることを確認してみましょう。同様に、/users/5 では [Unfollow] ボタンが表示されているはずです。

ログインユーザーのidが1で、DBの内容がフォローのサンプルデータの内容であるとすると、 /users/2 へのアクセス結果は以下の通りです。

スクリーンショット 2020-02-01 22.59.29.png

対応するRailsサーバーのログ(抜粋)は以下のようになります。

Started GET "/users/2" for 172.17.0.1 at 2020-02-01 13:55:21 +0000
  Rendering users/show.html.erb within layouts/application
  Rendered shared/_stats.html.erb (15.4ms)
  Rendered users/_follow.html.erb (3.2ms)
  Rendered users/_follow_form.html.erb (32.1ms)
  Rendered collection of microposts/_micropost.html.erb [30 times] (16.0ms)
  Rendered users/show.html.erb within layouts/application (148.5ms)

users/_follow.html.erb、ならびにusers/_follow_form.html.erbが描画されているのがわかります。

また、/users/5 へのアクセス結果は以下の通りです。

スクリーンショット 2020-02-01 22.59.35.png

対応するRailsサーバーのログ(抜粋)は以下のようになります。

Started GET "/users/5" for 172.17.0.1 at 2020-02-01 13:57:32 +0000
  Rendering users/show.html.erb within layouts/application
  Rendered shared/_stats.html.erb (10.7ms)
  Rendered users/_unfollow.html.erb (5.2ms)
  Rendered users/_follow_form.html.erb (31.5ms)
  Rendered collection of microposts/_micropost.html.erb [30 times] (27.0ms)
  Rendered users/show.html.erb within layouts/application (141.3ms)

users/_unfollow.html.erb、ならびにusers/_follow_form.html.erbが描画されているのがわかります。

1.2. /users/1 にアクセスすると、どのような結果が表示されるでしょうか?

ログインユーザーのidが1である場合、 /users/1 においては、[Follow]/[Unfollow]ボタンをレンダリングする場所そのものが確保されず、これらのボタンも表示されないはずです。どうなっているでしょうか。

スクリーンショット 2020-01-03 14.04.18.png

確かに想定通りの動作になっています。

対応するRailsサーバーのログ(抜粋)は以下のようになります。

Started GET "/users/1" for 172.17.0.1 at 2020-02-01 14:05:02 +0000
  Rendering users/show.html.erb within layouts/application
  Rendered shared/_stats.html.erb (12.5ms)
  Rendered users/_follow_form.html.erb (0.5ms)
  Rendered collection of microposts/_micropost.html.erb [30 times] (16.7ms)
  Rendered users/show.html.erb within layouts/application (132.9ms)

users/_follow.html.erbおよびusers/_unfollow.html.erbはいずれもレンダリングされず、users/_follow_form.html.erbのみがレンダリングされているのがわかります。

2. ブラウザからHomeページとプロフィールページを表示してみて、統計情報が正しく表示されているか確認してみましょう。

現在ログインしているユーザーは、id=1のユーザーであることを前提とします。前述の演習「演習 - フォローのサンプルデータ」より、以下の表示内容となることが期待されます。

  • Homeページ(/)とプロフィールページ(/users/1)の両方に統計情報パーシャルが表示される
  • 「following」の前に表示される数はUser.first.following.countと一致する
  • 「followers」の前に表示される数はUser.first.followers.countと一致する
>> User.first.following.count
=> 49

>> User.first.followers.count
=> 38

まずはHomeページの表示結果です。「49 following / 38 followers」という表示内容に問題はありません。

スクリーンショット 2020-02-02 19.32.31.png

このとき、Railsサーバーに記録されるログ(抜粋)は以下のようになります。

Started GET "/" for 172.17.0.1 at 2020-02-02 10:32:27 +0000
  Rendering static_pages/home.html.erb within layouts/application
  Rendered shared/_user_info.html.erb (7.1ms)
  Rendered shared/_stats.html.erb (10.7ms)
  Rendered shared/_error_messages.html.erb (0.5ms)
  Rendered shared/_micropost_form.html.erb (20.3ms)
  Rendered collection of microposts/_micropost.html.erb [30 times] (118.9ms)
  Rendered shared/_feed.html.erb (151.2ms)
  Rendered shared/_home_logged_in.erb (289.7ms)
  Rendered static_pages/home.html.erb within layouts/application (317.0ms)

統計情報パーシャルの実体であるshared/_stats.html.erbが描画されているのがわかりますね。

続いてプロフィールページの表示結果です。Homeページと同様、「49 following / 38 followers」という表示内容に問題はありません。

スクリーンショット 2020-02-02 19.32.25.png

このとき、Railsサーバーに記録されるログ(抜粋)は以下のようになります。

Started GET "/users/1" for 172.17.0.1 at 2020-02-02 10:31:33 +0000
  Rendering users/show.html.erb within layouts/application
  Rendered shared/_stats.html.erb (16.7ms)
  Rendered users/_follow_form.html.erb (0.5ms)
  Rendered collection of microposts/_micropost.html.erb [30 times] (13.5ms)
  Rendered users/show.html.erb within layouts/application (120.5ms)

こちらも、統計情報パーシャルの実体であるshared/_stats.html.erbが描画されている様子が記録されています。

3.1. Homeページに表示されている統計情報に対してテストを書いてみましょう。

ヒント: リスト 13.28で示したテストに追加してみてください。

テストコードの実体

統計情報に対するテストコードの実体は以下のようになります。

assert_select'strong',{id: 'following',text: /#{@user.following.count.to_s}/}assert_select'strong',{id: 'followers',text: /#{@user.followers.count.to_s}/}

上記コードは、以下の事柄についてテストを行っています。

  • CSS idがfollowingであり、テキストとしてログイン済みユーザーがフォローしているユーザーの数を含むstrong要素が描画されていること
  • CSS idがfollowersであり、テキストとしてログイン済みユーザーのフォロワーの数を含むstrong要素が描画されていること

Homeページに対するテストをどこに書くか

Homeページに対するテストの実装は、test/integration/site_layout_test.rbに既に存在します。しかしながら、test/integration/site_layout_test.rb上のテストというのは、header要素内やfooter要素内といった「サイト全体に適用されるレイアウトに関するテスト」と考えるべき趣旨のものです。コンテンツ内容に関するテストを含めるのは適当ではないと考えます。

というわけで、新たに統合テストを生成してしまいましょう。「Homeページに対するテスト」ということで、統合テストの名前はhomeとします。

# rails generate integration_test home
      invoke  test_unit
      create    test/integration/home_test.rb

実際のテストの記述

実際にHomeページに対するテストを記述していきます。記述場所は、只今生成したばかりのtest/integration/home_test.rbです。

test/integration/home_test.rb
require'test_helper'classHomeTest<ActionDispatch::IntegrationTestdefsetup@user=users(:rhakurei)log_in_as(@user)endtest"home should include following and followers with login"dogetroot_pathassert_select'strong',{id: 'following',text: /#{@user.following.count.to_s}/}assert_select'strong',{id: 'followers',text: /#{@user.followers.count.to_s}/}endend

Homeページ上の統計情報の描画に対するテストが成功することを確認する

現時点で、上記のテストは問題なく成功します。

# rails test test/integration/home_test.rb
Running via Spring preloader in process 754
Started with run options --seed 26350

  1/1: [===================================] 100% Time: 00:00:04, Time: 00:00:04

Finished in 4.34328s
1 tests, 2 assertions, 0 failures, 0 errors, 0 skips

Homeページ上の統計情報の描画に対するテストが失敗する例

例えば、app/views/shared/_home_logged_in.erbに以下の欠落がある場合を考えてみます。

app/views/shared/_home_logged_in.erb
<div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= render 'shared/user_info' %>
      </section>
      <section class="stats">
-       <%= render 'shared/stats' %>
</section>
      <section class="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
    <div class="col-md-8">
      <h3>Micropost Feed</h3>
      <%= render 'shared/feed' %>
    </div>
  </div>

この状態でtest/integration/home_test.rbに対するテストを行うと、以下のようにテストが失敗します。

# rails test test/integration/home_test.rb
Running via Spring preloader in process 767
Started with run options --seed 11814

 FAIL["test_home_should_include_following_and_followers_with_login", HomeTest, 3.5389004000026034]
 test_home_should_include_following_and_followers_with_login#HomeTest (3.54s)
        Expected at least 1 element matching "strong", found 0..
        Expected 0 to be >= 1.
        test/integration/home_test.rb:11:in `block in <class:HomeTest>'

  1/1: [===================================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.54956s
1 tests, 1 assertions, 1 failures, 0 errors, 0 skips

発展 - ログイン済みユーザーのHomeページに対するテストとして考えられる例

マイクロポスト投稿フォームが表示されていること

test "home should include new micropost form with login" do
  log_in_as @user
  get root_path
  assert_select 'form', id: 'new_micropost'
end

マイクロポスト表示フィードがレンダリングされること

test "home should render micropost feed placeholder with login" do
  log_in_as @user
  get root_path
  assert_select 'div' do
    assert_select 'h3', text: 'Micropost Feed'
  end
end

単純に「テキストが'Micropost Feed'であるh3要素を含むdiv要素が存在すること」についてテストを行っています。

3.2. 同様にして、プロフィールページにもテストを追加してみましょう。

テストコードの内容そのものは、上記演習3.1.のものと同一です。

プロフィールページの表示内容に対するテストの実体はtest/integration/users_profile_test.rbです。前述の内容を踏まえ、test/integration/users_profile_test.rb全体の変更内容は以下のようになります。

test/integration/users_profile_test.rb
  require 'test_helper'

  class UsersProfileTest < ActionDispatch::IntegrationTest
    include ApplicationHelper

    def setup
      @user = users(:rhakurei)
    end

    test "profile display" do
      get user_path(@user)
      assert_template 'users/show'
      assert_select 'title', full_title(@user.name)
      assert_select 'h1', text: @user.name
      assert_select 'h1>img.gravatar'
      assert_match @user.microposts.count.to_s, response.body
+     assert_select 'strong', { id: 'following', text: /#{@user.following.count.to_s}/ }
+     assert_select 'strong', { id: 'followers', text: /#{@user.followers.count.to_s}/ }
      assert_select 'div.pagination', count: 1
      @user.microposts.paginate(page: 1).each do |micropost|
        assert_match micropost.content, response.body
      end
    end
  end

プロフィールページ上の統計情報の描画に対するテストが成功することを確認する

現状の実装では、上記テストは問題なく成功します。

# rails test test/integration/users_profile_test.rb
Running via Spring preloader in process 584
Started with run options --seed 41315

  1/1: [===================================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.06740s
1 tests, 71 assertions, 0 failures, 0 errors, 0 skips

プロフィールページ上の統計情報の描画に対するテストが失敗する例

例えば、app/views/users/show.html.erbに以下の欠落がある場合を考えてみます。

app/views/users/show.html.erb(バグあり)
<% provide(:title, @user.name) %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        ...略
      </section>
      <section>
-       <%= render 'shared/stats' %>
</section>
    </aside>
    <div class="col-md-8">
      ...略
    </div>
  </div>

この状態でtest/integration/users_profile_test.rbに対するテストを行うと、以下のようにテストが失敗します。

# rails test test/integration/users_profile_test.rb
Running via Spring preloader in process 672
Started with run options --seed 11646

 FAIL["test_profile_display", UsersProfileTest, 4.164021200005664]
 test_profile_display#UsersProfileTest (4.16s)
        Expected at least 1 element matching "strong", found 0..
        Expected 0 to be >= 1.
        test/integration/users_profile_test.rb:17:in `block in <class:UsersProfileTest>'

  1/1: [===================================] 100% Time: 00:00:04, Time: 00:00:04

Finished in 4.17069s
1 tests, 7 assertions, 1 failures, 0 errors, 0 skips

[Following] と [Followers] ページ

ページの基本的な仕様

「フォローしているユーザー一覧」「フォロワー一覧」いずれも、そのレイアウトは類似するものとなります。具体的には、以下の要素が含まれることになります。

  • サイドバー
    • ログインユーザーの基本情報
    • ログインユーザーがフォローしているユーザーの数
    • ログインユーザーのフォロワーの数
    • フォローしているユーザー、またはフォロワーのアイコンを縮小表示して格子状に並べたもの
      • 当該ユーザーのプロフィールページへのリンクが貼られている
  • フォローしているユーザー、もしくはフォロワーのリスト

Railsチュートリアル本文では、フォローしているユーザーの一覧のモックアップを図 14.14で、フォロワーの一覧のモックアップを図 14.15で示しています。

フォロー/フォロワーページの認可のテスト

「フォローしているユーザーの一覧、フォロワーの一覧、いずれのページもログイン済みユーザーでなければアクセスできないこととする」「非ログインユーザーがこれら一覧ページにアクセスしようとした場合、 /login にリダイレクトする」という仕様を採用することをまず前提とします。これはTwitterにおける実装に倣ったものです。

となると、「これらのページへのアクセスにおいて、認可機構が正しく働いているか」のテストが必要となります。情報セキュリティに関する部分の仕様であるだけに、この部分の動作が正しいものであることは重要です。というわけで、実装より先にテストを書いていくこととします。

テストそのものの実体は以下のようになります。

test"should redirect following when not logged in"dogetfollowing_user_path(@user)assert_redirected_tologin_urlendtest"should redirect followers when not logged in"dogetfollowers_user_path(@user)assert_redirected_tologin_urlend

追加するテストの実装先は、test/controllers/users_controller_test.rbです。

test/controllers/users_controller_test.rb
  require 'test_helper'

  class UsersControllerTest < ActionDispatch::IntegrationTest

    def setup
      @user       = users(:rhakurei)
      @other_user = users(:mkirisame)
    end

    ...略
+
+   test "should redirect following when not logged in" do
+     get following_user_path(@user)
+     assert_redirected_to login_url
+   end
+
+   test "should redirect followers when not logged in" do
+     get followers_user_path(@user)
+     assert_redirected_to login_url
+   end
  end

現時点でテストが成功しないことの確認

新たに実装したテストは、当然ながら現時点では成功しません。

# rails test test/controllers/users_controller_test.rb
Running via Spring preloader in process 840
Started with run options --seed 47643

ERROR["test_should_redirect_followers_when_not_logged_in", UsersControllerTest, 1.9549646000086796]
 test_should_redirect_followers_when_not_logged_in#UsersControllerTest (1.96s)
AbstractController::ActionNotFound:         AbstractController::ActionNotFound: The action 'followers' could not be found for UsersController
            test/controllers/users_controller_test.rb:80:in `block in <class:UsersControllerTest>'

ERROR["test_should_redirect_following_when_not_logged_in", UsersControllerTest, 2.14522200000647]
 test_should_redirect_following_when_not_logged_in#UsersControllerTest (2.15s)
AbstractController::ActionNotFound:         AbstractController::ActionNotFound: The action 'following' could not be found for UsersController
            test/controllers/users_controller_test.rb:75:in `block in <class:UsersControllerTest>'

  11/11: [=================================] 100% Time: 00:00:04, Time: 00:00:04

Finished in 4.21912s
11 tests, 18 assertions, 0 failures, 2 errors, 0 skips

そもそもfollowingfollowersというアクションはまだ実装していないので、テストが通らないのみ当然といえば当然です。ただ、RoutingErrorではなくActionNotFoundなので、ルーティングの実装は正常に行えているようです。

Usersコントローラーに、followingアクションとfollowersアクションを実装する

先ほどテストで発生したエラーを解決するために、Usersコントローラーにfollowingアクションとfollowersアクションを実装していきます。

app/controllers/users_controller.rb
  class UsersController < ApplicationController
    ...略
+
+   def following
+   end
+
+   def followers
+   end

    private
      ...略
  end

Usersコントローラーにfollowingアクションとfollowersアクションがある状態でのテスト結果

この時点でtest/controllers/users_controller_test.rbを対象としてテストを行うと、その結果は以下のようになります。

# rails test test/controllers/users_controller_test.rb
Running via Spring preloader in process 861
Started with run options --seed 8813

ERROR["test_should_redirect_followers_when_not_logged_in", UsersControllerTest, 2.068188299992471]
 test_should_redirect_followers_when_not_logged_in#UsersControllerTest (2.07s)
ActionController::UnknownFormat:         ActionController::UnknownFormat: UsersController#followers is missing a template for this request format and variant.

        request.formats: ["text/html"]
        request.variant: []

        NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot.
            test/controllers/users_controller_test.rb:80:in `block in <class:UsersControllerTest>'

ERROR["test_should_redirect_following_when_not_logged_in", UsersControllerTest, 4.581023499995354]
 test_should_redirect_following_when_not_logged_in#UsersControllerTest (4.58s)
ActionController::UnknownFormat:         ActionController::UnknownFormat: UsersController#following is missing a template for this request format and variant.

        request.formats: ["text/html"]
        request.variant: []

        NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot.
            test/controllers/users_controller_test.rb:75:in `block in <class:UsersControllerTest>'

  11/11: [=================================] 100% Time: 00:00:04, Time: 00:00:04

Finished in 4.58386s
11 tests, 18 assertions, 0 failures, 2 errors, 0 skips

上記エラーが発生する状態で、Webブラウザから /users/1/following というリソースにアクセスすると、Railsサーバーには以下のようなログが記録されます。

Started GET "/users/1/following" for 172.17.0.1 at 2020-02-03 22:46:07 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#following as HTML
  Parameters: {"id"=>"1"}
Completed 406 Not Acceptable in 1125ms
...略

HTTPリクエストが406というエラーコードを返して終了している、という状態ですね。「406」というエラーコードの意味はさておき、この処理で返ってくるHTTPのレスポンスコードは「3XX(リダイレクト)」でなければなりません。

Usersコントローラーのfollowingアクションとfollowersアクションに対し、「ログイン済みユーザーでなければログイン画面にリダイレクトする」という動作が行われるようにする

表題記載の動作が行われるようにするためには、Usersコントローラーのbeforeフィルターに以下のコードを追加します。

before_action:logged_in_user,only: [:following,:followers]

実際にapp/controllers/users_controller.rbに適用する変更は以下のようになります。

app/controllers/users_controller.rb
  class UsersController < ApplicationController
-   before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
+   before_action :logged_in_user, only: [:index, :edit, :update, :destroy, :following, :followers]
    before_action :correct_user,   only: [:edit, :update]
    before_action :admin_user,     only: :destroy

    ...略
  end

この時点で、test/controllers/users_controller_test.rbを対象としたテストが成功するようになる

この時点で、test/controllers/users_controller_test.rbを対象としたテストは成功するようになります。

# rails test test/controllers/users_controller_test.rb
Running via Spring preloader in process 958
Started with run options --seed 46648

  11/11: [=================================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.49225s
11 tests, 20 assertions, 0 failures, 0 errors, 0 skips

しかしながら、現時点でfollowingアクションおよびfollowersアクションの動作は何も実装していません。これらの動作の実装が必要となります。

followingアクションとfollowersアクションの動作の実装

followingアクションとfollowersアクションの動作に対するテスト

following/followerをテストするためのfixture

test/fixtures/relationships.yml
one:follower:rhakureifollowed:skomeijitwo:follower:rhakureifollowed:rusamithree:follower:skomeijifollowed:rhakureifour:follower:mkirisamefollowed:rhakurei

このfixtureは、以下のようなフォロー関係を定義しています。

  • rhakureiがskomeijiとrusamiをフォローする
  • skomeijiとmkirisameがrhakureiをフォローする

following/followerページに対する統合テストを生成する

「実際にWebブラウザに描画される内容をテストしたい」という場面なので、テストの種類は統合テストとなります。following/followerページのビューの実装が現状存在しないので、対応する統合テストも現状存在しません。まずは必要な統合テストを生成することが始まりですね。テストの名前はfollowingとします。

# rails generate integration_test following
      invoke  test_unit
      create    test/integration/following_test.rb

following/followerページのテストの実体

前項で生成された統合テストのファイル名はtest/integration/following_test.rbとなります。テストそのものの内容は以下のようになります。

test/integration/following_test.rb
require'test_helper'classFollowingTest<ActionDispatch::IntegrationTestdefsetup@user=users(:rhakurei)log_in_as(@user)endtest"following page"dogetfollowing_user_path(@user)assert_not@user.following.empty?assert_match@user.following.count.to_s,response.body@user.following.eachdo|user|assert_select"a[href=?]",user_path(user)endendtest"followers page"dogetfollowers_user_path(@user)assert_not@user.followers.empty?assert_match@user.followers.count.to_s,response.body@user.followers.eachdo|user|assert_select"a[href=?]",user_path(user)endendend
assert_not @user.following.empty?assert_not @user.followers.empty?というテストの意味合い
assert_not@user.following.empty?

@user.following.empty?の戻り値がtrueである場合、このあとの@user.following.eachブロック内にあるassert_selectというテストが実行されなくなってしまいます。そのような状況で「テストが成功した」と主張するのは不適当です。ゆえに、「@user.following.empty?の戻り値がtrueである場合はテストを失敗させる」という処理を先に実行しています。@user.following.empty?の戻り値がtrueとなる場合には、例えば「fixtureの内容が不適当な場合」があります。

また、@user.followersに対する以下のテストも意味合いは同様です。

assert_not@user.followers.empty?

現状における、test/integration/following_test.rbを対象としたテストの結果

「Usersコントローラーに、following/followers両アクションのみが実装されており、following/followersアクションの処理内容が実装されていない」という状態で、test/integration/following_test.rbを実行してみます。結果は以下のようになります。

# rails test test/integration/following_test.rb
Running via Spring preloader in process 1077
Started with run options --seed 36731

ERROR["test_followers_page", FollowingTest, 2.2801150000013877]
 test_followers_page#FollowingTest (2.28s)
ActionController::UnknownFormat:         ActionController::UnknownFormat: UsersController#followers is missing a template for this request format and variant.

        request.formats: ["text/html"]
        request.variant: []

        NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot.
            test/integration/following_test.rb:19:in `block in <class:FollowingTest>'

ERROR["test_following_page", FollowingTest, 3.6601012000028277]
 test_following_page#FollowingTest (3.66s)
ActionController::UnknownFormat:         ActionController::UnknownFormat: UsersController#following is missing a template for this request format and variant.

        request.formats: ["text/html"]
        request.variant: []

        NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot.
            test/integration/following_test.rb:10:in `block in <class:FollowingTest>'

  2/2: [===================================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.66550s
2 tests, 0 assertions, 0 failures, 2 errors, 0 skips

HTTPリクエストが406というエラーコードを返して終了している、という状態ですね。「406」というエラーコードの意味はさておき、この処理で返ってくるHTTPのレスポンスコードは「200」でなければなりません。

Usersコントローラーにおける、followingアクションとfollowersアクションの動作の実装

  • 「誰がフォローしているユーザーか」「誰のフォロワーか」の「誰」の部分については、GETリクエストに渡すパラメータのid属性の値によって与える
  • ユーザー一覧の表示に対し、ページネーション処理を行う

上記箇条書きの内容を前提条件とすると、followingアクションとfollowersアクションの動作の実装内容は以下のようになります。

deffollowing@title="Following"@user=User.find(params[:id])@users=@user.following.paginate(page: params[:page])render'show_follow'enddeffollowers@title="Followers"@user=User.find(params[:id])@users=@user.followers.paginate(page: params[:page])render'show_follow'end

後述するように、フォローしているユーザーの一覧/フォロワーの一覧とも、一つのERbで両方の場合をカバーできる程度にページ構造は酷似しています。ゆえに、「コントローラーの2つのアクションが同一ビューを描画する」という実装になるわけです。

最終的に、app/controllers/users_controller.rbに対して加える変更の内容は以下のようになります。

app/controllers/users_controller.rb
  class UsersController < ApplicationController
    before_action :logged_in_user, only: [:index, :edit, :update, :destroy, :following, :followers]
    before_action :correct_user,   only: [:edit, :update]
    before_action :admin_user,     only: :destroy

    ...略

    def following
+     @title = "Following"
+     @user  = User.find(params[:id])
+     @users = @user.following.paginate(page: params[:page])
+     render 'show_follow'
    end

    def followers
+     @title = "Followers"
+     @user  = User.find(params[:id])
+     @users = @user.following.paginate(page: params[:page])
+     render 'show_follow'
    end

    private

      ...略
  end

followingアクションとfollowersアクションに必要なビューの実装

当然ながら、show_followというビューそのものの実装も必要となります。ファイル名はapp/views/users/show_follow.html.erbとします。

app/views/users/show_follow.html.erb
<%provide(:title,@title)%><divclass="row"><asideclass="col-md-4"><sectionclass="user_info"><%=gravatar_for@user%><h1><%=@user.name%></h1><span><%=link_to"view my profile",@user%></span><span><b>Microposts:</b><%=@user.microposts.count%></span></section><sectionclass="stats"><%if@users.any?%><divclass="user_avatars"><%@users.eachdo|user|%><%=link_togravatar_for(user,size: 30),user%><%end%></div><%end%></section></aside><divclass="col-md-8"><h3><%=@title%></h3><%if@users.any?%><ulclass="users follow"><%=render@users%></ul><%=will_paginate%><%end%></div></div>

再びtest/integration/following_test.rbを対象としたテストを実行する

Usersコントローラーのfollowingアクションとfollowersアクション、これらのアクションに必要なビュー、以上の実装が完了しました。この時点で、再びtest/integration/following_test.rbを対象としたテストを実行してみましょう。

# rails test test/integration/following_test.rb
Running via Spring preloader in process 1129
Started with run options --seed 47849

  2/2: [===================================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.18609s
2 tests, 10 assertions, 0 failures, 0 errors, 0 skips

ここまで実装したコードの内容に間違いがなければ、test/integration/following_test.rbを対象としたテストは成功するはずです。

# rails test
Running via Spring preloader in process 1142
Started with run options --seed 56786

  72/72: [=================================] 100% Time: 00:00:10, Time: 00:00:10

Finished in 10.74545s
72 tests, 357 assertions, 0 failures, 0 errors, 0 skips

テストスイート全体に対するテストも成功しましたね。

following/followerページの表示結果

現在のユーザーにフォローされているユーザーの一覧表示のスクリーンショットを以下に示します。アドレスバー部分を見てのとおり、followingアクションを経由してshow_followビューが呼び出された結果となります。

スクリーンショット 2020-02-05 19.07.38.png

続いて、現在のユーザをフォローしているユーザーの一覧表示のスクリーンショットを以下に示します。アドレスバー部分を見てのとおり、followersアクションを経由してshow_followビューが呼び出された結果となります。

スクリーンショット 2020-02-05 19.07.53.png

ログイン済みであれば、ログインユーザー以外のユーザーに対しても、当該ユーザーをフォローしているユーザーを一覧表示することも可能です。以下のスクリーンショットがその例です。

スクリーンショット 2020-02-05 19.14.19.png

演習 - [Following] と [Followers] ページ

1.1. ブラウザから /users/1/followers と /users/1/following を開き、それぞれが適切に表示されていることを確認してみましょう。

以下のスクリーンショットの通りです。

スクリーンショット 2020-02-05 19.07.38.png

スクリーンショット 2020-02-05 19.07.53.png

1.2. /users/1/followers や /users/1/following において、サイドバーにある画像は、リンクとしてうまく機能しているでしょうか?

/users/1/followers における、サイドバーにある単一の画像に対応するHTMLコードは、例えば以下のようになります。

サイドバーにある単一の画像に対応するHTMLコード
<ahref="/users/3"><imgalt="Berry Cremin"class="gravatar"src="https://secure.gravatar.com/avatar/2065436fdfe2d27dc7f06b6787a4a1af?s=30"></a>

リンク先が /users/3 であることを踏まえて、実際に当該画像をクリックしてみます。すると、Railsサーバーは以下のようなログを出力します。

Started GET "/users/3" for 172.17.0.1 at 2020-02-05 22:50:12 +0000
...略
Completed 200 OK in 1276ms (Views: 1154.3ms | ActiveRecord: 69.4ms)

/users/3 へのGETリクエストが発行され、「200 OK」でリクエストが完了しています。「サイドバーにある画像は、リンクとしてうまく機能していることが確認できた」といえそうです。

2. リスト 14.29assert_selectに関連するコードをコメントアウトしてみて、テストが正しくredに変わることを確認してみましょう。

app/views/users/show_follow.html.erbに以下の欠落がある場合、当該テストは、assert_selectのところで失敗するはずです。

app/views/users/show_follow.html.erb
<% provide(:title, @title) %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= gravatar_for @user %>
        <h1><%= @user.name %></h1>
        <span><%= link_to "view my profile", @user %></span>
        <span><b>Microposts:</b> <%= @user.microposts.count %></span>
      </section>
      <section class="stats">
        <%= render'shared/stats' %>
        <% if @users.any? %>
          <div class="user_avatars">
            <% @users.each do |user|%>
-             <%= link_to gravatar_for(user, size: 30), user %>
<% end %>
          </div>
        <% end %>
      </section>
    </aside>
    <div class="col-md-8">
      <h3><%= @title %></h3>
      <% if @users.any? %>
        <ul class="users follow">
-         <%= render @users %>
</ul>
        <%= will_paginate %>
      <% end %>
    </div>
  </div>

実際にテストを実行してみましょう。

# rails test test/integration/following_test.rb
Running via Spring preloader in process 1196
Started with run options --seed 9425

 FAIL["test_followers_page", FollowingTest, 2.336555000001681]
 test_followers_page#FollowingTest (2.34s)
        Expected at least 1 element matching "a[href="/users/919532091"]", found 0..
        Expected 0 to be >= 1.
        test/integration/following_test.rb:23:in `block (2 levels) in <class:FollowingTest>'
        test/integration/following_test.rb:22:in `block in <class:FollowingTest>'

 FAIL["test_following_page", FollowingTest, 2.4256373999960488]
 test_following_page#FollowingTest (2.43s)
        Expected at least 1 element matching "a[href="/users/314048677"]", found 0..
        Expected 0 to be >= 1.
        test/integration/following_test.rb:14:in `block (2 levels) in <class:FollowingTest>'
        test/integration/following_test.rb:13:in `block in <class:FollowingTest>'

  2/2: [===================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.42861s
2 tests, 8 assertions, 2 failures, 0 errors, 0 skips

想定通りの形でテストが失敗しました。

followingアクションおよびfollowersアクションの統合テストにおける、Railsチュートリアル本文記載のテストの不具合

実は、Railsチュートリアル本文のリスト 14.29に記述されているテストには、1つの不具合があります。不具合の内容とその修正については、別記事に記載しています。

[Follow] ボタン (基本編)

Relationshipsコントローラーの作成

「フォロー」「フォロー解除」という動作は、それぞれリレーションシップの作成と削除に対応しています。RESTアーキテクチャを前提とした場合、少なくともcreateアクションとdestroyアクションが確実に必要となる場面ですね。

というわけで、まずはRelationshipsコントローラーの作成から始めます。

# rails generate controller Relationships
Running via Spring preloader in process 1262
      create  app/controllers/relationships_controller.rb
      invoke  erb
      create    app/views/relationships
      invoke  test_unit
      create    test/controllers/relationships_controller_test.rb
      invoke  helper
      create    app/helpers/relationships_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/relationships.coffee
      invoke    scss
      create      app/assets/stylesheets/relationships.scss

Relationshipsコントローラーに対するテストの記述

認可に関係する動作なので、実装に万全を期すために、テストを先に書いてから実装に取り掛かっていくこととしましょう。

test/controllers/relationships_controller_test.rb
require'test_helper'classRelationshipsControllerTest<ActionDispatch::IntegrationTesttest"create should require logged-in user"doassert_no_difference'Relationships.count'dopostrelationships_pathendassert_redirected_tologin_urlendtest"destroy should require logged-in user"doassert_no_differende'Relationships.count'dodeleterelationship_path(relationships(:one))endassert_redirected_tologin_urlendend

テストの記述内容に問題がないのであれば、現時点におけるtest/controllers/relationships_controller_test.rbに対するテストの実行結果は以下のようになります。

# rails test test/controllers/relationships_controller_test.rb
Running via Spring preloader in process 1284
Started with run options --seed 280

ERROR["test_create_should_require_logged-in_user", RelationshipsControllerTest, 1.4866881000052672]
 test_create_should_require_logged-in_user#RelationshipsControllerTest (1.49s)
AbstractController::ActionNotFound:         AbstractController::ActionNotFound: The action 'create' could not be found for RelationshipsController
            test/controllers/relationships_controller_test.rb:7:in `block (2 levels) in <class:RelationshipsControllerTest>'
            test/controllers/relationships_controller_test.rb:6:in `block in <class:RelationshipsControllerTest>'

ERROR["test_destroy_should_require_logged-in_user", RelationshipsControllerTest, 1.673922499991022]
 test_destroy_should_require_logged-in_user#RelationshipsControllerTest (1.67s)
AbstractController::ActionNotFound:         AbstractController::ActionNotFound: The action 'destroy' could not be found for RelationshipsController
            test/controllers/relationships_controller_test.rb:14:in `block (2 levels) in <class:RelationshipsControllerTest>'
            test/controllers/relationships_controller_test.rb:13:in `block in <class:RelationshipsControllerTest>'

  2/2: [===================================] 100% Time: 00:00:01, Time: 00:00:01

Finished in 1.68059s
2 tests, 0 assertions, 0 failures, 2 errors, 0 skips

AbstractController::ActionNotFoundというエラーが発生しています。Relationshipsコントローラーに、createアクションもdestroyアクションも定義されていないためにエラーが発生しているのですね。

Relationshipsコントローラーに、createアクション・destroyアクション・logged_in_userフィルターを追加する

何はなくとも、まずRelationshipsコントローラーにcreateアクションおよびdestroyアクションの実装が必要となります。logged_in_userフィルターによるアクセス制御も同時に追加します。

app/controllers/relationships_controller.rb
  class RelationshipsController < ApplicationController
+   before_action :logged_in_user
+
+   def create
+   end
+
+   def destroy
+   end
  end

createアクション・destroyアクション・logged_in_userフィルターがあるRelationshipsコントローラーに対するテストの結果

ここまでの実装が完了したところで、現時点のtest/controllers/relationships_controller_test.rbを対象に、改めてテストを実行してみます。

# rails test test/controllers/relationships_controller_test.rb
Running via Spring preloader in process 1323
Started with run options --seed 62701

  2/2: [===================================] 100% Time: 00:00:01, Time: 00:00:01

Finished in 1.55615s
2 tests, 4 assertions, 0 failures, 0 errors, 0 skips

テストが無事成功しました。

Relationshipsコントローラーの完全な実装

Relationshipsコントローラーの完全な実装、すなわちapp/controllers/relationships_controller.rbの最終的な中身は、以下のようになります。

app/controllers/relationships_controller.rb
classRelationshipsController<ApplicationControllerbefore_action:logged_in_userdefcreateuser=User.find(params[:id])current_user.follow(user)redirect_touserenddefdestroyuser=Relationships.find(params[:id]).followedcurrent_user.unfollow(user)redirect_touserendend

非ログインユーザーが、Relationshipsリソースに直接POSTDELETEを行った場合の動作

実は、beforeフィルターがない状態でも、「非ログインユーザーがRelationshipsリソースに直接POSTDELETEを実行した場合、RDBの内容に変化は生じない」という動作は実現されています。その流れは以下の通りです。

  1. 非ログインユーザーが(curl)Relationshipsリソースに直接POSTDELETEを実行する
  2. createアクションにせよdestroyアクションにせよ、current_usernilになる
  3. followunfollowが呼び出された時点で例外が発生する

しかしながら、「アプリケーションロジックの正常な動作が、例外の発生に依存したものとなる」というのは避けたいパターンです。しかもそれが、「nilに対する参照」という例外であるならばなおさらです。ゆえに今回は、「beforeフィルターを追加する」という実装を行っています。

演習 - [Follow] ボタン (基本編)

1. ブラウザ上から /users/2 を開き、[Follow] と [Unfollow] を実行してみましょう。うまく機能しているでしょうか?

下記が初期状態のスクリーンショットです。「ログインユーザーはid=2のユーザーをフォローしていない」という状態です。

スクリーンショット 2020-02-07 7.52.39.png

[Unfollow]ボタンではなく[Follow]ボタンが表示されていますね。

では[Follow]ボタンを押してみましょう。結果は以下のようになります。

スクリーンショット 2020-02-07 7.52.47.png

[Follow]ボタンではなく[Unfollow]ボタンが表示されていますね。

では[Unfollow]ボタンを押してみましょう。結果は以下のようになります。

スクリーンショット 2020-02-07 7.52.53.png

[Unfollow]ボタンではなく[Follow]ボタンが表示されています。

2. 先ほどの演習を終えたら、Railsサーバーのログを見てみましょう。フォロー/フォロー解除が実行されると、それぞれどのテンプレートが描画されているでしょうか?

フォローが実行されたときの処理として、Railsサーバーには以下のログが出力されています。

Started POST "/relationships" ...略
Redirected to http://localhost:8080/users/2
Completed 302 Found in 65ms (ActiveRecord: 38.6ms)


Started GET "/users/2" for 172.17.0.1 at 2020-02-06 22:52:42 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#show as HTML
  Parameters: {"id"=>"2"}
  ...略
  Rendering users/show.html.erb within layouts/application
  ...略
  Rendered shared/_stats.html.erb (16.1ms)
  ...略
  Rendered users/_unfollow.html.erb (9.7ms)
  Rendered users/_follow_form.html.erb (41.2ms)
  ...略
  Rendered users/show.html.erb within layouts/application (168.1ms)
  ...略
Completed 200 OK in 629ms (Views: 576.3ms | ActiveRecord: 28.2ms)

_unfollow.html.erbが描画されている」というのが重要です。「_unfollow.html.erbは、_follow_form.html.erbにおいて、ログインユーザーが対象のユーザーをフォローしている場合に描画される」ように実装したのでしたよね。

一方、フォロー解除が実行されたときの処理としては、Railsサーバーには以下のログが出力されています。

Started DELETE "/relationships/88" ...略
Redirected to http://localhost:8080/users/2
Completed 302 Found in 55ms (ActiveRecord: 36.7ms)


Started GET "/users/2" for 172.17.0.1 at 2020-02-06 22:52:49 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#show as HTML
  Parameters: {"id"=>"2"}
  ...略
  Rendering users/show.html.erb within layouts/application
  ...略
  Rendered shared/_stats.html.erb (12.9ms)
  ...略
  Rendered users/_follow.html.erb (2.0ms)
  Rendered users/_follow_form.html.erb (30.1ms)
  ...略
  Rendered collection of microposts/_micropost.html.erb [30 times] (14.1ms)
  ...略
  Rendered users/show.html.erb within layouts/application (160.8ms)
  ...略
Completed 200 OK in 881ms (Views: 828.7ms | ActiveRecord: 24.9ms)

_follow.html.erbが描画されている」というのが重要です。「_follow.html.erbは、_follow_form.html.erbにおいて、ログインユーザーが対象のユーザーをフォローしていない場合に描画される」ように実装したのでしたよね。

なお、参考として、app/views/users/_follow_form.html.erbそのものの実装内容は以下のようになっていることを明記しておきます。

app/views/users/_follow_form.html.erb
<%unlesscurrent_user?(@user)%><divid="follow_form"><%ifcurrent_user.following?(@user)%><%=render'unfollow'%><%else%><%=render'follow'%><%end%></div><%end%>

[Follow] ボタン (Ajax編)

現状の[Follow]/[Unfollow]ボタンの実装の問題点

現状の[Follow]/[Unfollow]ボタンの実装では、「ボタンをクリックした後、ログインユーザー自身のプロフィールページにリダイレクトされる」という動作になっています。

しかしながら、[Follow]/[Unfollow]ボタンが表示されているのは、専用の投稿フォームではなく、任意のユーザーのプロフィールページです。「何らかのアクションをとると、勝手にページの移動が発生する」という挙動は、フォーム以外に内容のないページならともかく、そうでないページの場合はユーザーの期待に反する動作である可能性が高いです。

このような場合は、「ページ移動が発生せず、[Follow]/[Unfollow]ボタンのあるページに留まる」という実装のほうが望ましいのではないでしょうか。

Ajaxを使えば、上記の問題点に対し、より望ましい形の実装に持っていける

Ajaxを使えば、WebブラウザとWebサーバーの間での「非同期」処理が可能になります。「ページを移動することなくリクエストを送信する」という処理です。「ページ移動が発生せず、[Follow]/[Unfollow]ボタンのあるページに留まる」という処理も、Ajaxによって実現が可能です。

RailsにおけるAjaxの利用

Railsにおいても、Ajaxの利用は容易に可能です。ビューに記述されているform_forメソッドにremote: trueというオプションを追加すれば、それだけでRailsアプリケーションは自動的にAjaxを使うようになります。

form_for...,remote: true

Ajaxを使ったフォローフォーム・フォロー解除フォーム

Ajaxを使ったフォローフォームのコードは以下のようになります。

app/views/users/_follow.html.erb
- <%= form_for(current_user.active_relationships.build) do |f| %>
+ <%= form_for(current_user.active_relationships.build, remote: true) do |f| %>
<div><%= hidden_field_tag :followed_id, @user.id %></div>
    <%= f.submit "Follow", class: "btn btn-primary" %>
  <% end %>

一方、Ajaxを使ったフォロー解除フォームのコードは以下のようになります。

app/views/users/_unfollow.html.erb
<%= form_for(current_user.active_relationships.find_by( followed_id: @user.id),
-                                                         html:{ method: :delete })
+                                                         html:{ method: :delete },
+                                                         remote:true)
  do |f| %>
    <%= f.submit "Unfollow", class: "btn" %>
  <% end %>

上記埋め込みRubyで生成されるHTMLの内容

上記の埋め込みRubyでは、例えば以下のようなHTMLが生成されます。

<formclass="new_relationship"id="new_relationship"action="/relationships"accept-charset="UTF-8"data-remote="true"method="post">
...略
</form>

formタグの内部でdata-remote="true"が設定されている」というのがポイントです。この属性設定は、「JavaScriptによるフォーム操作を許可することをRailsに知らせる」という意味があります。

コントローラー側のAjax対応

respond_toメソッド

Ajaxに対応するためには、コントローラー側の実装も一部変更する必要があります。具体的には、「respond_toメソッドを使い、リクエストの種類によって応答を場合分けする」という実装が必要になります。respond_toメソッドの基本的な用法は以下のようになります。

respond_todo|format|format.html{redirect_touser}format.jsend

respond_toは引数としてブロックを取りますが、その動作は「ブロック内のコードのうち、いずれかの1行が処理される」というものになります。

Relationshipsコントローラーの実装を変更する

Relationshipsコントローラーの実体であるapp/controllers/relationships_controller.rbの内容は、以下のように変更します。

app/controllers/relationships_controller.rb
classRelationshipsController<ApplicationControllerbefore_action:logged_in_userdefcreate@user=User.find(params[:followed_id])current_user.follow(@user)respond_todo|format|format.html{redirect_to@user}format.jsendenddefdestroy@user=Relationship.find(params[:id]).followedcurrent_user.unfollow(@user)respond_todo|format|format.html{redirect_to@user}format.jsendendend

「Relationshipsコントローラーのアクションで使われる変数を、ローカル変数のuserではなくインスタンス変数の@userに変更した」という点には注意が必要です。

user@userに変更した理由を説明するにあたっては、「app/views/users/_follow_form.html.erbというビューは、@userの内容に応じて動作を分岐させるという実装である」というのが重要なポイントです。この実装を踏まえると、「ページ遷移が発生することなしに、[follow]/[unfollow]ボタンの描画状態に変化が発生する」というユースケースを実現するためには、Relationshipsコントローラーのアクションで直接@userを書き換える必要が出てきます。そのためuser@userに変更する必要が発生した、という次第です。

WebブラウザでJavaScriptが無効に設定されていた場合のための、Railsの設定の変更

Webブラウザ側でJavaScriptが無効にされていると、当然ながらWebブラウザでAjaxリクエストを発行することはできません。RailsアプリケーションでAjax対応を前提とした実装を行った場合、JavaScript無効のWebブラウザでアプリケーションを動かすためには、Rails側の設定を変更する必要があります。具体的には、「認証トークンがremoteフォームに埋め込まれるようにする」必要があります。

# 認証トークンをremoteフォームに埋め込むconfig.action_view.embed_authenticity_token_in_remote_forms=true

変更対象となるファイルはconfig/application.rbです。

config/application.rb
  require_relative 'boot'

  require 'rails/all'

  ...略

  module SampleApp
    class Application < Rails::Application
      ...略
+
+     # 認証トークンをremoteフォームに埋め込む
+     config.action_view.embed_authenticity_token_in_remote_forms = true
    end
  end

Ajaxリクエストを受信したときに呼び出される埋め込みRubyファイルの実装

生成すべきファイルの名前

まずは前提知識から。RailsアプリケーションがHTTPのGETリクエストを受信すると、対応するアクション(indexshownewedit)と同じ名前を持つHTML用の埋め込みRuby(例えばshowアクションに対するshow.html.erb)が自動で呼び出されます。Railsチュートリアルを第14章まで進めてきた人であれば、ここまでは既知かと思います。

RailsアプリケーションがAjaxリクエストを受信した場合も、その動作はHTTPのGETリクエストに対する動作と酷似したものになります。すなわち、「対応するアクションと同じ名前を持つ埋め込みRubyが自動で呼び出される」という動作をするのです。但し、Ajaxリクエストに対する動作の場合は、「呼び出される埋め込みRubyは、HTML用ではなくてJavascript用のものとなる」という違いがあります。

「Ajaxリクエストに対して実行される動作の内容の定義と、各動作に対応するerbファイルの名前」は、今回の場合、以下のような関係になります。

動作対応するアクションの内容対応するファイル名
フォローRelationshipオブジェクトのcreateapp/views/relationships/create.js.erb
フォロー解除Relationshipオブジェクトのdestroyapp/views/relationships/destroy.js.erb

jQueryによるDOM操作

前提となる記法

$("#follow_form")

上記の$(#follow_form)というオブジェクトは、「follow_formというCSS idを持つ要素」を指します。フォームそのものを指すものではありません。なお、クラスを指す場合は、#の代わりに.を用います。こちらもCSSと同様ですね。

$("#follow_form").html("foobar")

例えば、follow_formというCSS idを持つフォロー用フォーム全体を"foobar"という文字列で置き換えたい場合、以上のようなコードを使います。

 create.js.erbdestroy.js.erbの実際の中身

app/views/relationships/create.js.erb
$("#follow_form").html("<%=escape_javascript(render('user/unfollow'))%>");
$("#followers").html('<%=@user.followers.count%>');
app/views/relationships/destroy.js.erb
$("#follow_form").html("<%=escape_javascript(render('users/follow'))%>");
$("#followers").html("<%=@user.followers.count%>");

上記コードのポイントは以下です。

  • JS-ERbでは、素のJavaScriptとは異なり、組み込みRubyを使うことができる
  • JS-ERbでRailsのrenderメソッドを使ってJavaScriptファイル内にHTMLを挿入する際には、escape_javascriptメソッドで「JavaScriptのダメ文字」をエスケープする必要がある

Ajaxによる[Follow]/[Unfollow]ボタンの実装における注意事項

Ajaxによる[Follow]/[Unfollow]ボタンの実装が完了したら、一旦開発環境のサンプルアプリケーションからログアウトした上で、Railsサーバーを再起動し、再度ログインしましょう。

Ajaxによる[Follow]/[Unfollow]ボタンの実装後、[Unfollow]ボタンからDELETEリクエストを発行する動作が正常に行われるようにするためには、おそらく「ログアウト→再ログイン」という操作が必要となります。そうでないと、「/relationships/:id に対し、DELETEではなくPOSTを発行してしまい、ActionController::RoutingErrorが発生する」という事態になります。

演習 - [Follow] ボタン (Ajax編)

1. ブラウザから /users/2 にアクセスし、うまく動いているかどうか確認してみましょう。

スクリーンショット 2020-02-14 8.25.10.png

followersの数は0で、[Follow]ボタンが表示されています。

ここで[Follow]ボタンをクリックしてみます。次に出た画面のスクリーンショットは以下です。

スクリーンショット 2020-02-14 8.25.21.png

followersの数が1増え、[Follow]ボタンが[Unfollow]ボタンに変わりました。

ここで[Unfollow]ボタンをクリックしてみます。次に出た画面のスクリーンショットは以下です。

スクリーンショット 2020-02-14 8.25.16.png

followersの数が1減り、[Unfollow]ボタンが[Follow]ボタンに変わりました。

2. 先ほどの演習で確認が終わったら、Railsサーバーのログを閲覧し、フォロー/フォロー解除を実行した直後のテンプレートがどうなっているか確認してみましょう。

フォローを実行した直後のログ

[Follow]ボタンをクリックし、POSTリクエストが発行されてから、リクエストが完了するまでのRailsサーバーのログを以下に示します。

Started POST "/relationships" ...略
Processing by RelationshipsController#create as JS
  ...略
  Rendering relationships/create.js.erb
  ...略
  Rendered users/_unfollow.html.erb (4.6ms)
  ...略
  Rendered relationships/create.js.erb (30.4ms)
Completed 200 OK in 137ms (Views: 66.4ms | ActiveRecord: 35.8ms)

relationships/create.js.erbの描画が行われ、その中でusers/_unfollow.html.erbの描画が行われる」という順序でテンプレートの描画が行われたことがわかります。

フォロー解除を実行した直後のログ

[Follow]ボタンをクリックし、POSTリクエストが発行されてから、リクエストが完了するまでのRailsサーバーのログを以下に示します。

Started DELETE "/relationships/93" ...略
Processing by RelationshipsController#destroy as JS
  ...略
  Rendering relationships/destroy.js.erb
  Rendered users/_follow.html.erb (1.5ms)
  ...略
  Rendered relationships/destroy.js.erb (30.9ms)
Completed 200 OK in 178ms (Views: 78.8ms | ActiveRecord: 50.1ms)

relationships/destroy.js.erbの描画が行われ、その中でusers/_follow.html.erbの描画が行われる」という順序でテンプレートの描画が行われたことがわかります。

フォローをテストする

ユーザーのフォローに対するテスト

ユーザーのフォローに対するテストの核心は以下のコードです。

assert_difference'@user.following.count',1dopostrelationships_path,params: {followed_id: @other.id}end

テストの構造は、「/relationships に対してPOSTリクエストを発行し、それに対してRDBのレコード数が増えていることをテストする」というものになります。

Ajax版のテストは、以下の内容になります。通常版のテストとの違いは、postメソッドにおけるxhr: trueというオプションの有無だけです。

assert_difference'@user.following.count',1dopostrelationships_path,params: {followed_id: @other.id},xhr: trueend

ユーザーのフォロー解除に対するテスト

relationship=@user.active_relationships.find_by(followed_id: @other.id)assert_difference'@user.following.count',-1dodeleterelationship_path(relationship)end

「HTTPリクエストを発行し、それに対するRDBのレコード数の増減をテストする」というテストの構造は、前述「ユーザーのフォローに対するテスト」と類似したものとなります。より具体的な手順は以下の通りになります。

  1. 1人のユーザーをフォローする
  2. 1.で生成されたRelationshipモデルのオブジェクトを、relationship変数に代入する
  3. relationship変数を引数としてDELETEリクエストを発行し、フォロー数が1減ったことをテストする

2.の操作は、「ログインユーザーの能動的リレーションシップから、1.でフォローしたユーザーのidを検索する」という操作により行われます。

relationship=@user.active_relationships.find_by(followed_id: @other.id)assert_difference'@user.following.count',-1dodeleterelationship_path(relationship),xhr: trueend

Ajax版のテストは、以下の内容になります。通常版のテストとの違いは、postメソッドにおけるxhr: trueというオプションの有無だけです。

test/controllers/relationships_controller_test.rbに対する変更の内容

上記を踏まえると、test/controllers/relationships_controller_test.rb全体に対する変更の内容は、以下のようになります。

test/controllers/relationships_controller_test.rb
  require 'test_helper'

  class FollowingTest < ActionDispatch::IntegrationTest
    def setup
      @user  = users(:rhakurei)
      @other = users(:mkirisame)
      log_in_as(@user)
    end

    test "following page" do
      get following_user_path(@user)
      assert_not @user.following.empty?
      assert_match @user.following.count.to_s, response.body
      @user.following.each do |user|
        assert_select "a[href=?]", user_path(user), minimum: 2
      end
    end

    test "followers page" do
      get followers_user_path(@user)
      assert_not @user.followers.empty?
      assert_match @user.followers.count.to_s, response.body
      @user.followers.each do |user|
        assert_select "a[href=?]", user_path(user), minimum: 2
      end
    end
+
+   test "should follow a user the standard way" do
+     assert_difference '@user.following.count', 1 do
+       post relationships_path, params: { followed_id: @other.id }
+     end
+   end
+   test "should follow a user with Ajax" do
+     assert_difference '@user.following.count', 1 do
+       post relationships_path, xhr: true, params: { followed_id: +other_id }
+     end
+   end
+   test "should unfollow a user the standard way" do
+     @user.follow(@other)
+     relationship = @user.active_relationships.find_by(followed_id: +other.id)
+     assert_difference '@user.following.count', -1 do
+       delete relationship_path(relationship)
+     end
+   end
+   test "should unfollow a user with Ajax" do
+     @user.follo(@other)
+     relationship = @user.active_relationships.find_by(followed_id: other.id)
+     assert_difference '@user.following count', -1 do
+       delete relationship_path(relationship), xhr: true
+     end
+   end
  end

演習 - フォローをテストする

別記事で解説します。

Automation Anywhere v11のユーザーインターフェイスの呼び方 まとめ

$
0
0

RPAツールであるAutomation Anywhereのユーザーインターフェイスの名称について調べてみたのでメモ。
バージョンによって公式ドキュメントも表現に多少のゆれがあるみたいですが、これが最も一般的な名称であろうというものを記載してみました。

Control Room

image.png

参考記事

Enterprise クライアント

image.png

ワークベンチ (タスクエディター) 通常モード

image.png

参考記事

ワークベンチ (タスクエディター) デバッグモード

image.png

MetaBot Designer

image.png

その他の参考記事

初心者がCOTOHAを使ってみました~①環境構築~

$
0
0

1.概要

最近私はAIに興味を持ち、Pythonでディープラーニングの技術を取得しようといろいろ記事を調べております。AIを調べる中で、画像認識や自然言語処理、音声認識等が重要な技術とわかり、これらの技術に興味を持ちました。

その中で、QiitaのトップにCOTOHAの企画が出ているの見て、自然言語処理、音声認識の技術習得の1つとしていいかな、と思い記事を書いてみることにしました。
(COTOHA APIへのリンク)

今回は「①環境構築」について記載したいと思います。

※また、私の利用している環境は以下となっています。
OS:Windows10
プラウザ:Firefox(73.0)

2.COTOHAの構成と実行準備

簡単にCOTOHAの特徴をまとめると、以下のようになっているようです。

image.png

まず自分のPCにCOTOHA APIに接続できる環境を作り、そこから文字や音声のデータを送ります。そうすると、APIから送ったデータの解析結果が返ってくるという仕組みです。

では以下サイトから無料登録を行います。
image.png

登録が終われば、以下サイトからログインできます。

image.png

3.APIの基本構造

COTOHA APIは以下のようにして動かすようです。

image.png

STEP1 アクセストークンの発行

まず各APIを動かすためのアクセストークンを生成します。こちらは、各APIを動かすためのパスワードのようなもので、1回発行すると24時間使えます。

STEP2 各APIの利用

STEP1で発行したアクセストークンと各APIに必要なデータを送ることで、各APIを動かすことができます。例えば、構文解析をしたい場合、アクセストークン+解析したい文字データを構文解析APIに送ることで、構文解析の結果が返ってきます。

4.APIでアクセストークンを取得してみよう

次に以下ページをもとに、アクセストークン取得APIを動かしてみることにしました。

image.png

上記資料から、大きく以下2つの方法があることがわかります。

  • CASE01 ブラウザによるAPIの利用
  • CASE02 cURLによるAPIの利用(Mac, Linux等)

私のPCはWindows端末ですので、CASE01の方法を試してみます。説明文を読むとQiitaに設定の記事があるとのことで、対象のリンクをクリックします。

が、記事が見当たりません。。。

パット見た限り、説明の記事がないので「COTOHA Firefox」で検索をかけてみて、一番上位に来たこの記事が、おそらくプラウザによるAPIの動作を書いたものと思いました。
「初心者の私が、ブラウザで自然言語処理API(COTOHA API)を試せるようになるまで【基本ガイド】」

上記記事に内容通り、まずはFirefoxをインストールし、その後
https://addons.mozilla.org/ja/firefox/addon/restclient/
のアドオンを追加します。

image.png

そしてアクセストークン取得APIを動かすためのデータを作成します。
まず、画面左上のMethodを「POST」にします。

image.png

次にURLの部分に「Access Token Publish URL」の内容を入力します。

i<br>
mage.png

               ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

image.png

次に画面上部の「Headers」を選択し、「Custom Header」をクリックします。

image.png

表示されるポップアップに対して以下を入力し、「Okay」ボタンを押下します。
Name:「Content-Type」
Attribute Value:「application/json」

image.png

その後、以下のようにHeadersが画面に追加され、「Content-Type: application/json」と記載されていれば成功です。

image.png

その後Bodyに以下コードを入力してください。
また、コードに入力するclientIdとclientSecretはアカウントホームに表示されているClient IDとClient secretを記載してください。
(私は記事のソースをそのままコピペして使い、エラーを発生させました…)

image.png

               ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

{
"grantType":"client_credentials",
"clientId": "ログイン後の画面に表示されるClient ID",
"clientSecret": "ログイン後の画面に表示される Client Secret"
}

               ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

image.png

その後、「SEND」を押下します。

image.png

そして画面下側の「Response」を押下すると、COTOHA APIからの返信結果が返ってきています。

image.png

この発行された「access_token」を用いて、必要なAPIを動かして行くことになります。

5.まとめ

時間がかかりましたが、APIを動作するための準備が整いました。
次は各APIを動かしてみて、どんな結果が返ってくるのか試してみたいと思います。

【php】文字列の出現回数を調べる

$
0
0

substr_count()

文字列の出現回数を検索する ※大文字小文字を区別する

文字列の出現数を調べる「substr_count()」

<?php$text='This is a test';echosubstr_count($text,'is').'<br/>';// 実行結果 2?>

4文字目から検索

<?php$text='This is a test';// 文字列は 's is a test' になっているので, 1 が表示されるechosubstr_count($text,'is',3).'<br/>';?>

4文字目から3文字検索

<?php$text='This is a test';// テキストは 's i' になっているので, 0 が表示されるechosubstr_count($text,'is',3,3).'<br/>';?>

6文字目から10文字検索

<?php$text='This is a test';// 5+10 > 14 なので、警告が発生するechosubstr_count($text,'is',5,10).'<br/>';//PHP Warning:  substr_count(): Invalid length value in?>

重なった場合

<?php$text2='ABBABBA';// 重なっている副文字列はカウントされないので、1 が表示されるechosubstr_count($text2,'ABBA').'<br/>';?>

【GROUP BY句】#5 Webディレクター見習いが知識ゼロからSQLを学ぶ

$
0
0

こんにちは!  saku-chanです!
このアカウントは、社会人1年目のWebディレクター見習い(初心者)がSQLを学ぶ成長記録になっています。
ひよっこから成長する過程を残し、ついでに皆さんからアドバイスを頂けたら良いな! 私と同じように知識ゼロからSQLを始める人の一つの指針になればいいな!という思いで作成しました。
ちなみに、ブログを含め記事などを書いた経験がないため読みづらい部分も多々あるかと思いますが、そこも含めて皆さまアドバイス頂けると嬉しいですmm

◆記事の内容は、SQL初心者の自分が、同じような初心者の方でも理解しやすいように心がけながら、SQLの基本に関して学んだことを懇切丁寧に記載しています◆

目次

はじめに

今回はSQLのSELECT文を構成するGROUP BY句について勉強したことを記載します!
初めてSQLを勉強する方(プログラミング経験自体がなくてもOK)が気軽に読んでいただける記事になっています^^
ではスタート!

前回の記事【WHERE句】はこちら
前々回の記事【集約関数】はこちら

記事一覧

GROUP BY句とは

GROUP BY句とはなんでしょうか?
名前からして、なんとなくグループ化しそうだなーっとイメージして頂ける気がしますが、
その通り!
データをグループにまとめる働きをもっています。

行をいくつかのグループに分割して、それぞれのグループに対して集約関数を使用することが出来ます。

では実際に具体例をみながら確認していきましょう!
今回使用するテーブルはこちらです。

Table名:quiz_2

numberareateam_namepoints
01東京スマイル2
02大阪たこ焼き3
03東京乃木坂1
04福岡わんちーむ3
05大阪しらんけど7
06東京ガールズ43
07愛知しゃちほこ4

例えば、areaごとの平均pointsを知りたい時、
下記のようにクエリを記述します。

SELECTarea,    AVG(points)AS平均得点FROMquiz_2GROUPBYarea;

結果は下記の通りです。

area平均得点
東京2
大阪5
福岡3
愛知4

areaに分けて、それぞれのエリアの平均pointsを求めることが出来ました!
(東京の場合、(2+1+3)÷3=2のような感じです。)
また、以前の記事でも触れましたが、ASを使用することでカラム名を変更することが出来ます。

GROUP BY句を使うときの注意点

GROUP BY句を使うときの注意点は、GROUP BY句を使用する時、
SELECT句には、GROUP BY句で使用したカラム名または集約関数しか記載することができません。

どういうことかというと、たとえば下記のようなクエリはエラーになります。

SELECTarea,team_nameFROMquiz_2GROUPBYarea;

なぜなら、GROUP BY句でareaごとに絞ると、
東京、大阪、福岡、愛知
に分類されますが、SELECT句で指定したチーム名(team_name)が東京には3種類、大阪には2種類存在してしまっているためです。
1つのareaに対してteam_nameカラムにそれぞれどの値をいれれば良いかプログラムが困ってしまうということですね。

ちなみに集約関数はいくつでも記載できます。
以下に例を記載します!

Table名:quiz_2

numberareateam_namepoints
01東京スマイル2
02大阪たこ焼き3
03東京乃木坂1
04福岡わんちーむ3
05大阪しらんけど7
06東京ガールズ43
07愛知しゃちほこ4
SELECTarea,    AVG(points)AS平均得点,    MAX(points)AS最高得点,MIN(points)AS最低得点FROMquiz_2GROUPBYarea;

このようなクエリを実行すると、下記のような結果を得ることが出来ます。

area平均得点最高得点最低得点
東京231
大阪573
福岡333
愛知444

GROUP BY句で指定した値に対して集約関数がそれぞれ1行ずつ値を返すことが出来るので、エラーにならずにデータを抽出することが出来るんですね。

まとめ

  • GROUP BY句は行をいくつかのグループに分割して、それぞれのグループに対して集約関数を使用することが出来る
  • GROUP BY句を使用する時、 SELECT句には、GROUP BY句で使用したカラム名または集約関数しか記載することが出来ない。

最後に

今回はGROUP BY句について記事を書いてみました。
GROUP BY句を使ってエラーになる時はたいていSELECT句にGROUP BY句で使用していないカラム名を書いちゃったときな気がします、、(笑)
落ち着いてそれぞれの句で何をしているのか考えてみるのが大切ですね><
ではまた次回!

記事一覧


AWS試験対策(⑧ネットワーク)

$
0
0

個人的に苦手意識のあるまったくわからないゾーン!やってくよーー!
とりあえずわかるまでやるしかないのじゃ。資格とるには。

Route 53

DNSサービス。可用性高く低レイテンシー。自分でDNSサーバを構築する必要がなくなる。

パブリックホストゾーンとプライベートホストゾーンがある。
パブリックは、インターネット上に公開するDNSドメインのレコードを管理するホストゾーン。
プライベートはVPC内でのDNSドメインのレコードを管理するホストゾーン。
つまり公開する情報か公開しない情報かの違い。ちなみに四つのトップレベルドメインの、異なるロケーションに配置されたDNSサーバで管理されている。

エイリアスレコード

仮想リソースレコード。よくわからんな。

  • AWSサービスのエンドポイントをDNSに登録する場合 AWSサービスはエンドポイントのIPが変わり続けるため、Aレコードの設定ができない。Aレコードとは、ホスト名からIPを引き出すやつ。IP変わっちゃうなら登録しても無意味やなそりゃ。

なのでRoute53でエイリアスレコードを作成し、エンドポイントのIPに直接応答するときに使う。
よくあるのは、ロードバランサの名前はLB.comだけど、エンドユーザからは別名(site.com)でアクセスさせたいときなどに使う。
ELBは動的IPなのでAレコードは使えない。よってCNAMEを使うのが一般的だが、同じようにエイリアスレコードを使える。
どちらも別名を登録するという点では同じだが、エイリアスレコードの場合、内部で処理してから返してくれるので早い。そしてS3/CloudFront/ELBへのクエリは無料。
site.com→site.comはLB.comのことだったな…せや!LB.comのIPで応答したろ!みたいな感じ。気が利く。

  • Zone Apex 管理しているDNSドメインの頂点となるノードのこと。 例えば、「www.exam.com」の「exam.com」の部分のこと。すでにNSレコードにZone Apexが登録されている場合、CNAMEでの登録はできないため、エイリアスレコードを使う。

基本的にエイリアスレコード使えばいいんじゃないかな(適当)
別名ってことだけ覚えておこう。

ポリシーベースのルーティング

ポリシー名説明
シンプルルーティング従来と同様、事前に設定された値に基づいて応答
加重ルーティング優先度を先に決めておいて、それが高いほうから
レイテンシールーティング遅延が少ないほうから
フェイルオーバールーティングヘルスチェックの結果、利用可能なところから。一個も正常なのがない場合、逆にすべて正常とみなす。
位置情報ルーティングクライアントの位置情報に基づき応答

たとえば接続が不安定な場合、ダウンタイムを最小にしたいならレイテンシールーティングポリシーを設定すれば解決できる。

Direct Connect

オンプレとAWSを専用線でつなぐサービス。インターネットVPNはネット経由なのでそれぞれの特色がある。

項目インターネットVPN専用線
コスト安価なベストエフォートの回線を選択して利用することも可能高め
利用開始までの期間短い期間で開始可能数週間以上
帯域暗号化などのオーバーヘッド(負担)による制限あり占有型は1,10Gbps。共有型は~10Gbps
品質インターネットを利用してるため経路で影響を受けるキャリアによる高い品質保証
障害対応インターネットを利用しているため自社の範囲外だと困難どの経路を使用しているのかはっきりわかるので比較的容易

要するに、高くて準備に時間がかかるけど早くて品質よいのが専用線。すぐ使えて安いけど品質とか速度落ちるのがインターネットVPN。よくあるのはプライマリ接続は専用線、バックアップ回線はインターネットVPNにするなど。

Direct Connectの接続構成

  • 物理接続
    AWS自体はDCの場所を公開していないため、AWSのパートナーの設備に用意された相互接続ポイントを使って接続する。ルータを持ち込めば占有型、持ち込まないでパートナーのを共有して使うなら共有型になる。
    ちなみにパートナーのことをAPNパートナーというらしい。

  • 論理接続
    物理的な接続を論理的に分割し、複数のAWSアカウントで使用できる。要するに物理線一本引いて、その中で分割すれば部署や用途ごとに使える。

プライベート接続とパブリック接続

プライベート接続は、プライベートIPを利用し、VPCのサブネットへアクセスする接続。
パブリック接続は、パブリックIPを利用し、全リージョンのパブリックサービスへ接続。パブリックIPじゃないといけないのでオンプレ側にNAT機器が必要。

Direct Connect Gateway

プライベート接続でVPCに接続するとき、VGW(仮想プライベートゲートウェイ)に接続する。
ほかのリージョンのVPCへ接続する場合はVPC同士がピア接続していないといけないが、ここでDirect Connect Gatewayの出番。友達の友達は友達!!ってタイプのやつ。
しかし、注意としては、オンプレ同士は友達になれない。VPC同士もなれないってかピア接続っていう方式を使え。

課金体系

使用した時間とデータの転送量で課金される。

CloudFront

CDNサービスのこと。こいつがキャッシュを持ってればそこから配信してくれるし、もってなかったらこいつがオリジンサーバ(大本)からコンテンツを取得する。
要するに野球でいうショートやセカンドのカット(中継)みたいな感じ。外野から一本でバックホームすると負担がすごいからこいつらが受け取って代わりに投げてくれる(ちょっと違うか)
どの中継になるかはディストリビュージョンが決める。レフトだったらショートが、ライトだったらセカンドがカットに入るとかそういう決まりをディストリビュージョンっていう。(しつこい)
オリジンサーバの負荷軽減や、クライアントに対してレスポンス向上などのメリットがある。
DNSサーバへドメイン名の問い合わせ→CloudFrontへドメイン名を問い合わせ→最適なエッジサーバを応答→エッジサーバへアクセス→キャッシュから応答もしくはオリジンサーバから取得してきて応答
って流れ。

暗号化通信

言っちゃえばこいつは中継地点なので、クライアントとこいつの間で暗号化できるし、こいつとオリジンサーバ間でも暗号化できる。

  • クライアント-エッジサーバ間の暗号化
    デフォルトのドメインである、cloudfront.netドメインのSSL証明書を標準で利用できる。これによってデフォルトのドメイン名で利用する場合はHTTPSでクライアントとエッジサーバとの間で通信できる。
    また、独自ドメイン名の場合はX.509PEM形式のSSL証明書を入れれば利用可能。

  • エッジサーバ-オリジンサーバ間の暗号化
    オリジンサーバが何かによって、暗号化の種類が変わる。

S3バケット
デフォルトではクライアントからアクセスされたプロトコルで通信される。HTTPSを必須にするなら、CloudFrontにてViewer Protocol PolicyをRedirect HTTP to HTTPS、または、HTTPS Onlyにする必要がある。

S3静的ウェブホスティング
HTTPSを使用できない。S3静的ウェブホスティングのエンドポイントがHTTPS不可のため。
この場合、S3静的ウェブホスティングはカスタムオリジンとして暗号化しなければいけない。

カスタムオリジン
CloudFrontにてViewer Protocol PolicyをRedirect HTTP to HTTPS、または、HTTPS Onlyに設定し、かつ、カスタムオリジンサーバに自分で証明書を入れなければならない。

要するにS3バケットならCloudFrontの設定変えるだけでいいけど、それ以外は設定変えたうえで証明書入れてねってこと。

署名付きURL

コンテンツへのアクセスを期間限定にしたい場合、署名付きURLにてURLの有効期限を設定できる。
例えば期間限定で音楽配信したりとか。長い期間なら事業計画を投資家に配信したりとか。

オリジンサーバの保護

オリジンサーバのURLが外部にばれて直接アクセスされることはセキュリティ的に大きな問題となる。(CloudFrontとクライアント間で暗号化とかしてたら、ここすっ飛ばされて暗号化できないため)
これを避けるため、オリジンサーバを保護する機能がある。

S3バケットの場合
Origin Access Identity(OAI)(かっこいい)を使い、S3バケットへのアクセスをCloudFrontからのみに制限できる。

S3静的ウェブホスティング
無力。注意すべし。

カスタムオリジン
オリジンカスタムヘッダーを利用して、CloudFrontで指定された任意のヘッダーをオリジンサーバでチェックすることで、CloudFrontからのアクセスのみ受け付けにできる。
しかし、オリジンサーバへのアクセス自体はパブリックにしておかないといけないためURL流出は注意。

要するにS3バケットの場合制限できる。カスタムの場合は制限しててもパブリックなので危険。静的ウェブホスティングは危険ってこと。まず流出しないよう気を付けよう。

以上。次回DBへ。

『Unity C# 個人用メモ』 ガチ初心者の自分用の用語メモ

$
0
0

初心者の自分用のメモ
大体こんな感じだよって思ってればいいやつ

MonoBehaviour(モノビヘイビア)

UnityをMacやWindowsやPS4などで扱えるようにした規格。

ヒエラルキービュー

画面左。シーンビューに配置したオブジェクトの一覧、階層構造。

プロジェクトビュー

画面下。ゲームで使う素材を管理。

インスペクタ

画面右。選択しているオブジェクトの詳しい情報がわかる。 座標・オブジェクトの大きさ・配置など。

メソッド

void Start()
{

}

メソッドとはこのvoid 〇〇のこと。上の物であればStart()がメソッドになる

アクセス修飾子

public int = 1;
private int = 1;

アクセス修飾子とはこのpublicやprivateのこと

変数・型

int a ;

int ← 型
a  ← 変数
intという型のaという変数を作るという意味になる

引数(ひきすう)

Vector3 zahyou = new Vector3(〇,〇,〇);

new Vector3(〇,〇,〇);〇のことなど

『Unity C#』ガチ初心者の自分用メモ 型とか演算子

$
0
0

よく使う型

int ・・・整数
/// -1,0,1 
flot・・・小数点以下を含む
/// fが必須 -1.5f,1.5f 
string・・・文字列
/// ""が必須 "こんにちは"
bool()・・・真偽判定
/// true(真) or false(偽)
Vector3(2)・・・座標とかベクトル
/// VectorのVは大文字なのを忘れないで!!
/// 使う時は Vector3 zahyou = new Vector3(〇,〇,〇)の様に宣言する
/// Vector2の場合なら new Vector2(〇,〇)の様な感じ
GameObject・・・ゲームオブジェクト
/// 用途によってGameObjectとgameObjectの二つ使い分けるので気を付ける
/// 宣言するときは```private GameObject ○○ ```など
/// 使用するときは```gameObject.GetComponent<〇〇>();```など
Transform・・・トランスフォーム。オブジェクトのインスペクタ(Unity画面の右側)の上の方にある座標とか回転入力するときに使うやつ
/// 宣言するときは```private Transform ○○;```など
///使用するときは```transform.position;```など

演算子

計算で使うやつ
四則演算省略
+= 追加(=が後ろ)
-= 差引(=が後ろ)
++ 1増やす
-- 1減らす
=  代入

if分で使うやつ
== 同じであるとき
!= 同じではない時
&& どちら[も]
|| どちら[か]
>  大なり
>= 大なりイコール(=が後ろ)
<  小なり
>= 小なりイコール(=が後ろ)

初めての KotlinでHello Worldと出力する方法と変数の宣言について

$
0
0

前提条件

初めてKotlin(コトリン)に触れる人

Javaの基本的な構文を理解していると読みやすいです。

Hello Worldと出力する

本家サイトでKotlinを簡単に実行する

ソースコード

fun main() {
    println("Hello World")
}

以上をこちらで入力すれば「Hello World」と出力できます。

以下はKotlinでの変数の使い方についてザックリと解説しています。
※実際のソースコードに①や②は記載しない。

①fun main() {
②    var name:String = "World"
③    println("Hello ${name}")
  }

KotlinではJavaと異なり、文の末尾に;(コロン)は不要です。

①では「mainという名前の関数を定義」しています。
main関数はJavaと同じくエントリーポイントとなっています。
エントリーポイントとは、プログラム上で最初に実行される関数のことです。

②ではString型の変数nameを宣言しています。
Kotlinの型宣言は「型 変数名」ではなく、「変数名:型」が基本となっています。
varは変数の宣言時に必要な命令です。
var命令で宣言しなければ変数の利用はできません。

③については後述します。

変数の宣言についての構文

var name:type = initial

name は変数名
type はデータ型
initial は初期値

type(データ型)は省略でき以下のようにすることもできます。

var name = "山田"

この場合、nameに代入できるデータ型はString型に決まります。
type(データ型)が省略された場合、初期値からデータ型を類推します。
この仕組みのことを型推論と言います。

初期値を省略して以下のようにすることもできます。

var number:Int

なお、データ型と初期値の両方を同時に省略することはできません。

変数に任意の型を代入したい場合はAny型を宣言することで任意の型を代入できます。

var name:Any = "山田"
name = 111

以上ではエラーが発生しません。
全てのデータ型はAnyを継承しているのでエラーになりません。
Any型の変数には全てのデータ型のデータが代入できるので、
エラーの発生要因となるため非推奨です。

以下はよく使われる基本的なデータ型です。

データ型概要
Boolean真偽型
Byte8bit整数型
Short16bit整数型
Int32bit整数型
Long64bit整数型
Float32bit浮動小数点型
Double64bit浮動小数点型
Char文字型
String文字列型

文字列テンプレート

文字列に${}の形式で任意の式を埋め込むことができます。

fun main() {
    var number = 10
    println("変数numberは${number}です")
    println("変数numberは$number です")
}

実行結果は以下の通りです。

変数numberは10です
変数numberは10 です

中かっこ{}は省略できますが、変数の区切りの部分に半角空白を入れなければエラーとなります。
入れた空白は文字列にも反映されるため、省略は望ましくありません。

HSRPの基礎知識

$
0
0

【HSRPの基礎知識】
HSRP(Hot Standby Router Protocol)とは、デフォルトゲートウェイを冗長化するためのシスコ独自のプロトコル
グループとはHSRPに参加して仮想ルータをエミュレートするルータの集合。セグメントごとに1つグループを作成
priority値が設定されており、高い値を持つルーターがアクティブとなる。
Timer値のデフォルトはhello3秒、hold10秒となっている。
上記ではHelloパケットは、3秒間隔で交換されHelloパケットが途絶えてから10秒経過するとStandbyルータがActiveルータに切り替わる。
Timer値は変更可能である。

・priority値設定方法
→ (config-if)# standby group-number priority priority

・HSRPのHello/Holdタイマーの調整
→ (config-if)# standby group-number timers hellotime holdtime

■preempt
HSRPのデフォルトでは、HSRPグループでアクティブルータがすでに存在する場合、そのアクティブルータ
よりも高いプライオリティを持つルータが後から同じHSRPグループに参加しても、アクティブルータになる
ことは出来ない。
しかし、設定後や起動時の時差がある場合でも、プライオリティ値が高いデバイスが必ず
アクティブルータとなるようにできる機能があって、その機能をプリエンプト機能( preempt )と言う。

・preemptの設定方法
→ (config-if)# standby group-number preempt

■interface tracking
HSRPを有効にしていないIFがダウンした時に、HSRPを有効にしているIFのHSRPプライオリティ値を小さくして、
アクティブルータを切り替える機能をinterface trackingという。
例えば、HSRPを有効にしたLAN側 I/F だけがアクティブ状態でも、同じルータのWAN側 I/Fが
アクティブでなければ通信が継続しないので、tracking( 追跡 )することによって通信を継続できる。

・interface trackingの設定
→ (config-if)# standby group-number track interface-number interface-priority

Viewing all 21373 articles
Browse latest View live