20210227のRubyに関する記事は30件です。

【Ruby】オブジェクト指向について基礎的なあれこれ

はじめに

今後個人開発でもっと複雑なものや共同開発をしていく上でオブジェクト指向の理解が重要だと考え、勉強のアウトプットとして記事にしました。

この記事でわかること
 ・オブジェクト指向の概要
 ・三大要素
 ・オブジェクト指向での設計のポイント

参考にしました→オブジェクト指向でなぜ作るのか

オブジェクト指向の概要

オブジェクト指向という言葉自体はゼロックス社パロアルト研究所の計算機科学者アラン・ケイが70年代生み出した言葉です。その後、自由で曖昧な定義のまま発展を続けたので、どんなものかを説明するのが難しいです。

しかし、ざっくり説明するとソフトウエアの保守や再利用をしやすくすることを目的とした技術です。個々の部品により強く着目し、部品の独立性を高め、それらを組み上げてシステム全体の機能を実現するという考え方にもとづきます。
オブジェクト指向プログラミング言語はとOOP(ObjectOrientedProgramminglanguage)と呼ばれます。

【代表的なOOP】
Simula67、Smalltalk、C++、Python、Java、Ruby、JavaScript..などなど

また、オブジェクト指向には三大要素があります。

  • クラス(カプセル化)
  • ポリモーフィズム
  • 継承

また、オブジェクト指向には、抽象的な「汎用の整理術」と、具体的な「プログラミング技術」という2つの側面があります。

三大要素

クラス(カプセル化)

「分類」「種類」といった「同種のものの集まり」と言った意味。関連性の強いメソッドインスタンス変数を1つにまとめて粒度の大きいソフトウエア部品を作る仕組みです。
この仕組みの特徴は「まとめて、隠して、たくさん作る」ことです。

メソッドとは?

プログラムの一連の処理をまとめたパッケージのようなものです。言語によっては関数と呼ばれます。

インスタンスとは?

クラスをもとに生成されたオブジェクトの実体です。

1.まとめる

結びつきの強い(複数の)メソッドと(複数の)インスタンス変数を1つのクラスに「まとめる」ことができます。

名称未設定ファイル.png

これにより次のメリットが得られます。

【利点】

  • 部品の数が減る。
  • メソッドの名前づけが楽になる。
  • メソッドが探しやすくなる。

2.隠す

クラスに定義した変数とメソッドを、他のクラスから「隠す」ことができます。プログラムの保守性悪化の元凶となるグローバル変数を使わずにプログラムを書くことが可能です。
Rubyではprivateを使用します。

3.たくさん作る

いったんクラスとして定義すると、実行時にそこからいくつでもインスタンスを作ることができます。これにより、ファイル、文字列、顧客情報など、同種の情報を複数同時に扱う処理であっても、そのクラス内部のロジックをシンプルにできます。
Rubyではインスタンスメソッドを実行する際、下記の様にインスタンスを格納する変数名にピリオドをつけて、その後にメソッド名を書きます。

インスタンスを格納する変数名.メソッド名(引数)

【まとめ】

  • サブルーチンと変数を「まとめる」
  • クラスの内部だけで使う変数やサブルーチンを「隠す」
  • 1つのクラスからインスタンスを「たくさん作る」

ポリモーフィズム

類似したクラスに対するメッセージの送り方を共通にする仕組み。また、相手が具体的にどのクラスのインスタンスであるかを意識せずにメッセージを送れる仕組みです。

継承

継承は、クラス定義の共通部分を別クラスにまとめて、コードの重複を排除する仕組みです。
共通部分のクラスのことをスーパークラスと呼び、それを利用するクラスをサブクラス呼びます。

スーパークラス.png

三大要素まとめ

クラス ポリモーフィズム 継承
説明 サブルーチンと変数をまとめてソフトウェア部品を作る メソッドを呼び出す側を共通化する 重複するクラス定義を共通化する
目的 整理整頓 無駄を省く 無駄を省く

三大要素を使ったコーディング

Rubyを使用してオブジェクト指向を意識したコーディングをしていきます。

クラス(カプセル化)

sample.rb
class Apple
 def initialize(quantity, price)
  @quantity = quantity
  @price = price
 end

 def buy
  puts "りんごを#{@quantity}個買い、#{calculation(@quantity, @price)}円でした。"
 end

 private

 def calculation(quantity, price)
  quantity * price
 end
end

Appleクラスを作成しました。
内容は下記図の通りとなります。

名.png

irbを走らせ、インスタンスを生成しました。

irb(main):002:0> apple1 = Apple.new(2, 300)
=> #<Apple:0x00007fc92c82be58 @quantity=2, @price=300>
irb(main):003:0> apple1.buy
りんごを2個買い、600円でした。
irb(main):002:0> apple2 = Apple.new(3, 300)
=> #<Apple:0x00007fc92c82be58 @quantity=2, @price=300>
irb(main):003:0> apple2.buy
りんごを3個買い、900円でした。

ポリモーフィズム、継承

sample.rb
class Shopping
 def initialize(quantity, price)
  @quantity = quantity
  @price = price
 end

 def buy(product='')
  puts "#{product}#{@quantity}個買い、#{calculation(@quantity, @price)}円でした。"
 end

 private

 def calculation(quantity, price)
  quantity * price
 end
end

class Apple < Shopping
  def buy(product='りんご')
    super
  end
end

class Orange < Shopping
  def buy(product='オレンジ')
    super
  end
end

下記図の通り、スーパークラスとしてShoppingクラスを作成しました。Apple、OrangeクラスにはShoppingクラスを継承させました。オーバーライドしているbuyメソッドをsuperでよびだしました(ポリモーフィズム)。

名.png

irbを走らせ、インスタンスを生成しました。

irb(main):002:0> apple = Apple.new(2, 300)
=> #<Apple:0x00007fc92c82be58 @quantity=2, @price=300>
irb(main):003:0> apple.buy
りんごを2個買い、600円でした。
irb(main):002:0> orange = Orange.new(4 ,300)
=> #<Orange:0x00007fa55e96c5c0 @quantity=4, @price=300>
irb(main):003:0> orange.buy
オレンジを4個買い、1200円でした。

オブジェクト指向での設計のポイントについて

重複を排除する

重複部分が多いと規模が大きくなり、コードが複雑になります。また、変更時の修正漏れが発生する場合があります。
ポリモーフィズムや継承の仕組みを利用し、なるべく重複を排除することが推奨されます。

部品の独立性を高める

複雑なシステムを部品ごとに分割することでサブシステムや部品の機能がハッキリするため、変更する場合の修正個所の特定が容易になります。また、独立した部品を別のシステムに再利用することも可能になります。
部品やサブシステムの独立性を高めるための考え方として、「凝集度」「結合度」と呼ばれる2つの尺度があります。

凝集度
個々の部品の機能のまとまり度合いを評価する尺度です。この凝集度が「強い」ほど良い設計といえます。

結合度
部品間の結びつき度合いを評価する尺度です。この結合度は凝集度と反対に、「弱い」ほど良い設計といえます。

また、部品の独立性を高めるために具体的なコツとして下記3点があります。

  • ひと言で表現する名前をつける

  • 隠す仕組みを利用し秘密をたくさん作る

  • 小さく作る

依存関係を循環させない

クラス同士の依存関係を循環させると再利用がしづらくなるため、循環しないような設計が必要になります。

まとめ

オブジェクト指向について学ぶために一冊本を読みましたが、メモリ、要件定義、モデリング、アジャイル開発など幅広いところまで波及しており、とてつもなく奥が深いと感じました。まだまだこれからも勉強していきたいと思います。

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

Railsポートフォリオ #3 herokuにデプロイ

こんにちは:smiley:
今回はherokuへのデプロイを行いました。(前回記事(#3 DB設計))

私は、前職(ホテルの料飲部)における、コミュニケーションの課題を解決するアプリを作っているのですが、今回は、

herokuへのデプロイを行いました

元々はAWSでデプロイするつもりだったのですが(やったことあったので)、難しすぎて、一旦諦め、herokuで手を打つことにしました。。。

感じたこと

さらに

実際にアプリの中身を作っていたら、作っておいたER図が全然的外れだったと言うことにも気づきました。
こちらもやり直さねば。。。

次は、ER図を修正し、基本機能を実装していきます:fist:

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

【Rails】JSが読み込まれない時の対処法

railsアプリにて、JSの書き方は間違っていないはずなんだけど動かない!っていうことがありました。

原因

DOMが読み込まれる前に、JSが実行されて探しているDOMが見つからずにエラーとなっていた。

方法

実行したい処理を間に書いて下さい。
変数にDOMを指定する処理も全てこの中に書くようにして下さい。
そうすると、DOMが全て読み込まれてから実行されます。

document.addEventListener( 'DOMContentLoaded', function(){
    // 実行したい処理
}, false );
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Ruby on Rails] データベースの削除の仕方

色々あってDBを作り直すことになったので、その備忘録を書いていきます。
とても繊細なdatabase.ymlなどを修正する際にも使えるので覚えておこうと思います。
ちなみにシークエルプロと言うデータベースを視覚化できるアプリを使っています。

 

データベースを削除する方法

database.ymlに記載されている、エンコードの設定やデータベース名を誤った状態でデータベースを作成してしまった場合は、一度データベースを削除してから作り直す必要があります。

手順は以下のとおりです。
 
  

データベースを削除する
database.ymlを正しい形に修正する
データベースを再度作成する
 

①データベースを削除する

データベースを削除するためにはrails db:dropのコマンドを実行します。

% cd ~/projects/データベースを作り直したいアプリケーションのディレクトリ

ディレクトリ移動後にrails db:drop
このコマンドを実行することで、該当するアプリケーションのデータベースを削除することができます。
 

②database.ymlなどを修正する

データベースを削除できたら、この段階でdatabase.ymlなどを修正します。

③データベースを再度作成する
そして、rails db:createのコマンドでデータベースを再度作成します。

% cd ~/projects/データベースを作り直したいアプリケーションのディレクトリ

データベースの削除とマイグレーションファイルの適用を一括で行う方法を学ぼう
データベースの設定は間違っていないものの、再度データベースを作り直し、既存のマイグレーションファイルを適用したい場合があります。その場合は、rails db:migrate:resetのコマンドを実行します。
  

% cd ~/projects/データベースを作り直したいアプリケーションのディレクトリ
% rails db:migrate:reset

このコマンドを実行することで、以下の操作を一括で行ってくれます。
 

データベースを削除する
データベースを再度生成する
既存のマイグレーションファイルをすべて適用する
ただし、データベースを一度削除するため、保存されているデータなどはすべて削除されます。
  

極力、データベースを消すほどのミスはしたく無いものです。

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

Ruby/Railsの環境構築

これからRuby/Railsの環境構築を行っていきます。

まずは用語から説明していきます。
ご存知の方は飛ばしてもらって構いません。

シェルとは?

ターミナルで実行されたコマンドを読み取ってくれるOSの窓口役です
ターミナルから入力されたコマンドを読み取って、OSに対して指示を渡し、結果をターミナルに返して表示や実行などの動作をさせます。
このシェルがターミナルとOSの間に挟まって、コマンドによる命令と実行結果の橋渡しをしています。

シェルにも種類があり、プロンプトやコマンド実行後の出力で挙動が若干異なります。

zsh

zshはシェルの1つでターミナルで

% echo $SHELL

とコマンドを実行すると現在使用しているシェルはzshであることがわかります。
.zshrcに設定を記述して、PATHにアプリケーションの場所を示すことでコマンドを使用可能にします。

PATH

PATHとは、「環境変数」と呼ばれるOS用の変数のことです。PATHには、複数の絶対パスの情報が保存されており
コマンドが入力されたときに、シェルはPATHに記述されたパスのディレクトリ内のファイルを検索します。
PATHに絶対パスを保存してアプリケーションの場所を示すことでどこからでもアプリケーションのコマンドを打つことができます。
一般的に「PATHを通す」と表現します。

echoコマンド

「>>」に続けてファイル名を指定することで、ファイルに文字を追加できるLinuxコマンドで、設定を反映するためのコマンドを記述することで、PATHを通すことができます。

コマンドラインツール

コマンドで操作するアプリケーションのまとまりで
コマンドラインツールを導入することで、OSが初めからコマンドで操作できるアプリケーション以外の物もインストールできます。

Command Line Tools

macOS専用のコマンドラインツールのことです。
macOSでは、元々Linuxコマンドで操作できるアプリケーションや機能を標準搭載しています。Linuxコマンド以外で操作するアプリケーションの多くはCommand Line Toolsのインストールによって、まとめてPCに導入できます。

パッケージ管理ツール

パッケージとは、プログラムや処理をひとまとめにしたもののことでライブラリとも言えます。
パッケージ管理とは、パッケージやパッケージが持つライブラリなどの依存関係を考慮して
インストールやバージョンアップを行う管理のことです。
あるパッケージを利用したい場合、そのパッケージと依存関係にあるパッケージも一緒にインストールしてくれます。

Homebrew

macOSのパッケージ管理ツールでmacOS上で動作するアプリケーションの多くがHomebrewからインストールできます。
依存関係のあるパッケージが正しく動作するよう、複数のパッケージのバージョンをコントロールしてインストールできます。

コマンド 説明
brew -v Homebrewのバージョンを表示する
brew install [パッケージ名] パッケージをインストールする
brew uninstall [パッケージ名] パッケージをアンインストールする
brew search インストール可能なパッケージを表示する
brew update インストールしたパッケージを最新へ更新する

Node.js

Node.jsは、本来ブラウザ上で動くJavaScriptをサーバーサイドで動作させる「実行環境」です。
インストールされると、サーバーサイドで利用できるJavaScriptのパッケージを活用できます。
パッケージは依存関係を生むため、YarnなどのNode.jsのパッケージ管理ツールもあります。

Yarn

Node.jsの環境上で動作するパッケージを管理する、JavaScriptのパッケージ管理ツールです。

バージョン管理

変更したバージョンを記録あるいは外部から保存して、過去のバージョンや最新のバージョンに切り替えることなどをバージョン管理と呼びます。

バージョン管理をすることで、パッケージとの依存関係の問題を解消したり、変更して問題が発生したプロジェクトを過去の安定したバージョンに切り替える、などの対応ができます。

rbenv

Rubyのバージョンを切り替えるためのバージョン管理ツールです。
バージョン管理ツールを使わない場合、RubyのバージョンはすべてPC内で共通となってしまい「あるプロジェクトで使用しているRubyのライブラリが使用できなくなってしまう」などの依存関係の問題が生じます。
複数のバージョンのRubyをダウンロードしておいて、使用するRubyのバージョンをディレクトリごとに指定することも可能になります。

コマンド 説明
rbenv -v rbenvのバージョンを表示
rbenv install [バージョン][パッケージ名] Rubyバージョンを指定してインストールする。
rbenv uninstall [バージョン] Rubyバージョンを指定してアンインストールする。
rbenv versions インストールされているRubyバージョンの一覧を表示する。
rbenv global [バージョン] すべてのディレクトリで使用するRubyバージョンを切り替える。
rbenv local [バージョン] カレントディレクトリで使用するRubyバージョンを切り替える。
rbenv rehash RubyやGemに関するコマンドをバージョン変更後も使用できるようにする。

さてこれから環境構築に取り掛かっていきましょう!

シェルをzshに設定

# zshをデフォルトに設定
% chsh -s /bin/zsh

# ログインシェルを表示
% echo $SHELL
# 以下のように表示されれば成功
/bin/zsh

Command Line Toolsを用意

% xcode-select --install

Homebrewをインストール

% cd  # ホームディレクトリに移動
% pwd  # ホームディレクトリにいるかどうか確認
% /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" # コマンドを実行

Homebrewがインストールされているか確認

% brew -v
# 以下のように、Homebrewのバージョン情報が表示されれば無事にインストールされています。
Homebrew 2.5.1 # 数字は異なる場合があります。
% brew update # Homebrewをアップデート
%  sudo chown -R `whoami`:admin /usr/local/bin # Homebrewの権限を変更

rbenv と ruby-buildをインストール

% brew install rbenv ruby-build

rbenvをどこからも使用できるようにパスを通す。

% echo 'eval "$(rbenv init -)"' >> ~/.zshrc

設定ファイルであるzshrcを修正したので変更を反映

% source ~/.zshrc

ターミナルのirb上で日本語入力を可能にするreadlineをinstall

% brew install readline

readlineをどこからも使用できるようにする

% brew link readline --force

rbenvを利用してRubyをインストール

% RUBY_CONFIGURE_OPTS="--with-readline-dir=$(brew --prefix readline)"
% rbenv install 2.6.5

利用するRubyのバージョンを指定

% rbenv global 2.6.5

rbenvを読み込んで変更を反映

% rbenv rehash

Rubyのバージョンを確認

% ruby -v

MySQLのインストール

% brew install mysql@5.6

MySQLの自動起動設定

% mkdir ~/Library/LaunchAgents 
% ln -sfv /usr/local/opt/mysql\@5.6/*.plist ~/Library/LaunchAgents
% launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql\@5.6.plist 

mysqlコマンドをどこからでも実行できるようパスを通す

% echo 'export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"' >> ~/.zshrc  # mysqlのコマンドを実行できるようにする設定
% source ~/.zshrc #  設定を読み込むコマンド
% which mysql # mysqlのコマンドが打てるか確認する

# 以下のように表示されれば成功
/usr/local/opt/mysql@5.6/bin/mysql

MySQLの起動を確認

% mysql.server status # MySQLの状態を確認するコマンド

# 以下のように表示されれば成功
 SUCCESS! MySQL running

Rubyの拡張機能(gem)を管理するためのbundler(バンドラー)をインストール

% gem install bundler --version='2.1.4'

Railsをインストール

% gem install rails --version='6.0.0'

rbenvを再読み込み

% rbenv rehash

Node.jsを用意

% brew install node@14

Node.jsへのパスを通す

% echo 'export PATH="/usr/local/opt/node@14/bin:$PATH"' >> ~/.zshrc
% source ~/.zshrc

Node.jsが導入できたか確認。バージョンが表示されれば、問題なくインストールが完了

% node -v
v14.15.3 # 数値は異なる場合があります

yarnをインストール

% brew install yarn

yarnが導入できたか確認

% yarn -v

以上で環境構築は完了です。お疲れ様でした。

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

銀行振込を忘れないためのwebアプリ

はじめに

今はもう使われていない振込券をベースに作りました。
localhost_3000_.png

なぜ作ったか

  • 実家の会社で振込忘れや伝達ミスなどが多発していた
  • 督促状が届き面倒な手続きに時間を取られることが多かった
  • そんな状態を改善・解決できるのではないかと思った

開発環境

  • 言語
    • ruby
  • フレームワーク
    • rails
  • データベース
    • mysql
  • その他

基本機能

  • 振込チケットの管理
    未払いの振込チケットを振込期日順に閲覧することができます。
    また、見た目で分かりやすいように、いわゆるメガバンクはロゴの色に合わせてチケットの色を変えています。
    localhost_3000_.png

  • 振込チケットの作成
    チケットの作成の際には、"https://bank.teraren.com/"
    上記の銀行名、支店名を取得できるAPIを利用させていただき、自動で候補を取得し選択できるようにしました。
    ticket_output

  • 振込チケットの振込完了報告
    振込済みのチケットは別画面で閲覧できます。
    transfer

  • スマホ画面
    transfer_smart2.gif

検討している追加機能・改善点

  • 期日が近づいた際のwebプッシュ通知
  • 管理者権限の有無で使える機能の制限
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsで大量のデータを更新する際はin_batchesを使おう

Ruby on Railsのプロジェクトで、大量のデータを更新しようとしたらメモリ不足でエラーとなったため、解決方法をメモ。

こんな感じで、データを更新しようとしていました。

Hoge.all.map do |hoge|
  hoge.update(fuga: hoge.foo)
end

しかしデータが数十万件あり、メモリ不足で余裕で落ちました。

そんなときは in_batchesを使いましょう。
デフォルトでは1000件ずつ読み込んで処理してくれます。

Hoge.in_batches do |hoges|
  hoges.map do |hoge|
    hoge.update(fuga: hoge.foo)
  end
end

無事解決!
どれくらいの量のデータが扱われるかは、常に意識しないといけないですね。

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

いまさらSlackとLambdaとDynamoを連携させてみた

いまさらですが、LambdaとDynamoを触ってみたのでまとめておきたいと思います。
APIGateway/Lambda/Dynamoなど触ったことのない人の助けになれば幸いです。

今回やってみたことは以下です
sample.png
1. SlackのAppにメッセージを送信
2. Slackのevent apiでAPI Gatewayのエンドポイントにリクエストを送る
3. Lambdaで、飛んできたリクエスト内容をDynamoDBに保存
4. Lambdaで登録した旨をSlackに返信

作成手順

以下の順序で作成を行います

  1. Lambda関数の作成
  2. API Gatewayでエンドポイントを作成
  3. Slackのevent APIの設定と、メッセージの送信
  4. CloudWatchでログを確認する
  5. Lambdaでのメッセージ処理
  6. LambdaからDynamoにデータを登録

1. Lambda関数の作成

Lambdaとは

AWS Lambda はサーバーレスコンピューティングサービスで、サーバーのプロビジョニングや管理、ワークロード対応のクラスタースケーリングロジックの作成、イベント統合の維持、ランタイムの管理を行わずにコードを実行できます。Lambda を使用すれば、実質どのようなタイプのアプリケーションやバックエンドサービスでも管理を必要とせずに実行できます。

凄く雑な説明をすれば、Lambda上にスクリプトを置いておくと、
こちらが指定したトリガー(HTTPリクエストなど)のタイミングでそのスクリプトを実行してくれます。
スクリプトを置いておくサーバーは意識する必要はなく、課金は実行回数によって発生します。
月100万リクエストまで無料なので、遊びで使う分には(多分)お金は発生しません。

関数の作成

AWSのマネジメントコンソールからLambdaを検索します。
「関数の作成」というボタンがあるはずなので、クリックして作成しましょう。
スクリーンショット 2021-02-27 15.24.20.png
こういう画面になるので、関数名ランタイムの欄を入れて関数の作成をしましょう。
他に詳細設定などがありますが、今回は大枠を理解するだけなので、パスします。

作成後の画面に関数コードという画面があればOKです。
スクリーンショット 2021-02-27 15.32.15.png

関数のテスト

Testというボタンがあるので、イベント名に適当な名前を入れて作成
再度Testボタンを教えてみましょう。
画面に以下のようなテスト結果が出ればOKです!
スクリーンショット 2021-02-27 15.38.08.png

2. API Gatewayでエンドポイントを作成

API Gatewayの設定

非常に簡素ですが、Lambdaの関数は作成できました。
次はLambda関数の実行を外からできるように、API Gatewayを設定します。

Lambdaの関数の画面から、デザイナ>トリガーを追加 をクリック
スクリーンショット 2021-02-27 15.40.23.png

すると、トリガーを追加という画面がでてくるので、以下のように設定してください。
スクリーンショット 2021-02-27 15.43.49.png

:warning: 大枠を理解するため、また後で消すのでオープンにしていますが、そうでない場合はきちんと設定してください。
作成後にAPI Gatewayの項目にslack_sample_APIができていればOKです。

エンドポイントの作成

API Gatewayのリンクをクリックし、設定画面に行きます。
スクリーンショット 2021-02-27 15.49.17.png
アクションからメソッドの作成を行います。
POSTを選択した後、以下のように選択して、保存します
スクリーンショット 2021-02-27 15.53.09.png

作成後、再度アクションボタンからAPIのデプロイを実行します。
デプロイされるステージをdefaultを選択肢、デプロイします。
スクリーンショット_2021-02-27_15_59_01.png

このようなURLが表示されていればエンドポイントの作成は完了です。
とりあえず、テストでcurlを叩いてみましょう!
以下のように返ってきたら成功です!!

curl -X POST https://それぞれのURL/default/slack_sample
=> {"statusCode":200,"body":"\"Hello from Lambda!\""}%

3. Slackのevent APIの設定と、メッセージの送信

Slackのアプリ作成

こちらからAppの作成
名前を適当に入れ、workspaceを設定します。

request urlと、Lambada関数の修正

サイドバーのBasic Informationから
Add features and functionality>Event Subscriptionsを選択してください。
requestを送るURLを求められるので、先程作成したエンドポイントのURLを入れましょう。

すると、以下のように怒られます。
どうやらchallengeというパラメータを返す必要があるようです
スクリーンショット 2021-02-27 16.15.30.png

Lambda関数のメソッドを、以下のように修正した後、Deployボタンを押しましょう。

def lambda_handler(event:, context:)
  # event["challenge"]がどこから来たかは、後述します
  { statusCode: 200, body: JSON.generate('Hello from Lambda!'), challenge: event["challenge"] }
end

デプロイ後にSlack画面のretryボタンを押すと、Request URL Verified と表示されるはずです

Subscribe to bot events

botの権限を設定します。
今回はBotへのメンションをトリガーにrequestを送信。Botぽくそれに返信をしたいので
スクリーンショット 2021-02-27 17.29.13.png

app_mentions:readchat:writeを入れておきます

workspaceへのインストール

再び、Basic Informationに戻り、
今度はInstall your appからworkspaceにアプリをインストールします

Slackでメッセージを送る

Bot的に使いたいので、適当なchannelにbotを入れて、
メンションをつけてメッセージを送ってみましょう

4. CloudWatchでログを確認する

実はLambda関数を作成したタイミングで

のロググループが出来ています。
AWSでCloudWatchを検索し、
サイドバーのログ>ロググループ
から/aws/lambda/slack_sampleを見てみましょう。

ログストリームの項目に、先程メンションを送った時間のものが増えていれば、
SlackからAPI Gatewayを介してLambda関数が実行出来ています!!

5. Lambdaでのメッセージ処理

Botぽく反応させたいのでSlackのスレッドに返信するようにしましょう。
とりあえず、メンション付きで「hoge」と送ったら、
「hogeと受信したよ!」とでも返すようにします。

Slackからのeventを見る

slackからのメッセージをLambda上で処理したいので、requestがどんな形か知りたいです。
lambda関数を以下のように修正して

def lambda_handler(event:, context:)
    puts event
    { statusCode: 200, body: JSON.generate('Hello from Lambda!'), challenge: event["challenge"] }
end

デプロイしたあとに、
再度、slackでbotにメンション付きでメッセージを送りましょう。
CloudWatchのログに以下のようなものが出ていればOKです。

{
  "token"=>"hoge", 
  "team_id"=>"foo", 
  "api_app_id"=>"fuga", 
  "event"=>{
    "client_msg_id"=>"1234", 
    "type"=>"app_mention", 
    "text"=>"<@id> hoge", 
    "ts"=>"1614412451.000800",
  ~~~~~略~~~~
} 

スレッドに返信する

Token

返信するためにSlackのTokenが必要です。
Slackアプリ画面のサイバーの
Features>OAuth&PermissionsからBot User OAuth Tokenをメモしておきます。

コードの修正

Lambda関数を

と、先程のCloudWatchのログを参考に、以下のように書き換えます。

# サンプルなので、ここに書いていますが本当は適切に暗号化する必要があります
SLACK_TOKEN = "token"
def lambda_handler(event:, context:)
  event_data = event["event"]
  # メッセージにメンションがついてしまうため、送信されたテキストからIDを削除
  text = event_data.dig("text").delete("<@id>")

  channel = event_data.dig("channel")
  params = 
    {
      token: SLACK_TOKEN,
      channel: channel,
      as_user: true,
      text: "#{text}と受信したよ!",
      thread_ts: event_data["ts"]
    }

  uri = URI.parse("https://slack.com/api/chat.postMessage")
  Net::HTTP.post_form(uri, params)
  { statusCode: 200, body: "test", challenge: event["challenge"] }
end

これで、Slack API → API Gateway → Lambda →Slackの流れができました!

7. LambdaからDynamoにデータを登録

最後に、なんとなくDynamoにSlackのメッセージを入れてみましょう!

Dynamoとは

KeyValue型のNoSQLです。集計などには向きませんが、1件のデータの登録、抽出に優れています。

テーブルの作成

Dynamoの画面からテーブルの作成をクリック
テーブル名とプライマリーキーを入れます。
他の設定はデフォルトでOKです。

テーブルが作成できたら、項目の作成から増やしてみます。
今回はtextを増やしてみました。
スクリーンショット 2021-02-27 18.56.21.png

Lambdaの設定変更

このままだとLambdaからDynamoにまだアクセスできないのでポリシーを追加します
Lambda関数の画面のアクセス権限のタブから実行ロールのロール名をクリック
IAMの画面からポリシーをアタッチします。
スクリーンショット 2021-02-27 18.30.29.png

:warning: 自分しか使わないアカウントなのでFullAccessですが、本当は良くないです。

Lambda関数の修正

後は、

を参考に、
Lambda関数を以下のように修正します

require 'json'
require "net/http"
require 'aws-sdk-dynamodb'

SLACK_TOKEN = "token"
def lambda_handler(event:, context:)
  event_data = event["event"]
  # メッセージにメンションがついてしまうため、送信されたテキストからIDを削除
  text = event_data.dig("text").delete("<@id>")

  # Dynamoへのinput
  dynamodb = Aws::DynamoDB::Client.new(region: 'ap-northeast-1')
  item = {
    text: text,
    sender_id: event_data["user"],
  }
  params = {
    table_name: 'dynamo_table_name',
    item: item
  }
  dynamodb.put_item(params)

  # slackへの送信
  channel = event_data.dig("channel")
  params = 
    {
      token: SLACK_TOKEN,
      channel: channel,
      as_user: true,
      text: "#{text}と受信したよ!",
      thread_ts: event_data["ts"]
    }

  uri = URI.parse("https://slack.com/api/chat.postMessage")
  Net::HTTP.post_form(uri, params)
  { statusCode: 200, body: "test", challenge: event["challenge"] }
end

ここまでうまく行っていれば、このように
Dynamoにレコードが登録され、
スクリーンショット_2021-02-27_18_39_06.png

Slack上でも返信が来ていると思います。
スクリーンショット_2021-02-27_18_36_58.png

とりあえずしたかったことは出来たので、完了です。

最後に

やっぱり、インフラが分かって無くても、AWSがいい感じにしてくれるのはすごいですね。
工程も簡単で、この記事を書きながらしても3時間ぐらいで終わりました。
多分、作業だけすれば1時間ぐらいで終わると思います。

大枠を理解するために雑な紹介でしたが、
IAMの設定や、API Gatewayのアクセス制限、Lambda関数が失敗したときの処理など
本当はしないといけないことが、実はまだまだあります。
今回の記事で興味を持ってくれた人がいたら、ぜひやってみください。
「LambdaやDynamo全然わからないけど、なんか触ってみたい!」という人に役立てばば幸いです。

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

エラーページを表示させる

HTTPレスポンスのステータスコードとは

クライアントからのリクエストの結果を返す3桁の整数値のことです。
特定のHTTPリクエストが正常に完了したどうかを示します。
通常はリクエストが成功するとステータスコード200を返します。

ステータスコードの種類の例

403
アクセス権限がないことを示す。

404
リクエスト先が見つからない、またはページが存在していないためにアクセスができないことを示す。「指定されたページは存在しません」などのエラーページが表示されるケース。

500
webサーバーで何かしらのエラーが発生したことを示す。

503
リクエスト先が一時的にアクセス集中やメンテナンスなどで使用できないことを示す。

一時的に、ソースを変更したら再起動しなくてもリロードされるように設定します。
config/environments/production.rb

config.cache_classes = false

アクションにraiseメソッドを追記してアクセスをすると、強制的に例外の画面が表示されます。
画像はproductionモードでのデフォルトエラー画面です。

スクリーンショット 2021-02-27 9.10.58.png

ログを出力してみます。

tail -f log/production.log
satouyasuyukinoMacBook-Air:kiroku yasuyuki$ tail -f log/production.log
[008ffbf8-ca54-443f-927b-90ddff363516] puma (4.3.7) lib/puma/server.rb:472:in `process_client'
[008ffbf8-ca54-443f-927b-90ddff363516] puma (4.3.7) lib/puma/server.rb:328:in `block in run'
[008ffbf8-ca54-443f-927b-90ddff363516] puma (4.3.7) lib/puma/thread_pool.rb:134:in `block in spawn_thread'
I, [2021-02-27T09:10:05.937895 #32132]  INFO -- : [32b82d69-ef18-4283-87b3-7655b4b244c7] Started GET "/staff" for 127.0.0.1 at 2021-02-27 09:10:05 +0900
I, [2021-02-27T09:10:05.946757 #32132]  INFO -- : [32b82d69-ef18-4283-87b3-7655b4b244c7] Processing by Staff::TopController#index as HTML
I, [2021-02-27T09:10:05.951715 #32132]  INFO -- : [32b82d69-ef18-4283-87b3-7655b4b244c7] Completed 500 Internal Server Error in 5ms (Allocations: 399)
F, [2021-02-27T09:10:05.952131 #32132] FATAL -- : [32b82d69-ef18-4283-87b3-7655b4b244c7]   
[32b82d69-ef18-4283-87b3-7655b4b244c7] RuntimeError ():
[32b82d69-ef18-4283-87b3-7655b4b244c7]   
[32b82d69-ef18-4283-87b3-7655b4b244c7] app/controllers/staff/top_controller.rb:3:in `index'

このエラー画面を任意の画面にカスタマイズします。

app/controllers/application_controller.rb

 rescue_from StandardError, with: :rescue500


  private def rescue500(e)
    render "errors/internal_server_error", status: 500
  end

StandardErrorエラーが発生したら、rescue500のプライベートメソッドの処理が実行されるようにしています。
rescue500メソッドではrenderメソッドでerrorsフォルダのinternal_server_error.html.erbを返しています。
Rubyでは例外を表現するためのExceptionクラスというものが存在します。
StandardErrorはExceptionクラスを継承しています。

サーバーを再起動して以下の画面になってるとokです。

スクリーンショット 2021-02-27 18.50.43.png

※アクションのraiseメソッドを消すのを忘れずに。

参考:【Rails5】rescue_fromによる例外処理:アプリ固有のエラーハンドリングとエラーページ表示
rails6ガイド

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

Railsの便利メソッド pluckについて調べてみた

はじめに

Railsでアプリケーションを作成時に、モデルから特定のカラムをリストで抜いてきたい場面が時々あり、なにかいいメソッドはないかと探していたところ、このメソッドにたどり着いた。

idsメソッドなどidカラムのリストを取得するメソッドを知っていたが他のカラムは一々、allで全取得して、mapselect等を駆使してループ処理を行い、対象のカラムのリストを取得していたので、このメソッドを使用することでかなり簡略化につながる

pluckメソッドについて

上記でも簡単に説明したように、対象のカラムのみを指定してリストで取得する事ができる

Railsガイドでは以下のように説明されている。

pluckは、1つのモデルで使用されているテーブルからカラム (1つでも複数でも可) を取得するクエリを送信するのに使用できます。引数としてカラム名のリストを与えると、指定したカラムの値の配列を、対応するデータ型で返します。

実際の使い方について

以下のようなモデルがあると仮定して、サンプルのコードを書いていく

Userモデル

カラム名
id integer
name string
age integer
id name age
1 hoge 21
2 fuga 30
2 hogehoge 34

基本的な使い方

User.pluck(:name)
# SELECT name FROM users
# => ['hoge', 'fuga', 'hogehoge']

その他の使い方

引数を複数のカラムを渡すことで、指定したカラムのリストも取得できます。
戻り値が二次元配列になってかえってくるのでそこだけ注意が必要になります。

User.pluck(:name, :age)
# SELECT name, age FROM users
# => [['hoge', 21], ['fuga', 30], ['hogehoge', 34]]

pluckメソッドを使うことのメリット

コードの冗長化を防ぎ、スッキリ書ける

仮にpluckを使わないで上記と同じような結果が得られるコードを書いてみましょう

# pluckを使わない場合
User.select(:name).map{|user|user.name}

User.select(:name, :age).map{|user|[user.name, user.age]}

発行されるSQLが変わる

mapメソッドを駆使して同じような結果は得られますが、実はRailsが発行するSQL文が変わってきます。

# mapを使用したパターン
User.all.map{|user|user.name}
=> SELECT users.* FROM users

# pluckを使用したパターン
User.pluck(:name)
=> SELECT name FROM users

上記のSQLを見比べると前者はSELECT users.* FROM usersusersテーブルのカラムを一旦すべて取得しているのに対して、後者は SELECT name FROM usersusersテーブルのnameカラムのみを取得しています。

カラムすべてを取得するより、対象を指定してあげて取得する方がパフォーマンスや負荷もこ後者の方が基本的にはよくなるはずです。
ただ便利だから使うのではなく、パフォーマンス等も考慮して最適なメソッドを選べると良さそうです。

その他pluckを使う時の注意点について

Railsガイドでは以下のように記載されています

pluckは、selectなどのRelationスコープと異なり、クエリを直接トリガするので、その後ろに他のスコープをチェインすることはできません。ただし、構成済みのスコープをpluckの前に置くことはできます。

つまり、pluckメソッドの後ろには絞り込みやソートの条件を付ける事ができないということです。
以下のような書き方はエラーとなります。

User.pluck(:name).limit(3)
User.pluck(:age).order(id: "DESC")

ただし、pluckを使う前に予めスコープを設定しておくことで取得する事ができます。
例えば年齢カラムのリストを降順で取得したい場合は以下のように記述できます。

User.order(age: "DESC").pluck(:age)
# => [34, 30, 21]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「action_args」のソースから学ぶ「Ruby」② ソース分析1日目

「action_args」ソース分析1日目です。
以前Gem作り方で分かったのは、lib/action_args.rbがこのGemのスタートなのでそこから見てみます。

action_argsソース

以下のファイルが「action_args」の全てのソース

$ tree
.
├── CONTRIBUTING.md
├── Gemfile
├── MIT-LICENSE
├── README.md
├── Rakefile
├── action_args.gemspec
├── gemfiles
│   ├── rails_41.gemfile
│   ├── rails_42.gemfile
│   ├── rails_50.gemfile
│   ├── rails_51.gemfile
│   ├── rails_52.gemfile
│   ├── rails_60.gemfile
│   └── rails_edge.gemfile
├── lib
│   ├── action_args
│   │   ├── abstract_controller.rb
│   │   ├── callbacks.rb
│   │   ├── params_handler.rb
│   │   └── version.rb
│   ├── action_args.rb
│   └── generators
│       └── rails
│           ├── action_args_scaffold_controller_generator.rb
│           └── templates
│               └── controller.rb
└── test
    ├── controllers
    │   ├── action_args_controller_test.rb
    │   ├── hooks_test.rb
    │   ├── kwargs_controller_test.rb
    │   ├── kwargs_keyreq_controller_test.rb
    │   ├── ordinal_controller_test.rb
    │   └── strong_parameters_test.rb
    ├── fake_app.rb
    ├── kwargs_controllers.rb
    ├── kwargs_keyreq_controllers.rb
    ├── mailers
    │   └── action_mailer_test.rb
    ├── params_handler
    │   └── params_handler_test.rb
    └── test_helper.rb

lib/action_args.rbからスタート

以下の三つのファイルを読み込んでいる。

lib/action_args.rb
require 'action_args/params_handler'
require 'action_args/abstract_controller'
require 'action_args/callbacks'

本日はparams_handler.rbのextract_method_arguments_from_paramsメソッド分析

extract_method_arguments_from_params

パラメーターにメソッド名を渡すとそのメソッドのパラメーターが一般パラメーターの場合はパラメーターの値配列、キーワードパラメーターの場合は、キーワード名と値のハッシューを返すメソッド。
詳細な処理内容はテストケースを見るとわかりやすいです。

学習ポイント1

method関数について知らなかった!
method関数:オブジェクトのメソッドnameをオブジェクト化したMethodオブジェクトを返します。

params_handler.rb
method_parameters = method(method_name).parameters 

以下のようにmethodにメソッド名を渡せば、Methodオブジェクトを返すのでそのオブジェクトのparametersを取得できる

sample
irb(main):003:0> def hello(name)
irb(main):004:1>  "hello #{name}"
irb(main):005:1> end
=> :hello
irb(main):008:0> method(:hello).parameters
=> [[:req, :name]]

:req は必須の引数を意味
その他の説明はparametersを参考

parameters_sample
m = Class.new{define_method(:m){|x, y=42, *other, k_x:, k_y: 42, **k_other, &b|}}.instance_method(:m)
m.parameters #=> [[:req, :x], [:opt, :y], [:rest, :other], [:keyreq, :k_x], [:key, :k_y], [:keyrest, :k_other], [:block, :b]]
File.method(:symlink).parameters #=> [[:req], [:req]]

学習ポイント2

method(method_name).parametersは配列の配列を返し、各配列の要素は引数の種類に応じたSymbol が入るのでmapを利用してパラメーター名だけを配列に返している

params_handler.rb
parameter_names = method_parameters.map(&:last)

学習ポイント3

reverse_eachを使って後ろのパラメーターから処理している。
https://github.com/asakusarb/action_args/blob/e5e4f3e10e410b34cc7dfbf0056b6d3d289ef96d/lib/action_args/params_handler.rb#L11

a = [ "a", "b", "c" ]
a.reverse_each {|x| print x, " " }
# => c b a

学習ポイント4

subメソッドについて

trimmed_key = key.to_s.sub(/_params\z/, '').to_sym

ストロングパラメーターの場合、後ろの_params文字列を削除している。

学習ポイント5

params.key?メソッドについて

:reqは必須項目を意味するのでparamsにtrimmed_keyが入ってない場合、missing_required_params配列に入れて次の処理を行なっている。
:keyreq,:keyタイプのキーはkwargsハッシュに保存している

method_parameters.reverse_each do |type, key|
  trimmed_key = key.to_s.sub(/_params\z/, '').to_sym
  case type
  when :req
    missing_required_params << key unless params.key? trimmed_key
    next
  when :keyreq
    if params.key? trimmed_key
      kwargs[key] = params[trimmed_key]
    else
      missing_required_params << key
    end
  when :key
    kwargs[key] = params[trimmed_key] if params.key? trimmed_key
  when :opt
    break if params.key? trimmed_key
  end
  # omitting parameters that are :block, :rest, :opt without a param, and :key without a param
  parameter_names.delete key
end    

extract_method_arguments_from_paramsメソッドのテストケース

テストケースがわかりやすい
https://github.com/asakusarb/action_args/blob/e5e4f3e10e410b34cc7dfbf0056b6d3d289ef96d/test/params_handler/params_handler_test.rb#L7

学習ポイント6

@controller = Class.new(ApplicationController).new.tap {|c| c.params = params }

Object#tapについて
メソッドチェインの途中で直ちに操作結果を表示するためにメソッドチェインに "入り込む" ことが、このメソッドの主目的です。
以下のような使い方がありそう。生成されるSQLを表示

irb(main):010:0> User.where(name:'a').tap{|a| p a.to_sql}.all
"SELECT \"users\".* FROM \"users\" WHERE \"users\".\"name\" = 'a'"
  User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", "a"], ["LIMIT", 11]]

余談

1日目の最後に感じたのは、他人が作ったソースから学ぶのって楽しい!
まだ始まったばかりですが、最後まで頑張ります〜

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

ruby on rails 非同期いいね(編集中)

アプリケーションの立ち上げ

% rails new favorite-app -d mysql
% cd favorite-app
% rails db:create
% rails g scaffold post title:string user_id:integer   #postコントローラ作成
% rails g controller likes                             #likesコントローラ作成
% rails g model Like user_id:integer post_id:integer

Gemfileの下部に以下を追記

gem 'devise'
gem 'font-awesome-sass'

Gemfile編集後に実行

% bundle install
% rails g devise:install
% rails g devise user
% rails db:migrate

「application.css」を「application.scss」に変更

その後以下のコードを追記。

// application.scssの下部に以下を追記(既存のコードは消さない)
```ruby
/*
* This is a manifest file that'll be compiled into application.css
* vendor/assets/stylesheets directory can be referenced here using a relative path.
* 省略
*= require_tree .
*= require_self
*/

------------この部分を追記-----------------
@import "font-awesome-sprockets";
@import "font-awesome";
------------この部分----------------------
```

favorite-app/app/javascript/packs/application.jsを編集

require("@rails/ujs").start()
// require("turbolinks").start()   ⇐この行を削除
require("@rails/activestorage").start()
require("channels")
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ruby on rails 非同期いいね

アプリケーションの立ち上げ

% rails new favorite-app -d mysql
% cd favorite-app
% rails db:create
% rails g scaffold post title:string user_id:integer   #postコントローラ作成
% rails g controller likes                             #likesコントローラ作成
% rails g model Like user_id:integer post_id:integer

Gemfileの下部に以下を追記

gem 'devise'
gem 'font-awesome-sass'

Gemfile編集後に実行

% bundle install
% rails g devise:install
% rails g devise user
% rails db:migrate

「application.css」を「application.scss」に変更

その後以下のコードを追記。

// application.scssの下部に以下を追記(既存のコードは消さない)
```ruby
/*
* This is a manifest file that'll be compiled into application.css
* vendor/assets/stylesheets directory can be referenced here using a relative path.
* 省略
*= require_tree .
*= require_self
*/

------------この部分を追記-----------------
@import "font-awesome-sprockets";
@import "font-awesome";
------------この部分----------------------
```

favorite-app/app/javascript/packs/application.jsを編集

require("@rails/ujs").start()
// require("turbolinks").start()   ⇐この行を削除
require("@rails/activestorage").start()
require("channels")
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ruby on rails 非同期いいね (編集中)

アプリケーションの立ち上げ

% rails new favorite-app -d mysql
% cd favorite-app
% rails db:create
% rails g controller home                              #homeコントローラ作成
% rails g scaffold post title:string user_id:integer   #postコントローラ作成
% rails g controller likes                             #likesコントローラ作成
% rails g model Like user_id:integer post_id:integer

Gemfileの下部に以下を追記

gem 'devise'
gem 'font-awesome-sass'

Gemfile編集後に実行

% bundle install
% rails g devise:install
% rails g devise user
% rails db:migrate

「application.css」を「application.scss」に変更

その後以下のコードを追記。

// application.scssの下部に以下を追記(既存のコードは消さない)

/*
 * This is a manifest file that'll be compiled into application.css
 * vendor/assets/stylesheets directory can be referenced here using a relative path.
 * 省略
 *= require_tree .
 *= require_self
 */
------------この部分を追記-----------------
@import "font-awesome-sprockets";
@import "font-awesome";
------------この部分----------------------

favorite-app/app/javascript/packs/application.jsを編集

require("@rails/ujs").start()
// require("turbolinks").start()   ⇐この行を削除
require("@rails/activestorage").start()
require("channels")

favorite-app/config/routes.rb

Rails.application.routes.draw do
  devise_for :users
  root 'home#index'
  resources :posts
  post 'like/:id' => 'likes#create', as: 'create_like'
  delete 'like/:id' => 'likes#destroy', as: 'destroy_like'
end

favorite-app/app/controllers/posts_controller.rb

class PostsController < ApplicationController
  before_action :authenticate_user!
  before_action :set_post, only: %i[ show edit update destroy ]

  def index
    @posts = Post.all
  end

  def show
  end

  def new
    @post = Post.new
  end

  def edit
  end

  def create
    @post = Post.new(post_params)
    @post.user_id = current_user.id

    respond_to do |format|
      if @post.save
        format.html { redirect_to @post, notice: "Post was successfully created." }
        format.json { render :show, status: :created, location: @post }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  def update
    respond_to do |format|
      if @post.update(post_params)
        format.html { redirect_to @post, notice: "Post was successfully updated." }
        format.json { render :show, status: :ok, location: @post }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  def destroy
    @post.destroy
    respond_to do |format|
      format.html { redirect_to posts_url, notice: "Post was successfully destroyed." }
      format.json { head :no_content }
    end
  end

  private
    def set_post
      @post = Post.find(params[:id])
    end

    def post_params
      params.require(:post).permit(:title)
    end
end

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

テスト

テスト

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

【超かんたん】Active Hashで投稿ページにプルダウンメニューを作成しよう!

Active Hashを利用して投稿ページにプルダウンメニューを作成します。
超初心者向けにレシピ投稿アプリを例に作成していきます。

完成イメージ

7c1aa72d8269b4942f50794dc40e4b85.gif

Active Hashとは

Active Hashとは、「基本的に変更されないデータ」をモデルファイルに直接記述し取り扱うことができるGem。公式ドキュメント

Active Hashの導入

Active Hashのインストール

Gemfileに下記を記述しbundle installする。

Gemfile
gem 'active_hash'

Recipeモデルの作成

今回はレシピ投稿アプリなのでRecipeモデルを作成します。

ターミナル
rails g model recipe

マイグレーションファイルを編集。
今回、Active Hashを利用してカテゴリー(categoty)と所要時間(time_required)を保存するので、integer型:モデル名_idという形で記述します。
このあと作成するCategoryモデルとTimeRequiredモデルのidを外部キーとして管理するためです。

db/migrate/20XXXXXXXXXXXX_create_recipes.rb
class CreateRecipes < ActiveRecord::Migration[6.0]
  def change
    create_table :recipes do |t|
    #ここから
      t.string :title,          null: false 
      t.text :text,             null: false 
      t.integer :category_id,      null:false 
      t.integer :time_required_id, null: false 
    #ここまで
      t.timestamps
    end
  end
end
ターミナル
rails db:migrate

先ほど、integer型で指定したカテゴリーと所要時間の中身を作成していきましょう。

Category、TimeRequiredモデルの作成

モデルファイルの作成

ターミナル
touch app/models/categoty.rb

Active Hashを用いて作成するモデルはActiveHash::Baseクラスを継承します。
モデルファイルに以下のような形でプルダウンメニューに表示させたいデータをハッシュの中に記述していきましょう。

app/models/categoty.rb
class Category < ActiveHash::Base
  self.data =[
    {id: 0 , name: '---'},
    {id: 1 , name: 'すし・魚料理'},
    {id: 2 , name: '丼もの・揚げ物'},
    {id: 3 , name: 'ラーメン・麺類'},
    {id: 4 , name: '中華'},
    {id: 5 , name: '焼きもの・粉もの'},
    {id: 6 , name: '洋食・西洋料理'},
    {id: 7 , name: 'イタリアン'},
    {id: 8 , name: 'フレンチ'},
    {id: 9 , name: 'アジア・エスニック'},
    {id: 10 , name: 'お菓子・スイーツ'}
  ]
end

同じ要領でTimeRequiredモデルも作成していきます。

ターミナル
touch app/models/time_required.rb

※モデル名はアッパーキャメルケースで記述します。

app/models/time_required.rb
class TimeRequired < ActiveHash::Base
  self.data =[
    {id: 0 , name: '---'},
    {id: 1 , name: '10分以内'},
    {id: 2 , name: '10分〜20分'},
    {id: 3 , name: '20分〜30分'},
    {id: 4 , name: '30分〜45分'},
    {id: 5 , name: '45分〜60分'},
    {id: 6 , name: '60分以上'}
  ]
end

アソシエーションの設定

Recipeモデルのアソシエーションの設定

投稿するレシピ(Recipe)はひとつのカテゴリー(Category)と所要時間(TimeRequired)に紐づくのでbelongs_toを設定します。
また、Active Hashを用いて、belongs_toを設定するには、extend ActiveHash::Associations::ActiveRecordExtensionsと記述してモジュールを取り込みます。

app/models/recipe.rb
class Recipe < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :category
  belongs_to :time_required
end

Category、TimeRequiredモデルのアソシエーションの設定

カテゴリー(Category)と所要時間(TimeRequired)はたくさんのレシピ(Recipe)に紐付いているのでhas_manyを設定します。
また、Active Hashを用いて、has_manyを設定するには、include ActiveHash::Associationsと記述してモジュールを取り込みます。

app/models/categoty.rb
class Category < ActiveHash::Base
  self.data =[
    {id: 0 , name: '---'},
    {id: 1 , name: 'すし・魚料理'},
    {id: 2 , name: '丼もの・揚げ物'},
    {id: 3 , name: 'ラーメン・麺類'},
    {id: 4 , name: '中華'},
    {id: 5 , name: '焼きもの・粉もの'},
    {id: 6 , name: '洋食・西洋料理'},
    {id: 7 , name: 'イタリアン'},
    {id: 8 , name: 'フレンチ'},
    {id: 9 , name: 'アジア・エスニック'},
    {id: 10 , name: 'お菓子・スイーツ'}
  ]
#以下追記
  include ActiveHash::Associations 
  has_many :recipes
end
app/models/time_required.rb
class TimeRequired < ActiveHash::Base
  self.data =[
    {id: 0 , name: '---'},
    {id: 1 , name: '10分以内'},
    {id: 2 , name: '10分〜20分'},
    {id: 3 , name: '20分〜30分'},
    {id: 4 , name: '30分〜45分'},
    {id: 5 , name: '45分〜60分'},
    {id: 6 , name: '60分以上'}
  ]
#以下追記
  include ActiveHash::Associations 
  has_many :recipes
end

バリデーションの設定

presence: true  空データは登録できない
numericality   数値のみを許可する
{ other_than: 0 } numericalityのオプション、0以外を保存
先ほど、Active Hashを用いて作成したモデルのid: 0には'---'とデータが入っていないので0以外を保存するということになります。

app/models/recipe.rb
class Recipe < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :category
  belongs_to :time_required

#以下追記
  validates :title, presence: true
  validates :text, presence: true
  validates :category_id, numericality: { other_than: 0 }
  validates :time_required_id, numericality: { other_than: 0 }
end

ビューの作成

コントローラー、ビューファイルの作成

ターミナル
rails g controller recipes new

コントローラーに以下を記述。

app/controllers/recipes_controller.rb
class RecipesController < ApplicationController

  def index
  end

  def new
    @recipe = Recipe.new
  end

  def create
    @recipe = Recipe.new(recipe_params)
    if @recipe.save
      redirect_to root_path
    else
      render :new
    end
  end

  private
  def recipe_params
    params.require(:recipe).permit(:title, :text, :category_id, :time_required_id)
  end
end

ルーティングの設定

config/routes.rb
Rails.application.routes.draw do
  root to: 'recipes#index'
  resources :recipes, only: [:index, :new, :create]
end

ビューファイルの編集

一部、Bootstrapを使用しております。
Bootstapについての記事も投稿いているのでこちらを参照してくだい
【図解あり】Rails6でBootstrapを導入してトップページを作成する

Active Hashで作成したデータを表示させるにはcollection_selectというメソッドを使用します。
collection_selectは、下記のような順番で記述します。

<%= form.collection_select(保存するカラム名, オブジェクトの配列, カラムに保存する項目, 選択肢に表示するカラム名, オプション, htmlオプション) %>

先ほど作成したCategoryモデルだと下記のような記述になります。

<%= f.collection_select(:category_id, Category.all, :id, :name, {}, {class:"category"}) %>

第5引数のオプションは先頭に値のない選択肢を表示するinclude_blankなどがあります。
今回はid: 0 に'---'を指定しているので空にしてあります。
第5引数、第6引数についてはRailsドキュメントを参照してください。

それではビューファイルに記述していきましょう。

app/views/recipes/index.html.erb
<div class="recipe-form ">
  <h1 class="text-center">レシピを投稿する</h1>

  <%= form_with model: @recipe, local: true do |f| %>
    <div class="form-group">
      <label class="text-secondary">料理名</label><br />
      <%= f.text_field :title, class: "form-control"%>
    </div>
    <div class="form-group">
      <label class="text-secondary">カテゴリー</label><br />
      <%= f.collection_select(:category_id, Category.all, :id, :name, {}, {class:"category"}) %>
    </div>
    <div class="form-group">
      <label class="text-secondary">所要時間</label><br />
      <%= f.collection_select(:time_required_id, TimeRequired.all, :id, :name, {}, {class:"time"}) %>
    </div>
    <div class="form-group">
      <label class="text-secondary">作り方</label><br />
      <%= f.text_area :text, class: "form-control"%>
    </div>
    <div class="actions">
      <%= f.submit "投稿", class: "btn btn-primary" %>
    </div>
  <% end %>
</div>

最後にCSSを整えます。

.recipe-form{
  width: 500px;
  margin: 0 auto;
  margin-top: 40px;
}

これで完成になります。
では、実際に投稿できるか確認してみましょう。

7c1aa72d8269b4942f50794dc40e4b85.gif

しっかり保存されいています。
b7198fb1f8a26d99aea09fb5063a1e60.png

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

初めてのRuby On Rails その2(DB編)

テーブル作成手順

1.データベースに変更を指示するファイルを作成

マイグレーションファイルと呼ばれるデータベースに変更を指示するファイルを作成する

今回はpostsテーブルを作成する例を見てみる
この場合、Postと単数形にする

ターミナル
rails g model Post content:text

Post:モデル名
content:カラム名
text:データ型

以下の2ファイルが作成される

ツリー構造

 ├ app/
 │ └ models/
 │    └ post.rb
 ├ db/
   └ migrate/
      └ 20210226224717_create_posts.rb

post.rb
class Post < ApplicationRecord
end
20210226224717_create_posts.rb
class CreatePosts < ActiveRecord::Migration[5.0]
  def change
    create_table :posts do |t|
      t.text :content

      t.timestamps
    end
  end
end

2.データベースに変更を反映

ターミナル
rails db:migrate

自動で生成されるカラム
id , created_at , updated_at

rails console

後述するテーブルへのデータ保存で使うので記載しておく

開始する場合
ターミナル
rails console

対話型でコマンドを実行できるようになる

終了する場合
ターミナル
quit

テーブルに投稿データを保存しよう

手順

  1. new メソッドで Post モデルのインスタンスを作成
  2. posts テーブルに保存

1. インスタンスを作成

ターミナル
rails console
> post = Post.new(content:"Hello world")
> post2 = Post.new(content:"Hello world 2")
> post3 = Post.new(content:"Hello world 3")

2. 保存

DBに3つのデータが挿入される

ターミナル
> post.save
> post2.save
> post3.save

データ取得

最初のデータを取り出す

postsテーブルの最初のデータを取得

ターミナル
> post = Post.first
> post.content
=> "Hello world"

すべてのデータを取り出す

postsテーブルの全データを配列で取得
allメソッドを用いる

ターミナル
> posts = Post.all
> posts[1].content
=> "Hello world 2"

特定のデータを取り出す

postsテーブルの条件を指定した特定のデータを取得
find_byメソッドを用いる

ターミナル
> post = Post.find_by(id: 3)
> post.content
=> "Hello world 3"

並び替えた状態でデータを取り出す

orderメソッドを用いる
desc:降順、asc:昇順

ターミナル
> posts = Post.all.order(created_at: :desc)

データ更新

①編集したいデータを取得
②そのデータのcontentの値を上書き
③データベースに保存

ターミナル
> post = Post.find_by(id: 3)
> post.content = "Rails"
> post.save

上記のタイミングで、updated_atカラムの値がデータを更新したときの時刻に更新される

データ削除

ターミナル
> post = Post.find_by(id: 3)
> post.destroy

まとめ

DBの基礎知識があれば、特に難しいことはなかった。
SQLを書かないでデータ挿入、取得するのは少し違和感があった。

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

railsで投稿できないのを対処しました

スクリーンショット 2021-02-26 21.36.25.png

構築環境

macOS Big sur
ruby 2.6.5
rails 6.0.0

何があったのか

最初は、スクリーンショット 2021-02-26 21.36.25.png
こんな感じで、paramsが入っていないっていうエラー。しかしcontrollerを見てみてもおかしいところはなさそう
しかし、おかしいところを見つけましたね。
controllの記述を間違えてしまいました。

↓こちらは修正前の、投稿に関するcontroller

posts_controller.rb
class PostsController < ApplicationController
  before_action :authenticate_user!
  before_action :find_post, only: [:edit, :update, :show, :destroy]

    def index
        @posts = Post.all
      end

      def new
      @post = Post.new
    end

    def edit
      @post = Post.find(post_params)
    end

    def create
      return redirect_to new_profile_path,alert: "プロフィールを登録してください" if current_user.profile.blank?
      @post = current_user
      @post = Post.create params.require(:post).permit(:content, images: []) 
      if @post.save
        redirect_to root_path,notice:'投稿に成功しました'
        else
          render :new
        end
      end

      def update
        if @post.update(post_params)
          redirect_to root_path
        else
          render :edit
        end
      end

      def destroy
        if @post.destroy
          redirect_to root_path,alert: '投稿を削除しました'
        else
          redirect_to root_path
        end
      end

      private
        def post_params
          params.require(:post).permit(:content, images: [])
        end

        def find_post
          @post = Post.find(params[:id])
        end

        def force_redirect_unless_my_post
          return redirect_to root_path,alert:'権限がありません'if @post.user != current_user
        end
end

修正後↓

class PostsController < ApplicationController
  before_action :authenticate_user!
  before_action :find_post, only: [:edit, :update, :show, :destroy]

    def index
        @posts = Post.all
      end

      def new
      @post = Post.new
    end

    def edit
      @post = Post.find(post_params)
    end

    def create
      return redirect_to new_profile_path,alert: "プロフィールを登録してください" if current_user.profile.blank?
      @post = current_user
      @post = Post.create params.require(:post).permit(:content, images: []) 
      if @post.save
        redirect_to root_path,notice:'投稿に成功しました'
        else
          render :new
        end
      end

      def update
        if @post.update(post_params)
          redirect_to root_path
        else
          render :edit
        end
      end

      def destroy
        if @post.destroy
          redirect_to root_path,alert: '投稿を削除しました'
        else
          redirect_to root_path
        end
      end

      private
        def post_params
          params.require(:post).permit(:content, images: [])
        end

        def find_post
          @post = Post.find(params[:id])
        end

        def force_redirect_unless_my_post
          return redirect_to root_path,alert:'権限がありません'if @post.user != current_user
        end
end

20行目の、
@post = Post.create.params.require(:post).permit(:content, images: [])
の部分を、
@post = Post.create params.require(:post).permit(:content, images: [])に変えました。
上の間違った文章だと、Postのcreateメソドッドのparamsを撮ってこいってなってしまうので、そりゃあparamsなんてないよって上のエラーの画像みたいに怒られるでしょうね。
しかし、したのようにしたことで、Post.createと、paramsのストラロングパラメーターの部分と分離するという本来伝えたかった意味になるので、通ったわけです。

もうひとつやったことがありました。

class Post < ApplicationRecord
    has_many_attached :images
    belongs_to :user, optional: true
end

modelの部分のアソシエーションは、userと結びつけていますなので、userと投稿であるpostは、1対多になります
↓が、userに関するmodelです。deviceを使っているので、それに関する記述がありますがここでは説明しないので気にしないでください

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  has_many :posts
  has_one :profile, dependent: :destroy
end

has_many :postsとしているので、userは、一人でもたくさんの投稿ができるようになっています。

下が

post.rb
class Post < ApplicationRecord
    has_many_attached :images
    belongs_to :user, optional: true
end

optional: trueをuserにつけることで、親クラス(user)の外部キーのnilを許可するようにすることで、投稿機能の中にあるsaveメソッドをきのうさせることができました。

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

【デイリードリル】解説&知識補完記事 51~60編

目次

51.コールバックの適用について
52.CSRFについて
53.仕様が決められたrailsアプリケーションの作成
54.ぼっち演算子
55.マイグレーションファイルの管理について
56.仕様が決められたrailsアプリケーションの作成
57.CSS、SCSSのインポートについて
58.if,else問題1
59.if,else問題2
60.rubyのAPI問題1

コールバックの適用について

問題.1

Railsのコールバックを利用して以下の機能を実装してください。

Pictweetに機能を追加する。
②ユーザーが投稿を行ったら、textカラムに保存されるデータの最後に「!!」を自動で追加する。

解答
tweet.rb
  before_create :change_tweet

  def change_tweet
    self.text = text + "!!"
  end
end

解説

そもそもコールバックとは何でしょうか?

コールバックとは、オブジェクトのライフサイクル期間における特定の瞬間に呼び出されるメソッドのことです。
簡単に言うと、必要なタイミングで呼び出せるように、あらかじめ定義しておく関数のことです!

つまり、今回の問題では、「ユーザーが投稿を行ったら、textカラムに保存されるデータの最後に「!!」を自動で追加する関数を書け!!!」って問題です。
コールバックはmodelに定義します。詳しくはガイダンスを読みましょう。

ガイダンス:Active Record コールバック

次に、解答のコードの解説をしていきます。

tweet.rb
  before_create :change_tweet

  def change_tweet
    self.text = text + "!!"
  end
end

change_tweetっていうメソッドを定義し、それをbefore_createで呼び出しています。
before_createについては以下の記事を参照してください。

参考:https://morizyun.github.io/ruby/active-record-callback.html

上記コードのselfには、これから保存されるTweetクラスのインスタンスが代入されます。
そのため、self.textとするとユーザーが入力した投稿のtextを取得することができます(@tweet.textみたいな感じで値を取得出来るイメージ!)。
通常、この場合のselfは省略可能です。

※例外としてセッターメソッドを使う場合のselfは省略できないため、左辺のselfは必須となります。
よく分かりませんね、そういうものだと思いましょう。
詳しく知りたい方は自分でガイダンスを読んでくださいネ。

CSRFについて

問題

Railsには、悪意のある攻撃に対してセキュリティを高める仕組みが様々用意されています。
CSRFと呼ばれるサイトの成りすましによるクラッキングに対して、どのような対策が取られているか述べてください。

なお、CSRFがどのようなものか理解に自信がない場合はあらかじめこのことについて調べてからお答えください。

解答

ApplicationControllerにデフォルトで以下の記述がある。
protect_from_forgery with: :exception

これにより、アプリで作られたフォームに対してトークンが発行され、正しいフォームからの通信なのかを判別することができる。

↓以下のポイントが記述できていればOK!

  • protect_from_forgeryについて述べられている
  • トークンの発行によりクラッキングを防いでいることが述べられている

解説

まずは専門用語の解説をしていきましょう。

CSRF(クロスサイトリクエストフォージェリ)

Webアプリケーションに存在する脆弱性、もしくはその脆弱性を利用した攻撃方法のことをいいます。

脆弱性とは、コンピュータのOSやソフトウェアにおいて、プログラムの不具合等のミスが原因となって、発生した情報セキュリティ上の欠陥のことをいいます。
これにより、本来拒否すべき他サイトからのリクエストを受信して、処理してしまうという現象が生じる...。

..................わからない!!!ムズカシイ!!!(;^ω^)

めっちゃ簡単にまとめると、

悪い人がWebアプリサーバにイタズラする
スクリーンショット 2021-02-23 21.15.06.png

 ↓
訪問者がホームページにログインして操作する
スクリーンショット 2021-02-23 21.17.21.png
 ↓
被害に遭う
スクリーンショット 2021-02-23 21.20.00.png

ってことです。悪い人がアプリに細工するんです。いけませんねえ…

クラッキング

コンピュータネットワークに繋がれたシステムへ不正に侵入したり、コンピュータシステムを破壊・改竄するなど、コンピュータを不正に利用することをいう。

IDとパスワードを不正に入手してログインしたり、パスワードを総当たりして特定する悪いやつです。いけないですねえ。困った奴らだ…

対策

では、どのようにして対策しているのか??
実は、railsが対策を考えてくれているのです!!!

RailsではCSRF対策として「セキュリティトークンを仕込む」という方法を採用しています。
それをやってくれているのがprotect_from_forgeryメソッドと、Railsが提供するformのヘルパーです。

Railsが提供するform_withなどのヘルパーを使うと、自動でセキュリティトークン(authenticity_token)を仕込んでくれます。すごい!!

protect_from_forgeryメソッドを記述すると、Rails側で正しいauthenticity_tokenがセットされるかどうかをチェックしてくれるので、CSRFを防ぐことができます!

参考:
Rails セキュリティガイド:3 クロスサイトリクエストフォージェリ (CSRF)
3分でわかるXSSとCSRFの違い
RailsのCSRF対策について

仕様が決められたrailsアプリケーションの作成

問題

以下の仕様を満たすRailsアプリケーションを作成してください。ただし、scaffoldを使用して構いません。

・authorsテーブルがある
・booksテーブルがある
・authorsとbooksは1対多のアソシエーションが組まれている
・authorsテーブルのレコードを削除すると、関連するbooksテーブルのレコードも同時に削除される

解答
(手順例)
(ターミナルで以下を実行)
> rails new sample-app -d mysql
> rails g scaffold author name:string
> rails g scaffold book name:string author:references
author.rb
class Author < ApplicationRecord
  has_many :books ,dependent: :destroy
end

解説

アプリケーションを作成するので、ターミナルでコマンドを実行します。

ここであんまり馴染みがないのが、scaffoldだと思います。
scaffoldは、アプリケーションを作成する際に必要なモデルやコントローラー、ビューを作っていき、さらに必要なルーティングを作成していく作業をまとめて行って、簡単にアプリケーションの雛形を作ってくれる機能です。
すごいやつだ。便利〜(・∀・)

あとは、実装条件に必要なアソシエーションを記述していきます。

authorsテーブルのレコードを削除すると、関連するbooksテーブルのレコードも同時に削除される
これは、dependentを使用することで実装できます。
dependentは、親モデルを削除する際に、その親モデルに紐づく「子モデル」も一緒に削除できるオプションです。

参考:
覚えておくと超便利!Ruby on Railsのscaffoldの使い方【初心者向け】
[Rails] dependent: :destroy について

ぼっち演算子

問題

以下のような、Deviseを使ったRailsのコードがあるとします。
これはDeviseを使用したときのcurrent_userに対して、nicknameカラムに
あるデータを取得して@nicknameに代入することを意図したものです。

@nickname = current_user.nickname

ただし、ログインしないときにこれを実行するとnilに対してメソッドを使おうとしてエラーになってしまいます。

これを回避できる記述がRuby2.3からできるようになりました。
それがどのような記述か、またそれはどのような動きをするのか説明してください。

解答
@nickname = current_user&.nickname

解説

これはぼっち演算子(&)を知ってますか?って問題ですね。
知識があるかどうかの問題なので、ご存じない方はこれを機に頭の片隅に置いとくといいと思います。

&safe navigation operator、 lonely operator(ぼっち演算子)などと呼ばれる演算子です。

メソッドに続けて記述すると、そのメソッドがnilでなかった場合のみ右辺のメソッドが実行されます。
もしnilだった場合は全ての演算結果としてnilを返します。つまり@nicknameにnilが代入されます。

とても使い勝手の良い演算子のため、覚えて活用していきましょう。

なお、ぼっち演算子という命名は、&の記号が一人ぼっちで膝を抱えている人に見えるところからきています。

マイグレーションファイルの管理について

Railsのマイグレートに関して、以下の問いに答えてください。

問題1

Railsのマイグレーションファイルは、ファイルを作成後にマイグレートをして初めてDBに変更が加わります。

今存在している複数のマイグレーションファイルのうち、どのファイルまでマイグレートが終わっているか確認するためにターミナルでどのようなコマンドを打てばよいですか。

解答
rails db:migrate:status

問題2

マイグレートを行なった後、その内容に誤りがあることがわかりました。どのように修正すればよいか、手順を述べてください。

解答

例1)
①rails db:rollbackで一度マイグレートされていない状態に戻す
②マイグレーションファイルを修正する
③再びマイグレートを行う

例2)
①rails g migrationで新たなマイグレーションファイルを作成する
②修正箇所を正しい内容に変更するためのマイグレーションファイルを作成する
③マイグレートを実行する

問題3

一度実行されたマイグレーションファイルは、次のマイグレートに影響は及ぼしません。
そのため、マイグレート後にファイルを変更したり削除したとしても今のDBに悪影響を及ぼすことはありません。

ただし、上記の行為は絶対に行なってはいけません。
その理由はいくつかありますが、開発において致命的な不具合が出てしまう理由をお答えください。

解答

アプリケーションのファイルをデプロイ先にpushし、マイグレートを行なった時に、ローカル環境と違ったDBが出来上がったり、エラーになってしまうから。

仕様が決められたrailsアプリケーションの作成

問題

ターミナル
> cd ~/projects/
> rails new reverse-app -d mysql
> cd reverse-app/
> rails g scaffold item name:string
> bundle exec rake db:create
> bundle exec rake db:migrate

仕様

・登録されたitemnameが回文(上から読んでも下から読んでも同じになる文)なのかを判定するヘルパーメソッドpalindrome?を作成してください。
・ヘルパーメソッドは、回文だった時は「回文です」そうでない時は「回文ではありません」という文字列を返すものとします。
・一覧表示画面で、以下のようにその結果を表示させるようにしてください。

スクリーンショット 2021-02-27 12.15.56.png

解答
items_helper.rb
module ItemsHelper
  def palindrome?(word)
    word == word.reverse ? "回文です" : "回文ではありません"
  end
end
index.html.erb
<p id="notice"><%= notice %></p>

<h1>Items</h1>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @items.each do |item| %>
      <tr>
        <td><%= item.name %></td>
        <td><%= palindrome?(item.name) %></td>  #追加
        <td><%= link_to 'Show', item %></td>
        <td><%= link_to 'Edit', edit_item_path(item) %></td>
        <td><%= link_to 'Destroy', item, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Item', new_item_path %>

(17行目を追加)

解説

今回のポイントは、palindrome?メソッドを使用できるか?という部分です。

palindrome?メソッドは使用にも書いてあるように回分かどうかを判定するヘルパーメソッドです。

Railsにおいては helpersディレクトリにヘルパーメソッドを管理するファイルがあるので、今回はitems_helper.rbに記載します。

items_helper.rb
module ItemsHelper
  def palindrome?(word)
    word == word.reverse ? "回文です" : "回文ではありません"
  end
end

reverseメソッドは、文字列を文字単位で左右逆転した文字列を返します。
そして、三項演算子を使用し、回文です or 回文ではありませんを表示出来るように記載しています。

三項演算子とは、a ? b : cと書くことで、a が真であれば b さもなくば cという結果を返すことができます。ちょいちょい使うので覚えておくと便利です。

次に、作ったメソッドをビューで呼び出します。
ヘルパーメソッドはViewではどこからでも呼び出せるため、呼び出したい部分に記載します。

index.html.erb
#省略

 <% @items.each do |item| %>
      <tr>
        <td><%= item.name %></td>
        <td><%= palindrome?(item.name) %></td>  #追加
        <td><%= link_to 'Show', item %></td>
        <td><%= link_to 'Edit', edit_item_path(item) %></td>
        <td><%= link_to 'Destroy', item, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>

#省略

先程作ったメソッドを呼び出し、ビューに表示させています。
これで、使用の要件を満たすコードの完成です!!

参考:
Ruby 3.0.0 リファレンスマニュアル instance method String#reverse
ヘルパーメソッドをつくろう
Ruby入門 - 演算子

css, scssのインポートについて

問題

rails newを実行しアプリケーションを作成した段階では、application.cssに *= require_tree .という記述があり、これによってCSSファイルを読み込んでいます。

SCSSを使用する際は、ファイルの拡張子をscssに変更した上で、@importでファイルを読み込みます。

この時の、requireと@importはどう違うのか、それぞれのインポートの仕組みを踏まえて答えてください。

解答

requireはRailsのアセットパイプラインの仕組みを使ってファイルをインポートする。

アセットパイプラインは、cssファイルやJavaScriptのファイルを1つにまとめ、圧縮することで処理速度を早くするための仕組み。sprocketsというgemがこの機能を担っている。

それに対して@importはscssが用意しているメソッド。そのため、application.scssと拡張子を変更しないと使えない。また、application.scssからscssファイルをインポートするために使用する。

if,else問題1

問題

平日でないまたは休日の場合は「True」と返信し、
休日ではない場合は「False」と条件分岐させるメソッドを作りましょう。

呼び出し方

sleep_in(weekday, vacation)

出力例

sleep_in(false, false)  False
sleep_in(true, false)  False
sleep_in(false, true)  True

解答
def sleep_in(is_weekday, is_vacation)
  if (is_weekday != true) || (is_vacation == true)
    puts "True"
  else
    puts "False"
  end
end


is_weekday = true
is_vacation = false

sleep_in(is_weekday,is_vacation)

解説

ポイントは演算子をいくつも使っていることですね。

おそらくコードを見てよくわからない、と感じる方はsleep_inの中身がよくわからないのではないかと思います。

def sleep_in(is_weekday, is_vacation)
  if (is_weekday != true) || (is_vacation == true)
    puts "True"
  else
    puts "False"
  end
end

仮引数(is_weekday, is_vacation)の結果がtrueかどうかを判別するために、if文と論理演算子(||)、比較演算子(!===)を組み合わせて使用しています。

理論演算子(||)は、a または b が true であればという演算子です。
比較演算子は、!=a と b が等しくないとき==a と b が等しいとき、という演算子です。

つまり、is_weekdayがtrueと等しくなかったら(falseだったら)、もしくはis_vacationがtrueだったらputs "True"を実行してね、っていうコードになります。

ややこしいコードはいきなり細部から読んでもよくわからないので、if文が使われていて、その条件式の中に演算子が使われていて…と大枠から見ていくと解読しやすくなると思います。

参考:Ruby入門 - 演算子

if,else問題2

問題

あなたは警官です。aとb二人の容疑者の取り調べをしています。
どちらも証言がTrue、またはFalseであればその証言はTrueです。
しかしどちらかがFalseでTrueであればその証言はFalse、と出力するメソッドを作りましょう。

呼び出し方

police_trouble(a, b)

出力例

police_trouble(true, false)  False
police_trouble(false, false)  True
police_trouble(true, true)  True

解答
def police_trouble(a, b)
  if a && b || !a && !b
    puts "True"
  else
    puts "False"
  end
end

解説

こちらは問題58と似ていますね。条件式の中に演算子を用いています。

今回使用しているのは、論理演算子(!&&||)です。
!a が false であればという意味で、&&a かつ b が true であれば、という意味です。
||は、問題58でも記述したとおり、a または b が true であればという演算子です。

つまり、aとbがどちらもtrueである、もしくはaとbがどちらもfalseであるという条件のときに、puts "True"を実行してね、というコードになります。

参考:Ruby入門 - 演算子

rubyのAPI問題1

問題

任意の文字に対してn番目の文字を消し、
その消した文字を出力するメソッドを作りましょう。

※ヒント:APIを利用して問題を解きましょう。
参考URL: https://docs.ruby-lang.org/ja/search/

呼び出し方

missing_char(string, num)

出力例

missing_char('kitten', 1)  'itten'
missing_char('kitten', 2)  'ktten'
missing_char('kitten', 4)  'kittn'

解答
def missing_char(array, n)
  array.slice!(n)
  puts array
end

解説

今回は、任意の文字に対してn番目の文字を消すために必要な処理(メソッド)を使用する必要があるようです。

今回は、slice!メソッドを使用します。
slice!メソッドは、指定した範囲を文字列から取り除いたうえで取り除いた部分文字列を返します

注意点として、sliceメソッドとは違うので注意してください。
sliceメソッドは、文字列の任意の部分文字列を取得しますが、元の文字列には影響がありません。
対して、slice!メソッドは破壊的メソッドであるため、戻り値として指定した要素を返しますが、同時に自分自身から戻り値に返した部分を削除してしまいます

コードで見るとわかりやすいかと思います。

#sliceの場合
str = aab
puts str.slice(2)  #=>b
puts str           #=>aab

#slice!の場合
str = aab
puts str.slice!(2)  #=>b
puts str            #=>aa

今回は、n番目の文字を消すようなコードを書く問題であるため、slice!メソッドを使用します。

参考:
【Ruby入門説明書】ruby sliceについて解説
Ruby 3.0.0 リファレンスマニュアル instance method String#slice!

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

機密情報に関わる文字列の比較は == ではなく secure_compare を使おう

はじめに

パスワードハッシュやトークンなどの文字列を比較する際には、== ではなく Rack::Utils.secure_compare を使ったほうが良いということを学んだので、勉強の記録として共有します。

== での文字列比較の危険性

Rack::Utils.secure_compare を知るまでは、まさか == に危険性があるとは思いませんでした1が、パスワードハッシュやトークンなどを比較する際には == は使用しないほうが良いです。

その理由は、Timing Attack (タイミング攻撃) と呼ばれる攻撃によって機密情報である文字列を推測されてしまう可能性があるからです。

タイミング攻撃とは、機密情報となる文字列を左から見ていったときに、どこまで合っているかを、文字列比較の処理時間 (false が返ってくるまでの時間) を利用して、推測するという攻撃手法です。

この説明だけだといまいちわかりづらいと思いますので、もう少し詳しく説明します。

== による文字列の比較は、左から文字を見ていって、異なる文字が出てきた時点ですぐさま false を返します。

たとえば、"abcdef""abcefg" という文字列を比較する際、左の文字から比較していき、4 文字目で de という異なる文字が登場したので、5 文字目以降は見ずに false を返します。

abcdef
abcefg
   ^ ここで false を返す

この仕様は、処理の高速化のために採用されているものですが、これを攻撃に利用されてしまう可能性があるということです。

たとえば攻撃者が "xaeteuijft" というトークンを知りたかったとしましょう。まずは適当にありがちな文字列をサーバに投げて比較させます。

その際に、処理結果がすぐに返ってきた場合は、文字列の (左から見て) 早い段階で文字が間違っていることになります。

何回か繰り返しているうちに、左側の文字が合ってくると、処理結果が返ってくるまでにわずかに時間がかかるようになります。

処理結果が返ってくるのが若干遅くなれば合っている、速くなれば間違っている、というのを何度も繰り返すと、いずれ本当の文字列がバレてしまう可能性があるということです。短くて単純なものならなおさらです。

これがタイミング攻撃の攻撃手法になります。そして、この攻撃を防ぐための文字列比較のメソッドが Rack::Utils.secure_compare と呼ばれるものです。

Rack::Utils.secure_compare の実装を見てみる

では、Rack::Utils.secure_compare はいったいどんな実装になっているのでしょうか。さっそく見てみましょう。

    # Constant time string comparison.
    #
    # NOTE: the values compared should be of fixed length, such as strings
    # that have already been processed by HMAC. This should not be used
    # on variable length plaintext strings because it could leak length info
    # via timing attacks.
    if defined?(OpenSSL.fixed_length_secure_compare)
      def secure_compare(a, b)
        return false unless a.bytesize == b.bytesize

        OpenSSL.fixed_length_secure_compare(a, b)
      end
    else
      def secure_compare(a, b)
        return false unless a.bytesize == b.bytesize

        l = a.unpack("C*")

        r, i = 0, -1
        b.each_byte { |v| r |= v ^ l[i += 1] }
        r == 0
      end
    end

OpenSSL.fixed_length_secure_compare が定義されている場合はこれを使うようになっているのですが、今回は説明のわかりやすさの観点で、こちらではなく、Rack の実装のほうを見てみます。

def secure_compare(a, b)
  return false unless a.bytesize == b.bytesize

  l = a.unpack("C*")

  r, i = 0, -1
  b.each_byte { |v| r |= v ^ l[i += 1] }
  r == 0
end

引数 a に本当の文字列 (トークンなど)、引数 b に対象となる文字列 (クライアントからやってくる文字列など) が来るものとします。以後、ab をそれぞれ「本当の文字列」、「対象となる文字列」と呼ぶことにします。

バイト長の比較

return false unless a.bytesize == b.bytesize

まず最初の段階で、そもそもバイト長が違っていたら false を返します。

本当の文字列を 8 bit 符号なし整数の配列に変換

l = a.unpack("C*")

本当の文字列をアンパックします。

アンパックというのは、簡単に言うと特定のルールに従って文字列を分解し、配列で返すメソッドです。今回は引数に "C*" が指定されているので、文字列中の各々の文字を 8 bit 符号なし整数に変換して、配列で返します。

"abcdef".unpack("C*")
=> [97, 98, 99, 100, 101, 102]

"a"97"b"98"c"99、、、とそれぞれ対応しています。

変数の初期化

r, i = 0, -1

r0i-1 が入ります。

文字列の比較

b.each_byte { |v| r |= v ^ l[i += 1] }

ここがメインの部分で、少しだけ複雑なので丁寧に解説します。

対象となる文字列を each_byte でループ処理します。たとえば b"abcdef" だったら、v には 979899、、、が順番に入ってきます。

その v と、先ほど本当の文字列を 8 bit 符号なし整数でアンパックした配列 l とを、最初のほうから順番に比較していきます。

ぼく自身も最初見たときに少し戸惑ったのですが、l[i += 1] というのは、先に i += 1 が実行されてから l の中身が参照されます。i は最初 -1 から始まるので、先に i0 になってから l[0] が評価されるので、配列を最初から見ているということです。

そして v ^ l[i += 1]^ は XOR (排他的論理和演算子) です。こういったライブラリではなく、アプリケーションでしか Ruby を書かない人にとっては、あまり馴染みがないかもしれません。

排他的論理和

排他的論理和は、2 つの数値を 2 進数で表記した際に、両者が異なる数字なら 1、同じ数字なら 0 を返す演算です。

01110101
00100010

上記の 2 つの 2 進数の排他的論理和を取ると、以下のようになります。

01010111

左の 1 文字目は両者とも 0 なので 0、2 文字目は片方が 1 で片方が 0 なので 1、3 文字目は両者とも 1 なので 0、、、といった感じです。

1 2 3 4 5 6 7 8
1 つめの 2 進数 0 1 1 1 0 1 0 1
2 つめの 2 進数 0 0 1 0 0 0 1 0
排他的論理和 0 1 0 1 0 1 1 1

01110101 は 10 進数で表すなら 11700100010340101011187 となります。つまり、10 進数表記の 11734 の排他的論理和を取ると 87 になる、というわけです。

117 ^ 34
=> 87

さて、Rack の実装の話に戻ると、v ^ l[i += 1] なので、対象となる文字列と、本当の文字列の 8 bit 符号なし整数を左から順番に比較しているのでした。

ここで、両者が同じ文字 (数字) だったら、何が返ってくるでしょう。たとえば両者とも "a" だったら 97 を排他的論理和で比較することになります。

両方同じ数字だということは、当然すべての bit が同じなので、0 になります。

1100001
1100001

を比較すると、どの bit もすべて同じなので、

0000000

となります。

97 ^ 97
=> 0

つまり、同じ文字 (数字) なら 0 になり、そうじゃなければ 0 じゃない値になるということです。

そしてこの結果を r に代入していますが、ただの代入ではなく、論理和代入になっています。

r |= v ^ l[i += 1]

これは、以下と同様です。

r = r | v ^ l[i += 1]

つまり、r と、先ほどの文字 (数字) 比較を論理和演算した結果を r に代入しています。論理和演算は、どちらか片方が 1 なら 1 で、両者とも 0 なら 0 を返す演算です。

1 つめの bit 2 つめの bit 論理和
0 0 0
0 1 1
1 0 1
1 1 1

r0 で初期化されていたので、先ほどの文字 (数字) 比較の結果、すべての bit が 0 なら r もすべて 0 が入り、0 じゃない値がどこかの bit に入ったら、r もどこかの bit に 0 じゃない値が入ります。

ということは、本当の文字列と、対象となる文字列が完全に一致していれば、r はずっと 0 のままなので最終的にも 0 になり、逆にどこか一箇所でも違う文字になっていれば、つまり一致していなければ、r0 じゃない値になる、というわけです。

一度でも r0 じゃない bit が含まれてしまうと、その bit を再び 0 に戻すことは、論理和では無理です。なぜなら片方が 1 なら 1 になってしまうからです。つまり r は最初から最後までずっと 0 じゃないと、最終的にも 0 にはなりえないというわけです。

裏を返せば、b.each_byte で本当の文字列と対象となる文字列を一文字ずつ比較した結果、r0 のままであるなら両者の文字列は一致していることになります。

真偽値の返却

r == 0

最後に r0 かどうかを見ていて、0 (両者の文字列が一致している) ならこのメソッドは true を返し、0 じゃなければ (両者が一致していなければ) false を返すというわけです。


少し長い説明になりましたが、これで Rack::Utils.secure_compare がどのように文字列を比較しているかがわかりました。

Rack::Utils.secure_compare の安全性

文字列比較は == でもできるわけですから、肝心なのは、冒頭で説明したタイミング攻撃が防げるかどうかです。

== の場合は文字の比較の途中で違う文字が登場したらすぐさま false を返す仕様でしたが、Rack::Utils.secure_compare はどうでしょうか。

文字列の比較は以下の部分です。

b.each_byte { |v| r |= v ^ l[i += 1] }

処理の高速化という意味では、r が途中で 0 じゃなくなれば、文字列が一致していないことになるので、false を返すことができますが、それだと本末転倒です。途中で false を返したらタイミング攻撃が成立してしまう可能性があります。

しかし Rack::Utils.secure_compare は、文字列比較の途中で false を返すようなことはしていないので、途中で文字が違うことがわかっても最後まで比較し続けます。そのためループ内の処理時間は文字列が合っていようが間違っていようが変わらず、タイミング攻撃 (文字列そのものの推測に限る) は成立しないと言えます。

Rack::Utils.secure_compare の危険性

ただし注意点もあります。これは ソースコード内のコメント にも記載されていますが、タイミング攻撃で、本当の文字列の 長さ を推測されてしまう危険性はあります。

これが先ほど「文字列そのものの推測に限る」と書いた理由です。Rack::Utils.secure_compare はたしかに、タイミング攻撃による「文字列そのものの推測」は防ぐことができます。しかし、「文字列の長さの推測」までは防げない可能性があります。

理由はここにあります。

return false unless a.bytesize == b.bytesize

そもそも比較する 2 つの文字列のバイト長が異なっていたら文字列の比較をせずにすぐさま false を返しています。

もし本当の文字列が 10 文字だったとしたら、10 文字以外の長さの文字列を対象となる文字列として渡すと、すぐさま false が返ってくるので、順番に文字列の長さを変えて試していけば、そのうち文字列が長さがバレてしまいます。

つまりどういうことかというと、Rack::Utils.secure_compare可変長の平文文字列では安全ではない ということになります。

使用用途としては、HMAC のような、文字列長が常に一定になるアルゴリズムを使って、文字列をハッシュ化してから使うことになります。ハッシュ化せずそのままの文字列 (平文文字列) をこのメソッドに渡してしまうと長さが推測されてしまう可能性があります。

さいごに

今回は機密情報に関わる文字列を Ruby で安全に比較する方法として Rack::Utils.secure_compare をご紹介しました。

この記事では Ruby を例に挙げましたが、他の言語でも似たようなライブラリが提供されているかと思いますので、機密情報に関わる文字列を比較する際は一度調べてみることをおすすめします。

参考サイト


  1. 一応、誤解のないように書いておくと、機密情報に関わらない文字列の比較に == を使用することは何も問題はありません。Ruby の == の実装に脆弱性があるというわけではないです。 

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

「action_args」のソースから学ぶ「Ruby」① Gemの作り方

目的

Gemについて勉強したかったのでRailsで使われている「action_args」ソースから学ぶのが目的
ここで言うGemはRubyGemsが公開しているRubyのパッケージのことです

action_argsについて

action_argsは、コントローラーのアクションメソッドを拡張して、任意のアクションのメソッド定義で対象の引数を指定できるようにしたRailsプラグインです。詳細内容はREADME参考

git clone https://github.com/asakusarb/action_args.git

Gemの作り方

aciton_argsのソースを分析する前にGemの作り方から学ぶ

1. bundlerを最新にする

# gemのアップデート
$ gem update --system

# bundlerのアップデート
$ gem update bundler
$ bundler -v
Bundler version 2.2.11

2. Hello gemを作る

Gemのベースを作成します。
bundle gem hello_gem -tRspecが使えると思いましたが、minitestになってたので
公式を参考にして--test=rspecを指定して解決できました。
https://bundler.io/v2.1/bundle_gem.html

$bundle gem hello_gem --test=rspec
$cd hello_gem
$ tree
.
├── Gemfile
├── README.md
├── Rakefile
├── bin
│   ├── console
│   └── setup
├── hello_gem.gemspec
├── lib
│   ├── hello_gem
│   │   └── version.rb
│   └── hello_gem.rb
└── spec
    ├── hello_gem_spec.rb
    └── spec_helper.rb

3. hello_gem.gemspecを修正

hello_gem.gemspecファイルを開くとTODOと書かれているところがあるので自分が作るGemに合わせて修正しないとbundle installできません。
修正が終わったら依存gemをインストールします。

$bundle install

4. gemの実装

lib/hello_gem.rbに処理を書きます。
bundle gem hello_gemから自動生成された以下のファイルにコードを追加します。

lib/hello_gem.rb
# frozen_string_literal: true

require_relative "hello_gem/version"

module HelloGem
  class Error < StandardError; end

  # helloを出力
 def self.say_hello
    "hello!!!"
  end
end

4. テスト実装

spec/hello_gem_spec.rb
# frozen_string_literal: true

RSpec.describe HelloGem do
  it "has a version number" do
    expect(HelloGem::VERSION).not_to be nil
  end

  it "say_hello test" do
    expect(HelloGem.say_hello).to eq("hello!!!")
  end
end
$ bundle exec rake spec

5. 実行

$ ./bin/console
irb(main):001:0> HelloGem.say_hello
=> "hello!!!"

これでGemの基本的な作り方はわかったので次回からは本格的に「action_args」のソース分析に入ります

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

アプリ開発実践入門7 Modelとデータベース モデルの基本

今日の教科書

モデルを作る

rails generate model モデル名 設定項目:モデル作成のコマンド
基本モデル名とクラス名とファイル名は大文字小文字の差はあれど一緒である。

コマンドで作成されるファイルについて

モデル名.rb:モデルのソースコード
生成日時_create_モデル名の複数形.rb:マイグレーションというデータベースの更新に関する処理のためのファイル
モデル名_test.rb:テストのためのソースコード
モデル名の複数形.yml:テストに関する情報を記述したファイル

モデルのソースコード

class モデル名 < ApplicationRecord
end 

最初の記述内容。ApplicationRecordというクラスが継承されている。モデルは全てこれを継承している。ApplicationRecordは最初から組み込まれているものではない。application_record.rbファイルが作成されていて、ここに書かれている。
このクラスはActiveRecord:Baseというクラスを継承している。これはモデルの元になっている機能で含み、これの機能のクラスを使っている。

マイグレーションの実行

データベースを使うためにモデルに必要なテーブルをデータベースに用意する作業が必要。
マイグレーションはデータベースのアップデート作業であり、情報を用意しておきこれを元に
最新の状態に更新する。

rails db:migrate:マイグレーションのコマンド。これでなくブラウザのエラーメッセージと一緒に出るボタンを押してもできる。

マイグレーションファイルについて

class クラス名 < ActiveRecode::Migration[6.0]
  def change
    create_table :テーブル名 do |変数|
    処理
    end
  end
end

自動生成。

シードを作る

ダミーのデータを用意してデータベースの動きを把握する。そのためシードに記述する。

rails db:seedseeds.rbをシードとして実行するコマンド。データベースにデータが追加される。

コントローラーを作成

rails generate controller モデル名複数形 アクション:モデルのコントローラーとアクションとテンプレートが作成される。モデルは単数系、コントローラーは複数系という命名規則に則る。

アクションでデータを表示する

class PeopleController < ApplicationController

  def index
    @msg = `モデル名 data.`
    @data = モデル名.all
  end

end

変数 = モデル名.all:allはモデルのデータを全て、配列のようなものにして取り出すメソッド。
得られるものの正確な名称はActiveRecord::Relationというオブジェクト。
Enumerableという機能を使って配列のように値を順番に取り出せるようにしてくれる。

テンプレートの作成

繰り返し処理を使って順にデータを取り出していく。

<% @data.each do |obj| %>
  objを処理
<% end %>

@dataから順にデータを取り出して変数objに入れて処理を実行している。

モデルを利用してデータベースから取り出されるデータは全てモデルのインスタンス。
テーブルに用意されている項目で保管されていて、全て取り出せる。

<td><%= obj.id %></td>
・・・

モデル作成時に自動的に生成される項目がある。
id:データにつけられる番号
created_at:データの作成日時
updated_at:データの最終更新日時

ルーティグの設定

ジェネレータコマンドで作ると自動で生成される。

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

ローカル 環境開発 Ruby on Railsの環境構築方法

はじめに

ローカル環境でRailsの環境構築ができるようになり、http://localhost:3000/で以下の表示がされるとゴールです。
初めて、ローカル環境で構築しましたので、その方法を記載します。
スクリーンショット 2021-02-27 11.08.49.png

到達点

以下の1点を達成する
・http://localhost:3000/ で上記の図を表示する

仕様

初期仕様
・mac
・Ruby(2.6.3)

今回インストールするバージョン
・Ruby on Rails (6.1.1)
・MySQL (8.0.23)
・Homebrew (2.7.6)

流れ

① Homebrewインストール
② bundlerインストール
③ MySQLインストール
④ railsインストール
⑤ アプリケーションの新規作成
⑥ サーバー立ち上げ

① Homebrewインストール

ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

ターミナルで上記のコマンドでHomebrewインストールをします.

brew -v

インストールされたか確認します

② bundlerインストール

gem install bundler

ターミナルで上記のコマンドでbundlerをインストールします

③ MySQLインストール

brew install mysql

ターミナルで上記のコマンドでMySQLをインストールします

brew info mysql

インストールされたか、バージョンも含め確認します

MySQLの自動起動設定

brew services start mysql

このコマンドでmacが再起動すると、自動的にSQLが再起動されます

MySQLパスワード設定

MySQLはHomebrewでインストールすると、パスワードなしでしようできるため、
パスワードありにしたい場合、下記のコマンドをします

mysql_secure_installation


④ railsインストール

gem install rails

ターミナルで上記のコマンドでRailsをインストールします

rails -v

インストールされたか、バージョンも含め確認します

⑤ アプリケーションの新規作成

rails new app -d mysql

rails new app  *これでもできるかも

railsはmysqlがデフォルトでないため、-d mysqlを使いましたが必要ないかもしれません。
このコマンドで、アプリケーションの新規フォルダを作成します。
数分かかります。

⑥ サーバー立ち上げ

cd app

rails db:create  *データベースがないとエラーになりました

rails s

appフォルダに移動し,サーバーを立ち上げます。
http://localhost:3000/に以下の画面が表示されれば、成功です!!
スクリーンショット 2021-02-27 11.08.49.png

参考記事

VS codeの初期設定とRuby on Railsの環境設定 環境構築
ActiveRecord::NoDatabaseError: Unknown database 'アプリ名_development' 解決策

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

[Ruby]irbが動作しなくなったお話。[エラー]

先日、irbを起動させようと思ったら、、、

~ $ irb
/usr/local/Cellar/rbenv/1.1.2/libexec/rbenv-exec: /Users/inouesyo/.rbenv/versions/2.6.6/bin/irb: /Users/inoueshou/.rbenv/versions/2.6.6/bin/ruby: bad interpreter: No such file or directory
/usr/local/Cellar/rbenv/1.1.2/libexec/rbenv-exec: line 47: /Users/inouesyo/.rbenv/versions/2.6.6/bin/irb: Undefined error: 0

という表示が出て起動できず。
エラーを読むに原因は恐らく、以前にホームディレクトリの名前を変更したのでそれが原因でパスが通ってないのかな?と推測。

と言うことで、user/Users/inouesyo/.rbenv/versions/2.6.6/bin/irb:のirbファイルを編集する。
irbファイルはUNIX実行ファイルとなっているため、右クリック→このアプリケーションで開く→からテキストエディタで開く。
すると

irb
#!/Users/inoueshou/.rbenv/versions/2.6.6/bin/ruby
#
# This file was generated by RubyGems.
#
# The application 'irb' is installed as part of a gem, and
# this file is here to facilitate running it.
#

require 'rubygems'

version = ">= 0.a"

str = ARGV.first
if str
  str = str.b[/\A_(.*)_\z/, 1]
  if str and Gem::Version.correct?(str)
    version = str
    ARGV.shift
  end
end

if Gem.respond_to?(:activate_bin_path)
load Gem.activate_bin_path('irb', 'irb', version)
else
gem "irb", version
load Gem.bin_path("irb", "irb", version)
end

一行目のホームディレクトリの部分が以前のホームディレクトリ名になっていたので変更。

~ $ irb
irb(main):001:0> 

無事に動作し一安心。

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

Rspecを動かすまでまとめ(簡易版: 単体・統合テスト)

今回はテスト内容などにはあまり触れていません
Rspecを使ってテストするまでをまとめています。

Gemfile
group :test do
  gem 'capybara'
  gem 'rspec-rails'
  gem "factory_bot_rails"
  gem 'faker'
end

group :test do 〜 endの中を変更し、bundle install します。

ターミナル
$ bundle install
$ rails g rspec:install
spec/rails_helper.rb
 #最後の方に追加する
 config.include FactoryBot::Syntax::Methods
end

記述することでletを使用した際に、FactoryBotが使用できるようになります。

factories/item.spec.rb
FactoryBot.define do
  factory :"モデル名" do
    "カラム名" { Faker::Lorem.characters(number: 10) }
  end
end

Faker::Lorem.characters(number: 10)とは
テスト用の文字列を作成する。今回の場合はカラム名のところに10文字のテストデータを作成してくれます。

spec_helper.rb
RSpec.configure do |config|
  #追加します
  config.before(:each, type: :system) do
    driven_by :rack_test
  end

#省略
end

単体テスト

models/item_spec.rb
require 'rails_helper'

RSpec.describe 'Itemモデルのテスト', type: :model do
  describe 'バリデーションのテスト' do
    subject { item.valid? }

    let(:user) { create(:user) }
    let(:genre) { create(:genre) }
    let!(:item) { build(:item, user_id: user.id, genre_id: genre.id) }

    context 'nameカラム' do
      it '空でないこと' do
        item.name = ''
        is_expected.to eq false
      end
      it '2文字以上であること: 2文字は〇' do
        item.name = Faker::Lorem.characters(number: 2)
        is_expected.to eq true
      end
      it '2文字以上であること: 1文字は×' do
        item.name = Faker::Lorem.characters(number: 1)
        is_expected.to eq false
      end
    end
  end
end
ターミナル
$ rspec spec/models/item_spec.rb

単体テストの実行できます。
今回はItemモデルのテスト実行しています。

統合テスト

system/test.spec.rb
require 'rails_helper'

 describe 'トップ画面のテスト' do
   before do
     visit root_path
   end

   context '表示内容の確認' do
     it 'URLが正しい' do
        expect(current_path).to eq '/'
     end
   end
 end

ターミナル
$ rspec spec/system/test.spec.rb

統合テストの実行できます。

これでテスト環境は出来ましたので
追加したい項目を書いていくだけになります。

おまけ

system/test_spec.rb
let(:"モデル名") { FactoryBot.create(:"モデル名", "カラム名": "データ") }
let(:item) { create(:item, user_id: user.id, genre_id: genre.id) }

2個目の記述のようにFactoryBotを省略できます。

letとlet!の違い

item_spec.rb
let(:genre) { create(:genre) }
let!(:item) { build(:item, user_id: user.id, genre_id: genre.id) }
  • letを使用した場合は処理が行われずにitの中で呼ばれたときにcreateが実行されます。
  • let!を使用した場合はそのまま処理される。
    毎回呼び出す必要のないものは" ! "を外した方がわかりやすくなります。

createとbuildの違い

item_spec.rb
let(:genre) { create(:genre) }
let!(:item) { build(:item, user_id: user.id, genre_id: genre.id) }
  • createはにメモリにデータを保存する。
  • buildはDBにデータ保存する。
使い分け方

今回はアイテムを登録するためのバリデーションをテストしますので
createで実行すると一回でもテストに通るとメモリに残っているので
アイテムが登録されている状態でバリデーションのテスト行うようになってしまうため、
ちゃんとテストが実行されているかが判断できなくなります。
これから確認したい対象物にはbuildを使うことがいいと思います。

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

if文なしでじゃんけん

if文なしでじゃんけんに触発されました

プログラム

janken.rb
HANDS = ["✊", "✌️", "?"]
RESULTS = ["あいこ", "かち", "まけ"]
me = gets.chomp
enemy = gets.chomp
p RESULTS[(HANDS.index(me) - HANDS.index(enemy)).abs]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 Ruby on Rails 6.0 】 AWS + Nginx + Unicornでデプロイ⑦

始めに

前回の内容でNginxというWebサーバーを導入しRailsアプリを本番環境で起動出来ました。
しかし、現状だと開発環境で変更したものを本番環境に反映させるのに工数が多く不便です。
そこで、Capistranoという自動デプロイツールを導入しコマンドひとつでデプロイ作業を完了できるように設定していきます。

目次

目次 内容
セクション1 EC2インスタンス作成
セクション2 Linuxサーバー構築
セクション3 データベース設定
セクション4 EC2上でGemをインストールし環境変数を設定
セクション5 Railsアプリを起動
セクション6 Nginxの導入
セクション7 自動デプロイ(今回の内容)
セクション8 独自ドメイン取得

Capistranoの導入

必要なGemをインストールし、Capistranoを動かすために必要な設定ファイルを生成します。

Gemfile(ローカル)
group :development, :test do
  gem 'capistrano'
  gem 'capistrano-rbenv'
  gem 'capistrano-bundler'
  gem 'capistrano-rails'
  gem 'capist
end
ターミナル(ローカル)
$ bundle install

# gemを読み込めたら、下記のコマンドを打ちます。
$ bundle exec cap install

するとデプロイについての設定を書くファイルが自動で生成されます。

Capfileを編集

Capfileは、capistrano全体の設定ファイルです。

Capifile
require "capistrano/setup"
require "capistrano/deploy"
require 'capistrano/rbenv'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
require 'capistrano3/unicorn'

Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }

デプロイについての設定ファイルを編集

cap installコマンドを打つと、config/deploy配下にproduction.rbとstaging.rbの2 種類のファイルが生成されます。

production.rbですが2つファイルがあります
❌ config/environment/production.rb
⭕️ config/deploy/production.rb

今回作業はするのはconfig/deploy/production.rbです。

production.rbを下記のように編集

config/deploy/production.rb
# 最下部に追記
server 'XX.XXX.XX.XXX(Elastic IP)', user: 'ec2-user', roles: %w{app db web}

deploy.rbを編集

config/deploy.rb
# 全て削除して以下を追記
# config valid only for current version of Capistrano
# capistranoのバージョンを記載。固定のバージョンを利用し続け、バージョン変更によるトラブルを防止する
lock '○.○○.○(Capistranoのバージョン)'

# Capistranoのログの表示に利用する
set :application, '○○○(自身のアプリケーション名)'
set :deploy_to, '/var/www/○○○(アプリ名)'

# どのリポジトリからアプリをpullするかを指定する
set :repo_url,  'git@github.com:○○○(Githubのユーザー名)/○○○(レポジトリ名.git'

# バージョンが変わっても共通で参照するディレクトリを指定
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system', 'public/uploads')

set :rbenv_type, :user
set :rbenv_ruby, '○.○.○(rubyのバージョン)' #カリキュラム通りに進めた場合、2.5.1か2.3.1です

# どの公開鍵を利用してデプロイするか
set :ssh_options, auth_methods: ['publickey'],
                  keys: ['~/.ssh/○○○○○.pem(ローカルPCのEC2インスタンスのSSH鍵(pem)へのパス 例:~/.ssh/key_pem.pem))'] 

# プロセス番号を記載したファイルの場所
set :unicorn_pid, -> { "#{shared_path}/tmp/pids/unicorn.pid" }

# Unicornの設定ファイルの場所
set :unicorn_config_path, -> { "#{current_path}/config/unicorn.rb" }
set :keep_releases, 5

# デプロイ処理が終わった後、Unicornを再起動するための記述
after 'deploy:publishing', 'deploy:restart'
namespace :deploy do
  task :restart do
    invoke 'unicorn:restart'
  end
end

以下の5点は書き換えが必須です。

  1. 3行目のはご自身のバージョンを記述しましょう。
  2. 6行目の<自身のアプリケーション名>はご自身のものを記述しましょう。
  3. 9行目の/<レポジトリ名>も同様に、ご自身のもの記述してください。
  4. 15行目の<このアプリで使用しているrubyのバージョン>はご自身のものを確認して 記述してください。
  5. 19行目の<ローカルPCのEC2インスタンスのSSH鍵(pem)へのパス>も同様に、ご自 身のもの記述してください。

Capistranoのバージョン確認

capistranoのバージョンは、gemfile.lockに記載されています。
capistrano(3.11.0)ように記載されています。

rubyのバージョンの確認

ローカルのターミナルで以下のように確認します。

ターミナル
$ ruby -v

# 実行結果
ruby2.6.5....

unicorn.rbの記述を編集

capistrano導入後はアプリケーションのディレクトリ構造が変化するので以下のように記述を編集します。

config/unicorn.rb
app_path = File.expand_path('../../', __FILE__)

worker_processes 1

working_directory app_path
pid "#{app_path}/tmp/pids/unicorn.pid"
listen "#{app_path}/tmp/sockets/unicorn.sock"
stderr_path "#{app_path}/log/unicorn.stderr.log"
stdout_path "#{app_path}/log/unicorn.stdout.log"




# 以下のように変更↓




# ../が一つ増えている
app_path = File.expand_path('../../../', __FILE__)

worker_processes 1
# currentを指定
working_directory "#{app_path}/current"

# それぞれ、sharedの中を参照するよう変更
listen "#{app_path}/shared/tmp/sockets/unicorn.sock"
pid "#{app_path}/shared/tmp/pids/unicorn.pid"
stderr_path "#{app_path}/shared/log/unicorn.stderr.log"
stdout_path "#{app_path}/shared/log/unicorn.stdout.log"

Nginxの設定ファイルの記述を編集

同様に、Nginxの設定ファイルもディレクトリで構造が変化しているので編集します。

ターミナル(EC2)
# SSHログインしてから実行
[ec2-user@ip-172-31-25-189 ~]$ $ sudo vim /etc/nginx/conf.d/rails.conf
rails.conf
upstream app_server {
  # sharedの中を参照するよう変更(/shared/tmp/sockets/unicorn.sock;)
  server unix:/var/www/○○○○○(アプリケーション名/shared/tmp/sockets/unicorn.sock;
}

server {
  listen 80;
  server_name XX.XXX.XXElastic IP;

  # currentの中を参照するよう変更(/current/public;)
  root /var/www/○○○○○(アプリケーション名/current/public;

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
    # currentの中を参照するよう変更(/current/public;)
    root   /var/www/○○○○○○(アプリケーション名/current/public;
  }

  try_files $uri/index.html $uri @unicorn;

  location @unicorn {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://app_server;
  }

  error_page 500 502 503 504 /500.html;
}

編集し終わったら、escキーを押して:wqで保存します。
Nginxの設定を変更したら、忘れずに再読込・再起動をします。

ターミナル(EC2)
# nginx起動
[ec2-user@ip-172-31-25-189 ~]$ sudo systemctl reload nginx
[ec2-user@ip-172-31-25-189 ~]$ sudo systemctl restart nginx


# nginxのステータスを確認(active runningであれば成功)
$ sudo systemctl status nginx.service
 nginx.service - The nginx HTTP and reverse proxy server
   Loaded: loaded (/usr/lib/systemd/system/nginx.service; disabled; vendor preset: disabled)
   Active: active (running) since  2021-02-17 11:28:00 UTC; 7h ago


# エラーが出た場合はnginxのエラーログを確認
[ec2-user@ip-172-31-45-167 アプリ名]$ sudo cat /var/log/nginx/error.log

データベースの起動を確認

データベースが立ち上がっていないとデプロイが失敗するので起動します。

ターミナル(EC2)
[ec2-user@ip-172-31-25-189 ~]$ sudo systemctl restart mariadb

# ステータスを確認(active runningなら成功)
[ec2-user@ip-172-31-25-189 ~]$ sudo systemctl status mariadb

unicornのプロセスをkill

自動デプロイを実行する前にunicornのコマンドをkillします。

ターミナル(EC2)
# プロセスを確認
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ ps aux | grep unicorn

ec2-user 17877  0.4 18.1 588472 182840 ?       Sl   01:55   0:02 unicorn_rails master -c config/unicorn.rb -E production -D
ec2-user 17881  0.0 17.3 589088 175164 ?       Sl   01:55   0:00 unicorn_rails worker[0] -c config/unicorn.rb -E production -D
ec2-user 17911  0.0  0.2 110532  2180 pts/0    S+   02:05   0:00 grep --color=auto unicorn

# 一番上のunicorn_rails masterをkillしたいので、下記を実施
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ kill 17877

ローカルでの修正を全てmasterにpush

ローカルでのコードの変更が、全てmasterにpushされていることを確認しましょう。

自動デプロイ実行

これで、ローカルのターミナルからコマンド一発でデプロイできるようになりました。

ターミナル(ローカル)
# アプリケーションのディレクトリで実行する
$ bundle exec cap production deploy

エラーがなく完了したらデプロイ完了です!
スクリーンショット 2021-02-27 7.41.00.png

ブラウザからElastic IPでアクセスすると、アプリケーションにアクセスできます!
確認してみましょう。

もし途中でエラーが出たら

  • EC2のターミナルでless log/unicorn.stderr.logコマンドでエラーログを確認
  • ローカルでの編集のpushやpullを忘れていないか確認
  • mariaDBやNginxを再起動してみる
  • EC2インスタンスを再起動してみる

終わりに

ここまででRailsアプリを本番環境にデプロイする工程が終了しました!
ただ、IPアドレスだと覚えにくいので次回で独自ドメインを取得方法をまとめたいと思います。
お疲れさまでした。。。

次回
独自ドメイン取得

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

アセットパイプラインに関わる画像が表示されないエラー

今回のエラー

制作環境は

  • Ruby on Rails
  • unicorn
  • AWS EC2
    • Nginx

Nginxを導入して設定をした後から、本番環境でトップページに表示される予定の画像が表示されない状態になっていた。
ログイン画面のアニメーションとして用意してあった音声ファイルが読みこまれていない。
一部では表示されている画像もあった。

原因

アセットパイプラインの参照先の記述に問題があった。

問題箇所

今回の不具合箇所は2点

  • トップページの画像の記述
    <%= image_tag("/assets/title_logo.png", class: "header-logo" ) %>

  • ログイン画面の音声ファイル
    <%= audio_tag("/assets/submit", autoplay: false, controls: true, id:"audio_sub") %>

対処

行ったことは
1. 参照先URLを修正
2. git hubに修正をpush
3. 本番環境でプリコンパイル、プロセス再起動

1. コードを以下のように変更

  • トップページの画像の記述
    <%= image_tag "title_logo.png", class: "header-logo" %>

  • ログイン画面の音声ファイル
    <%= audio_tag "submit", autoplay: false, controls: true, id:"audio_sub" %>

 変更箇所

変更したのは参照先のURL部分
/assets/部分を削除した

2. git hubに変更をpush

git hubデスクトップ上でpush
ブランチを切っているなら、masterにmarge
※GUI、ブラウザでの操作だったので詳細割愛

3. 本番環境でプリコンパイル、プロセス再起動

以下ターミナルで操作

  • 今回はAWSのEC2でデプロイしているので、EC2のデプロイ済みのインスタンスにログイン
  • git masterをpull
    • git pull origin master
  • プリコンパイル
    • rails assets:precompile RAILS_ENV=production
  • nginxを再読み込み
    • sudo systemctl reload nginx
    • sudo systemctl start nginx
  • プロセス再起動

    • プロセス確認ps aux | grep unicorn
    • プロセス停止kill (rails masterのプロセス番号)
    • アプリケーションサーバー起動RAILS_SERVE_STATIC_FILES=1 unicorn_rails -c config/unicorn.rb -E production -D

以上で改善した

気づいた事

  • 開発環境ではimage_tag,audio_tagを複数使用しており、URLに/assets/が混同していても問題なかったが、unicorn、nginx(webサーバー)が加わり問題が起きた
  • コードが改善していても改めてプリコンパイルをしないと表示されなかった

原因の仮説

以下は今回の対処にあたって思ったこと。
調べきれていないので、メモとして書いておく。

  • Nginxを利用するとプリコンパイルあたりで違う挙動をする?
  • webサーバー(Nginx)は静的な情報を返すもので、動的な情報はアプリケーションサーバーに割り振るような仕組みだと理解しているが、静的な情報としてプリコンパイルをする際に、アセットパイプラインで参照している? そのため、'''/assets/'''と記述してあるとアセットパイプラインとは別に、個別参照になりプリコンパイルがうまくいかなかった?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSのEC2でデプロイ後、背景画像が本番環境で反映されない・・・

初めに

今回初投稿になります。
私は現在プログラミング学習を始めて約4ヶ月一通り簡単なポートフォリオは作成できる技術はついてきましたが、まだまだ初学者でエラーで止まることが多いため、振り返り学習を兼ねてエラーで詰まったことや日々の学習での気付きをその都度投稿します。

今回EC2でのデプロイ時、ローカル環境で反映されていたポートフォリオの背景画像が本番環境で反映されないエラー解決に苦戦したので私が行った解決方法をまとめておきます。

今回の問題について

Rubyをベースにポートフォリオを作成して、AWSのEC2を利用し設定して本番環境にデプロイを試みたところ、ブラウザー上で作成したポートフォリオの背景画像のみうまく表示されず、背景がまっさらの状態に・・・・

同じimagesディレクトリ内に一緒に置いているロゴの画像は反映されているので、EC2の設定のせいで画像の読み込みが出来ていないということはない様子。
アプリ自体の他の機能も問題ない状態でした。

アプリケーションサーバーはUnicorn
WEBサーバーはNginxを使用しています。

考えられる要因

1.背景画像の容量が大きい為、反映されない
2.画像を適用しているCSSの記述のミス
3.デプロイの設定時のミス

試みた対処法

まずはロゴ画像は反映されている状況から①の画像容量の問題かと思い、画像容量を落とした背景画像の差し替えて再度デプロイして確認しましたが、相変わらず背景はまっさらなままでした。
そのため画像容量の問題ではないと判断。

次に②の背景画像を適用しているCSSの記述の見直しの為、同じ症状のエラーがないか調べて記述を見直ししました。
修正前
・style.css

.big-bg {
background-image: url('main-bg.jpg');
min-height: 95vh;
min-width: 100vw;
}


修正後
・index.scss

.big-bg {
background-image: image-url('main-bg.jpg');
min-height: 95vh;
min-width: 100vw;
}

背景画像の記述をstyle.cssからindex.scssへ書き換え、
background-image: url('main-bg.jpeg') → background-image: image-url('main-bg.jpeg')に変更

調べた限りではこの方法で解消される可能性が高いようなので、改めてデプロイして確認したところ・・・・・ 背景まっさら・・・・・なぜ????
他の方で同じ症状の方はこの方法で大抵解消されているようでした。

③のデプロイの設定もまだ初学者の為、カリキュラムを都度都度確認しながら実装しているし、他の機能は問題ない為、設定は間違ってはいないはずなのでエラーログを確認します。
※最終的にまずエラーログから確認するべきでした・・・

EC2内のログをコマンドを入力して確認します
[ec2-user@ip-172-31-23-189 <リポジトリ名>]$ less log/production.log

エラーログを発見し、内容としてはpublicディレクトリ内のassetsディレクトリ内にmain-bg.jpegが無いとゆうものでした。
?????? 読み込み先がpublicディレクトリのassetsディレクトリ? 
普段からassetsディレクトリ内のimagesディレクトリに画像を入れていて問題なかったので、ずっとそうしていましたが、今回はpublicディレクトリを読み込んでいるようです。

その為、さらに同じ症状でのエラー解消をした内容を探してpublicディレクトリ内にassetsディレクトリを作成して、その中にimagesディレクトリを移して背景画像ファイルを配置しました。

その後、改めてデプロイして確認したところ・・・・・  背景画像が反映されています!!!やりました!!!

解決方法

今回はEC2内のエラーログを確認して、エラー内容を確認。
public/assets/images/main-bg.jpeg(画像)と配置することで解消できました。
その前に行ったCSSの記述をSCSSファイルに記述し直したことも必要だったと思います。

さらにその後、デプロイ時の設定を見直しておそらくですがアセットファイルのコンパイルのコマンドを設定し忘れていたような気もするので、そこも気をつけて今後EC2の設定をしようと思いました。

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

【Rails】 初心者向け!gem 'dotenv-rails'の使い方

はじめに

最近当たり前のように使用している'dotenv-rails'のgemと環境変数という言葉ですが、使い始めた頃は何が何だかよく分からなかった覚えがあります。そんな自分自身の復習と、同じような初心者の方の参考になればと思い、なるべく一つ一つ丁寧に、また画像なども掲載しつつ投稿させていただいております。

開発環境

ruby 2.6.3
Rails 5.2.4.4

dotenv-railsとは

環境変数を管理することができるgemです。
自身で作成したアプリケーションの直下に.envファイルを作成することで、パスワードなどネット上に公開させたくない情報を扱い、自動で読み込むことが可能です。

環境変数とは

参考書やネットで当たり前に出てくる「環境変数」という言葉。
私は当初この言葉の意味すら分かりませんでしたので、説明を掲載しておきます。
簡単に言うと、ネット上に公開させたくない情報を入れておく箱になります。この箱自体には、自身で任意の名前をつけることが可能です。つまり、ネット上に公開させたくない情報は、一度この環境変数という箱の中に入れ、コード上で使用することで、情報漏洩を防ぐことができます。

gemを導入

Gemfile.
gem 'dotenv-rails'
terminal.
$ bundle install


.env ファイルを作成

① アプリケーション名のディレクトリにカーソルを合わせ、右クリックします。

② New Fileを選択します。

③ ファイル名を「.env」にします。
  画像のような配置でファイルを作成できていれば、完成です!

④ 以下のような形で必要な情報を記述してください。

(参考画像)
 

.env
# Google map用
GOOGLE_API_KEY = "***************"

# 問い合わせ機能用
SEND_MAIL = "***************"
SEND_MAIL_PASSWORD = "***************"

# デプロイ用
DB_USERNAME = "***************"
DB_PASSWORD = "***************"
DB_HOST = "***************"
DB_DATABASE = "***************"


環境変数の記述の仕方

ENV['設定した名前']
たったこれだけの記述で環境変数を扱うことができます。

ENV['GOOGLE_API_KEY']
ENV['SEND_MAIL']


.gitignoreファイルへ追記

アプリケーション直下にある、Gitの管理に含めないファイル名を記述するファイルです。
こちらのファイルの他にも、ファイル名の前に.(ドット)が付いているものは、Gitの管理に含めないファイルとして取り扱われています。
※ こちらに「/.env」の記述をしないと、全ての情報がネット上に公開されてしまうので注意!

.gitignore
# 最終行に下記の記述を加えてください。
/.env


万が一GitHubにpushしてしまったら...

誤ってGithub上に公開してしまっている方は、下記のコマンドで.envをGitの管理から外してください。
コマンド実行後は、.gitignoreファイルに「/.env」を追記した上で、再度git pushをし直してください。

● git rm --cached [ファイル名]
  ファイル自体は残したままGitの管理から外すことが可能なコマンド

terminal.
$ git rm --cached .env


終わり

今回は以上になります。
私自身もプログラミング初心者ですが、同じ様な立場の方に少しでも参考になれば幸いです。
また、もし内容に誤りなどがございましたら、ご指摘いただけますと幸いです。

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