Rails Tutorialの記録

dev
Sponsored links
  1. 第3章
      1. provideとyield
      2. minitest reportersとguard
        1. minitest reporters
        2. guard
  2. 第4章
  3. 第5章 Fill in the layout
  4. 第6章 Modeling Users
      1. validation
      2. reload
      3. secure password
  5. 第7章 Sign up
      1. debugger
      2. DBリセット
      3. content_tag
      4. SSL設定
      5. 本番環境のDB設定
  6. 第8章 Basic Login
    1. rails routes
    2. Login form
    3. レイアウトを変更する
  7. 第9章 Advanced Login
    1. remenber_digest
    2. ソルト
    3. クラスメソッドの定義方法
      1. 1. User.method_name (クラス名をつける)
      2. 2. selfをつける(インスタンスのselfとは違うのでややこしい)
      3. 3. class
    4. セッションとcookies
    5. assignsメソッド
  8. 第10章 Updating, showing, and deleting users
    1. POSTとPATCHの区別
    2. target="_blank"のセキュリティ脆弱性
    3. 認可(authorization)
    4. パーシャルのリファクタリング
      1. ユーザー一覧の表示部分のリファクタ前
      2. リファクタ1
      3. リファクタ2
  9. 第11章 Account activation
      1. activation_token, activation_digest
      2. assignメソッドの罠
      3. error in sending mail on production
  10. 第12章 Password reset
      1. pathとurl
      2. form_withのscopeとは
  11. 第13章 User microposts
    1. micropostsのfixturesのテストデータを消しとかないとテスト通らない
    2. user:references
    3. InsufficientPrivilege Error
      1. time_ago_in_words(micropost.created_at)
      2. Active Job
      3. Active Storage (ストレージサービス設定)
          1. 本番環境(Herokuでconfig:setで環境変数設定)
          2. 開発環境(docker-composeでenvironementで環境変数設定)
  12. 第14章 Following users
    1. Relationshipモデル
    2. following, followers
    3. Ajaxで更新処理
      1. unfollow(destroy, delete)でのログ比較

第3章

provideとyield

home.html.erbとかで

<% provide(:title, "Help") %>

とやって、yieldで表示するのは

  
    <%= yield(:title) %> | Ruby on Rails Tutorial Sample App
  

以下と同じなんじゃないかと思うがprovide, yieldを使う利点はなんだろうか。

<% @title="Help" %>

とやって、

  
    <%= @title %> | Ruby on Rails Tutorial Sample App
  

layoutファイル(appication.html.erb)を使う時にファイル間でprovideじゃないと参照できないのかなと思ったけど一緒だった。一応メモして残しておこう。

minitest reportersとguard

高度なテストのセットアップを行っておく。テスト結果が見やすくなるのと、ファイル監視して自動でテストを回してくれるらしい。

Gemfile に以下設定済みなので使用できる準備は整っている

group :test do
・・・・・
  gem 'minitest-reporters',       '1.3.8'
  gem 'guard',                    '2.16.2'
  gem 'guard-minitest',           '2.4.6'
end
minitest reporters

minitest-reportersは test_helper.rb に以下を追加

 ENV['RAILS_ENV'] ||= 'test'
 require_relative '../config/environment'
 require 'rails/test_help'
+require "minitest/reporters"
+Minitest::Reporters.use!

これだけで使える。テスト結果が見やすくなる。

  • before
$ rails t
Running via Spring preloader in process 2626
Run options: --seed 11806

# Running:

.....

Finished in 2.249599s, 2.2226 runs/s, 4.0007 assertions/s.
5 runs, 9 assertions, 0 failures, 0 errors, 0 skips
  • after
[email protected]:/src/sample_app# rails t
Running via Spring preloader in process 2750
Started with run options --seed 54151

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

Finished in 1.61358s
5 tests, 9 assertions, 0 failures, 0 errors, 0 skips
guard
  • 初期化
$ bundle exec guard init
08:56:00 - INFO - Writing new Guardfile to /src/sample_app/Guardfile
08:56:00 - INFO - minitest guard added to Guardfile, feel free to edit it

Guardfileをチュートリアル用に最適化 (チュートリアルのままコピペ)

# Guardのマッチング規則を定義
guard :minitest, spring: "bin/rails test", all_on_start: false do
  watch(%r{^test/(.*)/?(.*)_test\.rb})
  watch('test/test_helper.rb') { 'test' }
  watch('config/routes.rb') { interface_tests }
  watch(%r{app/views/layouts/*}) { interface_tests }
  watch(%r{^app/models/(.*?)\.rb}) do |matches|
    "test/models/#{matches[1]}_test.rb"
  end
  watch(%r{^app/controllers/(.*?)_controller\.rb}) do |matches|
    resource_tests(matches[1])
  end
  watch(%r{^app/views/([^/]*?)/.*\.html\.erb}) do |matches|
    ["test/controllers/#{matches[1]}_controller_test.rb"] +
    integration_tests(matches[1])
  end
  watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches|
    integration_tests(matches[1])
  end
  watch('app/views/layouts/application.html.erb') do
    'test/integration/site_layout_test.rb'
  end
  watch('app/helpers/sessions_helper.rb') do
    integration_tests << 'test/helpers/sessions_helper_test.rb'
  end
  watch('app/controllers/sessions_controller.rb') do
    ['test/controllers/sessions_controller_test.rb',
     'test/integration/users_login_test.rb']
  end
  watch('app/controllers/account_activations_controller.rb') do
    'test/integration/users_signup_test.rb'
  end
  watch(%r{app/views/users/*}) do
    resource_tests('users') +
    ['test/integration/microposts_interface_test.rb']
  end
end

# 与えられたリソースに対応する統合テストを返す
def integration_tests(resource = :all)
  if resource == :all
    Dir["test/integration/*"]
  else
    Dir["test/integration/#{resource}_*.rb"]
  end
end

# インターフェースが該当するすべてのテストを返す
def interface_tests
  integration_tests << "test/controllers/"
end

# 与えられたリソースに対応するコントローラのテストを返す
def controller_test(resource)
  "test/controllers/#{resource}_controller_test.rb"
end

# 与えられたリソースに対応するすべてのテストを返す
def resource_tests(resource)
  integration_tests(resource) << controller_test(resource)
end

以下で実行できるが、

$ bundle exec guard

エラー出る。。

/usr/local/bundle/gems/guard-2.16.2/lib/guard/guardfile/evaluator.rb:93:in `evaluate': invalid byte sequence in US-ASCII (ArgumentError)

どうもdocker環境だとエラーになるっぽい。
参考) https://teratail.com/questions/171538

環境変数を設定するといけるらしいのでそのようにする。Dockerfileにも書いておいたほうがいいのか。

export LANG=C.UTF-8

なぞだが、安心してつかっていいみたいな。。
参考) https://qiita.com/methane/items/dac75ef5019b311a0f10

とりあえずこれでguardも実行できた。

第4章

シングルクォートとダブルクォートは式展開(#{var})ができるかどうかが違う

>> city="岡山市"
>> puts city
岡山市
=> nil
>> pref = "岡山県"
>> pref
=> "岡山県"
>> puts "#{city} #{pref}"
岡山市 岡山県
=> nil
>> puts "#{city}\t#{pref}"
岡山市  岡山県
=> nil
>> puts '#{city}\t#{pref}'
#{city}\t#{pref}
=> nil

後続if やunlessもrubyに特徴的

>> x = "exs"
>> puts "x is not empty" if !x.empty?
x is not empty

>> string = "foobar"
>> puts "The string '#{string}' is nonempty." unless string.empty?
The string 'foobar' is nonempty.

メソッドの返り値はreturnが無くても最後の式が返される。
kotlinもreturnが省略できたような
でも書いた方がいいよね。

moduleはincludeでclassでそのメソッドを使えるようになるが(mixin), railsのhelperクラスはincludeしなくても
どこからでも使えるようになるとのこと。おそらくそのcontrollerの名前に合ったhelperクラスである必要がありそう。

第5章 Fill in the layout

メモしていたけど、Wordpressのセッション切れていて消えたorz
書き始める前にちゃんとログインしよう。。

とりあえず、この章は

  • asset pipeline (jsやcss, 画像などのリソースをいい感じで管理してくれる)
  • _header.html.irbのようなパーシャルを使ってrenderメソッドで部分的にhtmlを読み込もう
  • Bootstrapでいい感じにデザイン、cssの上位互換scssで効率的にスタイリング
  • 名前付きルート
  • リンクをシミュレートする統合テスト

第6章 Modeling Users

validation

class User < ApplicationRecord
    validates :name, presence: true
end

引数の最後がハッシュなら{}は省略できる。
そのため以下と一緒

class User < ApplicationRecord
    validates :name, {presence: true}
end

reload

保存を行わずにreloadを実行すると、データベースの情報を元にオブジェクトを再読み込みする。
逆に保存後の値を確かめたい時はsave後に実行すると良さそう。

# saveせずにreload
>> user.email = "[email protected]"
>> user.reload.email
  User Load (0.7ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT2  [["id", 2], ["LIMIT", 1]]
=> "[email protected]"

# save後にreload
>> user.email = "[email protected]"
>> user.save
   (0.7ms)  SAVEPOINT active_record_1
  User Exists? (1.1ms)  SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(1) AND "users"."id" !=2 LIMIT 3  [["email", "[email protected]"], ["id", 2], ["LIMIT", 1]]
  User Update (1.1ms)  UPDATE "users" SET "email" =1, "updated_at" = 2 WHERE "users"."id" =3  [["email", "[email protected]"], ["updated_at", "2020-06-29 02:19:25.302483"], ["id", 2]]
   (0.6ms)  RELEASE SAVEPOINT active_record_1
=> true
>> user.reload.email
  User Load (1.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT2  [["id", 2], ["LIMIT", 1]]
=> "[email protected]"

secure password

Userモデルに has_secure_password メソッドを追加する。

  • usersテーブルに password_digest カラムが存在することが条件
  • bcrypt gemが必要
  • 仮想的な password 属性と password_confirmation 属性が追加される。
    • この2つの属性が合致するかのvalidationは自分で組む必要があり?
  • password_confirmation は nil だと userはvalidになるけど、なにか値が入っていないと、password と合っていないとinvalidになるっぽい

>> user.password = "aaaaaa"
>> user.password_confirmation
>> user.valid?
  User Exists? (1.0ms)  SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(1) AND "users"."id" !=2 LIMIT 3  [["email", "[email protected]"], ["id", 4], ["LIMIT", 1]]
=> true
>> user.password_confirmation = "bbbbbb"
>> user.valid?
  User Exists? (1.5ms)  SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(1) AND "users"."id" != 2 LIMIT3  [["email", "[email protected]"], ["id", 4], ["LIMIT", 1]]
=> false
>> user.errors.messages
=> {:password_confirmation=>["doesn't match Password"]}
>> user.password_confirmation = "aaaaaa"
>> user.valid?
  User Exists? (1.6ms)  SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(1) AND "users"."id" !=2 LIMIT $3  [["email", "[email protected]"], ["id", 4], ["LIMIT", 1]]
=> true
>> user.errors.messages
=> {}

update_attribute は 認証無しで更新できる?

has_secure_passwordを使うようになったので、save時にパスワードによる認証が必要になったっぽい。が update_attributeを使うとパスワード無しで更新できるっぽい

>> user.update_attribute(:name, "higehige")
   (0.6ms)  BEGIN
  User Update (1.3ms)  UPDATE "users" SET "name" = 1, "updated_at" =2 WHERE "users"."id" = 3  [["name", "higehige"], ["updated_at", "2020-06-29 03:01:03.002078"], ["id", 5]]
   (2.2ms)  COMMIT
=> true
>> user.name
=> "higehige"
# rails console
>> user = User.find_by(email: "[email protected]")
  User Load (0.5ms)  SELECT "users".* FROM "users" WHERE "users"."email" =1 LIMIT $2  [["email", "[email protected]"], ["LIMIT", 1]]
>> user.name
=> "higehige"

第7章 Sign up

debugger

debugger というワードを埋め込むとそこで実行をストップしてデバッグできるらしい。
byebug gemの機能。

def show
    @user = User.find(params[:id])
    debugger
  end

DBリセット

途中でテスト的に登録したユーザーを全部消すため、

$ rails db:migrate:reset

を行ったが、以下のようなエラーになったよ。

PG::ObjectInUse: ERROR:  database "sample_app_development" is being accessed by other users
DETAIL:  There are 2 other sessions using the database.

セッションが2つまだ残っているということ。

[email protected]:/src/sample_app# ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 05:25 pts/0    00:00:00 irb
root           6       0  0 05:25 pts/1    00:00:00 /bin/bash
root          11       0  0 05:26 pts/2    00:00:00 /bin/bash
root          85       1  0 05:49 pts/1    00:00:00 spring server | sample_app | started 46 mins ago
root          87      85  0 05:49 ?        00:00:03 spring app    | sample_app | started 46 mins ago
root         155      85  0 06:12 ?        00:00:03 spring app    | sample_app | started 23 mins ago
root         253       6  0 06:26 pts/1    00:00:00 /usr/local/bin/ruby bin/rails c
root         255      87  0 06:26 ?        00:00:00 rails_console
root         411       6  0 06:34 pts/1    00:00:00 /usr/local/bin/ruby bin/rails c
root         415      87  0 06:34 ?        00:00:00 rails_console
root         518      11  0 06:36 pts/2    00:00:00 ps -ef
# 確かに rails consoleのセッションが残っていたっぽい。PIDの255と415を以下のようにして消すとresetが成功したよ
[email protected]:/src/sample_app# kill -9 255

content_tag

content_tagを使うと ごちゃごちゃしたerbがすっきりする

  • before
      <% flash.each do |message_type, message| %>
        
"><%= message %>
<% end %>
  • after
      <% flash.each do |message_type, message| %>
        <%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
      <% end %>

SSL設定

本番環境でSSLを有効にするにはproduction.rbの以下をtrueにするだけでよいらしい
ただし、Herokuのサブドメイン化の場合のみ

# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
  config.force_ssl = true

自分のドメインでHeroku上で有効にするには以下を参照とのこと
https://devcenter.heroku.com/articles/ssl

本番環境のDB設定

database.ymlに記載した情報はHerokuでいつ設定したのかなと気になってしまった。
SAMPLE_APP_DATABASE_PASSWORD 環境変数にPostgresのパスワードを設定しているっぽいが、Herokuでどうやって設定しているのか。

↓で見てみたが設定されていないっぽい。空でログインできる?

$ heroku run printenv

どうやら Herokuで自動的にDBの接続情報が設定され、
環境変数 DATABASE_URL の設定の方が 優先されるっぽい。

Railsガイドの以下あたりに記載ありました。

確かに以下のような感じでHerokuに設定されていました。(それぞれプレースホルダー当ててます)

$ heroku run printenv DATABASE_URL
postgres://{username}:{password}@{appurl}:5432/{database}

database.ymlに設定した以下はいったい。。

production:
  adapter: postgresql
  encoding: unicode
  # For details on connection pooling, see Rails configuration guide
  # https://railsguides.jp/configuring.html#データベース接続をプールする
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  database: sample_app_production
  username: sample_app
  password: <%= ENV['SAMPLE_APP_DATABASE_PASSWORD'] %>

第8章 Basic Login

セッションをRESTfulなリソースとしてモデリング

今まで作ってきたUsersリソースと同じようにモデリングするが、保存先がRDBではなくCookiesになる。

rails routes

全ルーティングを確認できる。grepでパイプすると便利そう

$ rails routes | grep user   
                               signup GET    /signup(.:format)                                                                        users#new
                                users GET    /users(.:format)                                                                         users#index
                                      POST   /users(.:format)                                                                         users#create
                             new_user GET    /users/new(.:format)                                                                     users#new
                            edit_user GET    /users/:id/edit(.:format)                                                                users#edit
                                 user GET    /users/:id(.:format)                                                                     users#show
                                      PATCH  /users/:id(.:format)                                                                     users#update
                                      PUT    /users/:id(.:format)                                                                     users#update
                                      DELETE /users/:id(.:format)                                                                     users#destroy

$ rails routes | grep session
                         sessions_new GET    /sessions/new(.:format)                                                                  sessions#new
                                login GET    /login(.:format)                                                                         sessions#new
                                      POST   /login(.:format)                                                                         sessions#create
                               logout DELETE /logout(.:format)                                                                        sessions#destroy

こうやって見ていると、sessionsの1番目と2番目が同じメソッドを指していることに気づいた。
rails g controller Sessions new で自動的にできたroutingを消していなかった。

routes.rb

Rails.application.routes.draw do
  get 'sessions/new' # これいらない

  get '/login', to: 'sessions#new'
  post '/login', to: 'sessions#create'
  delete '/logout', to: 'sessions#destroy'

end

sessions/newを消すと、想定通りになった。session#createやsessions#destroyはまだSessionsControllerにメソッドを書いていないが、出力されていることに注意しておく。

# rails routes | grep session
                                login GET    /login(.:format)                                                                         sessions#new
                                      POST   /login(.:format)                                                                         sessions#create
                               logout DELETE /logout(.:format)                                                                        sessions#destroy

Login form

セッションはActive Recordオブジェクトではないので、以前のようにActive Recordがよしなにエラーメッセージを表示してくれるということは期待できません。

なるほど、SignupではUsersのActive Recordがエラーの場合、users.errors.messagesにエラーをセットしてくれていた。そして以下のようにフォームにdivタグまでつけてくれていた。

第7章 7.3.3のあたり

Railsは、無効な内容の送信によって元のページに戻されると、CSSクラスfield_with_errorsを持ったdivタグでエラー箇所を自動的に囲んでくれます。

Active Recordの恩恵があったわけだと思うが、それがない場合どうすればいいのか確認したい。

レイアウトを変更する

ドロップダウンメニュー作成のためjqueryとbootstrapをjs側で使えるようにする

$ yarn add [email protected] [email protected]

こんな感じでpackage.jsonに追加される

$ git diff
diff --git a/package.json b/package.json
index 85e5f83..2e8305d 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,8 @@

+    "bootstrap": "3.4.1",
+    "jquery": "3.4.1",
     "turbolinks": "^5.2.0"

diff --git a/yarn.lock b/yarn.lock
index ce9a0b7..58346db 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1457,6 +1457,11 @@ [email protected]^1.0.0, [email protected]~1.0.0:
   resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
   integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=

[email protected]:
+  version "3.4.1"
+  resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.4.1.tgz#c3a347d419e289ad11f4033e3c4132b87c081d72"
+  integrity sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==
+
 [email protected]^1.1.7:
   version "1.1.11"
   resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -3915,6 +3920,11 @@ [email protected]^25.4.0:
     merge-stream "^2.0.0"
     supports-color "^7.0.0"

[email protected]:
+  version "3.4.1"
+  resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2"
+  integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==
+
 [email protected]^2.1.8:
   version "2.6.1"
   resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.1.tgz#c328374225d2e65569791ded73c258e2c59334c7"

第9章 Advanced Login

パスワードとトークンとの一般的な違いは、パスワードはユーザーが作成・管理する情報であるのに対し、トークンはコンピューターが作成・管理する情報である点です。

なるほど

remenber_digest

記憶トークンに使う文字列は SecureRandom.urlsafe_base64 メソッドを使う。
ランダムな22文字の文字列を生成してくれる。

# rails c
Running via Spring preloader in process 229
Loading development environment (Rails 6.0.3)
>> 
>> SecureRandom.urlsafe_base64
=> "fCjzgRW45nbwbdHPorun5Q"
>> SecureRandom.urlsafe_base64
=> "CGufSw6xxEHd-JSNPvk4ew"
>> SecureRandom.urlsafe_base64
=> "KZN9loW9D8Gbma8ZUoVp7A"
>> SecureRandom.urlsafe_base64
=> "2cZefkB_VwX6rdQr2kSEpg"
>> SecureRandom.urlsafe_base64
=> "kUfkkTZUcYSSlabcaG95dQ"
>> SecureRandom.urlsafe_base64.length
=> 22
>> SecureRandom.urlsafe_base64.length
=> 22
>> SecureRandom.urlsafe_base64.length
=> 22

ソルト

同じパスワードでもソルトが違えば、ハッシュ化した後の値は変わる。
railsのbcryptのハッシュ化はソルト化されている。
ソルトは

念には念を入れて「塩ひとつまみ」を加えるというイメージであり、英語の「take it with a grain of salt (半分疑ってかかる)」という言い回しが語源

らしい。おもしろい。

クラスメソッドの定義方法

今のところ3つ出てきた。

1. User.method_name (クラス名をつける)

def User.digest(string)
    ・・
    ・・
end

2. selfをつける(インスタンスのselfとは違うのでややこしい)

def self.digest(string)
    ・・
    ・・
end

3. class

class << self
    def digest(string)
        ・・
    end

    def new_token
        ・・
    end
end

クラス名.メソッド名の1がわかりやすい気がするが、出てきたら混乱しないようにしたい。

セッションとcookies

ユーザーIDをcookiesに保存する。

cookies[:user_id] = user.id

このようにすると生のユーザーIDが保存されてしまうので署名付きcookiesを使って暗号化する。

cookies.signed[:user_id] = user.id

permanentメソッドでcookiesが20年間保存されるらしい。

cookies.permanent.signed[:user_id] = user.id

cookiesからuserを取り出すには

User.find_by(id: cookies.signed[:user_id])

cookies.signedで取り出される際に復号化されるらしい。

assignsメソッド

テストでControllerで定義したインスタンス変数の仮想の属性にアクセスするためのメソッド

Controller#createで以下のように@user変数を定義していると

@user = User.find_by(email: params[:session][:email].downcase)

テストでは

assigns(:user).remember_token

のようにシンボルを使ってアクセスできる。cookiesに保存したセッショントークンが正しいかのテストは以下

assert_equal cookies[:remember_token], assigns(:user).remember_token

第10章 Updating, showing, and deleting users

POSTとPATCHの区別

form_withのコードはusers/new.html.erbとusers/edit.html.erbで両方全く同じだが、railsはPOSTリクエストとPATCHリクエストをどうやって区別しているのかが説明してある。
Active Recordの new_record? メソッドを使って新規レコードか既存レコードか区別しているらしい。

$ rails console
>> User.new.new_record?
=> true
>> User.first.new_record?
=> false

target="_blank"のセキュリティ脆弱性

リンクへの遷移にこのまま使うと

リンク先のサイトがHTMLドキュメントのwindowオブジェクトを扱えてしまう

という脆弱性が含まれているらしい。特に自分のサイトじゃないサイトへ誘導する際は気をつけておく。フィッシングの詐欺に合う場合があるらしい。

対策は以下行うだけなので簡単

aタグのrel(relationship)属性に、"noopener"と設定するだけ

参考:
https://edge.sincar.jp/web/target_blank-security/

認可(authorization)

ウェブアプリケーションの文脈では、認証(authentication)はサイトのユーザーを識別することであり、認可(authorization)はそのユーザーが実行可能な操作を管理することです。

railsでは以下のように before_action で 実現される。

beforeフィルターを使って、認可(アクセス制御)を実現した
この章のまとめより

パーシャルのリファクタリング

railsの類推力というか、魔法っぽいところでこれは知らないと絶対使えないと思ったのでメモしておく。

ユーザー一覧の表示部分のリファクタ前

    <% @users.each do |user| %>
  • <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %>
  • <% end %>

リファクタ1

    <% @users.each do |user| %> <%= render user %> <% end %>

render user とやることで、以下のように切り出した_user.html.erb のパーシャルファイルを読み込んでくれるようになる。

  • <%= gravatar_for user, size: 50 %> <%= link_to user.name, user %>
  • ここまでは、おーすごいと便利に感じる。

    リファクタ2

      <%= render @users %>

    ユーザー一覧の@users変数をまるごとrenderに渡すだけで同じようにrenderingしてくれる。
    もうここまでくると魔法みたいでよくわからん。知ってないと使えない。

    render @usersを実行すると、自動的に_user.html.erbパーシャルを参照し、各ユーザーをコレクションとして表示する
    この章のまとめより

    第11章 Account activation

    activation_token, activation_digest

    before_create :create_activation_digest
    
    private
        # 有効化トークンとダイジェストを作成および代入する
        def create_activation_digest
          self.activation_token  = User.new_token
          self.activation_digest = User.digest(activation_token)
        end
    

    before_create コールバックでUserインスタンスが作られるとこれら2つの属性が作成されるが、
    rails consoleからみる感じではUser.newの段階で作成されていない。

    >> user = User.new
    >> user.activation_token
    => nil
    

    activation_digestは保存されているみたいだった。

    >> user1 = User.first
      User Load (1.2ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1  [["LIMIT", 1]]
    >> user1.activation_token
    => nil
    >> user1.activation_digest
    => "2a10tf69UDk4bdZnv.rTTaE/SOsRZjJFnZVjUUsqhWK5Vw186J73a.UdS"
    

    ということはインスタンスが作成された時ではなく、DBに保存する時にbefore_createはコールバックされるっぽい。ではbefore_saveとの違いは?

    >> user = User.new(name: "hige", email:"[email protected]", password: "higehige", password_confirmation: "higehige")
    >> user.valid?
      User Exists? (0.8ms)  SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(1) LIMIT2  [["email", "[email protected]"], ["LIMIT", 1]]
    => true
    >> user.activation_token
    => nil
    >> user.activation_digest
    => nil
    >> user.save
       (0.7ms)  BEGIN
      User Exists? (1.9ms)  SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(1) LIMIT2  [["email", "[email protected]"], ["LIMIT", 1]]
      User Create (1.0ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest", "activation_digest") VALUES (1,2, 3,4, 5,6) RETURNING "id"  [["name", "hige"], ["email", "[email protected]"], ["created_at", "2020-07-15 15:07:49.661033"], ["updated_at", "2020-07-15 15:07:49.661033"], ["password_digest", "2a10XqT7ckKa5eP6YQ1n0AkZ5uG1i.b68sr9tR5L4bxp9vRoob3rW6C4u"], ["activation_digest", "2a10lomFN7UICj4/xaD6PDveHewamTg3R1hGmWTYnL6cn6GbqRrN90LSy"]]
       (6.0ms)  COMMIT
    => true
    >> user.activation_token
    => "rUZjwL_6ADiO5rqMNgtRxg"
    >> user.activation_digest
    => "2a10$lomFN7UICj4/xaD6PDveHewamTg3R1hGmWTYnL6cn6GbqRrN90LSy"
    

    こちらによると、before_createはbefore_saveの後に、新規データの場合に実行されるらしい。
    https://www.techscore.com/blog/2012/12/25/rails%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%AB%E3%83%90%E3%83%83%E3%82%AF%E3%81%BE%E3%81%A8%E3%82%81/

    オブジェクトが登録されるとき (new_record? が true のとき) は before_create が実行されます。
    オブジェクトが更新されるとき (new_record? が false のとき) は before_update が実行されます。
    登録と更新のどちらの場合にも同じ処理を行うのであれば、before_save を使用すると便利です。

    本文にも

    before_createコールバックの方はユーザーが作成される前に呼び出されることです

    と書いてあったが、User.newでインスタンスを作成後にコールバックされるように捉えてしまったので混乱した。

    assignメソッドの罠

    進めていると途中から以下エラーでテストが通らなくなった。

    ERROR["test_login_with_remembering", #, 2.0458919250013423]
     test_login_with_remembering#UsersLoginTest (2.05s)
    NoMethodError:         NoMethodError: undefined method `remember_token' for nil:NilClass
                test/integration/users_login_test.rb:58:in `block in '
    
      45/45: [=============================================================================================================================================] 100% Time: 00:00:02, Time: 00:00:02
    

    9章で作成したテストのusers_login_test.rbの以下 assigns(:user)部分でエラー

     test "login with remembering" do
       log_in_as(@user, remember_me: '1')
       # assert_not_empty cookies[:remember_token]
       assert_equal cookies[:remember_token], assigns(:user).remember_token
     end
    

    assigns(:user) が nil になっているのが原因
    そもそもassignsって何か?
    たまたま本章の users_signup_test.rbで出てきていたのでわかった。

    このassignsメソッドを使うと対応するアクション内のインスタンス変数にアクセスできるようになります。

    sessions_controller.rb#createメソッドにactivateの判定を加えた時にテキストの内容をコピペして上書きしてしまったのが原因だったっぽい。

    第9章の演習でremember_tokenをテストするコードに改良した。
    その時にローカル変数userをインスタンス変数@userにしてまさに、テストからassign(:user)でコントローラーの@userにアクセスできるようにした。
    しかし、11章でテキストのコードをコピペしてしまうと、第9章で行った改良が元に戻ってしまう。
    そのためテストからassign(:user)でコントローラーのインスタンス変数@userにアクセスしようとしてもnullになってしまっていた。

    このassignメソッドはRails 5以降で非推奨になっているっぽい。
    rails-controller-testing というgemを使うと現在でも利用できるらしい。実際にrails tutorialでもgemに追加されていた。

    group :test do
    ・・・
      gem 'rails-controller-testing', '1.0.4'
    ・・・
    

    以下のようにコメントを書いておけば思い出せたかもしれない。どこの章、節で編集したかわかるように番号も記載しておくことにする。

    test "login with remembering" do
       log_in_as(@user, remember_me: '1')
       # 9.28 remember_token属性を検証するためにSessionController#createで定義した@userにアクセスするためassignsメソッドを使う
       # assert_not_empty cookies[:remember_token]
       assert_equal cookies[:remember_token], assigns(:user).remember_token
     end
    

    ここでlog_in_asの引数の@userは以下のようにテストのsetupで代入したもので、また別のものなので注意。これも混乱の原因の一つ。

     def setup
        @user = users(:michael)
     end
    

    さらに忘れないように SessionsController#createにもコメントしておく。そもそもここを見てメソッド内でしか使われていなさそうだし、ローカル変数でいいよねって納得してしまったのでテキストのコピペを疑わず行ってしまったのも罠の一つだったので。。

      def create
        # user_login_testにてassignでアクセスできるようにインスタンス変数で定義する
        @user = User.find_by(email: params[:session][:email].downcase)
         ・・・
    

    error in sending mail on production

    2020-07-16T02:08:22.386245+00:00 app[web.1]: I, [2020-07-16T02:08:22.386172 #17]  INFO -- : [3d0afe54-3a34-49e4-bf07-5062ffbe467f] Completed 500 Internal Server Error in 304ms (ActiveRecord: 19.6ms | Allocations: 6734)
    2020-07-16T02:08:22.387578+00:00 app[web.1]: F, [2020-07-16T02:08:22.387503 #17] FATAL -- : [3d0afe54-3a34-49e4-bf07-5062ffbe467f]
    2020-07-16T02:08:22.387579+00:00 app[web.1]: [3d0afe54-3a34-49e4-bf07-5062ffbe467f] Net::SMTPAuthenticationError (535 Authentication failed: account disabled
    

    DBにはユーザー登録されていたので、もう一回検証するために、DBをリセットする。

    # DBリセットして
    heroku pg:reset DATABASE
    # テーブル作り直し heroku run rails db:migrate
    # データ入れる
    heroku run rails db:seed
    

    エラー内容でググると以下の方の記事が見つかった。ありがとうございます。
    https://qiita.com/yuppymam/items/50f893df99cfb1683234

    しかし、アプリを作り直しても変わらない。。
    Herokuにチケットを上げてサポートを求める。。

    Dear support,
    
    I am not sure the reason my sendgrid account is flozen but
    when I send mail via my app, the log below is output.
    Could you activate my sendgrid account ?
    
    the log below was output.
    2020-07-16T02:08:22.386245+00:00 app[web.1]: I, [2020-07-16T02:08:22.386172 #17]  INFO -- : [3d0afe54-3a34-49e4-bf07-5062ffbe467f] Completed 500 Internal Server Error in 304ms (ActiveRecord: 19.6ms | Allocations: 6734)
    2020-07-16T02:08:22.387578+00:00 app[web.1]: F, [2020-07-16T02:08:22.387503 #17] FATAL -- : [3d0afe54-3a34-49e4-bf07-5062ffbe467f]
    2020-07-16T02:08:22.387579+00:00 app[web.1]: [3d0afe54-3a34-49e4-bf07-5062ffbe467f] Net::SMTPAuthenticationError (535 Authentication failed: account disabled
    My sendgrid usename is below
    $ heroku config:get SENDGRID_USERNAME
     ›   Warning: heroku update available from 7.42.0 to 7.42.1.
    ******@heroku.com
    Thank you in advance.
    

    以下Sendgridにログインしようとしても、
    https://app.sendgrid.com/login
    以下表示される。ログイン/パスワードが違うと別のメッセージなので、また違うエラーなのかな。

    You are not authorized to access SendGrid, please contact Support.
    

    以下SendGridにも問い合わせてみる

    My sendgrid account is below.
    ******@heroku.com
    
    when I login, the messeage below shown.
    You are not authorized to access SendGrid, please contact Support.
    
    Probably my account is flozen. Could you activate it ?
    When I send emai via herokuapp, the log below shown.
    Net::SMTPAuthenticationError (535 Authentication failed: account disabled
    
    Thank you in advance.
    

    第12章 Password reset

    pathとurl

    前章までにもたびたび出てきたが、名前付きルートのpathとurlはどう違うのか、だいたいpathを使って、リダイレクトの時にはurlを使うことが決まりになったいることぐらいしか理解していないかった。

    第5章の 5.3.2 RailsのルートURL にちゃんと書いてあった。こういうのを理解していないと。

    例えばルートURLを定義すると、root_pathやroot_urlといったメソッドを通してURLを参照することができます。ちなみに前者はルートURL以下の文字列を、後者は完全なURLの文字列を返します。

    基本的には_path書式を使い、リダイレクトの場合のみ_url書式を使うようにします。これはHTTPの標準としては、リダイレクトのときに完全なURLが要求されるためです。

    なるほど、アカウント有効化のURLをメールのリンクにする時は完全なURLが良いのでurlをつかっていたのか。

    form_withのscopeとは

        <%= form_with(url: password_resets_path, scope: :password_reset, local: true) do |f| %>
          <%= f.label :email %>
          <%= f.email_field :email, class: 'form-control' %>
          <%= f.submit "Submit", class: "btn btn-primary" %>
        <% end %>
    

    これもここで初めて出てきたものではなくおさらい。
    12.1.2の演習にもこのscopeの値について問題が出ている。

    form_withメソッドで、@password_resetではなく:password_resetを使っている理由を考えてみましょう。

    scopeはなんだっけ?という話と、"@password_resetではなく:password_reset" を使っている理由というのは別の話で、一緒に考えるものではないかもしれない。

    まずscopeは何かという話だけども、理解したのはフォーム送信先のactionでparamとして受け取ることのできるハッシュの名前になるということ。

    scope: :password_reset と書くと、

    フォーム送信先のアクションである PasswordResetsController#create で入力値を以下のように取得できるっぽい。

    params[:password_reset][:email]
    

    生成されたhtmlを見ても、name="password_reset[email]" になっている。

    
    

    scopeを pswd_rst に変えた時、

    
    

    なるほど、ではscopeを記載しなかった場合、

    
    

    なるほど、scopeはparamとして受け取ることができるパラメータの箱の名前みたいなものと理解した。
    なのでおそらく、scopeを記載しなかったら取得する時も、以下でいけるのだろう。
    この辺はテストで確かめることにする。

    params[:email]
    

    ここからは
    "@password_resetではなく:password_reset" を使っている理由 について

    混乱する一因として、
    サインアップやログインの時は@userを使っていたが、そもそもそれはscopeではなくてmodelに使っていたからだろう。

    /signupのusers#newと/users/1/editのusers#edit ではformを共有してパーシャル(_form.html.erb)に切り出していたが、ここでは以下のようform_withのmodelに@userを使っていた。

    <%= form_with(model: @user, local: true) do |f| %>
      ・・・
    <% end %>
    

    このmodel: @userのようにせず、今回はシンボルを使ってscopeに設定しているので何を比べているのかわかりにくい。model: @password_resetでなく、url, scopeのセットを使っているのはなぜか と聞いてくれるとわかりやすかったのではないかと思った。

    <%= form_with(url: password_resets_path, scope: :password_reset, local: true) do |f| %>
      ・・・
    <% end %>
    

    modelに@userを指定すると以下のようにrailsがよしなにルーティングしてくれる。

    • @userがまだ存在しないときにpostメソッド
    • @userが存在している場合にpatchメソッド(実際にはpostでhiddenフィールドでpatchを指定して擬似的に作成)

    今回はpassword_resetというモデルは用意していなく、urlを指定してその名前付きルートに対応するControlloerのアクション、今回では以下を使いたいためというのが演習の答えになるだろうか?

    password_resets_path => PasswordResetsController#create

    第13章 User microposts

    micropostsのfixturesのテストデータを消しとかないとテスト通らない

    まずこの章ではrailsコマンドでMicropostモデルを作成するが、

    $ rails generate model Micropost content:text user:references
    

    その時作られる /test/fixtures/microposts.yml の中身を消しておかないと、最初の方のテストに通らない。
    もし、このテストデータを使うのであれば、中身は以下のような感じなので、user: oneやtwoを作成しておかないといけない。

    one:
       content: MyText
       user: one
    
     two:
       content: MyText
       user: two
    

    Userモデルを作成した時は fixturesのymlファイルの中身を消しておくこと、とちゃんと書いてあった気がするけど、今回は自分で気づけよ ということかもしれない。

    user:references

    MicropostにはUserモデルへの関連(belongs_to)が自動的に付加されるが、
    Userには自動的にはつかない。

    has_many :microposts
    を自分でつけなければならない。

    InsufficientPrivilege Error

    テストで以下のようなエラーでるようになった。

    This is most likely caused due to missing permissions.
    Rails needs superuser privileges to disable referential integrity.
    
        cause: PG::InsufficientPrivilege: ERROR:  permission denied: "RI_ConstraintTrigger_a_51807" is a system trigger
    
    
            DRb::DRbRemoteError: PG::ForeignKeyViolation: ERROR:  insert or update on table "microposts" violates foreign key constraint "fk_rails_558c81314b"
            DETAIL:  Key (user_id)=(762146111) is not present in table "users".
    WARNING: Rails was not able to disable referential integrity.
    
    This is most likely caused due to missing permissions.
    Rails needs superuser privileges to disable referential integrity.
    

    Rails needs superuser privileges to disable referential integrity.
    という文からテストデータを作るために参照整合性を無効にする権限が必要らしい

    とりあえずDBのユーザーがsuper userじゃないとだめみたいな。
    調べます。

    [email protected]:/# psql -U appuser -d sample_app_development
    psql (12.3 (Debian 12.3-1.pgdg100+1))
    Type "help" for help.
    
    sample_app_development=> SHOW is_superuser;
     is_superuser 
    --------------
     off
    (1 row)
    

    やっぱり superuserじゃない。
    railsで使うユーザーはsuperuserじゃないとだめなのか。superuser権限をつけることにする

    superuserでログインし直して ALTER USER する

    [email protected]:/# psql -U postgres
    psql (12.3 (Debian 12.3-1.pgdg100+1))
    Type "help" for help.
    
    postgres=# \du
                                       List of roles
     Role name |                         Attributes                         | Member of 
    -----------+------------------------------------------------------------+-----------
     appuser   | Create DB                                                  | {}
     postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
    
    postgres=# ALTER USER appuser WITH SUPERUSER;
    ALTER ROLE
    postgres=# \du
                                       List of roles
     Role name |                         Attributes                         | Member of 
    -----------+------------------------------------------------------------+-----------
     appuser   | Superuser, Create DB                                       | {}
     postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
    
    

    これでappuserはSuperuserになったので、テストも成功するはず。
    実際に成功!
    RailsでPostgresをデータベースの場合、テストのために参照整合性をOFFにする必要があり、superuser権限じゃないとダメっぽい。

    time_ago_in_words(micropost.created_at)

    よくニュース記事とかの一覧でも見る、何時間前とか何日前とかを表示してくれるメソッド。すごすぎる。覚えておいてつかわなければ。

    Active Job

    Railsの非同期処理を行う仕組みらしい。紹介だけで終わっているが、一応覚えておくためにメモ
    https://railsguides.jp/active_job_basics.html

    Active Storage (ストレージサービス設定)

    Amazon S3でのやり方を書いてあったが、そちらはやったことあったので、こちらを見てGCPのCloud Storageで行ってみた。
    https://hixonrails.com/ruby-on-rails-tutorials/ruby-on-rails-active-storage-aws-s3-google-cloud-platform-microsoft-azure/

    Amazon S3に比べると設定が必要な項目が多いが、普通に設定していると本番でもdockerでの開発環境でもエラーで画像が登録できなかった。どうもPrivate Keyの設定でダブルクォーテーションで囲まないといけないらしい。

    本番環境(Herokuでconfig:setで環境変数設定)

    まず、エラーは詳細なエラーログがでない500エラーとなる。

    $ heroku config:set GCS_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\nMI......
    

    とコマンドでセットしていたが、Herokuのコンソールから設定できるみたいだった。
    そっちの方が早くて見やすい。実際にそちらから確認して、""(ダブルクォーテーション)で囲んだ後、画像の投稿が成功することを確認!

    開発環境(docker-composeでenvironementで環境変数設定)

    開発環境でも以下エラー出ているみたいだ。秘密鍵と公開鍵がないようなエラーかな
    Netither A nor B で AもBも両方ないということだろうか。

    Neither PUB key nor PRIV key: nested asn1 error
    

    以下のようにして、docker-compose.yamlのenvironmentに設定しているが他環境変数は
    全体を""(ダブルクォート)で囲っているが、GCS_PRIVATE_KEY(秘密鍵)は’’(シングルクォート)で囲っていることに注目。そしてvalueは""(ダブルクォート)で囲っている。

    services:
      app:
        build: app
        tty: true
        volumes:
            - ./app/src:/src
        environment: 
            - "GCS_CLIENT_ID=*********"
            - 'GCS_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----*******\n-----END PRIVATE KEY-----\n"'
    

    (バックスラッシュ)で”をエスケープとかもいろいろやってみたが、yamlの解析エラーになったりしてうまくいかなかった。シングルクォートで囲むこの方法でうまくいったのでとりあえずOKということにしておく。

    第14章 Following users

    Relationshipモデル

    今回はrelationship.ymlをいったん空にしておくように書いてある。post.ymlの時には書かれていなかったと思うのに。

    following, followers

    ルーティングに初見のmemberメソッドが出てくる。
    URLにIDを使えるようになるらしい。
    ルーティングの多様なオプションについては以下参照
    https://railsguides.jp/routing.html

    Rails.application.routes.draw do
    ・・
      resources :users do
        member do
          get :following, :followers
        end
      end
    ・・
    

    Ajaxで更新処理

    form_with の local: true を remote: true に変更するだけで非同期処理で更新処理が実行されるようになるらしい。

    controllerのcreateなら以下respond_toメソッドのどちらかの行が実行されるらしい。
    つまり remote: trueで非同期処理の場合、format.jsが実行されるが、その実ファイルはアクションに対応する hoge.js.erb (ここではcreate.js.erb) になる。

        def create
            @user = User.find(params[:followed_id])
            current_user.follow(@user)
            respond_to do |format| # 以下のどちらからの行が実行される
                format.html { redirect_to @user } # form_withで local:trueならこちら
                format.js # remote: trueならこちら
            end
        end
    

    テストの時も、postやdeleteのリクエストに xhr: true を書くだけで非同期処理になるだと。。

    # 普通に
    post relationships_path, params: { followed_id: @other.id }
    # ajax
    post relationships_path, xhr: true, params: { followed_id: @other.id }
    

    unfollow(destroy, delete)でのログ比較

    followでなく、なぜかunfollowで確認したが、本質は同じことなので。。

    普通の通信ではdestroyのactionのでredirect しているのでshowアクションが実行される。
    その結果、ヘッダーやフッターまで再描画されてしまっている。
    一方Ajaxの方ではdestroyでdelete後、必要な部分だけ(followボタンの描画、following人数の描画)描画されていることがわかる。

    Ajaxではない普通の通信のrendering

      Started DELETE "/relationships/110" for 172.23.0.1 at 2020-07-25 07:37:30 +0000
    Cannot render console from 172.23.0.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
    Processing by RelationshipsController#destroy as HTML
      Parameters: {"authenticity_token"=>"4C5MMWOPZa+ENPiuUzdxmrpt81HruWEFJ3blg+adxt18+a8mmbwAzvU9MkgylO1KnmyadjvdIswqvyT3Yvcj8w==", "commit"=>"Unfollow", "id"=>"110"}
      User Load (1.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT2  [["id", 1], ["LIMIT", 1]]
      ↳ app/helpers/sessions_helper.rb:19:in `current_user'
      Relationship Load (0.4ms)  SELECT "relationships".* FROM "relationships" WHERE "relationships"."id" = 1 LIMIT2  [["id", 110], ["LIMIT", 1]]
      ↳ app/controllers/relationships_controller.rb:14:in `destroy'
      User Load (0.5ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT2  [["id", 16], ["LIMIT", 1]]
      ↳ app/controllers/relationships_controller.rb:14:in `destroy'
      Relationship Load (0.7ms)  SELECT "relationships".* FROM "relationships" WHERE "relationships"."follower_id" = 1 AND "relationships"."followed_id" =2 LIMIT 3  [["follower_id", 1], ["followed_id", 16], ["LIMIT", 1]]
      ↳ app/models/user.rb:103:in `unfollow'
       (0.8ms)  BEGIN
      ↳ app/models/user.rb:103:in `unfollow'
      Relationship Destroy (0.5ms)  DELETE FROM "relationships" WHERE "relationships"."id" =1  [["id", 110]]
      ↳ app/models/user.rb:103:in `unfollow'
       (6.1ms)  COMMIT
      ↳ app/models/user.rb:103:in `unfollow'
    Redirected to http://localhost:3000/users/16
    Completed 302 Found in 34ms (ActiveRecord: 10.2ms | Allocations: 4954)
    
    
    Started GET "/users/16" for 172.23.0.1 at 2020-07-25 07:37:30 +0000
    Cannot render console from 172.23.0.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
    Processing by UsersController#show as HTML
      Parameters: {"id"=>"16"}
      User Load (0.7ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT2  [["id", 16], ["LIMIT", 1]]
      ↳ app/controllers/users_controller.rb:7:in `show'
      Rendering users/show.html.erb within layouts/application
       (1.3ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = 1  [["follower_id", 16]]
      ↳ app/views/shared/_stats.html.erb:5
       (0.6ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" =1  [["followed_id", 16]]
      ↳ app/views/shared/_stats.html.erb:11
      Rendered shared/_stats.html.erb (Duration: 8.3ms | Allocations: 2078)
      User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT2  [["id", 1], ["LIMIT", 1]]
      ↳ app/helpers/sessions_helper.rb:19:in `current_user'
      User Exists? (0.9ms)  SELECT 1 AS one FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = 1 AND "users"."id" =2 LIMIT 3  [["follower_id", 1], ["id", 16], ["LIMIT", 1]]
      ↳ app/models/user.rb:108:in `following?'
      Rendered users/_follow.html.erb (Duration: 3.0ms | Allocations: 554)
      Rendered users/_follow_form.html.erb (Duration: 8.8ms | Allocations: 1806)
      Micropost Exists? (0.4ms)  SELECT 1 AS one FROM "microposts" WHERE "microposts"."user_id" =1 LIMIT $2  [["user_id", 16], ["LIMIT", 1]]
      ↳ app/views/users/show.html.erb:16
      Rendered users/show.html.erb within layouts/application (Duration: 23.0ms | Allocations: 5415)
      Rendered layouts/_rails_default.html.erb (Duration: 42.6ms | Allocations: 17206)
      Rendered layouts/_shim.html.erb (Duration: 0.2ms | Allocations: 5)
      Rendered layouts/_header.html.erb (Duration: 1.4ms | Allocations: 199)
      Rendered layouts/_footer.html.erb (Duration: 0.4ms | Allocations: 49)
    Completed 200 OK in 79ms (Views: 68.2ms | ActiveRecord: 4.2ms | Allocations: 24589)
    
    

    Ajax通信でのrendering

    Started DELETE "/relationships/111" for 172.23.0.1 at 2020-07-25 07:39:32 +0000
    Cannot render console from 172.23.0.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
    Processing by RelationshipsController#destroy as JS
      Parameters: {"authenticity_token"=>"g8D8JoEMZ6E3helxeH2Ea/EbMFUHSBDSPLgvMUyBfLMfFx8xez8CwEaMI5cZ3hi71RpZctcsUxsxce5FyOuZnQ==", "commit"=>"Unfollow", "id"=>"111"}
      User Load (0.7ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT2  [["id", 1], ["LIMIT", 1]]
      ↳ app/helpers/sessions_helper.rb:19:in `current_user'
      Relationship Load (0.3ms)  SELECT "relationships".* FROM "relationships" WHERE "relationships"."id" = 1 LIMIT2  [["id", 111], ["LIMIT", 1]]
      ↳ app/controllers/relationships_controller.rb:14:in `destroy'
      User Load (0.9ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT2  [["id", 16], ["LIMIT", 1]]
      ↳ app/controllers/relationships_controller.rb:14:in `destroy'
      Relationship Load (0.5ms)  SELECT "relationships".* FROM "relationships" WHERE "relationships"."follower_id" = 1 AND "relationships"."followed_id" =2 LIMIT 3  [["follower_id", 1], ["followed_id", 16], ["LIMIT", 1]]
      ↳ app/models/user.rb:103:in `unfollow'
       (0.2ms)  BEGIN
      ↳ app/models/user.rb:103:in `unfollow'
      Relationship Destroy (1.1ms)  DELETE FROM "relationships" WHERE "relationships"."id" =1  [["id", 111]]
      ↳ app/models/user.rb:103:in `unfollow'
       (1.1ms)  COMMIT
      ↳ app/models/user.rb:103:in `unfollow'
      Rendering relationships/destroy.js.erb
      Rendered users/_follow.html.erb (Duration: 1.7ms | Allocations: 467)
       (0.7ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = $1  [["followed_id", 16]]
      ↳ app/views/relationships/destroy.js.erb:2
      Rendered relationships/destroy.js.erb (Duration: 8.0ms | Allocations: 1825)
    Completed 200 OK in 33ms (Views: 10.4ms | ActiveRecord: 5.4ms | Allocations: 8081)
    

    Comments

    タイトルとURLをコピーしました