AWS/IAMユーザーの作成方法

f:id:AK474747:20190810211957p:plain

 

先日AWSのS3を使う上で、セキュリティ対策を行わなければいけないということで、
その手順をまとめましたが、

IAMというサービスとS3の登録方法と言ったところも深掘りしていかないとなかなか辻褄の合いにくい箇所もあっただろうということで、今日はIAMについて書きます。

下記記事と連携して当記事をご覧ください。

 

ak474747.hatenablog.com

 

IAMとは

AWS Identity and Access Management (IAM) は、AWS リソースへのアクセスを安全にコンソールするためのウェブサービスです。 要は、できることを制限した擬似アカウントを作れるということみたいです。 このアカウントを他人と共有して使うこともできるみたいですよ。 今回はS3機能だけが使えるIAMユーザーを作成したいと思います。

権限を制限したユーザーを作成する

ログインしているユーザーの権限は一番強いものはrootになります。AWS上の、どんな操作も可能であるため、もしこのアカウントを不正利用されたら、高額な請求が来てしまう可能性もあります。

代わりに、使える権限を制限したユーザーをIAMを利用して作成します。

AWSにログインしたら、サービスからIAMを開きます。

遷移先のページで下記をクリックします。

f:id:AK474747:20190810210921p:plain

左上のユーザーを追加を選択。

f:id:AK474747:20190810210914p:plain

 

ここで作成するユーザー名を登録します。

f:id:AK474747:20190810210908p:plain

次はアクセス権限の設定です。

f:id:AK474747:20190810210951p:plain

「AmazonS3FullAccess」を選択してください。

f:id:AK474747:20190810210954p:plain

選択したら、「次のステップ:タグ」をクリックします。

f:id:AK474747:20190810210959p:plain



ここでは何もせずに、「次のステップ:確認」を押します。

f:id:AK474747:20190810211003p:plain

この画面まできたら、ユーザーの作成をクリックします。

f:id:AK474747:20190810211009p:plain

※忘れずに認証情報をダウンロードしてください

f:id:AK474747:20190810211014p:plain

IAMユーザーにパスワードを持たせる

IAMのメニューから作成したIAMユーザーをクリックします。 遷移したページで「管理」をクリックします。

f:id:AK474747:20190810211018p:plain

そのまま下の画像のように「有効化」「自動生成パスワード」をクリックします。

f:id:AK474747:20190810211026p:plain

ここでもおなじように認証情報をダウンロードしておきます。

f:id:AK474747:20190810211030p:plain

作成したIAMユーザーにログインする

今作成した、制限つきユーザーでログインしてみます。

IAMのトップページの以下のリンクをコピーし、URL欄に貼り付けて遷移します

f:id:AK474747:20190810211035p:plain

下記画面に遷移するので、ユーザー名とパスワード、それぞれダウンロードした認証情報を入力してサインインすることができます。

f:id:AK474747:20190810211039p:plain

hogehogefugafuga書き疲れた。

AWS利用上のセキュリティを確保する方法

f:id:AK474747:20190810013536p:plain


本番環境を利用してChatSpaceをデプロイしたわけですが、セキュリティ的に色々あるみたいです。 色々あるってザックリしすぎだな。

超簡単にいうと、AWSアクセスキーが盗まれるとすげー金持ってかれるかもしれん、という話

ChatSpaceはメッセージアプリ(というよりwebサイト)になるんですが、ユーザ間でメッセージだけでなく画像ファイルの送受信をすることができます。 今回のデプロイでは画像ファイルの保存先として、AWSのS3を使ってバケットを作成し、バケットの中に画像ファイルをアップロードするという手法を取っています。

その際にRailsアプリケーションとS3との相互認証にaws_access_key_idやaws_secret_accesskeyといったものを設定する必要があります。

本当はデプロイ環境を整えるところからブログを書いていきたかったんですが、絶対押さえておかなきゃいけないところだと感じたので先にまとめます。

git-secretsを導入する

Homebrewを経由してgit-secretsを導入します。

ローカル環境

brew install git-secrets

導入ができたら、設定を適用したいディレクトリに移動して、git-secretsを有効にします。

ローカル環境

 cd chat-space
 git secrets --install

git-secretsの条件を設定する

下記コマンドを実行することで、アップロードしたくないAWS関連の秘密情報を一括で設定できます。

ローカル環境(chat-spaceディレクトリ)

git secrets --register-aws --global

これでgit secrets --installを行なったディレクトリでは、AWSの秘密情報を含んだコミットができなくなります。

「うっかりパスワードをGithubにのせてしまった」という事態を防ぐことができます。

git-secretsの設定を今後作成される全てのディレクトリに適用する

今後作成する全てのディレクトリについて、git-secretsを適用したい場合は、下記コマンドを実行することで、自動で設定が適用されるようになります。

ローカル環境(chat-spaceディレクトリ)

$ git secrets --install ~/.git-templates/git-secrets
$ git config --global init.templatedir '~/.git-templates/git-secrets'

GitHub Desktop経由でgit secretsを利用する

GitHub Desktop経由でgit secretsを利用する場合は、下記コマンドも実行する必要があります。

ローカル環境(chat-spaceディレクトリ)

sudo cp /usr/local/bin/git-secrets /Applications/GitHub\ Desktop.app/Contents/Resources/app/git/bin/git-secrets

※ No such file or directoryのエラーが出る場合 こっちを使いましょう。GitHub Desktopのバージョンが古い場合などに起こるようです。

sudo cp /usr/local/bin/git-secrets /Applications/GitHub\ Desktop.app/Contents/Resources/git/bin/git-secrets

S3で保存先を用意する

いろんなところに書いてあると思うので、バケットの作成方法は省略します。

ですが、バケットポリシーというものを記載する必要があるのでそれだけ書いておきます。

{
    "Version": "2012-10-17",
    "Id": "Policy1544152951996",
    "Statement": [
        {
            "Sid": "Stmt1544152948221",
            "Effect": "Allow",
            "Principal": {
                "AWS": "************①****************"
            },
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::************②**********"
        }
    ]
}

①にはIAMユーザーのARNをいれ、②には作成したバケット名を入れます。

画像のアップロード先をS3に変更する

fog-awsというgemを導入する必要があります。 Gemfileを編集します。

Gemfile

(省略)
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
end
gem 'carrierwave'
gem 'fog-aws'

この状態でbundle installコマンドを実行しておきます。

次に、image_uploader.rbを編集します。 stroge: fogに変更しましょう。

# encoding: utf-8

class ImageUploader < CarrierWave::Uploader::Base

  # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  # include CarrierWave::MiniMagick

  # Choose what kind of storage to use for this uploader:
  storage :fog

  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

(省略)

次に/config/initializers直下に、carrierwave.rbというファイルを作成します。


require 'carrierwave/storage/abstract'
require 'carrierwave/storage/file'
require 'carrierwave/storage/fog'

CarrierWave.configure do |config|
  config.storage = :fog
  config.fog_provider = 'fog/aws'
  config.fog_credentials = {
    provider: 'AWS',
    aws_access_key_id: Rails.application.secrets.aws_access_key_id,
    aws_secret_access_key: Rails.application.secrets.aws_secret_access_key,
    region: '自分で調べて入れてください' #例 'ap-northeast-1'
  }

  config.fog_directory  = 'ここにバケット名を入れます'
  config.asset_host = 'https://s3-ここにリージョン名を入れます.amazonaws.com/ここにバケット名を入れます'
end

リージョンというのはAWSでEC2インスタンスやS3を利用するときに選択する地域のことです。 今回であればアジアパシフィック(東京)を選択しているので、「ap-northeast-1」となります。

別のリージョンを選択している場合はその都度調べる必要があります。

.gitignoreを編集する

設定ファイルの中にはawsaccesskeyidやawssecretaccesskeyなどのインターネットに公開してはいけないファイルもあります。

これらを公開してしまうと、AWSのリソースを悪用されてしまい、高額請求の被害に遭う可能性もあります。

そのため、絶対にGitHubに公開してはいけません。

gitignoreを編集することで、コミットしてはいけないファイルを指定します。

ここではsecrets.ymlというファイルにアクセスキーなどを記載する必要があるので、コミット対象外にする必要があります。

.gitignore

# ファイルの最下部に追記
config/secrets.yml

※一度gitの監視下に置かれてしまったファイルは、後からgitignoreに記載しても監視下から除外されません。

下記コマンドを実行して、secrets.ymlをgitの監視下から外す必要があります。

ローカル環境(chat-spaceディレクトリ)

git rm --cached config/secrets.yml

環境変数を設定する

S3にへの接続に必要な認証情報を環境変数として設定します。

この記事では省いていますが、IAMという擬似アカウント?のようなものを事前に作成する必要があり、認証情報をCSV ファイルでダウンロードできます。下記コマンドに乗っているものはそのCSVファイルに書いてある値を示しています。

ローカル環境(chat-spaceディレクトリ)

 vim ~/.bash_profile

# iを押してインサートモードに移行し、下記を追記する。既存の記述は消去しない。
# 編集が終わったらescapeキーを押してから:wqと入力して保存して終了
export AWS_SECRET_ACCESS_KEY='ここにCSVファイルに乗っている値をコピー'
export AWS_ACCESS_KEY_ID='ここにCSVファイルに乗っている値をコピー'

# 編集した.bash_profileを読み込み直して、追加した環境変数を使えるようにする
 source ~/.bash_profile

本番環境(chat-spaceディレクトリ)

 vim ~/.bash_profile

# iを押してインサートモードに移行し、下記を追記する。既存の記述は消去しない。
# 編集が終わったらescapeキーを押してから:wqと入力して保存して終了
export AWS_SECRET_ACCESS_KEY='ここにCSVファイルに乗っている値をコピー'
export AWS_ACCESS_KEY_ID='ここにCSVファイルに乗っている値をコピー'

# 編集した.bash_profileを読み込み直して、追加した環境変数を使えるようにする
 source ~/.bash_profile

secrets.ymlへの登録

config/secrets.ymlでは、外部に公開したくないIDやパスワードなどを記述して管理します。

先ほど設定した環境変数をここで読み込むように記述します。

config/secrets.yml

development:
  secret_key_base: ~~~~~~~~
  aws_access_key_id: <%= ENV["AWS_ACCESS_KEY_ID"] %>
  aws_secret_access_key: <%= ENV["AWS_SECRET_ACCESS_KEY"] %>

test:
  secret_key_base: ~~~~~~~~

production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
  aws_access_key_id: <%= ENV["AWS_ACCESS_KEY_ID"] %>
  aws_secret_access_key: <%= ENV["AWS_SECRET_ACCESS_KEY"] %>

capistranoの記述を変更する

自動デプロイで一度設定したcapistranoですが、環境変数の読み出しのために記述を変更します。

config/deploy.rb

set :default_env, {
  rbenv_root: "/usr/local/rbenv",
  path: "/usr/local/rbenv/shims:/usr/local/rbenv/bin:$PATH",
  AWS_ACCESS_KEY_ID: ENV["AWS_ACCESS_KEY_ID"],
  AWS_SECRET_ACCESS_KEY: ENV["AWS_SECRET_ACCESS_KEY"]
}

secrets.ymlは.gitignoreに追加されているため、本番環境のリポジトリには追加されません。 環境変数を本番環境にアップロードするよう、記述を追加する必要があります。 本番環境でsecrets.ymlを参照する必要がある場合、

config/deploy.rb

# secrets.yml用のシンボリックリンクを追加
set :linked_files, %w{ config/secrets.yml }

# 元々記述されていた after 「'deploy:publishing', 'deploy:restart'」以下を削除して、次のように書き換え

after 'deploy:publishing', 'deploy:restart'
namespace :deploy do
  task :restart do
    invoke 'unicorn:restart'
  end

  desc 'upload secrets.yml'
  task :upload do
    on roles(:app) do |host|
      if test "[ ! -d #{shared_path}/config ]"
        execute "mkdir -p #{shared_path}/config"
      end
      upload!('config/secrets.yml', "#{shared_path}/config/secrets.yml")
    end
  end
  before :starting, 'deploy:upload'
  after :finishing, 'deploy:cleanup'
end

set :linked_filesで指定されたファイルは、config/secrets.ymlを参照する代わりに、shared/config/secrets.ymlを参照するようになります。

desc upload secrets.yml以下の記述は、ローカル環境にあるconfig/secrets.ymlを本番環境のshared/config/secrets.ymlに反映するための設定を行なっています。

これで、.gitignoreに記載されているsecrets.ymlを、Githubを経由せずにデプロイすることができます。

変更をデプロイする

ここまで設定ができたら、変更をコミットしてGithubにプッシュ、本番環境にgit pullした後、ローカル環境のchat-spaceディレクトリでbundle exec cap production deployを実行します。

設定がうまく言っていれば、S3のバケットに「uploads」というディレクトリが出来上がり、ChatSpaceで投稿された画像ファイルが格納されています。

あ〜〜〜書けたんじゃ〜〜〜〜。

なっがいね!そしてIAMとS3の設定を省いたから途中途中でわけわっかんないね!

明日はIAMとS3についてなんとか書きたい・・・。

おやすみなさーい。

 

viとvimについて

f:id:AK474747:20190807013421p:plain

 

viとvimってなんぞっていうそういう話。

平日だけど、今日も頑張ってまとめていくよ。

viとは

サーバー上で設定ファイルなどを編集したい場合、エディタが必要になります。 CentOSAmazon Linuxでは標準でこのviというソフトウェアがインストールされており、これを利用することが多いそうです。

あとは編集モードの切り替えなどが特徴的かなと思います。

他にもviを発展させてvimというツールもあります。

vimとは

viの拡張版という位置付けとなります。このvimが広くつかwれている理由として、下記のようなメリットがあるからです。

1.コマンドで操作が可能

テキストエディタ上で移動・編集・ファイル操作などを行うことができます。

2.モードを切り替えて操作する

コマンドモードにより操作を切り替えることができます。 例えばインサートモードで文字の編集、移動や編集の時はコマンドモードに変更するなど。 キーボードから手を離すことなくほぼ全ての操作が可能になっている点もメリットとなっています。

3.動作が軽量

とにかくものすごく軽快に操作することが可能です。 サクッと立ち上がってサクッと処理ができるという操作感になれてしまうと、IDEでは遅く感じてしまうそうです。    

4.CUIでそのまま利用が可能

要はターミナルなどの黒い画面上で、文字だけで全ての操作を行う環境を持っています。

他にもたくさんメリットがあり、rubypythonといった様々な言語のシンタックスハイライトに対応していたり、独自のカスタマイズ性を持っていたり・・・。 シンプルかつ高性能なエディターということで根強いファンがたくさんいる・・・らしい!

僕のような初心者からすると、まずコマンド覚えられねえ!とか、サーバー上の階層構造がイメージできねえ!とか色々あります・・・・。

調べている感じだと、玄人向けという立ち位置ではあるらしいですね。

viを開く

vi [ファイル名]でviを開くことができます。

実行するとsample[New file]と画面下に表示されます。これはviを開くと同時に、sampleという新しいファイルを作成したという意味です。

# sampleファイルをviで開く
$ vi sample

通常モード

viエディタでは「通常モード」と「インサートモード」があります。

通常モードのコマンドは以下のようなものがあります。

コマンド 説明
:w 作成・編集したファイルを保存します
:q viコマンドを終了します。
:q! 編集した内容を保存しないでviコマンドを強制終了します。
:wq 編集した内容を保存してviコマンドを強制終了します。

インサートモード

「通常モード」では文字を入力することができません。文字を入力したい場合は「i」キーを押すことで「インサートモード」に切り替えて必要があります。

「インサートモード」から「通常モード」に戻るにはESCキーを押します。

とりあえず基本の基本はこんなところかな。

今サーバーサイドの勉強をしていますが、正直全体の構造が全然見えてこない・・・。

railsアプリケーションではIDEに取り込むことでディレクトリ一覧が出てきていたのでなんとなく構造がイメージできたんですが、主にviを使って行っているのでコマンドと文字だけではなかなかイメージが・・・

サーバー構成の知識もそうですが、このLinuxコマンドと合わせてちゃんと理解していきたいです。

Linuxについて

 

f:id:AK474747:20190805211340p:plain

今日はLinuxについて書いていこうと思うよ。

もともとターミナルによく打ち込んではいたんだけど、正直ちゃんと理解していたわけではない・・。 カリキュラムに頼りっぱなしなところがある。

てことでせめてLinuxの概念とか、簡単なLinuxコマンドくらいは簡単にまとめようかなと思います。

Linuxとは

Linuxとは、UnixオペレーティングシステムカーネルであるLinuxカーネル、およびそれをカーネルとして周辺を整備したシステムである。 (引用:https://ja.wikipedia.org/wiki/Linux)

わかりにくいっすね・・・。

Linuxとはコンピュータ用のOS(オペレーティングシステム)の一種のことで、コンピュータ全体を管理している基本的なプログラムのことを言うみたいです。

例えば、

  • キーボードから文字を入力すると、入力した文字が画面に表示される。
  • マウス・タッチパネルなどで画面を操作できる。
  • イヤフォンをさすと、イヤフォンから音が聞こえる。

普段僕たちがパソコンを使用する上で、特段ストレスなく操作することを可能にしてくれているということのようです。

Linuxの特徴

主にサーバー向けによく利用されています。理由としては、

  • オープンソースであり、誰でも自由に無償で利用できる
  • 品質の高い多くのソフトウェアが利用できる
  • 世界中でサーバ用途として広く利用されているため信頼性が高い
  • 操作を自動化するための仕組みが用意されており、サーバの運用が行いやすい

無償かつ拡散性があり、スペックが高く、世界中で共通認識として使えるってところで色々便利みたいですね。

仕事するときにMicrosoftのOfiiceをよく使うと思うんですが、ああいう感じなんでしょうね。

基本的なコマンド

コマンドを実際に試していくわけですが、普段ターミナルに打ち込むかと思います。

そのときにユーザーの入力したコマンドを解釈するソフトウェアのことをシェルと呼びます。

普段何気なくターミナルに打ち込んでいましたが、あとはシェルが読み込んでくれているみたいですね。

ls

lsはlistの略です。 lsコマンドでは、カレントディレクトリにあるファイルやフォルダの名前一覧を出してくれます。

userMacBookPro:~ username$ ls

Applications        Google ドライブ     Public          pitchshiftLogFile.txt   workspace
BackgroundMusic     Library         SHOW            projects
Desktop         Movies          bash_profile        rbenv
Documents       Music           dump.sql        reset.sql
Downloads       Pictures        evalonly.txt        ruby

cp

cpコマンドはcopyの略で、そのままファイルをコピーするコマンドです。

下記コマンドで同じディレクトリにコピーファイルを作成します。

userMacBookPro:blog username$ cp scope.rb scope1.rb
                          <!--cp [コピー元] [コピー先] で表します-->

mv

ファイルの移動、およびリネームを行います。

userMacBookPro:blog username$ mv scope.rb /Users/kouyamaakira/projects/ruby
                          <!--mv [移動させたいもの] [移動先]-->
                       
userMacBookPro:blog username$ mv scope.rb rename.rb
                          <!--mv [元のファイル] [新しいファイル名]-->

rm

ファイルを削除するコマンドです

userMacBookPro:blog username$ rm kesu.rb
                          <!--rm [消したいファイル名]-->

mkdir

Linuxで新規にディレクトリ(フォルダ)を作成するコマンドです。

userMacBookPro:blog username$ mk make
                          <!--mk [作りたいファイル名]-->

cat

ファイルの内容を表示できるコマンドです。 サーバーをつなげたときに、エラーを見たいときなんかによく使います。


userMacBookPro:blog username$ cat cat1.rb
なかがみえるよ

こんなかんじ

2つのファイルを結合して中身を見ることもできます。

userMacBookPro:blog username$ cat cat1.rb cat2.rb
なかがみえるよ

こんなかんじ

なかみを

くっつけれるよ

tail

ファイルの終わり部分を表示するコマンドです。

オプションをつけずに実行すると終わりから10行表示されます。

これもエラーデバッグなんかで使われます。

userMacBookPro:blog username$ tail scope.rb


(5)
1 数値型のid , 文字列型のname , 数値型のpriceをカラムに持つfoodsテーブルを作成しろ
2 foodsテーブルのid,name,priceそれぞれの値として、いれろ
3 foodsテーブルのid2のデータのpriceを600に更新
4 foodsテーブルのid2のデータを削除
5 nameがカレーライスであり、かつpriceが1000より小さいデータを検索
6 nemeがカレーライスまたはラーメンのデータを検索
7 nameがカレーライスのデータを探し、priceごとにまとめろ

この辺はアプリケーションをサーバーに繋いで動かすときによく使う気がします。 次回はvimとかについて書きたいな。

RubyOnRails/自動更新機能の実装

f:id:AK474747:20190805002512p:plain

 

昨日宣言した通り、自動更新機能について書くよ。 あんまり凝りすぎるとなかなか更新頻度が上がらないので、極力手早く、かつ後から見たときに理解できるようにを目指します。

 

自動更新機能とは

メッセージの自動更新はあるブラウザで投稿された最新の投稿に対し、それよりも新しいメッセージがDBにあればその分を取ってきて自動で更新するといった仕組みである。 (引用:https://qiita.com/yoshimasahosoi1125/items/b73763cbb169b48688f4)

Qiitaから引用しちゃいました。

普遍的な回答ではないですが、イメージ優先で。

メッセージアプリの場合は、例えばAさんがメッセージを送ったら、Bさんの画面にAさんのメッセージが自動で反映されるよ、って感じですね。

 

自動更新機能の実装

1.表示されているメッセージのHTMLにid情報を追加

カスタムデータ属性というものをHTML上で設定することで、JavaScriptから簡単に値を読み出すことができます。 HTMLであればHTMLのタグ内にdata-任意の名前=任意の値と記載すればいいのですが、 僕の場合はHamlを使っていたので下記のようになりました。

今回は省きますが、他にもIDが必要な箇所は都度カスタムデータIDを付与させてください。

/app/views/messages/_message.html.haml

.message{"data-message-id": "#{message.id}"}
  .upper-info
 (省略)

 

2.メッセージを更新するためのアクションを実装

今回は新たにapp/controllers/api/messages_controller.rbというものを作成します。 json形式で情報を受け取り、レスポンスするアクションはwebAPIという概念に相当するためです。 webAPIにあたるアクションを書くコントローラのファイルは全て「api」というディレクトリに置くのが作法・・・らしい。

/app/controllers/api/messages_controller.rb

class Api::MessagesController < ApplicationController
    def index
    
     # そのグループの情報をインスタンス変数@groupに保存
        @group = Group.find(params[:group_id])
        # idがパラメータで取得するidよりも大きいものを、@groupと関連するメッセージの中から検索する。
        # includesはN+1問題対策
        @messages = @group.messages.includes(:user).where('id > ?', params[:id])
        
        # html形式とjson形式のリクエストでそれぞれ振り分ける
        respond_to do |format|
            format.html
            format.json
        end
    end
end

1行目のクラス名定義で::というものを使っています。 これを名前空間といい、controllers/messages_controller.rbcontrollers/api/messages_controller.rbを区別するために使っています。

ちなみに区別するのはプログラム君です。

api/messages_controller動けよというリクエストがあったら、プログラムが区別して処理します。

そのリクエストのルーティングは次の項目で設定します。

 

3.追加したアクションを動かすためのルーティングを実装

/config/routes.rb

Rails.application.routes.draw do
  devise_for :users
  root to:  "groups#index"
  resources :users  , only:[:index,:edit,:update,:show]
  resources :groups , only:[:edit,:new,:create,:update] do
    resources :messages , only: [:index , :create]

    namespace :api do
      resources :messages, only: :index, defaults: { format: 'json' }
    end
  end
end

このようにnamespace :ディレクトリ名 do ~ endと囲む形でルーティングを記述すると、そのディレクトリ内のコントローラのアクションを指定できます。

/groups/:id/api/messagesというパスでリクエストを受け付け、api/messages_controller.rbのindexアクションが動くようになります。(rake routes参照)

また、defaultオプションではjson形式でレスポンスするよう指定しています。

json形式で値をレスポンスするためのファイルも用意します。

json.array! @messages do |message|
    json.content message.content
    json.image message.image.url
    json.date message.created_at.strftime("%Y/%m/%d %H:%M")
    json.user_name message.user.name
    json.id message.id
    end

array!を使って、@messagesに入っている情報をひとつずつmessageに取り出し、それぞれのカラムとjson形式と結びつけています。

 

4.追加したアクションをリクエストするよう実装

/app/assets/javascripts/message.js

$(document).on('turbolinks:load', function(){
(省略)

      // ここから自動更新機能
      var reloadMessages = function(){
      if (window.location.href.match(/\/groups\/\d+\/messages/)){    // group/:group_id/messagesというURLの時だけ、以降の記述が実行されます。
      var href = 'api/messages#index {:format=>"json"}'              // リクエスト先と形式を指定しています。
      var last_message_id = $('.message:last').data('message-id');   // カスタムデータ属性を利用して、最新のメッセージIDを取得しています。
      
      // ajaxの形式をそれぞれ指定しています。
      $.ajax({
        url:  href,
        type: 'GET',
        data: {id: last_message_id},
        dataType: 'json'
      })
      

      .done(function(messages){        // フォームに入力されたデータを引数として取得しています。
        var insertHTML='';
          messages.forEach(function(message){
            insertHTML = buildHTML(message);
            $('.messages').append(insertHTML);
            $('.messages').animate({scrollTop: $('.messages')[0].scrollHeight}, 'fast');
          });
      })
      .fail(function(){
        alert("自動更新に失敗しました")
      });
    };
  };
  

コード自体は長いですが、1文でやっていることは特に難しくはないでしょう。 詳しくはコメントアウトに譲ります。

私的には、ajaxの形式指定や、hrefの値などで苦戦しました・・・。

 

5.取得した最新のメッセージをブラウザのメッセージ一覧に追加

/app/assets/javascripts/message.js

$(document).on('turbolinks:load', function(){
    function buildHTML(message) {
      var content = message.content ? `${ message.content }` : "";
      var img  = message.image ? `<img class="lower-info__image" src="${ message.image }">` : "";
      var html = `<div class="message" data-message-id="${message.id}">
                    <div class="upper-info">
                      <p class="upper-info__user">
                        ${message.user_name}
                      </p>
                      <p class="upper-info__date">
                        ${message.date}
                      </p>
                    </div>
                      <div class="lower-info">
                        <p class="lower-info__content">
                            ${content}
                        </p>
                            ${img}
                      </div>
                  </div>`
    return html;
    }
(省略)

これも難しくはないでしょう。 参考演算子の中でクラスまで指定する、それに合わせて変数htmlの中のクラス、タグ構成を調節する

というところが引っかかりポイントでした。

 

6.数秒ごとにリクエストするよう実装

/app/assets/javascripts/message.js

(省略)
      .fail(function(){
        alert("自動更新に失敗しました")
      });
    };
  };
  setInterval(reloadMessages, 5000);
});

reloadMessagesのfunction直後にsetIntervalを差し込みます。

5000ms毎に処理を繰り返します。

 

これで自動更新の実装は終わりです。

2,3日くらい試行錯誤しましたが、終わってみるとそんなに難しくはないかな?

Ajaxが起動⇨api::messages_controllerのindex処理開始⇨respond format:jsonjbuilderに変換⇨doneメソッド⇨HTML再構築

非同期通信系はこの流れになるようなので、これを意識するのは必須かなと。

エラーが出た時なんかもこの辺の繋がりが分かっていれば特定しやすいという気がします。

RubyOnRails/インクリメンタルサーチの実装

 

 

f:id:AK474747:20190803184614p:plain

インクリメンタルサーチというやつを実装したよ。

ChatSpaceでは、作ったグループ内に集まったメンバー間でのメッセージのやり取りをするという仕様なわけだけれども。

今回追加したインクリメンタルサーチは、グループ新規作成時とグループ情報編集時に、ユーザーネームを検索するという機能のために実装したよ。

コードの解説に行く前に。

インクリメンタルサーチとは

検索したい単語をすべて入力した上で検索するのではなく、入力のたびごとに即座に候補を表示させる。 逐語検索、逐次検索とも。

(引用:https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%AF%E3%83%AA%E3%83%A1%E3%83%B3%E3%82%BF%E3%83%AB%E3%82%B5%E3%83%BC%E3%83%81)

グーグル検索で「あ」とか打ち込むと、検索候補に「アマゾン」ってできてきたりするじゃないですか。 あれのことです。 今回はイメージしやすくていいですね。 では解説に行きます。

インクリメンタルサーチの実装

1.ルーティングなどのAPIの準備をする 今回はusers#indexを使うためのルーティングを用意します。

/config/routes.rb

Rails.application.routes.draw do
  (省略)
  resources :users  , only:[:index,:edit,:update,:show]
  (省略)
  # ただindexアクションを追加しただけです。  

 

2.テキストフィールドを作成する

これはファイルによりけりなので、ソースコードを省略します。  ちなみに僕の場合の作業ファイルは下記でした。

app/views/groups/_form.html.haml

(同階層のindex.html.hamlからrenderでformを読みだしてます。)

 

3.テキストフィールドに入力されるたびにイベントが発火するようにする

app/assets/javascripts/user.js

    $('#user-search-field').on('keyup', function(e){
        var input = $("#user-search-field").val();

'#user-search-field'がユーザー検索フォームです。 ここに文字を打ち込むたびに処理が発火します。 2行目は、変数input対して、フォームに入力された値を代入しています。

 

4.イベント時に非同期通信できるようにする


    $('#user-search-field').on('keyup', function(e){
        var input = $("#user-search-field").val();

        $.ajax({
            type: 'GET',                // HTTPメソッドはGETで
            url:  '/users',             // /usersのURLに (これによりusersコントローラのindexアクションが起動)
            data: { keyword: input},    // keyword: inputを送信する
            dataType: 'json'            // サーバから値を返す際はjsonである
        })

        .done(function(users){                // usersにjson形式のuser変数が代入される。複数形なので配列型で入ってくる
            
            if (input.length === 0) {         // フォームの文字列長さが0であれば、インクリメンタルサーチ結果を表示しないようにする
                $('#user-search-result').empty();
              }

            else if (input.length !== 0){     // 値が等しくないもしくは型が等しくなければtrueを返す。
                $('#user-search-result').empty();
                users.forEach(function(user){ // users情報をひとつずつとりだしてuserに代入
                    appendUser(user)
                });
            }

            else {
                $('#user-search-result').empty(); // ユーザーが見つからなければ「見つからない」を返す。
                appendErrMsgToHTML("一致するユーザーが見つかりません");
            }
        })

        .fail(function() {
            alert('ユーザー検索に失敗しました');
        });
    });

ちょっと長いので詳細はコメントアウトの記述に譲りますが、流れを解説します。(※appendUser(user)とappendErrMsgToHTML("一致するユーザーが見つかりません")はメソッドです。後述します。)

 

keyupにより発火。

ajaxで通信をする。その際のメソッド、リクエストの指定、データの指定、データタイプの指定をしています。

これをコントローラが受け取って、jbuilderの記述に従ってjson形式でJavaScriptに戻ってきます。 (コントローラとjbuilderの記述は冗長になるので省きます。)

done⇨通信が成功した場合は文字列の長さと型(integerかstringか)で判断します。

fail ⇨アラートを出します。

 

5.非同期通信の値を元に、HTMLに反映します。

$(document).on('turbolinks:load', function(){

    var search_list = $("#user-search-result");
    var member_list = $("#member-append");

    function appendUser(user){
        var html = 
                    `<div class="chat-group-user clearfix">
                        <p class="chat-group-user__name">${user.name}</p>
                        <div class="user-search-add chat-group-user__btn chat-group-user__btn--add" data-user-id=${user.id} data-user-name=${user.name}>追加</div>
                    </div>`;
                    search_list.append(html);
    }

    function appendErrMsgToHTML(msg){
        var html = 
                    `<div class="chat-group-user clearfix">
                        <p class="chat-group-user__name">${msg}</p>
                    </div>`;
                    search_list.append(html);
    }

4の際に記述されていた、appendUser(user)とappendErrMsgToHTML("一致するユーザーが見つかりません")のメソッドを定義しています。 ()内に記載しているのは引数です。

 

これでインクリメンタルサーチの大まかな流れは終わりです。 とっかかりは凄くハードルが高く感じますが、一個一個解説していくとそんなに難しくないかなぁと思います。

ポイントはやっぱり$.ajax内部で指定している部分かなと思います。 自動更新機能の実装なんかもしたのですが、ここの設定によるミスで頻繁にエラーが発生しました。。。

明日は自動更新機能も書きたいと思っています。

APIについて

たまにはプログラミング備忘録をば。

手早く書きます。

 

APIとは

Railsでアプリケーションを開発していると、Ajaxを使って操作性の高いページを作ることが多い。通常、Railsは必要なHTMLを組み立てて返すのが仕事になるが、JavaScriptから便利に扱えるようにJSON形式でデータを返すようにすることもできる。

HTMLだけでなく、必要なデータをJSONなどの別形式で返すサーバの仕組みのことをAPI、と呼ぶ。

 

APIの作り方

APIの機能をRailsに追加する時には大きく分けて下記3通りの方式がある。

1.HTMLを返すためのコントローラを共用して、APIとして使えるようにする。

⇨手軽にAPIを追加できる

2.API専用のコントローラを作成する

APIの機能が大きくなるとコントローラを分けた方が可読性が良くなる

3.APIを作成するためのgemを利用する

⇨HTMLとAPIのロジックが大きく異なる場合や、APIの分量が大きい場合は専用のgemを使うと便利

 

コントローラでの振り分け

HTMLを返す時にはERBやHAMLを使ったビューを利用して必要なページを生成するが、APIとしてJSONを返す場合はHTMLほどのロジックが必要ないため、ビューを使わずにデータを生成することができる。

前項の1でも触れたが、Railsには1つのコントーラーのアクションの中でHTMLとJSONというフォーマットごとに条件分岐できる仕組みがあるので簡単に触れる。

 

respond_toメソッド

Railsのコントローラで利用できるrespond_toメソッドを使用すると、リクエストがHTMLを求めているかもしくはJSONを求めているかを拡張子やヘッダー情報を利用して自動的に条件分岐してくれる。

 

ファイル:XXXX_controller.rb

respond_to do |format|
 format.html { render ... } # この中はHTMLリクエストの場合に呼ばれる
 format.json { render ... } # この中はJSONリクエストの場合に呼ばれる
end

 

こんな感じか。

主に非同期通信なんかで使われるよ。

忘れたら見返せよ、未来の自分。