form_withでredirect_toがページ遷移しない時の原因

【結論】

・form_withはデフォルトでremote: true
 というオプションが設定されている

・remote: trueを設定すると
 フォーム送信を非同期通信で実行できるが、
 ページ遷移されなくなるので、
 redirect_toが機能しなくなる

・この場合、local: trueというオプションを
 設定すれば解決する

【目次】

【本題】

form_withを初めて使って見た

今まで、フォームの生成には
「form_tag」「form_for」を利用してきましたが、
新たに「form_with」を使ってみることにしました。

というのも、Rails5.1以降は、「form_tag」「form_for」が
非推奨となっており、いずれ「form_with」に統合される見通しの為です。

なお「form_with」は、「form_tag」「form_for」の
いいとこ取りをした様なビューヘルパーです。

という訳で、全く仕様を理解しないまま使っていると、
思わぬところでつまづいたので、今回はそれをまとめます。

redirect_toでページ遷移できない

「form_with」で下記の様な
新規登録フォームを実装しました

def create
    @request = request.new(request_params)
    if @request.save
        redirect_to requests_path
    else
        render :new
    end
end

正常に保存が完了すれば、redirect_toで
指定したパスに飛ぶ様な記述になっています。

ところが、登録操作をしても全くページ遷移しません。
データベースには正常にデータが保存されており、
更にbinding.pryを設置して確認すると、
trueでIF文が分岐している事が分かりました。

原因が特定できず、現場のメンターに
確認すると、「form_with」が原因である事が分かりました。

オプション remote: trueについて

その理由は、「form_with」には、
「 remote: true」というオプションが設定されている事です。

「remote: true」とは、リクエストを
HTML形式ではなく、JS形式で送信するオプションです。

これにより、フォーム送信が非同期通信で実行されます。
但し、非同期通信でリクエストを送った場合、
redirect_toが設定されていても、ページ遷移はなされません。

この仕様により、データは保存されているにも関わらず
ページが遷移しないという問題が発生していたのです。

実際に、先ほどのコードを実行した直後の
処理結果をターミナルで確認すると、
下記の様なJS形式になっていました

Started POST "/request/1" for 127.0.0.1 at 2019-03-26 18:38:00 +0900
Processing by RequestsController#create as JS

HTML形式の場合、下記の様になります。

Started POST "/request/1" for 127.0.0.1 at 2019-03-26 18:38:00 +0900
Processing by RequestsController#create as HTML

解決策:local: trueの設定

この問題を解決するには、
「form_with」に「local: true」という
オプションを設定します。

これにより、フォームの送信は
同期通信に切り替わるので、ページ遷移が可能となります。

 <%= form_with model: @request ,local: true  do |f| %>

MySQLの環境構築で詰まった時に実施したクリーンインストール

【結論】

・以前からMySQLを利用していて
 新しいバージョンをインストールした際
 以前のバージョンの設定が干渉して
 正常に起動しない場合がある

・その場合、以前のバージョンも含め
 全てアンインストールしてから
 インストールし直すと改善する可能性がある

【目次】

【本題】

MySQLの環境構築

以前までローカル環境では、MySQLの5.6を利用していたのですが、
職場で5.7を利用する案件があったので、環境構築をし直しました。

但し、下記の様なエラーが発生して上手くMySQLが起動しません。

ERROR! The server quit without updating PID file 
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)

sockを新たに作成したりなんやかんやしましたが、
結局改善せず、現場の長に相談したところ、クリーンインストールで直りました!

MySQLクリーンインストール

brew services stop mysql@5.7
brew uninstall mysql@5.7
brew cleanup

sudo rm -rf /usr/local/mysql
sudo rm -rf /usr/local/var/mysql
sudo rm -rf /usr/local/mysql*

この後、パソコンも再起動し直しています。

長いわく、以前からMySQLを利用していて新しいバージョンをインストールし様とすると、
以前のバージョンの設定が干渉して正常に起動しない場合があるそうです。

確かに以前は5.6をスクールのカリキュラムそのままコピペで
なんとか環境構築したので、そこでの設定が何か影響を及ぼしていたのかもしれません。

なお、クリーンインストールすると、 今までのローカル環境のデータは全て消えるので、
ご利用の際は、その点注意が必要です。

基本的な脆弱性攻撃について(SQLインジェクション、XSS、CSRF)

【結論】

SQLインジェクションとは、
 フォームに直接SQLを入力する事で、  意図しないSQL文を実行させる攻撃手法

XSSとは、入力フォームなどを用いて
 WebページにJavaScriptなどのスクリプトを仕込み、
 意図しない動作を実行させる攻撃手法

CSRFとは、対象のWebサービスへのログイン状態が
 維持されたユーザーに、悪意あるスクリプトが組み込まれた
 URLをクリックさせる事で、ユーザーが意図していない操作を
 強制的に実行させる攻撃手法

【目次】

【本題】

脆弱性攻撃とは

ソフトウェアに、開発者が意図しない操作を実行させて、
本来出来ない様な情報の取得/改竄などを行う事を指します。

脆弱性は、セキュリティホールとも呼ばれています。
今回は、代表的な脆弱性攻撃と対策についてまとめます。

※参考URL
railsguides.jp

SQLインジェクション

アプリケーションが意図していないSQL文を実行させる事で、
本来アクセス出来ない情報の取得や改竄などを行う手法です。

実行されると、下記の様な問題が起こる可能性があります。
・データベース内の個人情報を取得する
・アカウント認証を回避してサービスを利用する
・別ユーザーになりすまして、サービスを利用する
・データベースの情報を削除して、サービスを機能しなくする

具体的な実行方法としては、
ログイン画面のユーザー名・パスワードといった
入力フォームに直接SQLを入力する方法です。

脆弱性があると、そのSQLをアプリケーションが実行してしまい、
その命令文に応じた処理を行ってしまいます。

対策

Railsの場合、findやwhereなどのメソッドが 指定した条件に基づいて、SQL文を生成します。

モデルクラスのインスタンスメソッドである 「find」「find_by」に関しては、「'」「"」「NULL」「改行」といった 特殊なSQL文字をエスケープする仕様が元々実装されています。

しかし、「where」「execute」「find_by_sql」に関しては、 その仕様が実装されていない為、手動でエスケープする必要があります。

なお、条件オプションで文字列を直接渡さず、 ハッシュか配列を渡すことで、汚染された文字列をサニタイズすることもできます。 サニタイズとは、特殊文字を一般的な文字列に変換する処理の事です。

↓例

○ハッシュ

User.where(name: params[:name], password: params[:password])

○配列

User.where(["name = ? and password = ?", params[:name], params[:password]])

×文字列

User.where("name = '#{params[:name]}', password = '#{params[:password]}'")

XXS

入力フォームなどを用いて
WebページにJavaScriptなどのスクリプトを仕込み、
意図しない動作を実行させる攻撃手法です。

クロスサイトスクリプティング と呼ばれる下記の様な攻撃を行います。
・クッキーの盗難による不正アクセス
・偽サイトに誘導してフィッシングでアカウント情報を不正取得
・不正なスクリプトを含むリクエストを送信し権限を変更

これを防ぐには、フォームで「<」「>」「?」などHTMLタグとして
認識される文字が入力された際に、エスケープさせる必要があります。

Railsでは、<%= %>というタグを用いれば、
文字列の変換が自動的に行われるような仕組みになっています。

CSRF

あるWebサービスへのログインが維持された状態で 悪意あるスクリプトが組み込まれたURLをクリックする事で、 ユーザーが意図していない操作を実行させる攻撃手法です。

ユーザーがURLをクリックすると、 ブラウザのCookieに保持されているログイン情報を用いて WEBサービスに強制的にアクセスさせ、
ユーザーの権限で実行できる様々な操作を行わせます。

Railsの場合、ApplicationControllerに
下記の記述を施す事で対策しています。

protect_from_forgery with: :exception

これはアプリのフォームに対してトークンを発行するコードで、 フォーム送信時にこのトークンも合わせて送信する事で、 正しいフォームからの通信なのかを判別することができます。

これにより、Cookieに保持されたセッション情報だけで
WEBサービスにアクセス出来ない様になっています。

Unicornの設定ファイルを読み解く(Part2)

【結論】
・「ワーカープロセス」とは、
 アプリケーションが実行されるプロセス

・ソケットとは、ネットワークの出入り口となる部分

・プロセスIDとは、実行されているプロセスを見分ける為の識別子


【目次】


【本題】

前回の続き

ryoutaku-jo.hatenablog.com

「app_path」の値は、「/var/www/アプリ名」という
絶対パスを文字列にしたものだと判明しました。

↓参考URL
Unicorn設定のまとめ - Qiita
unicorn.rb - ソフトウェアエンジニアリング - Torutk
rails-sample/config/unicorn.rb at master · violetyk/rails-sample · GitHub

ワーカープロセス数の設定

worker_processes 1


「ワーカープロセス」の数を設定します。

「ワーカープロセス」とは、
アプリケーションが実行されるプロセスのことです。

つまり、「worker_processes」では、
並行して処理できるプロセスの数を指定できます。

なお、「プロセス」とは、
パソコンのCPUが実行するひとまとまりの処理を指します。

並行して処理できるプロセスの数は、 CPUのコア数に依存するので、
CPUのコア数以下に設定できません。
なので、サーバーのメモリなどによって変更する必要があります。

また、autoにすることで自動でコア数を設定する事も可能です。

アプリを実行するディレクトリを設定

working_directory "#{app_path}/current"

Unicornの起動コマンドを実行するディレクトリを指定します。

前回の投稿で説明した通り、Capistranoで自動デプロイしている際は、
「current」ディレクトリのアプリを起動させます。

app_pathには、「/var/www/アプリ名」という絶対パスを文字列にした
ものが入っているので、式展開してcurrentとくっつける事で
「/var/www/アプリ名/current」という絶対パスが指定できます。

ソケットの設定

listen "#{app_path}/shared/tmp/sockets/unicorn.sock"

上記はソケットファイルを生成する記述です。
ソケットとは、ネットワークの出入り口となる部分です。

今回は、nginxとの通信を行う為に記述しています。
下記の通り、nginx側にもUnicornのソケットファイルが
指定する記述がなされています。。

rails.conf(Nginxの設定)

upstream app_server {
  server unix:/var/www/<アプリケーション名>/tmp/sockets/unicorn.sock;
}

プロセスIDの設定

pid "#{app_path}/shared/tmp/pids/unicorn.pid"

上記はプロセスIDファイルを生成する記述です。
プロセスIDとは、実行されているプロセスを見分ける為の識別子です。

これが無いと、どのプロセスでunicornが実行されているのか特定できません。

ログファイルの出力先設定

stderr_path "#{app_path}/shared/log/unicorn.stderr.log"
stdout_path "#{app_path}/shared/log/unicorn.stdout.log"

Unicornのエラーログと通常ログの位置を指定します。
unicorn.stderr.log」がエラーで
unicorn.stdout.log」が通常ログです。

動作検証用の接続先設定

listen 3000
timeout 60

とりあえず起動して動作確認をしたい時の設定。

EC2インスタンスのセキュリティグループのポートを
タイプを「カスタムTCPルール」、
プロトコルを「TCP」、
ポート範囲を「3000」、
送信元を「カスタム / 0.0.0.0」に設定しておく必要がある

接続タイムアウト時間も合わせて設定する。

再起動設定

更新時にダウンタイム無しで起動ができる

preload_app true
GC.respond_to?(:copy_on_write_friendly=) && GC.copy_on_write_friendly = true

接続チェック設定

check_client_connection false

アプリケーションを読み込む前に呼ぶ前に、unicornの接続が
同じホスト上のクライアントに限定された状態になっているかチェックする。
切断された接続に対してアプリケーションを呼び出すのを防ぐ

処理制限設定

run_once: true

処理を一度しか実行されない様に限定する

再起動時の古いプロセスの削除設定

before_fork do |server, worker|
  defined?(ActiveRecord::Base) &&
    ActiveRecord::Base.connection.disconnect!

  if run_once
    run_once = false # prevent from firing again
  end

  old_pid = "#{server.config[:pid]}.oldbin"
  if File.exist?(old_pid) && server.pid != old_pid
    begin
      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
      Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH => e
      logger.error e
    end
  end
end

この設定をしておけば、再起動時に古いプロセスをkillしてくれます。

Unicornの設定ファイルを読み解く(Part1)

【結論】

Unicornを導入する際、設定ファイルの内容を
 コピペするだけだと、エラーが発生した際に
 対処しきれない場面が多々ある

・ riコマンドとは、Ruby のクラスやメソッドの
 ドキュメントを、コマンドラインで確認できる機能
 メソッドの仕様を読み解く際に重宝する


【目次】


【本題】

Unicornの設定ファイルの内容を理解したい

AWS(EC2)+Nginx+Unicorn+MySQL+Capistranoという構成で、
何度かアプリのデプロイを行っているのですが、
設定ファイルは毎度コピペなので、イマイチ理解ができていません。

それが原因で、デプロイ時のエラーに何度も躓いたことがありました。
unicornが起動しない(記述内容の誤り)
・エラーログの出力先が分からない などなど・・・

なので今回は設定ファイルの内容を読み解いていきたいと思います。

参考にする設定ファイル

今回は、何度かお世話になっている
下記の内容を読み解いていきたいと思います。

app_path = File.expand_path('../../../', __FILE__)

worker_processes 1

working_directory "#{app_path}/current"
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"

listen 3000
timeout 60

preload_app true
GC.respond_to?(:copy_on_write_friendly=) && GC.copy_on_write_friendly = true

check_client_connection false

run_once = true

before_fork do |server, worker|
  defined?(ActiveRecord::Base) &&
    ActiveRecord::Base.connection.disconnect!

  if run_once
    run_once = false # prevent from firing again
  end

  old_pid = "#{server.config[:pid]}.oldbin"
  if File.exist?(old_pid) && server.pid != old_pid
    begin
      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
      Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH => e
      logger.error e
    end
  end
end

after_fork do |_server, _worker|
  defined?(ActiveRecord::Base) && ActiveRecord::Base.establish_connection
end

riコマンドとは

今回、設定ファイルの内容を読み解くにあたり、
「riコマンド」を多用しました。

riコマンドとは、
Ruby がインストールされているとターミナルで実行可能になるコマンドです。
Ruby Interactive」の略で、Ruby のクラスやメソッドのドキュメントを
ターミナル上で確認できるという便利機能です。

アプリのパス指定

では、設定ファイルの一番上から順に読み解いていきます。
最初の一行はこちらです。

app_path = File.expand_path('../../../', __FILE__)


「File.expand_path('../../../', __FILE__)」を、「app_path」に代入していますが、
「File.expand_path('../../../', __FILE__)」が何をしているのか分かりません。

riコマンドで調べると、下記の様な内容でした。

= File.expand_path

(from ruby core)

File.expand_path(file_name [, dir_string] ) -> abs_file_name



Converts a pathname to an absolute pathname. Relative paths are
referenced from the current working directory of the process unless
dir_string is given, in which case it will be used as the starting
point. The given pathname may start with a ``~'', which expands to the
process owner's home directory (the environment variable HOME must be
set correctly). ``~user'' expands to the named user's home
directory.

File.expand_path("~oracle/bin") #=> "/home/oracle/bin"

A simple example of using dir_string is as follows.
File.expand_path("ruby", "/usr/bin") #=> "/usr/bin/ruby"

A more complex example which also resolves parent directory is as
follows. Suppose we are in bin/mygem and want the absolute path of
lib/mygem.rb.

File.expand_path("../../lib/mygem.rb", __FILE__)
#=> ".../path/to/project/lib/mygem.rb"

So first it resolves the parent of __FILE__, that is bin/, then go to
the parent, the root of the project and appends lib/mygem.rb.


上記から、簡単にメソッドの仕様をまとめました
相対パス絶対パスにして文字列で返す
・第二引数を指定した場合、第二引数で指定したディレクトリを相対パスの基準にする
・「__FILE__」は、コードが実行されたディレクトリを取得する

「__FILE__」で取得されるディレクト

まず、「__FILE__」で取得されるディレクトリを確認します。

Capistranoは自動デプロイの度に「releases」というディレクトリの中に
デプロイした日時のディレクトリを作成して、アプリケーションのファイルを配置します。
(前の状態に簡単に戻せる様に、上書きはしていない)

その配置したディレクトリの一番新しい日時のディレクトリに対して、
「current」ディレクトリからシンボリックリンクが張られます。

そしてアプリケーション起動時には、この「current」ディレクトリが実行されるので、
「__FILE__」で取得されるディレクトリは、「current」という事になります。

「../../../」で指定した相対パス

そして、第一引数で指定されている「../../../」についてですが、
「.../」は一つの上のディレクトリを指定する記述です。
「../../../」だと三階層上になるので、「current」ディレクトリだと
「var/www/アプリ名」が相対パスになります。

「app_path」の内容

そして「var/www/アプリ名」の絶対パスの文字列が「app_path」に代入されるので、
「app_path」の値は、「/var/www/アプリ名」になります。


長くなったので、続きは次回に回します。

Herokuを使ったデプロイ方法

【結論】
・HerokuとはPaaSと呼ばれるアプリケーションを
 実行する為のプラットフォーム

AWSなどの様な複雑なセットアップ作業を経ずに、
 簡単にアプリケーションをインターネットに公開出来るサービス


【目次】


【本題】

Herokuとは

PaaSと呼ばれるアプリケーションを実行する為のプラットフォームです。

jp.heroku.com


Herokuは、本来であれば複雑な処理を行って形成するアプリケーションを
公開する為の土台(プラットフォーム)を、WEB上で提供してくれるサービスになります。

※注意※
下記は、「Rails 5.2.2」で、rails newした際に
「-d mysql」のオプションを指定したアプリで実行しています。
環境によっては、同じように動作しないかもしれないので、ご注意ください

1:Herokuのアカウント登録

まずHerokuのアカウント登録を行います。

jp.heroku.com

1:Heroku CLIのインストール

Herokuのコマンドを使える様にする為に、
下記サイトからHeroku CLIをインストールします。

The Heroku CLI | Heroku Dev Center

2:Gemfileの編集

Herokuを利用するにあたってgemfileを編集していきます

# gem 'mysql2', '>= 0.4.4', '< 0.6.0'
〜中略〜
group :development, :test do
  gem 'mysql2', '>= 0.4.4', '< 0.6.0'
end

まず、データベースに関するgem(今回はmysql2、デフォルトならsqlite3)を、
コメントアウトして、「group :development, :test do」に追加します。

これは、Herokuが「PostgreSQL」というデータベースを利用しているので、
本番環境では別のgemを読み込ませる必要があるからです。

PostgreSQL」のgemは本番環境だけ読み込めれば良いので、
下記のように追加します。

group :production do
  gem 'pg'
end

次にbundle installですが、下記のコマンドで実行します。

bundle install --without production

これは、「group :production」で指定した先ほどの
「gem 'pg'」以外のgemだけインストールする為のコマンドです。

3:database.ymlの編集

Railsではデータベースに接続する為の情報を、
config/database.yml というファイルに保存しているのですが、
デフォルトだとsqlite(今回はmysql)に適した記述になっているので、
そちらを編集します。

production:
  <<: *default
  adapter: postgresql
  encoding: unicode
  pool: 5

adapter: postgresqlが、postgresqlのデータベースに接続する為の記述
encoding: unicodeが、文字コードunicodeで利用する為の記述
pool: 5が、データベースに接続出来る上限を5に制限する記述です。
※5である理由は不明だが、どのサイトも5で説明されていたのでデフォルト値?

4:config/environments/production.rbの編集

Railsの初期状態だとプリコンパイルがオフになっているので、
下記の様に設定を変えます。

config.assets.compile = true

変えていないと、デプロイ時に下記の様なエラーが発生します。

5:ターミナルからHerokuにログイン

ターミナルで下記コマンドを実行して、Herokuにログインします。

heroku login

6:アプリを登録

URLにもなるアプリ名を登録します。

heroku create mini-blogsapp

※「mini-blogapp」は任意です

↑成功
↓失敗(他に登録されている名前と重複)

7:アプリのデプロイとマイグレーション

下記のコマンドでアプリをデプロイさせます。

git push heroku master

ちなみに、リポジトリ名と先ほど設定した名前が異なると、
下記の様なエラーが出ます。

そして下記のコマンドで本番環境のマイグレーションを行います。

heroku run rake db:migrate

無事デプロイできました!

Railsが提供するセッション機能について

【結論】
・セッションとは、
 ステートフルな通信を実現する為に用いられる技術

・ステートフルとは、前に通信した情報を
 引き継いで通信を行う方法

・逆に、前に通信した情報を保持しない通信方法は、
 ステートレスと呼ばれる


【目次】


【本題】

セッションとは

ステートフルな通信を実現する為に用いられる技術です。

ステートフルとは、前に通信した情報を引き継げる通信方法です。
逆に、前に通信した情報を保持しない通信方法を、ステートレスと呼びます。

下記の様な日常会話に当てはめるとイメージしやすいかもしれません。

・ステートレスな場合

男「初めまして、都手藻影臼男です」
女「初めまして」
↓(1週間後)
男「お久しぶりです」
女「・・・あなた誰ですか?」

・ステートフルな場合

男「初めまして、池面太郎です!」
女「こちらこそ初めまして!!」
女(脳裏に焼き付けておこう!!)
↓(1週間後)
男「お久しぶr」
女「お久しぶりです 、池面太郎さん!! 」

HTTP通信はステートレスなので、「クライアントがリクエストを送信し、それに対してサーバがレスポンスを返す」
という一連の通信がそれぞれ独立しているので、前に通信した内容を引き継ぐ事が出来ません。

この特性から、セキュリティ面で優れているのですが、ある機能の実装を困難にしてしまいます。
それがログイン機能(アカウント認証機能)です。

なぜなら、一度ログインしたとしても、通信がステートレスだと、
次の通信ではログインしたという内容が無かったことになってしまうので、
またログインし直さないといけなくなってしまいます。

そこで登場するのが、セッションです。

セッションを用いれば、通信が切り替わっても
ログイン情報を保持しておけるので、ログインし直す必要なく、
次に操作を行う事が可能となります。

セッションの実装方法

セッションは下記の様にして実装できます。

session[キー] = 値

http://railsdoc.com/references/sessionrailsdoc.com

セッションの暗号化

Railsが初期設定の状態であれば、
セッションの情報は、ブラウザのCookieに保存されます。

ブラウザ上にあるので、悪用されるリスクを懸念すると思いますが、
上記の実装方法だと、自動的に暗号化してくれます。

CookieStoreはセッションデータの保管場所をencrypted cookie jarで安全に暗号化します。
これにより、cookieベースのセッションの内容の一貫性と機密性を同時に保ちます。
暗号化鍵は、signed cookieに用いられる検証鍵と同様に、secret_key_base設定値から導出されます。

Rails 5.2から暗号化cookieとセッションはAES GCM暗号化を用いて保護されるようになりました。
この暗号化手法は、認証と暗号化を一括で行える認証付き暗号(Authenticated Encryption)の一種であり、
生成される暗号化テキストが従来のアルゴリズムに比べて短いという特徴があります。AES GCM方式で暗号化されたcookieの鍵の導出には、config.action_dispatch.authenticated_encrypted_cookie_salt設定値で定義されるsalt値が用いられます。

https://railsguides.jp/securityrailsguides.jp

とはいえ、Cookieが丸ごと盗み出されてしまうと、
それをそのまま利用した再生攻撃に利用されるケースもあるので、
暗号化も万能ではありません。

https://railsguides.jp/security#cookiestore%E3%82%BB%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AB%E5%AF%BE%E3%81%99%E3%82%8B%E5%86%8D%E7%94%9F%E6%94%BB%E6%92%83

セッションの管理方法

Railsのセッション管理方法には、
先ほどの方式と合わせて、3つの管理方法があります。

・CookieStore
先ほど説明した方式で、
クライアントのブラウザのCookieに、セッション情報を保存する方法です。
デフォルトでは、こちらが設定されています。

メリット・・・デフォルトなので直ぐ使える。処理が早い。
デメリット・・・クライアント側に保存されるので、不正利用しやすい

・ActiveRecordSessionStore
「ActiveRecordSessionStore」というgemを用いた方法です。
DBへセッション情報を保存できます。

メリット・・・サーバー側に保存するので、不正利用されにくい
デメリット・・・DBに負荷が掛かりやすい

・DalliStore
「Dalli」というgemを利用した方法です。
サーバーのメモリにセッション情報を保存します。

メリット・・・サーバー側に保存するので、不正利用されにくい。処理が早い
デメリット・・・一番導入が面倒

CarrierWaveでの画像アップロード機能の実装方法

【結論】
・CarrierWaveとは、画像アップロード機能を
 簡単に実装する為のgem

・MiniMagickと併用することで
 画像のリサイズが可能になる


【目次】


【本題】

CarrierWaveについて調べた

アプリ開発で画像アップロード機能を実装する際、
CarrierWaveを利用しているのですが、
解像度の設定など、機能面を理解していない部分が多々あったので、いろいろ調べて見ました。

CarrierWaveとは

Railsのgemの一つで、画像アップロード機能を簡単に実装できます。

github.com

CarrierWaveの実装方法

0:「ImageMagick」をインストール
CarrierWaveを使用する為には「ImageMagick」という
画像を操作したり表示したりするためのソフトウェアが必要なので、
まだ導入されていない場合は、下記をターミナルで実行し、インストールします。

brew install imagemagick


1:gemを導入する
「carrierwave 」と「minimagick」を
Gemfileに追記し、ターミナルからbundle installを実行します。

gem 'carrierwave'
gem 'mini_magick'

「minimagick」とは、画像をリサイズする為のgemです。
他にも「rmagick」という似た機能を持つgemも存在しますが、
最新版がインストール出来ない不具合があるそうなので、今回は使用を見送ります。


2:アップローダーを作成する
画像アップロード時の設定を行う為のアップローダーを作成します。
ターミナルで、下記コマンドを実行すると、
app/uploadersディレクトリ以下にimage_uploader.rbが作成されます。

rails g uploader image

3:MiniMagickで画像リサイズが出来る様に記述する
image_uploader.rbに、MiniMagickを読み込んで、
指定した設定で画像リサイズが出来る様に記述をします。

  include CarrierWave::MiniMagick
  process resize_to_fit: [800, 800]

process resize_to_fit: [800, 800]は、
縦横比を維持したまま、縦横800pxで解像度を指定します。

resize_to_fitの他にも、resize_to_limitという設定もあり、
こちらも縦横比を維持したままリサイズしますが
画像のサイズが指定された縦横のサイズ以内の場合は、
リサイズされません。


4:画像の登録用にカラムを追加する
マイグレーションを作成して、画像の登録用にカラムを追加します。
データ型はstringを指定します。

rails g migration add_image_to_users image:string
rails db:migrate

5:モデルにimage_uploaderをマウントする
カラムを追加したモデルで、image_uploaderを認識させて
使える状態にする為に、image_uploaderをマウントする記述をします。

mount_uploader :image, ImageUploader

GitLabのコードリーディングをしてみた(後編)

【結論】
・機能を理解しやすい命名を行う事で、
 その後のコードリーディングを円滑に進めることが出来る。

・include元のメソッドは、include先でも使用できる

・文字と文字の間を、大文字にして区切りを表す記述方法を
「キャメルケース」といい、クラス名に利用される。

【目次】


【本題】

前回までの続き

下記の記事の続きです。

ryoutaku-jo.hatenablog.com

ryoutaku-jo.hatenablog.com

命名は非常に重要

前回は、set_sort_orderの定義元を探し出し、
そこで定義されている条件式の中のメソッドの定義元まで探りました。

ここでそれぞれのメソッド名を改めて見ていきます。

「set_sort_order_from_user_preference」・・・ユーザー設定からソート順を設定する
「set_sort_order_from_cookie」・・・クッキーからソート順を設定する
「default_sort_order」・・・デフォルトのソート順

これだけでも、set_sort_orderが何をするメソッドなのか、
大体読み取れるかと思います。

ここで思ったのは、
命名は本当に大事!!
ということです。

正直、まだまだRubyRails)を触り出して日が浅い私には
コードの動きを全て理解する事は困難ですが、
機能を理解出来る様な名前が付いているだけで、
コードリーディングの捗り方が全く違いました!

では、コードリーディングに戻ります。

8:finder_typeの内容を掘り下げる

これ以上「finder_options」を掘り下げるとキリが無いので、一旦戻ります。

元々、「finder_options」を掘り下げていたのも、
finder_class.newで生成されるオブジェクトがどういったプロパティを持っているのか
特定したかったからです。

  def issuable_finder_for(finder_class)
    finder_class.new(current_user, finder_options)
  end

そもそも、「issuable_finder_for」が引数で渡している「finder_class」が
どういった値なのか分からないと特定できない事を見落としていました・・・
とりあえずfinder_typeの内容から探り直します。

  def finder
    @finder ||= issuable_finder_for(finder_type)
  end

同じファイルでは定義されていませんでした。
その様な場合は、include元のファイルを探ります。
なぜなら、include元のメソッドは、include先でも使用できるからです。

では、include元の「issues_controller」を見てみます。

ありました!「IssuesFinder」という記述だけです。

なお、こういった文字と文字の間を、大文字にして区切りを表す記述方法を
「キャメルケース」
と言います。
そして、キャメルケースは、クラス名で使用される記述方法です。
その為、この「IssuesFinder」もクラス名を指している事が考えられます。

では、「IssuesFinder」クラスを定義しているファイルを探して来ます。

見つけました!
また、「IssuableFinder」というクラスを継承している事もわかったので、
そちらも確認してみます。

見つけました!
また、このクラスにはinitializeメソッドが定義されています。

initializeメソッドとは、それを定義しているクラスのオブジェクトが
新たに生成された際に、自動的に呼び出されるメソッドです。

これにより、「issuable_finder_for」では、
「@current_user」「@params」が自動生成されていることがわかります。

そして、そのインスタンス変数が、finderメソッドによって、
@finderに代入されます(空の場合)

9:issuables_collectionの内容を掘り下げる

finderの内容はわかったので、前に戻って、
issuables_collectionの内容を読み解きます。

  def issuables_collection
    finder.execute.preload(preload_for_collection)
  end

「execute」「preload」は、いずれもRails組み込みのメソッドの様ですね

http://railsdoc.com/references/executerailsdoc.com

railsguides.jp


では、引数で指定されている「preload_for_collection」を見ていきます。

「collection_type」も、同じファイルに定義されています。

「finder_type」がIssueかどうかで、引数の値が変わる様です。
「finder_type」は、先ほど探し出した通り、「IssuesFinder」と定義されています。

preloadの仕組みが調べてもよく分かりませんでしたので、
かなり曖昧になりますが、finderで取得したデータに対して、
何らかのSQL文を実行して、データを取得していると考えられます。

そして、そこで得られたデータを、set_issuables_indexメソッドで@issuablesにセットして、
最終的にindexで@issuesに代入され、issuesの一覧表示が可能になっていると考えられます。

総括

最後はかなり無理矢理進めましたが、一応最後までindexアクションを
構造を読み進めました。正直、現在の知識で理解するのが難しい内容ですが、
ただ手を動かすだけでは得られないノウハウを実践レベルで知れたのは、
今後のスキルアップに大いに役立つと感じました。

コードリーディングは、今後も継続していきたいと思います。

GitLabのコードリーディングをしてみた(中編)

【結論】
・「||=」は、nilガードと呼ばれるもので、
 代入先がnilの時だけ代入され、既に値が入っている場合には、代入されない


【目次】


【本題】

前回までの続き

下記の記事の続きです。
ryoutaku-jo.hatenablog.com

4:issuables_collectionの定義元を探す

前回は、「@issuables」を定義している
「set_issuables_index」というメソッドを発見しました。

次は、「@issuables」に代入されている
「issuables_collection」の定義元を探します。

同じファイル内に「issuables_collection」というメソッドがありました。
メソッドには、「finder.execute.preload(preload_for_collection)」と記述されています。

5:finderの定義元を探す

「finder.execute.preload(preload_for_collection)」がどういった値かを探りますが、
メソッドチェーンされている場合は、先頭から定義を探します。

なので「finder」の定義元を探します。

これも同じファイル内に定義を確認できました。
メソッドは「@finder ||= issuable_finder_for(finder_type)」と定義されています。

なお、ここで出てくる「||=」は、nilガードと呼ばれるもので、
代入先がnilの時だけ代入され、既に値が入っている場合には、代入されないという記述です。

6:issuable_finder_forの定義元を探す

では次は「issuable_finder_for」の定義元を探します。

これも同じファイル内で発見できました。

「issuable_finder_for」の引数(finder_class)に対して
newアクションでモデルオブジェクトを生成しています。

そのモデルオブジェクトには(current_user, finder_options)という
プロパティが付与されます。

7:finder_optionsの定義元を探す

current_userは、現在ログイン中のユーザーという事はわかりますが、
finder_optionsがどういった値かは現時点で不明で、そちらを探します。

こちらも同じファイルにありました。
複数行に亘っているので、先頭から読み解いて行きます。

params[:state] = default_state if params[:state].blank?

まずこちらは、下記の様な処理になります。
1 「params[:state]」が「blank?」(空)だった場合
2「default_state」を、「params[:state] 」に代入する
3「params[:state]」が「blank?」(空)で無い場合は、ここでは何もしない

「default_state」がどういった値か不明なので、探します。

見つけました。「'opened'」という文字列だけの様です。

では「finder_options」の読み解きに戻ります。

    options = {
      scope: params[:scope],
      state: params[:state],
      sort: set_sort_order
    }

「options」という変数に、
{scope: params[:scope],state: params[:state],sort: set_sort_order}
を代入しています。

Railsでは、リクエスト情報をひとまとめにして、
params[:パラメータ名]という形式で取得
できます。

リクエスト情報とは、リンクをクリックしたり、
フォームのサブミットをクリックする事で送信されるので、
このparamsの値がどういったものか知るには、ビュー側を見る必要がある・・・

・・・のでしょうが・・・・いくらビューを見回しても、特定できず・・・
直接キーワードで検索しても、関係ない物ばかりヒットしてしまい、
今の私のレベルでは、これ以上を読み解くことができませんでした・・・

という訳で、paramsの部分は飛ばします!

sort: set_sort_order

「set_sort_order」の定義元を探ります。

論理和で繋がっているので、全てtureであれば、式はtureを返して、
いずれか一つでもfalseであれば、式はfalseを返すことになります。

では、この条件式がどうなっているのか
「set_sort_order_from_user_preference」
「set_sort_order_from_cookie
「default_sort_order」を見て行きます。

総括

ちょっと長過ぎ・・・
また明日に持ち越します・・・

《今日の学習進捗》

チーム開発:22日目
各種リンクの設定を完了させる。
ローカル環境と本番環境で保存されているデータの種類の違いから、
NilClassエラーが発生し対応に苦慮する。

学習開始からの期間 :70日
今日までの合計時間:738h
一日あたりの平均学習時間:10.6h
今日までに到達すべき目標時間:639h
目標との解離:99h
「10,000時間」まで、

残り・・・「9262時間!」